Why is the ELF execution entry point virtual address of the form 0x80xxxxx and not zero 0x0?

14,813

Solution 1

As Mads pointed out, in order to catch most accesses through null pointers, Unix-like systems tend to make the page at address zero "unmapped". Thus, accesses immediately trigger a CPU exception, in other words a segfault. This is quite better than letting the application go rogue. The exception vector table, however, can be at any address, at least on x86 processors (there is a special register for that, loaded with the lidt opcode).

The starting point address is part of a set of conventions which describe how memory is laid out. The linker, when it produces an executable binary, must know these conventions, so they are not likely to change. Basically, for Linux, the memory layout conventions are inherited from the very first versions of Linux, in the early 90's. A process must have access to several areas:

  • The code must be in a range which includes the starting point.
  • There must be a stack.
  • There must be a heap, with a limit which is increased with the brk() and sbrk() system calls.
  • There must be some room for mmap() system calls, including shared library loading.

Nowadays, the heap, where malloc() goes, is backed by mmap() calls which obtain chunks of memory at whatever address the kernel sees fit. But in older times, Linux was like previous Unix-like systems, and its heap required a big area in one uninterrupted chunk, which could grow towards increasing addresses. So whatever was the convention, it had to stuff code and stack towards low addresses, and give every chunk of the address space after a given point to the heap.

But there is also the stack, which is usually quite small but could grow quite dramatically in some occasions. The stack grows down, and when the stack is full, we really want the process to predictably crash rather than overwriting some data. So there had to be a wide area for the stack, with, at the low end of that area, an unmapped page. And lo! There is an unmapped page at address zero, to catch null pointer dereferences. Hence it was defined that the stack would get the first 128 MB of address space, except for the first page. This means that the code had to go after those 128 MB, at an address similar to 0x080xxxxx.

As Michael points out, "losing" 128 MB of address space was no big deal because the address space was very large with regards to what could be actually used. At that time, the Linux kernel was limiting the address space for a single process to 1 GB, over a maximum of 4 GB allowed by the hardware, and that was not considered to be a big issue.

Solution 2

Why not start at address 0x0? There's at least two reasons for this:

  • Because address zero is famously known as a NULL pointer, and used by programming languages to sane check pointers. You can't use an address value for that, if you're going to execute code there.
  • The actual contents at address 0 is often (but not always) the exception vector table, and is hence not accessible in non-privileged modes. Consult the documentation of your specific architecture.

As for the entrypoint _start vs main: If you link against the C runtime (the C standard libraries), the library wraps the function named main, so it can initialize the environment before main is called. On Linux, these are the argc and argv parameters to the application, the env variables, and probably some synchronization primitives and locks. It also makes sure that returning from main passes on the status code, and calls the _exit function, which terminates the process.

Share:
14,813

Related videos on Youtube

Michael L.
Author by

Michael L.

Updated on December 28, 2020

Comments

  • Michael L.
    Michael L. over 3 years

    When executed, program will start running from virtual address 0x80482c0. This address doesn't point to our main() procedure, but to a procedure named _start which is created by the linker.

    My Google research so far just led me to some (vague) historical speculations like this:

    There is folklore that 0x08048000 once was STACK_TOP (that is, the stack grew downwards from near 0x08048000 towards 0) on a port of *NIX to i386 that was promulgated by a group from Santa Cruz, California. This was when 128MB of RAM was expensive, and 4GB of RAM was unthinkable.

    Can anyone confirm/deny this?

  • datenwolf
    datenwolf almost 13 years
    In C null pointers may have completely different value than 0 on the lowest level. Within the scope of C (source code) the invalid pointer value of the machine must map to 0. Technically there's no requirement for C null pointer to actually map to address zero.
  • Ciro Santilli OurBigBook.com
    Ciro Santilli OurBigBook.com about 9 years
    _GLOBAL_OFFSET_TABLE_ also points in the 0x200XXX range in Binutils 2.24.
  • yyny
    yyny about 4 years
    @datenwolf This is a moot point, all modern processors represent addresses as two's complement integers, representing NULL as anything other than 0 in that case would be a pointless performance hit. Just because the standard allows it doesn't mean it's a good idea. Even in embedded environments with very limited memory address 0x00 is typically reserved for NULL.
  • datenwolf
    datenwolf about 4 years
    @yyny: Something something 8086 real mode… Also I wasn't referring to 2s completment vs. other kind of numer representation, but trap values.
  • yyny
    yyny about 4 years
    @datenwolf I know of no C compilers that target 8086. This is my entire point. The ANSI C standard was written with forwards compatibility in mind, in a time where it was still conceivable that segmented memory would become common-place. Nowadays virtually every processor uses twos complement integer-based addressing, meaning there is no practical reason to represent NULL as a non-zero value. C is over 30 years old at this point, and it's been pretty well agreed upon that converting a NULL pointer constant to an integer results in a value of 0.
  • yyny
    yyny about 4 years
    Or from another angle, with paged memory the 0x00 address can be mapped to whatever page table the kernel pleases. In fact, on some mother boards and with certain bootloaders, it is possible to leave the first RAM slot empty, meaning there in some sense isn't even a physical address 0. However, the entire point is still moot. Fact is, every conceivable processor that runs C can represent an address of 0x00, and every conceivable kernel and application expects this to be the case. Citing the C standard as if it's the ultimate truth is counter-productive, and harmful to new programmers.