glibc scanf Segmentation faults when called from a function that doesn't align RSP

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

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 ответ

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

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

При входе в функцию RSP находится на расстоянии 8 байт от 16-байтового выравнивания, потому что call выдвинул 8-байтовый обратный адрес. См. Печать чисел с плавающей точкой из x86-64, по-видимому, требует сохранения% rbp, выравнивания основного и стекового и вызова 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 для переменных функций gcc использует test al,al / je, чтобы пролить полные 16-байтовые регистры XMM xmm0..7 с выровненными хранилищами в этом случае. __m128i может быть аргументом для переменной функции, а не только double и gcc не проверяет, читает ли функция когда-либо 16-байтовые аргументы FP.

Другие вопросы по тегам