Skip to content

Commit c4deb5e

Browse files
committed
Avoid inflating file sizes needlessly and allow binaries to be stripped
The current approach to changing sections in ET_DYN executables is to move the INTERP section to the end of the file. +This means changing PT_PHDR to add an extra PT_LOAD section so that the new section is mmaped into memory by the elf loader in the kernel. In order to extend PHDR, this means moving it to the end of the file. Its documented in BUGS there is a kernel 'bug' which means that if you have holes in memory between the base load address and the PT_LOAD segment that contains PHDR, it will pass an incorrect PHDR address to ld.so and fail to load the binary, segfaulting. To avoid this, the code currently inserts space into the binary to ensure that when loaded into memory there are no holes between the PT_LOAD sections. This inflates the binaries by many MBs in some cases. Whilst we could make them sparse, there is a second issue which is that strip can fail to process these binaries: $ strip fixincl Not enough room for program headers, try linking with -N [.note.ABI-tag]: Bad value This turns out to be due to libbfd not liking the relocated PHDR section either (#10). Instead this patch implements a different approach, leaving PHDR where it is but extending it in place to allow addition of a new PT_LOAD section. This overwrites sections in the binary but those get moved to the end of the file in the new PT_LOAD section. This is based on patches linked from the above github issue, however whilst the idea was good, the implementation wasn't correct and they've been rewritten here. Signed-off-by: Richard Purdie <[email protected]>
1 parent c1f89c0 commit c4deb5e

File tree

1 file changed

+40
-32
lines changed

1 file changed

+40
-32
lines changed

src/patchelf.cc

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class ElfFile
170170
std::string & replaceSection(const SectionName & sectionName,
171171
unsigned int size);
172172

173+
bool haveReplacedSection(const SectionName & sectionName);
174+
173175
void writeReplacedSections(Elf_Off & curOff,
174176
Elf_Addr startAddr, Elf_Off startOffset);
175177

@@ -578,6 +580,15 @@ unsigned int ElfFile<ElfFileParamNames>::findSection3(const SectionName & sectio
578580
return 0;
579581
}
580582

583+
template<ElfFileParams>
584+
bool ElfFile<ElfFileParamNames>::haveReplacedSection(const SectionName & sectionName)
585+
{
586+
ReplacedSections::iterator i = replacedSections.find(sectionName);
587+
588+
if (i != replacedSections.end())
589+
return true;
590+
return false;
591+
}
581592

582593
template<ElfFileParams>
583594
std::string & ElfFile<ElfFileParamNames>::replaceSection(const SectionName & sectionName,
@@ -672,51 +683,51 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
672683

673684
debug("last page is 0x%llx\n", (unsigned long long) startPage);
674685

686+
/* Because we're adding a new section header, we're necessarily increasing
687+
the size of the program header table. This can cause the first section
688+
to overlap the program header table in memory; we need to shift the first
689+
few segments to someplace else. */
690+
/* Some sections may already be replaced so account for that */
691+
unsigned int i = 1;
692+
Elf_Addr pht_size = sizeof(Elf_Ehdr) + (phdrs.size() + 1)*sizeof(Elf_Phdr);
693+
while( shdrs[i].sh_addr <= pht_size && i < rdi(hdr->e_shnum) ) {
694+
if (not haveReplacedSection(getSectionName(shdrs[i])))
695+
replaceSection(getSectionName(shdrs[i]), shdrs[i].sh_size);
696+
i++;
697+
}
675698

676-
/* Compute the total space needed for the replaced sections and
677-
the program headers. */
678-
off_t neededSpace = (phdrs.size() + 1) * sizeof(Elf_Phdr);
699+
/* Compute the total space needed for the replaced sections */
700+
off_t neededSpace = 0;
679701
for (auto & i : replacedSections)
680702
neededSpace += roundUp(i.second.size(), sectionAlignment);
681703
debug("needed space is %d\n", neededSpace);
682704

683-
684705
size_t startOffset = roundUp(fileContents->size(), getPageSize());
685706

686707
growFile(fileContents, startOffset + neededSpace);
687708

688-
689709
/* Even though this file is of type ET_DYN, it could actually be
690710
an executable. For instance, Gold produces executables marked
691-
ET_DYN. In that case we can still hit the kernel bug that
692-
necessitated rewriteSectionsExecutable(). However, such
693-
executables also tend to start at virtual address 0, so
711+
ET_DYN as does LD when linking with pie. If we move PT_PHDR, it
712+
has to stay in the first PT_LOAD segment or any subsequent ones
713+
if they're continuous in memory due to linux kernel constraints
714+
(see BUGS). Since the end of the file would be after bss, we can't
715+
move PHDR there, we therefore choose to leave PT_PHDR where it is but
716+
move enough following sections such that we can add the extra PT_LOAD
717+
section to it. This PT_LOAD segment ensures the sections at the end of
718+
the file are mapped into memory for ld.so to process.
719+
We can't use the approach in rewriteSectionsExecutable()
720+
since DYN executables tend to start at virtual address 0, so
694721
rewriteSectionsExecutable() won't work because it doesn't have
695-
any virtual address space to grow downwards into. As a
696-
workaround, make sure that the virtual address of our new
697-
PT_LOAD segment relative to the first PT_LOAD segment is equal
698-
to its offset; otherwise we hit the kernel bug. This may
699-
require creating a hole in the executable. The bigger the size
700-
of the uninitialised data segment, the bigger the hole. */
722+
any virtual address space to grow downwards into. */
701723
if (isExecutable) {
702724
if (startOffset >= startPage) {
703725
debug("shifting new PT_LOAD segment by %d bytes to work around a Linux kernel bug\n", startOffset - startPage);
704-
} else {
705-
size_t hole = startPage - startOffset;
706-
/* Print a warning, because the hole could be very big. */
707-
fprintf(stderr, "warning: working around a Linux kernel bug by creating a hole of %zu bytes in '%s'\n", hole, fileName.c_str());
708-
assert(hole % getPageSize() == 0);
709-
/* !!! We could create an actual hole in the file here,
710-
but it's probably not worth the effort. */
711-
growFile(fileContents, fileContents->size() + hole);
712-
startOffset += hole;
713726
}
714727
startPage = startOffset;
715728
}
716729

717-
718-
/* Add a segment that maps the replaced sections and program
719-
headers into memory. */
730+
/* Add a segment that maps the replaced sections into memory. */
720731
phdrs.resize(rdi(hdr->e_phnum) + 1);
721732
wri(hdr->e_phnum, rdi(hdr->e_phnum) + 1);
722733
Elf_Phdr & phdr = phdrs[rdi(hdr->e_phnum) - 1];
@@ -729,15 +740,12 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary()
729740

730741

731742
/* Write out the replaced sections. */
732-
Elf_Off curOff = startOffset + phdrs.size() * sizeof(Elf_Phdr);
743+
Elf_Off curOff = startOffset;
733744
writeReplacedSections(curOff, startPage, startOffset);
734745
assert(curOff == startOffset + neededSpace);
735746

736-
737-
/* Move the program header to the start of the new area. */
738-
wri(hdr->e_phoff, startOffset);
739-
740-
rewriteHeaders(startPage);
747+
/* Write out the updated program and section headers */
748+
rewriteHeaders(hdr->e_phoff);
741749
}
742750

743751

0 commit comments

Comments
 (0)