This is an archive of the discontinued LLVM Phabricator instance.

[lld|ELF] Fix virtual address of the first PT_LOAD segment when linker script is being used
ClosedPublic

Authored by evgeny777 on Jun 27 2016, 5:00 AM.
Tokens
"Like" token, awarded by pelikan.

Details

Reviewers
ruiu
ikudrin
Summary

When linker script is used base address of PT_PHDR is set to 0 (zero). When you try to run linked image, Linux kernel tries to map executable segment starting from zero VA, which can't be used by userspace process. This causes exec() to return -1 with errno = EPERM (not a single instruction is executed)

Diff Detail

Event Timeline

evgeny777 updated this revision to Diff 61956.Jun 27 2016, 5:00 AM
evgeny777 retitled this revision from to [lld|ELF] Fix virtual address of the first PT_LOAD segment when linker script is being used.
evgeny777 updated this object.
evgeny777 added reviewers: rui314, ikudrin.
evgeny777 set the repository for this revision to rL LLVM.
evgeny777 added a project: lld.
evgeny777 edited reviewers, added: ruiu; removed: rui314.Jun 27 2016, 5:27 AM
ruiu added inline comments.Jun 27 2016, 6:37 AM
ELF/Writer.cpp
70–71

I think function overloading is used too casually here. You can always pass an argument explicitly instead of defining two functions with the same name.

1195

assignFileOffsets should set file offsets only as the name implies, but this function call will change section VAs. This should be moved to assignAddresses.

evgeny777 updated this revision to Diff 62065.Jun 28 2016, 1:03 AM
evgeny777 removed rL LLVM as the repository for this revision.

Review updated according to comments from Rui Ueyama

ruiu added inline comments.Jun 28 2016, 2:10 AM
ELF/LinkerScript.cpp
37

Target.h contains this declaration, so please remove.

232–233

It is not clear for me as to what you are trying to do with this code. Can you elaborate?

ELF/Writer.cpp
1183–1184

I'm wondering why you need to update this function. File offsets are basically orthogonal to section addresses, no?

evgeny777 added inline comments.Jun 28 2016, 2:40 AM
ELF/LinkerScript.cpp
232–233

Ok, what I need to do is to calculate VA of the first PT_LOAD segment, which contains PT_PHDR
To do this I calculate suitable page aligned VA, which is less than first section address and FirstSectionVA - BaseVA >= (ELF_hdr size + PHDR size)
Does this make sense?

ELF/Writer.cpp
1183–1184

File offsets and section VA should be equal modulo page size, shouldn't they?
Looking at kernel sources I don't see any relocation, only mmap'ing

ruiu added inline comments.Jun 28 2016, 11:48 PM
ELF/LinkerScript.cpp
232–233

After applying this patch and poking around, I think I understand what this is for. So you are trying to place ELF and Program headers right before the first section.

I think this code is a bit tricky and has room for improvement.

Instead of computing BaseVA in the loop, I'd just record the minimum VA.

254

... by adding MinVA = std::min(MinVA, Dot) here.

260

... and assign MinVA - Out<ELFT>::ElfHeader->getSize() - Out<ELFT>::ProgramHeaders->getSize() to ElfHeader and MinVA - Out<ELFT>::ProgramHeaders->getSize() to ProgramHeaders.

evgeny777 added inline comments.Jun 29 2016, 12:54 AM
ELF/LinkerScript.cpp
254

MinVA isn't necessary VA of the very first section. Technically speaking if you take MinVA here, you should also do the same in assignFileOffsets
Meanwhile first PT_LOAD segment isn't obliged to have lowest VA, it can be placed anywhere.

evgeny777 updated this revision to Diff 62187.Jun 29 2016, 2:16 AM

Review updated: now minimum VA of section is calculated (it turned out that gold also does this).
I had to update assignFileOffsets as well to reflect this change

ruiu edited edge metadata.Jun 29 2016, 2:36 AM

This is looking better.

ELF/LinkerScript.cpp
228

