linux assembly nasm x86-64 calling-convention

linux - Glibc scanf Fallos de segmentación cuando se llama desde una función que no alinea RSP



assembly nasm (1)

Use 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 una call .

O mejor es empujar / pop rcx un registro ficticio, por ejemplo, push rdx / pop rcx , o guardar / restaurar un registro conservado de llamadas como RBP.

En la entrada de la función, RSP está a 8 bytes de la alineación de 16 bytes porque la call insertó una dirección de retorno de 8 bytes. Vea Imprimir números de coma flotante desde x86-64 parece que se necesita guardar% rbp , alineación principal y de pila y Llamando a printf en x86_64 usando el ensamblador GNU . Este es un requisito de ABI que solía ser capaz de evitar violar cuando no había ningún argumento FP para printf. Pero ya no más.

El código-gen de gcc para glibc scanf ahora depende de la alineación de la pila de 16 bytes, incluso cuando AL == 0 .

Parece tener una copia auto-vectorizada de 16 bytes en algún lugar de __GI__IO_vfscanf , que el scanf normal llama después de derramar su registro args a la pila 1 . (Las muchas formas similares de llamar a scanf comparten una gran implementación como back-end a 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 7z x blah.deb los archivos (con 7z x blah.deb y tar xf data.tar , porque 7z sabe cómo extraer muchos formatos de archivo).

Puedo reprochar 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 set env LD_LIBRARY_PATH /tmp/bionic-libc/lib/x86_64-linux-gnu . 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 │

Así que copió dos objetos de 8 bytes en la pila con movq + movhps para cargar y movaps para almacenar. Pero con la pila desalineada, movaps [rbp-0x470],xmm0 fallas.

No tomé una compilación de depuración para averiguar 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 ha vuelto lo suficientemente inteligente como para aprovechar mejor el SSE2 de esta manera.

Nota al pie 1: ¡printf / scanf con AL != 0 siempre ha requerido una alineación de 16 bytes porque el código-gen de gcc para funciones variad usa la prueba al, al / je para derramar los registros XMM completos de 16 bytes xmm0..7 con las tiendas alineadas en Ese caso. __m128i puede ser un argumento para una función variadic, no solo el double , y gcc no comprueba si la función alguna vez lee realmente cualquier argumento FP de 16 bytes.

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

utilizando:

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

y luego correr

./example

se ejecuta, imprime: ingresa un número: pero luego se bloquea e imprime: falla de segmentación (núcleo volcado)

Así que printf funciona bien pero no scanf. ¿Qué estoy haciendo mal con scanf así?