x86 Assembly, getting segmentation fault

2019-03-03 08:41发布

问题:

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?

回答1:

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 reting. 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.



回答2:

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).