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í?