Why does returning from _start segfault?

2019-03-03 10:17发布

问题:

I tried to put code not in the main function, but directly into _start:

    segment .text
    global _start
_start:
    push rbp
    mov rbp, rsp
    ; ... program logic ...
    leave
    ret

Compile:

yasm -f elf64 main.s
ld -o main main.o

Run:

./main
Segmentation fault(core dumped)

I read, leave is

mov esp,ebp
pop ebp

But why is it that such an epilogue to the pop stack frame and the set base frame pointer to a previous frame's base results in a segmentation fault?

Indeed, making an exit system call exits gracefully.

回答1:

As per ABI1 the stack at the entry on _start is

There is no "return address".
The only way to exit a process is through SYS_EXIT

xorl %edi, %edi   ;Error code
movl $60, %eax    ;SYS_EXIT
syscall

1 Section 3.4.1 Initial Stack and Register State.



回答2:

The LEAVE instruction is defined to not cause any exceptions, so it cannot be the source of your fault. You should be using GDB. Debuggers are invaluable in solving these sorts of problems.

This is what happens:

$ gdb ./main
[...]
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000001 in ?? ()

(gdb) x /gx $rsp-8
0x7fffffffe650: 0x0000000000000001

So, most likely your program ran to completion, but the first thing on the stack that 0x0000000000000001. RET popped that into the RIP register, and then it segfaulted because that address is not mapped.

I don't write a lot of code on Linux, but I would bet that _start is required to use the exit system call. The only way you could possibly return to a useful address is if the kernel put a function somewhere that would do this for you.