This is a cool way to write a largest number, but is it only me who thinks uintX_t(-1) is more conventional?

260

I'm wondering if ELF header needs to start at beginning of a page. What if you remove alignDown?

evgeny777 added inline comments.Jun 29 2016, 2:44 AM
ELF/LinkerScript.cpp
228

Who knows :). I don't mind replacing it to uintX_t(-1), if you like.

260

Sorry, but this will not work

(FileOffset mod PageSize) should be equal to (VA mod PageSize)
ELF header has file offset equal to zero (0), so

VA mod PageSize = 0, means VA should be page aligned

ruiu added inline comments.Jun 29 2016, 2:55 AM
ELF/LinkerScript.cpp
228

Well, on second thought, I think numeric_limits is probably better.

260

Got it. Please add a comment to describe this piece of code.

// ELF and Program headers need to be right before the first section in memory.
// Set their addresses accordingly.
ELF/Writer.cpp
1186

getFileAlignment is the function to take care of this. Doesn't it work?

evgeny777 added inline comments.Jun 29 2016, 3:11 AM
ELF/LinkerScript.cpp
260

Sure. May I commit this patch after that?

ELF/Writer.cpp
1186

It is not used in case section has SHT_NOBITS type and linker script may put such section (for example .bss) first
This can also be fixed calling getFileAlignment() for such sections, in case they go first. Let me know your thoughts

ruiu added inline comments.Jun 29 2016, 3:12 AM
ELF/Writer.cpp
1186

I prefer just calling getFileAlignment for all sections because it's simpler.

evgeny777 added inline comments.Jun 29 2016, 3:28 AM
ELF/Writer.cpp
1186

Ok current version has this code

for (OutputSectionBase<ELFT> *Sec : OutputSections) {
   if (Sec->getType() == SHT_NOBITS) {
     Sec->setFileOffset(Off);
     continue;
   }

   Off = getFileAlignment<ELFT>(Off, Sec);

As you may have noticed getFileAlignment is not called for SHT_NOBITS sections. Doing otherways breaks unit tests (for example ELF/edata-etext.s). The problem is that SHT_NOBITS section *may* be the very first section with VA set explicitly when linker script is used. For example:

SECTIONS
 {
   . = 0x10000000;
   .bss : { *(.bss) }
   .data : { *(.data) }
   . = 0x0F000000;
   .text : { *(.text) }
 }

In such case I see two ways to fix:

a) Explicitly check for script mode (as I'm doing currently)
b) If SHT_NOBITS section goes first call getFileAlignment() to calculate its offset

Hope this makes sense

ruiu added inline comments.Jun 29 2016, 4:03 AM
ELF/Writer.cpp
1134–1135

This is not your fault, but this code does not make sense. We want to fix only VAs in this function and let assignFileOffsets to fix file offsets. So please remove setFileOffset from this function.

1186

We don't have to handle linker scripts in a special manner here. Define the following helper in this function,

uintX_t Off = 0;

auto Set = [&](OutputSectionBase<ELFT> *Sec) {
  if (Sec->getType() == SHT_NOBITS) {
    Sec->setFileOffset(Off);
    return;
  }
  Off = getFileAlignment<ELFT>(Off, Sec);
  Sec->setFileOffset(Off);
  Off += Sec->getSize();
};

and use it as

Set(Out<ELFT>::ElfHeader);
Set(Out<ELFT>::ProgramHeaders);
for (OutputSectionBase<ELFT> *Sec : OutputSections)
  Set(Sec);

The above code should work both for both with and without linker scripts.

evgeny777 updated this revision to Diff 62207.Jun 29 2016, 4:51 AM
evgeny777 edited edge metadata.

Review updated.

Rui, your proposed changes don't work correctly in case .bss (SHT_NOBITS) section is mentioned firrst in the linker script. I used this script:

SECTIONS {
   . = 0x10000000;
   .bss : { *(.bss) }
   .data : { *(.data) }
   . = 0x0F000000;
   .text : { *(.text) }
 }

