Search code examples
cassemblyx86-64program-entry-point

Return values in main vs _start


Note, this question already has similar answers here, which I want to point out to:

However this question is asking more about the return formats of them and how they relate to each other (which I don't think is entirely covered in the above questions).


What are the differences between _start and main ? It seems to me like ld uses _start, but that gcc uses main as the entry point. The other difference that I've noticed is that main seems to return the value in %rax, whereas _start returns the value in %rbx

The following is an example of the two ways I'm seeing this:

.globl _start
_start:
    mov $1, %rax
    mov $2, %rbx
    int $0x80

And to run it:

$ as script.s -o script.o; ld script.o -o script; ./script; echo $?
# 2

And the other way:

.globl main
main:
    mov $3, %rax
    ret

And to run it:

$ gcc script.s -o script; ./script; echo $?
3

What is the difference between these two methods? Does main automatically invoke _start somewhere, or how do they relate to each other? Why does one return their value in rbx whereas the other one returns it in rax ?


Solution

  • TL:DR: function return values and system-call arguments use separate registers because they're completely unrelated.


    When you compile with gcc, it links CRT startup code that defines a _start. That _start (indirectly) calls main, and passes main's return value (which main leaves in EAX) to the exit() library function. (Which eventually makes an exit system call, after doing any necessary libc cleanup like flushing stdio buffers.)

    See also Return vs Exit from main function in C - this is exactly analogous to what you're doing, except you're using _exit() which bypasses libc cleanup, instead of exit(). Syscall implementation of exit()

    An int $0x80 system call takes its argument in EBX, as per the 32-bit system-call ABI (which you shouldn't be using in 64-bit code). It's not a return value from a function, it's the process exit status. See Hello, world in assembly language with Linux system calls? for more about system calls.

    Note that _start is not a function; it can't return in that sense because there's no return address on the stack. You're taking a casual description like "return to the OS" and conflating that with a function's "return value". You can call exit from main if you want, but you can't ret from _start.

    EAX is the return-value register for int-sized values in the function-calling convention. (The high 32 bits of RAX are ignored because main returns int. But also, $? exit status can only get the low 8 bits of the value passed to exit().)

    Related: