glibc scanf Errores de segmentación cuando se llama desde una función que no alinea RSP
Al compilar el siguiente código:
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
usando:
nasm -f elf64 example.asm -o example.o
gcc -no-pie -m64 example.o -o example
y luego correr
./example
se ejecuta, imprime: ingrese un número: pero luego falla e imprime: Fallo de segmentación (núcleo volcado)
Entonces printf funciona bien pero scanf no. ¿Qué estoy haciendo mal con scanf entonces?
Utilice sub rsp, 8
/ add rsp, 8
al inicio/final de su función para realinear la pila a 16 bytes antes de que su función realice un archivo call
.
O mejor empujar/hacer estallar un registro ficticio, por ejemplo push rdx
/ pop rcx
, o un registro de llamada preservada como RBP que realmente desea guardar de todos modos. Necesita que el cambio total a RSP sea un múltiplo impar de 8 contando todas las pulsaciones y sub rsp
, desde la entrada de función hasta cualquiera call
.
es decir, 8 + 16*n
bytes para números enteros n
.
Al ingresar la función, RSP está a 8 bytes de la alineación de 16 bytes porque call
envió una dirección de retorno de 8 bytes. Consulte Imprimir números de punto flotante desde x86-64 parece requerir que se guarde %rbp ,
la alineación principal y de la pila , y Llamar a printf en x86_64 usando el ensamblador GNU . Este es un requisito de ABI que solías poder violar cuando no había ningún argumento de FP para printf. Pero ya no más.
Consulte también ¿Por qué la ABI x86-64/AMD64 System V exige una alineación de pila de 16 bytes?
Para decirlo de otra manera, RSP % 16 == 8
al ingresar una función, debe asegurarse de tener una función RSP % 16 == 0
ante usted . call
Cómo haces esto no importa. (No todas las funciones fallarán si no lo hace, pero la ABI sí lo requiere/garantiza).
La generación de código de gcc para glibc scanf ahora depende de la alineación de la pila de 16 bytes
incluso cuandoAL == 0
.
Parece haber copiado automáticamente 16 bytes en algún lugar __GI__IO_vfscanf
, lo que scanf
llama regularmente después de derramar sus argumentos de registro en la pila 1 . (Las muchas formas similares de llamar a scanf comparten una gran implementación como back-end para los diversos puntos de entrada de libc como scanf
, fscanf
etc.)
Descargué el paquete binario libc6 de Ubuntu 18.04: https://packages.ubuntu.com/bionic/amd64/libc6/download y extraje los archivos (con 7z x blah.deb
y tar xf data.tar
, porque 7z sabe cómo extraer muchos formatos de archivos).
Puedo reproducir tu error con LD_LIBRARY_PATH=/tmp/bionic-libc/lib/x86_64-linux-gnu ./bad-printf
, y también resulta con el sistema glibc 2.27-3 en mi escritorio Arch Linux.
Con GDB, lo ejecuté en su programa y set env LD_LIBRARY_PATH /tmp/bionic-libc/lib/x86_64-linux-gnu
luego lo hice run
. Con layout reg
, la ventana de desmontaje se ve así en el punto donde recibió 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 │
Entonces copió dos objetos de 8 bytes a la pila con movq
+ movhps
para cargar y movaps
almacenar. Pero con la pila desalineada, movaps [rbp-0x470],xmm0
fallas.
No tomé una compilación de depuración para descubrir exactamente qué parte de la fuente C se convirtió en esto, pero la función está escrita en C y compilada por GCC con la optimización habilitada. A GCC siempre se le ha permitido hacer esto, pero solo recientemente se volvió lo suficientemente inteligente como para aprovechar mejor SSE2 de esta manera.
Nota al pie 1: printf / scanf with AL != 0
siempre ha requerido una alineación de 16 bytes porque el code-gen de gcc para funciones variadas usa test al,al / je para derramar los registros XMM completos de 16 bytes xmm0..7 con tiendas alineadas en ese caso. __m128i
puede ser un argumento para una función variada, no solo double
, y gcc no verifica si la función alguna vez lee algún argumento FP de 16 bytes.