glibc scanf Errores de segmentación cuando se llama desde una función que no alinea RSP

Resuelto user3057544 asked hace 6 años • 0 respuestas

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?

user3057544 avatar Jun 28 '18 03:06 user3057544
Aceptado

Utilice sub rsp, 8/ add rsp, 8al 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*nbytes para números enteros n.

Al ingresar la función, RSP está a 8 bytes de la alineación de 16 bytes porque callenvió 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 == 8al ingresar una función, debe asegurarse de tener una función RSP % 16 == 0ante usted . callCó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 scanfllama 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, fscanfetc.)

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.deby 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-gnuluego 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+ movhpspara cargar y movapsalmacenar. Pero con la pila desalineada, movaps [rbp-0x470],xmm0fallas.

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 != 0siempre 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. __m128ipuede 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.

Peter Cordes avatar Jun 27 '2018 22:06 Peter Cordes