Updated in 2024-01.
This article describes target-specific details about x86 in ELF linkers. I will use "x86" to refer to both x86-32 and x86-64.
Global Offset Table
The Global Offset Table consists of two sections:
.got.plt
holds code addresses for PLT..got
holds other addresses and offsets.
The symbol _GLOBAL_OFFSET_TABLE_
is defined at the
beginning of the .got.plt
section. If .got.plt
is absent, GNU ld defines _GLOBAL_OFFSET_TABLE_
at the
beginning of the .got
section.
.got.plt
has 3 reserved entries.
.got.plt[0]
holds the link-time address of
_DYNAMIC
for a legacy reason. Versions of glibc prior to
2.35 have the _DYNAMIC
requirement. See All
about Global Offset Table.
.got.plt[1]
and .got.plt[2]
are for lazy
binding PLT. Linkers communicate the address of .got.plt
to
rtld with the dynamic tag DT_PLTGOT
.
GOT optimization
See All about Global Offset Table#GOT optimization.
Procedure Linkage Table
Indirect Branch Tracking
See .note.gnu.property
below for Indirect Branch
Tracking. See All about
Procedure Linkage Table#x86 for detail.
The scheme used in GNU ld unnecessarily uses two sections
.plt
and .plt.sec
. ld.lld follows suit to
avoid adding complexity to tools like objdump (PLT recognition). mold
adopts an alternative scheme (what Indirect Branch Tracking should have
used in the first place). Unfortunately, this is a scenario that the
ship has sailed and adding an alternative would not simplify the world.
I think this just adds implementation complexity for other tools which
want to support its scheme.
Retpoline
Retpoline is for Spectre v2 mitigation. PLT entries are synthesized
by the linker and need adaptation as well. This can be enabled in ld.lld
with -z retpolineplt
.
mark-plt
See x86 PLT rewriting on All about Procedure Linkage Table.
Thread Local Storage
x86 uses TLS Variant II: the static TLS blocks are placed below the thread pointer.
Beside the traditional general dynamic and local dynamic TLS models, there are TLSDESC ABIs for x86-32 and x86-64.
The linker performs TLS optimization.
See All about thread-local storage.
Program Property
A .note.gnu.property
section contains program property
notes which describe special handling requirements for the linker and
the dynamic loader.
x86 psABIs define many property notes but many don't seem particularly useful.
GNU_PROPERTY_X86_ISA_1_BASELINE
,GNU_PROPERTY_X86_ISA_1_V2
,GNU_PROPERTY_X86_ISA_1_V2
,GNU_PROPERTY_X86_ISA_1_V3
: these properties describe the x86 ISA levelGNU_PROPERTY_X86_ISA_1_USED
,GNU_PROPERTY_X86_ISA_1_NEEDED
: deprecated when the x86 ISA level was introducedGNU_PROPERTY_X86_FEATURE_1_IBT
,GNU_PROPERTY_X86_FEATURE_1_SHSTK
: used by Intel CET (see Control flow integrity). See below
For x86, the linker parses input .note.gnu.property
sections and recognize -z force-ibt
and
-z shstk
to compute the output
.note.gnu.property
(type is SHT_NOTE
)
section.
The following code (extracted from ld.lld) describes the behavior.
Basically, without extra options, the output has the
GNU_PROPERTY_X86_FEATURE_1_IBT
bit if all input
.note.gnu.property
sections have the bit (logical AND).
-z force-ibt
forces setting the bit with a warning.
The output has the GNU_PROPERTY_X86_FEATURE_1_SHSK
bit
if all input .note.gnu.property
sections have the bit
(logical AND). -z shstk
forces setting the bit without a
warning.
1 | for (ELFFileBase *f : ctx.objectFiles) { |
.eh_frame
.eh_frame
sections are usually of type
SHT_PROGBITS
. It is regarded as a mistake that a
special-purpose section does not have a dedicated type. The x86-64 psABI
says that SHT_X86_64_UNWIND
should be used. For future
architectures, it would be good not to reserve 0x70000001 for other
purposes.
Clang since rL252300 emits .eh_frame
sections of type
SHT_X86_64_UNWIND
to conform to the psABI. Linkers need to
allow mixed SHT_PROGBITS
and SHT_X86_64_UNWIND
.eh_frame
sections.
.gnu.linkonce.t.__x86.get_pc_thunk.bx
The magic symbol prefix .gnu.linkonce
was used before
COMDAT was introduced into ELF. .gnu.linkonce
was very
obsoleted now, but unfortunately
.gnu.linkonce.t.__x86.get_pc_thunk.bx
remained relevant in
glibc x86-32 until glibc 2.32 (2020-08).
Split stack
gccgo uses a segmented stack scheme called "split stack". Split stack permits a discontiguous stack which is grown automatically as needed. (The gc Go compiler now uses stack copying rather than stack splitting. Using stack copying in gccgo is difficult as the compiler needs to have accurate stack maps for all frames on the stack, knowing absolutely every pointer.)
The scheme calls runtime functions (__morestack*
)
defined in libgcc and requires the linker to rewrite some code
sequences.
Each relocatable object file using split stack has a marker section
named .note.GNU-split-stack
. The linker uses this section
to recognize relocatable object files compiled with split stack
support.
For a function call from a split-stack relocatable object file to a non-split-stack relocatable object file, the linker rewrites the function prologue. The prologue can be of either of the following two schemes.
1 | cmp rsp, qword ptr fs:[0x70] |
1 | lea r10, [rsp-0x100] # The register is r10 or r11 |