Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is address 0x400000 chosen as a start of text segment in x86_64 ABI?

In this document on p. 27 it says that text segment starts at 0x400000. Why was this particular address chosen? Is there any reason for that? The same address is chosen in GNU ld on Linux:

$ ld -verbose | grep -i text-segment   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS; 

It's surprising because this address is bigger in 32-bit x86 executables:

$ ld -verbose | grep -i text-segment   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS; 

I read this question which discusses why 0x080xxxxx address was chosen for i386 but it doesn't explain a change in x86_64. It's hard to find any explanation on that matter. Does anybody have a clue?

like image 282
user1042840 Avatar asked Sep 25 '16 17:09

user1042840


1 Answers

Bottom line: some technical limitations that amd64 has in using large addresses suggest dedicating the lower 2GiB of address space to code and data for efficiency. Thus the stack has been relocated out of this range.


In i386 ABI1

  • stack is located before the code, growing from just under 0x8048000 downwards. Which provides "a little over 128 MB for the stack and about 2 GB for text and data" (p. 3-22).
  • Dynamic segments start at 0x80000000 (2GiB),
  • and the kernel occupies the "reserved area" at the top which the spec allows to be up to 1GiB, starting at at least 0xC0000000 (p. 3-21) (which is what it typically does).
  • The main program is not required to be position-independent.
  • An implementation is not required to catch null pointer access (p. 3-21) but it's reasonable to expect that some of the stack space above 128MiB (which is 288KiB) will be reserved for that purpose.

amd64 (whose ABI is formulated as an amendment to the i386 one (p. 9)) has a vastly bigger (48-bit) address space but most instructions only accept 32-bit immediate operands (which include direct addresses and offsets in jump instructions), requiring more work and less efficient code (especially when taking instruction interdependency into consideration) to handle larger values. Measures to work around these limitations are summarized by the authors by introducing a few "code models" they recommend to use to "allow the compiler to generate better code". (p. 33)

  • Specifically, the first of them, "Small code model", suggests using addresses "in the range from 0 to 231-224-1 or from 0x00000000 to 0x7effffff" which allows some very efficient relative references and array iteration. This is 1.98GiB which is more than enough for many programs.
  • "Medium code model" is based on the previous one, splitting the data into a "fast" part under the above boundary and the "slower" remaining part which requires a special instruction to access. While code remains under the boundary.
  • And only the "large" model makes no assumptions about sizes, requiring the compiler "to use the movabs instruction, as in the medium code model, even for dealing with addresses inside the text section. Additionally, indirect branches are needed when branching to addresses whose offset from the current instruction pointer is unknown." They go on to suggest splitting the code base into multiple shared libraries since these measures do not apply for relative references with offsets that are known to be within bounds (as outlined in "Small position independent code model").

Thus the stack was moved to under the shared library space (0x80000000000, 128GiB) because its addresses are never immediate operands, always referenced either indirectly or with lea/mov from another reference, thus only relative offset limitations apply.


The above explains why the loading address was moved to a lower address. Now, why was it moved to exactly 0x400000 (4MiB)? Here, I came empty so, summarizing what I've read in the ABI specs, I can only guess that it felt "just right":

  • It's large enough to catch any likely incorrect structure offset, allowing for larger data units that amd64 operates on, yet small enough to not waste much of the valuable starting 2GiB of address space.
  • It's equal to the largest practical page size to date and is a multiple of all other virtual memory unit sizes one can think of.

1Note that actual x32 Linuxes have been deviating from this layout more and more as time goes. But we're talking about the ABI spec here since the amd64 one is formally based on it rather than any derived layout (see its paragraph for citation).

like image 177
ivan_pozdeev Avatar answered Oct 03 '22 10:10

ivan_pozdeev