assembly - español - nasm tutorial
Asamblea x86, obteniendo falla de segmentación (2)
La subrutina print_string está modificando el puntero de pila sin restaurarlo. La subrutina main
tiene el siguiente diseño:
push ebp ;save the stack frame of the caller
mov ebp, esp ;save the stack pointer
<code for subroutine>
mov esp, ebp ;restore the stack pointer
pop ebp ;restore the caller''s stack frame
ret ;return to address pushed onto the stack by call
De forma similar, la subrutina print_string debe tener el mismo diseño, guardando el puntero de pila y luego restaurándolo antes de ret
. Cualquier subrutina que use la pila debe guardar y restaurar el puntero de la pila.
push ebp
mov ebp, esp
pusha
push msg
call printf ;should print "Hello"
add esp, 4
popa
mov esp, ebp
pop ebp
ret
Sin guardar el puntero de pila y restaurarlo, la subrutina llamada modifica el stackpointer, donde la instrucción de call
guardó la dirección de retorno. En consecuencia, cuando se encuentra ret
, la ejecución salta a la dirección de devolución incorrecta, por lo tanto segfaulting. Obtenga más información sobre las convenciones y funciones de llamada en ensamblaje.
section .data
msg: db "hello!", 10, 0 ;my message
section .text
extern printf ;C printf function
global main
main:
push ebp
mov ebp, esp
call print_string
mov esp, ebp
pop ebp
ret ;end of program
print_string:
pusha
push msg
call printf ;should print "Hello"
popa
ret ;return back to main
Cuando ejecuto este código obtengo:
¡Hola!
Fallo de segmentación (núcleo volcado)
¿Qué está mal con el código?
printf espera un puntero empujado en la pila para un argumento, pero bajo la convención de llamadas C es su tarea eliminar este argumento de la pila.
Usted omitió esto y entonces la instrucción popa
pone los valores incorrectos en todos los GPRS y la instrucción ret
utiliza el valor original de EAX como una dirección de destino desencadenando así un error de segmentación.
Solución 1 limpiando manualmente
print_string:
pusha
push msg
call printf ;should print "Hello"
add esp, 4 ; <-- Clean-up
popa
ret ;return back to main
Solución 2 usando prolog / epilog en print_string
print_string:
push ebp
mov ebp, esp
push msg
call printf ;should print "Hello"
mov esp, ebp ; <-- Clean-up
pop ebp
ret ;return back to main
La solución 2 solo es posible porque printf es una función que se comporta bien y que preserva el registro EBP. Al mover EBP a ESP, cada elemento extra que se empujó entre el prólogo y el epílogo desaparece . La solución 2 puede salvarlo de muchos de los que add esp, 4
instrucciones add esp, 4
(cuando las rutinas se vuelven más largas).