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
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. |
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? |
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 | |
ELF/Writer.cpp | ||
1183–1184 | File offsets and section VA should be equal modulo page size, shouldn't they? |
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. |
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 |
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
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? |
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 |
ELF/Writer.cpp | ||
---|---|---|
1186 | I prefer just calling getFileAlignment for all sections because it's simpler. |
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) Hope this makes sense |
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. |
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
ELF/Writer.cpp | ||
---|---|---|
1188 | Why don't you call this function unconditionally for all sections? Why only the first? |
ELF/Writer.cpp | ||
---|---|---|
1188 | This was a "hack" to make script case work whan .bss is first segment in program. 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. |
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
ELF/Writer.cpp | ||
---|---|---|
1222–1227 | Your new test passed without this code, so I suspect you don't need it. |
Removed the newly added code - will make a separate patch for the issue
Can this be committed?
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. |
Target.h contains this declaration, so please remove.