| From 79c093226e609b99fa889f6e37480b92b399610d Mon Sep 17 00:00:00 2001 |
| From: Richard Purdie <richard.purdie@linuxfoundation.org> |
| Date: Tue, 7 Mar 2017 21:08:34 +0000 |
| Subject: [PATCH] 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 |
| (https://github.com/NixOS/patchelf/issues/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 <richard.purdie@linuxfoundation.org> |
| |
| Fetch from: https://github.com/NixOS/patchelf/commit/c4deb5e9e1ce9c98a48e0d5bb37d87739b8cfee4 |
| |
| Backported to v0.9 |
| |
| Signed-off-by: Conrad Ratschan <conrad.ratschan@rockwellcollins.com> |
| --- |
| src/patchelf.cc | 71 ++++++++++++++++++++++++++++--------------------- |
| 1 file changed, 40 insertions(+), 31 deletions(-) |
| |
| diff --git a/src/patchelf.cc b/src/patchelf.cc |
| index 1d58061..c2147af 100644 |
| --- a/src/patchelf.cc |
| +++ b/src/patchelf.cc |
| @@ -209,6 +209,8 @@ private: |
| string & replaceSection(const SectionName & sectionName, |
| unsigned int size); |
| |
| + bool haveReplacedSection(const SectionName & sectionName); |
| + |
| void writeReplacedSections(Elf_Off & curOff, |
| Elf_Addr startAddr, Elf_Off startOffset); |
| |
| @@ -632,6 +634,15 @@ void ElfFile<ElfFileParamNames>::writeReplacedSections(Elf_Off & curOff, |
| replacedSections.clear(); |
| } |
| |
| +template<ElfFileParams> |
| +bool ElfFile<ElfFileParamNames>::haveReplacedSection(const SectionName & sectionName) |
| +{ |
| + ReplacedSections::iterator i = replacedSections.find(sectionName); |
| + |
| + if (i != replacedSections.end()) |
| + return true; |
| + return false; |
| +} |
| |
| template<ElfFileParams> |
| void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary() |
| @@ -648,52 +659,53 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary() |
| |
| debug("last page is 0x%llx\n", (unsigned long long) startPage); |
| |
| + /* Because we're adding a new section header, we're necessarily increasing |
| + the size of the program header table. This can cause the first section |
| + to overlap the program header table in memory; we need to shift the first |
| + few segments to someplace else. */ |
| + /* Some sections may already be replaced so account for that */ |
| + unsigned int i = 1; |
| + Elf_Addr pht_size = sizeof(Elf_Ehdr) + (phdrs.size() + 1)*sizeof(Elf_Phdr); |
| + while( shdrs[i].sh_addr <= pht_size && i < rdi(hdr->e_shnum) ) { |
| + if (not haveReplacedSection(getSectionName(shdrs[i]))) |
| + replaceSection(getSectionName(shdrs[i]), shdrs[i].sh_size); |
| + i++; |
| + } |
| |
| - /* Compute the total space needed for the replaced sections and |
| - the program headers. */ |
| - off_t neededSpace = (phdrs.size() + 1) * sizeof(Elf_Phdr); |
| + /* Compute the total space needed for the replaced sections */ |
| + off_t neededSpace = 0; |
| for (ReplacedSections::iterator i = replacedSections.begin(); |
| i != replacedSections.end(); ++i) |
| neededSpace += roundUp(i->second.size(), sectionAlignment); |
| debug("needed space is %d\n", neededSpace); |
| |
| - |
| size_t startOffset = roundUp(fileSize, getPageSize()); |
| |
| growFile(startOffset + neededSpace); |
| |
| - |
| /* Even though this file is of type ET_DYN, it could actually be |
| an executable. For instance, Gold produces executables marked |
| - ET_DYN. In that case we can still hit the kernel bug that |
| - necessitated rewriteSectionsExecutable(). However, such |
| - executables also tend to start at virtual address 0, so |
| + ET_DYN as does LD when linking with pie. If we move PT_PHDR, it |
| + has to stay in the first PT_LOAD segment or any subsequent ones |
| + if they're continuous in memory due to linux kernel constraints |
| + (see BUGS). Since the end of the file would be after bss, we can't |
| + move PHDR there, we therefore choose to leave PT_PHDR where it is but |
| + move enough following sections such that we can add the extra PT_LOAD |
| + section to it. This PT_LOAD segment ensures the sections at the end of |
| + the file are mapped into memory for ld.so to process. |
| + We can't use the approach in rewriteSectionsExecutable() |
| + since DYN executables tend to start at virtual address 0, so |
| rewriteSectionsExecutable() won't work because it doesn't have |
| - any virtual address space to grow downwards into. As a |
| - workaround, make sure that the virtual address of our new |
| - PT_LOAD segment relative to the first PT_LOAD segment is equal |
| - to its offset; otherwise we hit the kernel bug. This may |
| - require creating a hole in the executable. The bigger the size |
| - of the uninitialised data segment, the bigger the hole. */ |
| + any virtual address space to grow downwards into. */ |
| if (isExecutable) { |
| if (startOffset >= startPage) { |
| debug("shifting new PT_LOAD segment by %d bytes to work around a Linux kernel bug\n", startOffset - startPage); |
| - } else { |
| - size_t hole = startPage - startOffset; |
| - /* Print a warning, because the hole could be very big. */ |
| - fprintf(stderr, "warning: working around a Linux kernel bug by creating a hole of %zu bytes in ā%sā\n", hole, fileName.c_str()); |
| - assert(hole % getPageSize() == 0); |
| - /* !!! We could create an actual hole in the file here, |
| - but it's probably not worth the effort. */ |
| - growFile(fileSize + hole); |
| - startOffset += hole; |
| } |
| startPage = startOffset; |
| } |
| |
| |
| - /* Add a segment that maps the replaced sections and program |
| - headers into memory. */ |
| + /* Add a segment that maps the replaced sections into memory. */ |
| phdrs.resize(rdi(hdr->e_phnum) + 1); |
| wri(hdr->e_phnum, rdi(hdr->e_phnum) + 1); |
| Elf_Phdr & phdr = phdrs[rdi(hdr->e_phnum) - 1]; |
| @@ -706,15 +718,12 @@ void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary() |
| |
| |
| /* Write out the replaced sections. */ |
| - Elf_Off curOff = startOffset + phdrs.size() * sizeof(Elf_Phdr); |
| + Elf_Off curOff = startOffset; |
| writeReplacedSections(curOff, startPage, startOffset); |
| assert(curOff == startOffset + neededSpace); |
| |
| - |
| - /* Move the program header to the start of the new area. */ |
| - wri(hdr->e_phoff, startOffset); |
| - |
| - rewriteHeaders(startPage); |
| + /* Write out the updated program and section headers */ |
| + rewriteHeaders(hdr->e_phoff); |
| } |
| |
| |
| -- |
| 2.17.1 |
| |