Вызов ассемблера для printf - различное поведение в WSL и автономной Ubuntu 18.04 64bit - PullRequest
2 голосов
/ 04 мая 2019

В настоящее время я работаю над созданием компилятора в рамках проекта бакалавра. Трудным требованием является то, чтобы он мог работать в 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
...