Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[lld] Unsorted loadable program headers with -Ttext= argument #138584

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pskrgag opened this issue May 5, 2025 · 4 comments
Open

[lld] Unsorted loadable program headers with -Ttext= argument #138584

pskrgag opened this issue May 5, 2025 · 4 comments
Labels

Comments

@pskrgag
Copy link
Contributor

pskrgag commented May 5, 2025

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_vaddrs from the base passed in -T).

Note

This is not a synthetic example. It was observed in real life with very strict elf loader.

@pskrgag
Copy link
Contributor Author

pskrgag commented May 5, 2025

CC @MaskRay I guess?

@llvmbot llvmbot added the lld label May 5, 2025
@pskrgag pskrgag added the bug Indicates an unexpected problem or unintended behavior label May 6, 2025
@llvmbot
Copy link
Member

llvmbot commented May 6, 2025

@llvm/issue-subscribers-bug

Author: Pavel Skripkin (pskrgag)

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 &gt; ~/Documents/git/llvm-project/build/bin/ld.lld -e main -Ttext=0x800000 test.o -o a.out

~/Documents/compiler_ws/lld
paskripkin &gt; 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 0x0000000000800000 0x0000000000800000 0x00000d 0x00000d R E 0x1000
  LOAD           0x001010 0x0000000000801010 0x0000000000801010 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&lt;ELFT&gt;::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-&gt;add(ctx.out.elfHeader.get());
      load-&gt;add(ctx.out.programHeaders.get());
    }

In normal case it gets merged into some of other segments because of the following code

    bool sameLMARegion =
        load &amp;&amp; !sec-&gt;lmaExpr &amp;&amp; sec-&gt;lmaRegion == load-&gt;firstSec-&gt;lmaRegion;
    if (load &amp;&amp; sec != relroEnd &amp;&amp;
        sec-&gt;memRegion == load-&gt;firstSec-&gt;memRegion &amp;&amp;
        (sameLMARegion || load-&gt;lastSec == ctx.out.programHeaders.get()) &amp;&amp;
        (ctx.script-&gt;hasSectionsCommand || sec-&gt;type == SHT_NOBITS ||
         load-&gt;lastSec-&gt;type != SHT_NOBITS)) {
      load-&gt;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-&gt;getImageBase(), which may be far from the base of text (IIUC lld starts calculating p_vaddrs from the base passed in -T).

Note

This is not a synthetic example. It was observed in real life with very strict elf loader.

@EugeneZelenko EugeneZelenko removed the bug Indicates an unexpected problem or unintended behavior label May 6, 2025
@rui314
Copy link
Member

rui314 commented May 7, 2025

It looks like all PT_LOAD segments are sorted by p_vaddr in your example. The program has three loadable segments, and in ascending order their virtual addresses are the following.

0x200000
0x800000
0x801010

Am I missing something?

If you are talking about PT_PHDR, it's not a bug that the segment's p_vaddr is smaller than the first PT_LOAD entry. In fact, the ELF specification requires that the PT_PHDR precede any PT_LOAD entry in the program header. The spec states that all loadable segments (i.e., all PT_LOAD segments) must be sorted by p_vaddr, and nothing more.

@pskrgag
Copy link
Contributor Author

pskrgag commented May 7, 2025

@rui314 Sorry, I've added extra 0 while writing this bug report. It should be -Ttext=0x80000, i.e text should be lower than default base for !pie case (0x200000).

I will update it in moment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants