В настоящее время я работаю над созданием компилятора в рамках проекта бакалавра. Трудным требованием является то, чтобы он мог работать в Linux как 64-битный бинарный файл.
Помимо других функциональных возможностей, язык, который мы реализуем, предлагает возможность записи на стандартный вывод. В рамках построения компилятора мы генерируем код в сборку, и нам разрешается вызывать printf из сборки для печати в stdout.
Однако в настоящее время мы наблюдаем некоторое неожиданное поведение с printf. Кажется, он ведет себя по-разному или зависит от разных вещей, в зависимости от того, запускаем ли мы компилятор на WSL или на реальном физическом дистрибутиве Linux. По сути, во многих случаях он дает ошибку сегментации на WSL, но выводит правильный вывод и не дает ошибок на физическом дистрибутиве.
Причина, по которой мы задаем этот вопрос, заключается в том, что после нескольких дней отладки мы не можем обнаружить, что есть какие-то проблемы с тем, как мы генерируем наш код. Таким образом, мы остаемся с вопросом о том, есть ли конкретное различие между этими двумя вариантами в том, как он обрабатывает вызовы вызовов библиотеки c, или нам нужно вернуться к генерации кода и сделать еще один шаг.
Причина, по которой мы полагаем, что printf вызывает проблемы, заключается в том, что valgrind указывает, что программа завершает работу при вызове printf, и, похоже, она ведет себя странно или вызывает проблемы в WSL в определенных ситуациях.
Мы используем синтаксис AT & T, но это не особенно важно.
EDIT:
Пример (написано на языке «котенок», язык, который мы реализуем):
var x: int;
var b: record of {c: int};
func factorial(n: int): int
var a:int;
var c:int;
var d:int;
var e:int;
var f:int;
var g:int;
var h:int;
var i:int;
var j:int;
var k:int;
var l:int;
var m:int;
b.c = b.c + 1;
m = n;
if (m == 0) || (m == 1) then{
return 1;
}
else{
return m * factorial(m-1);
}
end factorial
allocate b;
b.c = 5;
write factorial(6);
write b.c;
Записи аналогичны структурам C. Причина всех объявлений переменных состоит в том, чтобы заставить генератор кода использовать стек.
Сборочный выход:
.section .data
heap:
next:
.section .rodata
form: .asciz "%d\n"
.globl main
# Function definition START
L1:
push %rbp
movq %rsp, %rbp
push %rbx
subq $112, %rsp
L2:
cmp $1, %rdx
je L4
movq 16(%rbp), %rsi
jmp L3
L4:
movq %rbp, %rsi
L3:
movq $0, %rbx
push %rdx
movq 80(%rsi), %rdx
leaq (%rdx, %rbx, 8), %rcx
pop %rdx
L5:
cmp $1, %rdx
je L7
movq 16(%rbp), %rsi
jmp L6
L7:
movq %rbp, %rsi
L6:
movq $0, %rbx
push %rdx
movq 80(%rsi), %rdx
leaq (%rdx, %rbx, 8), %rcx
pop %rdx
movq $1, -40(%rbp)
movq (%rcx), %rbx
movq %rbx, -48(%rbp)
movq -40(%rbp), %rbx
addq %rbx, -48(%rbp)
movq -48(%rbp), %rbx
movq %rbx, (%rcx)
movq $1, -64(%rbp)
movq 96(%rbp), %rbx
cmp %rbx, -64(%rbp)
je L10
movq $0, -72(%rbp)
jmp L11
L10:
movq $1, -72(%rbp)
L11:
movq $0, -80(%rbp)
movq 96(%rbp), %rbx
cmp %rbx, -80(%rbp)
je L12
movq $0, -88(%rbp)
jmp L13
L12:
movq $1, -88(%rbp)
L13:
cmp $0, -88(%rbp)
jne L8
cmp $0, -72(%rbp)
jne L8
movq $0, -56(%rbp)
jmp L9
L8:
movq $1, -56(%rbp)
L9:
cmp $0, -56(%rbp)
je L14
movq $1, -96(%rbp)
movq -96(%rbp), %rax
jmp L15
L14:
movq $1, -104(%rbp)
movq 96(%rbp), %rbx
movq %rbx, -112(%rbp)
movq -104(%rbp), %rbx
subq %rbx, -112(%rbp)
push -112(%rbp)
push %rdi
push %r8
push %r9
push %r10
push %r11
push %r12
push %r13
push %r14
push %r15
cmp $1, %rdx
jle L16
push 16(%rbp)
jmp L17
L16:
push %rbp
L17:
addq $1, %rdx
call L1
addq $8, %rsp
pop %r15
pop %r14
pop %r13
pop %r12
pop %r11
pop %r10
pop %r9
pop %r8
pop %rdi
addq $8, %rsp
movq 96(%rbp), %rbx
imulq %rax, %rbx
movq %rbx, -120(%rbp)
movq -120(%rbp), %rax
L15:
addq $112, %rsp
pop %rbx
movq %rbp, %rsp
pop %rbp
subq $1, %rdx
ret
# Function definition END
main:
# Program prologue.
push %rbp
movq %rsp, %rbp
push %rbx
# Allocating heap.
movq $16384, %rdi
call malloc
# Assigning "heap" and "next" to start of heap.
movq %rax, heap
movq %rax, next
# Stack frame level. Starts at 0.
movq $0, %rdx
movq $next, %r8
movq $1, %rcx
imulq $8, %rcx
addq %rcx, next
movq $0, %rbx
leaq (%r8, %rbx, 8), %rcx
movq $5, %r9
movq %r9, (%rcx)
movq $6, %r10
push %r10
push %rdi
push %r8
push %r9
push %r10
push %r11
push %r12
push %r13
push %r14
push %r15
cmp $1, %rdx
jle L18
push 16(%rbp)
jmp L19
L18:
push %rbp
L19:
addq $1, %rdx
call L1
addq $8, %rsp
pop %r15
pop %r14
pop %r13
pop %r12
pop %r11
pop %r10
pop %r9
pop %r8
pop %rdi
addq $8, %rsp
push %rdi
push %r8
push %r9
push %r10
push %r11
push %r12
push %r13
push %r14
push %r15
push %rsi
push %rdx
push %rcx
leaq form(%rip), %rdi
movq %rax, %rsi
xor %rax, %rax
call printf
pop %rcx
pop %rdx
pop %rsi
pop %r15
pop %r14
pop %r13
pop %r12
pop %r11
pop %r10
pop %r9
pop %r8
pop %rdi
movq $0, %rbx
leaq (%r8, %rbx, 8), %rcx
push %rdi
push %r8
push %r9
push %r10
push %r11
push %r12
push %r13
push %r14
push %r15
push %rsi
push %rdx
push %rcx
leaq form(%rip), %rdi
movq (%rcx), %rsi
xor %rax, %rax
call printf
pop %rcx
pop %rdx
pop %rsi
pop %r15
pop %r14
pop %r13
pop %r12
pop %r11
pop %r10
pop %r9
pop %r8
pop %rdi
# Program epilogue.
pop %rbx
movq %rbp, %rsp
pop %rbp
movq $0, %rax
ret
Ожидаемый результат от этого кода должен состоять в том, что он печатает 720 (из factorial (6)) и 11 (b.c назначается 5, а затем увеличивается в 6 раз).
В WSL это дает ошибку сегментации, в то время как в физическом дистрибутиве он печатает правильные значения и корректно завершается.
Мы используем gcc -m64 -no-pie "filename"
для компиляции этого вывода сборки.
При запуске GDB в WSL с исполняемым файлом отладчик возвращает следующее:
Starting program:
Program received signal SIGSEGV, Segmentation fault.
__GI___tcgetattr (fd=1, termios_p=termios_p@entry=0x7ffffffed528) at ../sysdeps/unix/sysv/linux/tcgetattr.c:42
42 ../sysdeps/unix/sysv/linux/tcgetattr.c: No such file or directory.
Использование x / i $ rip в GDB возвращает следующее:
0x7fffff115cc1 <__GI___tcgetattr+65>: movdqa (%rsp),%xmm0
И информационные регистры возвращают:
rax 0x0 0
rbx 0xffffffffffffff80 -128
rcx 0x0 0
rdx 0x0 0
rsi 0xff 255
rdi 0x1 1
rbp 0x7fffff3ec760 0x7fffff3ec760 <_IO_2_1_stdout_>
rsp 0x7ffffffed4d8 0x7ffffffed4d8
r8 0x7ffffffed518 140737488278808
r9 0x0 0
r10 0x0 0