Jason Yu
Jason Yu

Reputation: 2038

Questions regarding glibc startup code for X86-64

I am looking into the startup code in Glibc for x86-64, and I am curious about the below two places which I highlighted in the screenshot directly.

Code: https://github.com/bminor/glibc/blob/master/sysdeps/x86_64/start.S

  1. Why do we need to store the so-called "garbage" on stack?
  2. Why do we just simply reset the registers r8 and rcx instead of passing the function pointers of .init and .fini to them?

enter image description here

Upvotes: 3

Views: 375

Answers (1)

Florian Weimer
Florian Weimer

Reputation: 33694

pushq %rax is needed for stack alignment: RSP % 16 == 0 before any call instruction.

In current ELF version, ELF constructors and destructors are identifiable through dynamic tags (DT_INIT_ARRAY and DT_FINI_ARRAY is generally preferred because of improved unwinding support). In glibc before version 2.34, the statically linked startup code (of which the quoted assembler fragment is a part) actually had its own loop to process DT_INIT_ARRAY. In glibc 2.34, this code was removed because it turned out that it contained a very generic jump-oriented programming (JOP) gadget.

The .init function pointer is still passed as zero because this allows to share the implementation of __libc_start_main (which is called further down) for old and new binaries. glibc versions 2.34 and later look at this function pointer and call it if non-null, else use DT_INIT and DT_INIT_ARRAY to find the functions to call.

Another reason for making the .init change this way was that it was necessary to change the startup code on all 21 glibc architectures, and it is easier to change a parameter to zero than to remove it because it does not affect the function signature or calling convention.

Pretty much the same rationale applies to the .fini change, except in this case, the old dynamically linked __libc_start_main implementation already ignored it.

Upvotes: 3

Related Questions