glibc scanf Ошибка сегментации при вызове из функции, которая не выравнивает RSP - PullRequest
0 голосов
/ 27 июня 2018

При компиляции ниже кода:

global main
extern printf, scanf

section .data
   msg: db "Enter a number: ",10,0
   format:db "%d",0

section .bss
   number resb 4

section .text
main:
   mov rdi, msg
   mov al, 0
   call printf

   mov rsi, number
   mov rdi, format
   mov al, 0
   call scanf

   mov rdi,format
   mov rsi,[number]
   inc rsi
   mov rax,0
   call printf 

   ret

с помощью:

nasm -f elf64 example.asm -o example.o
gcc -no-pie -m64 example.o -o example

, а затем запустить

./example

запускается, выведите: введите число: но потом вылетает и печатает Ошибка сегментации (ядро сброшено)

Так что printf работает нормально, но scanf нет. Что я делаю не так с scanf?

1 Ответ

0 голосов
/ 28 июня 2018

Используйте sub rsp, 8 / add rsp, 8 в начале / конце вашей функции , чтобы заново выровнять стек до 16 байтов, прежде чем ваша функция выполнит call.

Или лучше выдвинуть / выдвинуть фиктивный регистр, например, push rdx / pop rcx, или сохраните / восстановите регистр, сохраняющий вызов, такой как RBP.

При входе в функцию RSP находится на расстоянии 8 байтов от 16-байтового выравнивания, поскольку call выдвинул 8-байтовый адрес возврата. См. Для печати чисел с плавающей запятой из x86-64 требуется% rbp для сохранения , main и stack выравнивание и Вызов printf в x86_64 с использованием ассемблера GNU . Это требование ABI, которое вы привыкли обходить нарушением, когда не было аргументов FP для printf. Но не больше.


Код gcc для glibc scanf теперь зависит от выравнивания стека 16 байтов, даже если AL == 0.

Кажется, что оно имеет векторизованное копирование 16 байтов где-то в __GI__IO_vfscanf, которое регулярно scanf вызывает после выброса его регистровых аргументов в стек 1 . (Многие похожие способы вызова scanf совместно используют одну большую реализацию в качестве серверной части для различных точек входа libc, таких как scanf, fscanf и т. Д.)

Я скачал двоичный пакет libc6 для Ubuntu 18.04: https://packages.ubuntu.com/bionic/amd64/libc6/download и извлек файлы (с 7z x blah.deb и tar xf data.tar, потому что 7z знает, как извлечь много форматов файлов).

Я могу воспроизвести вашу ошибку с помощью LD_LIBRARY_PATH=/tmp/bionic-libc/lib/x86_64-linux-gnu ./bad-printf, а также получается с системой glibc 2.27-3 на моем рабочем столе Arch Linux.

С GDB я запустил его в вашей программе и сделал set env LD_LIBRARY_PATH /tmp/bionic-libc/lib/x86_64-linux-gnu, затем run. С layout reg окно разборки выглядит так в том месте, где оно получило SIGSEGV:

   │0x7ffff786b49a <_IO_vfscanf+602>        cmp    r12b,0x25                                                                                             │
   │0x7ffff786b49e <_IO_vfscanf+606>        jne    0x7ffff786b3ff <_IO_vfscanf+447>                                                                      │
   │0x7ffff786b4a4 <_IO_vfscanf+612>        mov    rax,QWORD PTR [rbp-0x460]                                                                             │
   │0x7ffff786b4ab <_IO_vfscanf+619>        add    rax,QWORD PTR [rbp-0x458]                                                                             │
   │0x7ffff786b4b2 <_IO_vfscanf+626>        movq   xmm0,QWORD PTR [rbp-0x460]                                                                            │
   │0x7ffff786b4ba <_IO_vfscanf+634>        mov    DWORD PTR [rbp-0x678],0x0                                                                             │
   │0x7ffff786b4c4 <_IO_vfscanf+644>        mov    QWORD PTR [rbp-0x608],rax                                                                             │
   │0x7ffff786b4cb <_IO_vfscanf+651>        movzx  eax,BYTE PTR [rbx+0x1]                                                                                │
   │0x7ffff786b4cf <_IO_vfscanf+655>        movhps xmm0,QWORD PTR [rbp-0x608]                                                                            │
  >│0x7ffff786b4d6 <_IO_vfscanf+662>        movaps XMMWORD PTR [rbp-0x470],xmm0                                                                          │

Таким образом, он скопировал два 8-байтовых объекта в стек с movq + movhps для загрузки и movaps для хранения. Но со смещением стека, movaps [rbp-0x470],xmm0 сбоев.

Я не взял отладочную сборку, чтобы выяснить, какая именно часть исходного кода C превратилась в эту, но функция написана на C и скомпилирована GCC с включенной оптимизацией. GCC всегда разрешалось делать это, но лишь недавно он стал достаточно умным, чтобы таким образом использовать преимущества SSE2.


Сноска 1: для printf / scanf с AL != 0 всегда требовалось выравнивание по 16 байт, потому что код gcc для переменных функций использует test al, al / je, чтобы пролить полные 16-байтовые регистры XMM xmm0..7 с выровненным магазины в этом случае. __m128i может быть аргументом переменной функции, а не только double, и gcc не проверяет, действительно ли функция когда-либо читает какие-либо 16-байтовые аргументы FP.

...