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
When I run this code I get:
hello!
Segmentation fault (core dumped)
What is wrong with the code?
The print_string subroutine is modifying the stack pointer without restoring it.
The subroutine main
has the following layout:
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
Similarly, the print_string subroutine should have that same layout, saving the stack pointer and then restoring it before ret
ing. Any subroutines that use the stack should save and restore the stack pointer.
push ebp
mov ebp, esp
pusha
push msg
call printf ;should print "Hello"
add esp, 4
popa
mov esp, ebp
pop ebp
ret
Without saving the stack pointer and restoring it, the called subroutine modifies the stackpointer, where the call
instruction saved the return address. Consequently, when ret
is encountered, execution jumps to the wrong return address, therefore segfaulting. Read more on calling conventions and functions in assembly.
printf expects a pointer pushed on the stack for an argument but under C calling convention it is your task to remove this argument from the stack.
You omitted this and so the instruction popa
puts wrong values in all GPRS and the instruction ret
uses the original value of EAX as a destination address thus triggering a segmentation fault.
Solution 1 cleaning up manually
print_string:
pusha
push msg
call printf ;should print "Hello"
add esp, 4 ; <-- Clean-up
popa
ret ;return back to main
Solution 2 using prolog/epilog on 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
Solution 2 is only possible because printf is a well-behaving function that preserves the EBP register. By moving EBP into ESP every extra item that was pushed between the prolog and epilog disappears. Solution 2 can save you from a lot of those add esp, 4
instructions (when routines become longer).