Description
ELF spec say that program headers must be sorted by p_vaddr
Loadable segment entries in the program header table appear in ascending order,
sorted on the p_vaddr member
However it does not work if -Ttext
argument is used.
Consider following example:
int main(void)
{
while (1);
}
~/Documents/compiler_ws/lld
paskripkin > ~/Documents/git/llvm-project/build/bin/ld.lld -e main -Ttext=0x80000 test.o -o a.out
~/Documents/compiler_ws/lld
paskripkin > llvm-readelf -l a.out
Elf file type is EXEC (Executable file)
Entry point 0x800000
There are 5 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000000200040 0x0000000000200040 0x000118 0x000118 R 0x8
LOAD 0x000000 0x0000000000200000 0x0000000000200000 0x000158 0x000158 R 0x1000
LOAD 0x001000 0x0000000000080000 0x0000000000080000 0x000016 0x000016 R E 0x1000
LOAD 0x001018 0x0000000000081018 0x0000000000081018 0x00003c 0x00003c R 0x1000
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0
Section to Segment mapping:
Segment Sections...
00
01
02 .text
03 .eh_frame
04
None .comment .symtab .shstrtab .strtab
Note that first loadable segment has a p_vaddr
less than the second one. (still reproduces on current master c50cba6).
Sort of analysis
The first loadable segment is the one that contains ELF header and program headers. It's added in Writer<ELFT>::createPhdrs
.
// Add the headers. We will remove them if they don't fit.
// In the other partitions the headers are ordinary sections, so they don't
// need to be added here.
if (isMain) {
load = addHdr(PT_LOAD, flags);
load->add(ctx.out.elfHeader.get());
load->add(ctx.out.programHeaders.get());
}
In normal case it gets merged into some of other segments because of the following code
bool sameLMARegion =
load && !sec->lmaExpr && sec->lmaRegion == load->firstSec->lmaRegion;
if (load && sec != relroEnd &&
sec->memRegion == load->firstSec->memRegion &&
(sameLMARegion || load->lastSec == ctx.out.programHeaders.get()) &&
(ctx.script->hasSectionsCommand || sec->type == SHT_NOBITS ||
load->lastSec->type != SHT_NOBITS)) {
load->p_flags |= newFlags;
} else {
load = addHdr(PT_LOAD, newFlags);
flags = newFlags;
}
However if -Ttext
is specified, predicate above may fail and new segment will be inserted. Then during LinkerScript::assignAddresses
, first PT_LOAD
segment gets assigned to ctx.target->getImageBase()
, which may be far from the base of text
(IIUC lld starts calculating p_vaddr
s from the base passed in -T
).
Note
This is not a synthetic example. It was observed in real life with very strict elf loader.