with a very basic C program

This is what your patch produces (look at offset 0x2e0 and VA - they aren't equal modulo page size):

LOAD           0x000000 0x000000000efff000 0x000000000efff000 0x0002e0 0x0002e0 R   0x1000
LOAD           0x0002e0 0x0000000010000000 0x0000000010000000 0x000d90 0x000070 RW  0x1000

And this is correct result:

LOAD           0x000000 0x000000000efff000 0x000000000efff000 0x0002e0 0x0002e0 R   0x1000
LOAD           0x001000 0x0000000010000000 0x0000000010000000 0x000070 0x000070 RW  0x1000

May be add unit test for above?

evgeny777 updated this revision to Diff 62253.Jun 29 2016, 11:17 AM

Bug found - diff updated

ruiu added inline comments.Jun 29 2016, 4:20 PM
ELF/Writer.cpp
1188

Why don't you call this function unconditionally for all sections? Why only the first?

evgeny777 added inline comments.Jun 29 2016, 11:34 PM
ELF/Writer.cpp
1188

This was a "hack" to make script case work whan .bss is first segment in program.
Using getFileAlignment for all sections will break following tests:

Failing Tests (5):
    lld :: ELF/edata-etext.s
    lld :: ELF/relocatable.s
    lld :: ELF/relocation-copy-flags.s
    lld :: ELF/tls-offset.s
    lld :: ELF/tls.s

For some reason SHT_NOBITS section alignments are not respected in assignFileOffsets.
Not sure why and whether this is a bug or feature. I suggest reverting the change above (2 lines)
and make a separate patch for this later

ruiu added a comment.Jun 29 2016, 11:36 PM

I'd just update the tests.

evgeny777 updated this revision to Diff 62357.Jun 30 2016, 7:22 AM
evgeny777 set the repository for this revision to rL LLVM.

Review updated.

It turned out that current implementation treats NOBITS sections correctly. In general file offsets for those sections should not be aligned, because they don't occupy any space in ELF.
What really should be done (IMHO) is aligning file offsets of PT_LOAD segments containing those sections in case file size of such segment is not zero. In case non-zero sized PT_LOAD segment file offset and VA are not properly aligned vm_mmap() kernel function returns -EINVAL and image is not loaded.

All lld test cases pass and linker scripts work fine as well

ruiu added a comment.Jun 30 2016, 9:56 PM

Your patch contains space characters at end of lines. Please remove them.

ELF/Writer.cpp
1222–1227

I spent more than 20 minutes to understand this piece of code without success. Do you really need this?

test/ELF/linkerscript-phdr-check.s
17

Remove trailing newline.

ruiu added inline comments.Jun 30 2016, 10:09 PM
ELF/Writer.cpp
1222–1227

Your new test passed without this code, so I suspect you don't need it.

evgeny777 updated this revision to Diff 62467.Jun 30 2016, 10:57 PM

Removed the newly added code - will make a separate patch for the issue
Can this be committed?

ruiu added a comment.Jun 30 2016, 10:58 PM

Is there an issue in the first place?

ruiu accepted this revision.Jun 30 2016, 11:29 PM
ruiu edited edge metadata.

Anyway, this change LGTM. Please commit after the following change.

ELF/LinkerScript.cpp
262

You have trailing whitespace in this line and other lines. Please remove them before committing.

This revision is now accepted and ready to land.Jun 30 2016, 11:29 PM
evgeny777 closed this revision.Jul 5 2016, 8:06 AM

Review updated.

It turned out that current implementation treats NOBITS sections correctly. In general file offsets for those sections should not be aligned, because they don't occupy any space in ELF.
What really should be done (IMHO) is aligning file offsets of PT_LOAD segments containing those sections in case file size of such segment is not zero. In case non-zero sized PT_LOAD segment file offset and VA are not properly aligned vm_mmap() kernel function returns -EINVAL and image is not loaded.

All lld test cases pass and linker scripts work fine as well

(~~~~)