diff options
author | Roman Lacko <backup.rlacko@gmail.com> | 2012-06-04 16:18:34 +0200 |
---|---|---|
committer | Roman Lacko <backup.rlacko@gmail.com> | 2012-06-04 16:18:34 +0200 |
commit | 19a9e07971be60648c11fdc8f5a5632b7eeb0286 (patch) | |
tree | 1c3230790a1a22614a7e2518a2485ecd7855048c /sources/patchelf/patchelf.cc | |
parent | f82195bf001f5f75ffb27735ecf6e98941ce4405 (diff) |
don't ignore patchelf sources
Diffstat (limited to 'sources/patchelf/patchelf.cc')
-rw-r--r-- | sources/patchelf/patchelf.cc | 1176 |
1 files changed, 1176 insertions, 0 deletions
diff --git a/sources/patchelf/patchelf.cc b/sources/patchelf/patchelf.cc new file mode 100644 index 000000000..e8b70b4af --- /dev/null +++ b/sources/patchelf/patchelf.cc @@ -0,0 +1,1176 @@ +#include <string> +#include <vector> +#include <map> +#include <algorithm> + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <limits.h> + +#include "elf.h" + +using namespace std; + + +#ifdef MIPSEL +/* The lemote fuloong 2f kernel defconfig sets a page size of 16KB */ +const unsigned int pageSize = 4096*4; +#else +const unsigned int pageSize = 4096; +#endif + + +static bool debugMode = false; + +static bool forceRPath = false; + +static string fileName; + + +off_t fileSize, maxSize; +unsigned char * contents = 0; + + +#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn +#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn + + +template<ElfFileParams> +class ElfFile +{ + Elf_Ehdr * hdr; + vector<Elf_Phdr> phdrs; + vector<Elf_Shdr> shdrs; + + bool littleEndian; + + bool changed; + + bool isExecutable; + + typedef string SectionName; + typedef map<SectionName, string> ReplacedSections; + + ReplacedSections replacedSections; + + string sectionNames; /* content of the .shstrtab section */ + + /* Align on 4 or 8 bytes boundaries on 32- or 64-bit platforms + respectively. */ + unsigned int sectionAlignment; + +public: + + ElfFile() + { + changed = false; + sectionAlignment = sizeof(Elf_Off); + } + + bool isChanged() + { + return changed; + } + + void parse(); + +private: + + struct CompPhdr + { + ElfFile * elfFile; + bool operator ()(const Elf_Phdr & x, const Elf_Phdr & y) + { + if (x.p_type == PT_PHDR) return true; + if (y.p_type == PT_PHDR) return false; + return elfFile->rdi(x.p_paddr) < elfFile->rdi(y.p_paddr); + } + }; + + friend struct CompPhdr; + + void sortPhdrs(); + + struct CompShdr + { + ElfFile * elfFile; + bool operator ()(const Elf_Shdr & x, const Elf_Shdr & y) + { + return elfFile->rdi(x.sh_offset) < elfFile->rdi(y.sh_offset); + } + }; + + friend struct CompShdr; + + void sortShdrs(); + + void shiftFile(unsigned int extraPages, Elf_Addr startPage); + + string getSectionName(const Elf_Shdr & shdr); + + Elf_Shdr & findSection(const SectionName & sectionName); + + Elf_Shdr * findSection2(const SectionName & sectionName); + + unsigned int findSection3(const SectionName & sectionName); + + string & replaceSection(const SectionName & sectionName, + unsigned int size); + + void writeReplacedSections(Elf_Off & curOff, + Elf_Addr startAddr, Elf_Off startOffset); + + void rewriteHeaders(Elf_Addr phdrAddress); + + void rewriteSectionsLibrary(); + + void rewriteSectionsExecutable(); + +public: + + void rewriteSections(); + + string getInterpreter(); + + void setInterpreter(const string & newInterpreter); + + typedef enum { rpPrint, rpShrink, rpSet } RPathOp; + + void modifyRPath(RPathOp op, string newRPath); + +private: + + /* Convert an integer in big or little endian representation (as + specified by the ELF header) to this platform's integer + representation. */ + template<class I> + I rdi(I i); + + /* Convert back to the ELF representation. */ + template<class I> + I wri(I & t, unsigned long long i) + { + t = rdi((I) i); + return i; + } +}; + + +/* !!! G++ creates broken code if this function is inlined, don't know + why... */ +template<ElfFileParams> +template<class I> +I ElfFile<ElfFileParamNames>::rdi(I i) +{ + I r = 0; + if (littleEndian) { + for (unsigned int n = 0; n < sizeof(I); ++n) { + r |= ((I) *(((unsigned char *) &i) + n)) << (n * 8); + } + } else { + for (unsigned int n = 0; n < sizeof(I); ++n) { + r |= ((I) *(((unsigned char *) &i) + n)) << ((sizeof(I) - n - 1) * 8); + } + } + return r; +} + + +/* Ugly: used to erase DT_RUNPATH when using --force-rpath. */ +#define DT_IGNORE 0x00726e67 + + +static void debug(const char * format, ...) +{ + if (debugMode) { + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + } +} + + +static void error(string msg) +{ + if (errno) perror(msg.c_str()); else fprintf(stderr, "%s\n", msg.c_str()); + exit(1); +} + + +static void growFile(off_t newSize) +{ + if (newSize > maxSize) error("maximum file size exceeded"); + if (newSize <= fileSize) return; + if (newSize > fileSize) + memset(contents + fileSize, 0, newSize - fileSize); + fileSize = newSize; +} + + +static void readFile(string fileName, mode_t * fileMode) +{ + struct stat st; + if (stat(fileName.c_str(), &st) != 0) error("stat"); + fileSize = st.st_size; + *fileMode = st.st_mode; + maxSize = fileSize + 8 * 1024 * 1024; + + contents = (unsigned char *) malloc(fileSize + maxSize); + if (!contents) abort(); + + int fd = open(fileName.c_str(), O_RDONLY); + if (fd == -1) error("open"); + + if (read(fd, contents, fileSize) != fileSize) error("read"); + + close(fd); +} + + +static void checkPointer(void * p, unsigned int size) +{ + unsigned char * q = (unsigned char *) p; + assert(q >= contents && q + size <= contents + fileSize); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::parse() +{ + isExecutable = false; + + /* Check the ELF header for basic validity. */ + if (fileSize < (off_t) sizeof(Elf_Ehdr)) error("missing ELF header"); + + hdr = (Elf_Ehdr *) contents; + + if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0) + error("not an ELF executable"); + + littleEndian = contents[EI_DATA] == ELFDATA2LSB; + + if (rdi(hdr->e_type) != ET_EXEC && rdi(hdr->e_type) != ET_DYN) + error("wrong ELF type"); + + if ((off_t) (rdi(hdr->e_phoff) + rdi(hdr->e_phnum) * rdi(hdr->e_phentsize)) > fileSize) + error("missing program headers"); + + if ((off_t) (rdi(hdr->e_shoff) + rdi(hdr->e_shnum) * rdi(hdr->e_shentsize)) > fileSize) + error("missing section headers"); + + if (rdi(hdr->e_phentsize) != sizeof(Elf_Phdr)) + error("program headers have wrong size"); + + /* Copy the program and section headers. */ + for (int i = 0; i < rdi(hdr->e_phnum); ++i) { + phdrs.push_back(* ((Elf_Phdr *) (contents + rdi(hdr->e_phoff)) + i)); + if (rdi(phdrs[i].p_type) == PT_INTERP) isExecutable = true; + } + + for (int i = 0; i < rdi(hdr->e_shnum); ++i) + shdrs.push_back(* ((Elf_Shdr *) (contents + rdi(hdr->e_shoff)) + i)); + + /* Get the section header string table section (".shstrtab"). Its + index in the section header table is given by e_shstrndx field + of the ELF header. */ + unsigned int shstrtabIndex = rdi(hdr->e_shstrndx); + assert(shstrtabIndex < shdrs.size()); + unsigned int shstrtabSize = rdi(shdrs[shstrtabIndex].sh_size); + char * shstrtab = (char * ) contents + rdi(shdrs[shstrtabIndex].sh_offset); + checkPointer(shstrtab, shstrtabSize); + + assert(shstrtabSize > 0); + assert(shstrtab[shstrtabSize - 1] == 0); + + sectionNames = string(shstrtab, shstrtabSize); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::sortPhdrs() +{ + /* Sort the segments by offset. */ + CompPhdr comp; + comp.elfFile = this; + sort(phdrs.begin(), phdrs.end(), comp); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::sortShdrs() +{ + /* Translate sh_link mappings to section names, since sorting the + sections will invalidate the sh_link fields. */ + map<SectionName, SectionName> linkage; + for (unsigned int i = 1; i < rdi(hdr->e_shnum); ++i) + if (rdi(shdrs[i].sh_link) != 0) + linkage[getSectionName(shdrs[i])] = getSectionName(shdrs[rdi(shdrs[i].sh_link)]); + + /* Idem for sh_info on certain sections. */ + map<SectionName, SectionName> info; + for (unsigned int i = 1; i < rdi(hdr->e_shnum); ++i) + if (rdi(shdrs[i].sh_info) != 0 && + (rdi(shdrs[i].sh_type) == SHT_REL || rdi(shdrs[i].sh_type) == SHT_RELA)) + info[getSectionName(shdrs[i])] = getSectionName(shdrs[rdi(shdrs[i].sh_info)]); + + /* Idem for the index of the .shstrtab section in the ELF header. */ + SectionName shstrtabName = getSectionName(shdrs[rdi(hdr->e_shstrndx)]); + + /* Sort the sections by offset. */ + CompShdr comp; + comp.elfFile = this; + sort(shdrs.begin(), shdrs.end(), comp); + + /* Restore the sh_link mappings. */ + for (unsigned int i = 1; i < rdi(hdr->e_shnum); ++i) + if (rdi(shdrs[i].sh_link) != 0) + wri(shdrs[i].sh_link, + findSection3(linkage[getSectionName(shdrs[i])])); + + /* And the st_info mappings. */ + for (unsigned int i = 1; i < rdi(hdr->e_shnum); ++i) + if (rdi(shdrs[i].sh_info) != 0 && + (rdi(shdrs[i].sh_type) == SHT_REL || rdi(shdrs[i].sh_type) == SHT_RELA)) + wri(shdrs[i].sh_info, + findSection3(info[getSectionName(shdrs[i])])); + + /* And the .shstrtab index. */ + wri(hdr->e_shstrndx, findSection3(shstrtabName)); +} + + +static void writeFile(string fileName, mode_t fileMode) +{ + string fileName2 = fileName + "_patchelf_tmp"; + + int fd = open(fileName2.c_str(), + O_CREAT | O_TRUNC | O_WRONLY, 0700); + if (fd == -1) error("open"); + + if (write(fd, contents, fileSize) != fileSize) error("write"); + + if (close(fd) != 0) error("close"); + + if (chmod(fileName2.c_str(), fileMode) != 0) error("chmod"); + + if (rename(fileName2.c_str(), fileName.c_str()) != 0) error("rename"); +} + + +static unsigned int roundUp(unsigned int n, unsigned int m) +{ + return ((n - 1) / m + 1) * m; +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::shiftFile(unsigned int extraPages, Elf_Addr startPage) +{ + /* Move the entire contents of the file `extraPages' pages + further. */ + unsigned int oldSize = fileSize; + unsigned int shift = extraPages * pageSize; + growFile(fileSize + extraPages * pageSize); + memmove(contents + extraPages * pageSize, contents, oldSize); + memset(contents + sizeof(Elf_Ehdr), 0, shift - sizeof(Elf_Ehdr)); + + /* Adjust the ELF header. */ + wri(hdr->e_phoff, sizeof(Elf_Ehdr)); + wri(hdr->e_shoff, rdi(hdr->e_shoff) + shift); + + /* Update the offsets in the section headers. */ + for (int i = 1; i < rdi(hdr->e_shnum); ++i) + wri(shdrs[i].sh_offset, rdi(shdrs[i].sh_offset) + shift); + + /* Update the offsets in the program headers. */ + for (int i = 0; i < rdi(hdr->e_phnum); ++i) { + wri(phdrs[i].p_offset, rdi(phdrs[i].p_offset) + shift); + if (rdi(phdrs[i].p_align) != 0 && + (rdi(phdrs[i].p_vaddr) - rdi(phdrs[i].p_offset)) % rdi(phdrs[i].p_align) != 0) { + debug("changing alignment of program header %d from %d to %d\n", i, + rdi(phdrs[i].p_align), pageSize); + wri(phdrs[i].p_align, pageSize); + } + } + + /* Add a segment that maps the new program/section headers and + PT_INTERP segment into memory. Otherwise glibc will choke. */ + 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]; + wri(phdr.p_type, PT_LOAD); + wri(phdr.p_offset, 0); + wri(phdr.p_vaddr, wri(phdr.p_paddr, startPage)); + wri(phdr.p_filesz, wri(phdr.p_memsz, shift)); + wri(phdr.p_flags, PF_R | PF_W); + wri(phdr.p_align, pageSize); +} + + +template<ElfFileParams> +string ElfFile<ElfFileParamNames>::getSectionName(const Elf_Shdr & shdr) +{ + return string(sectionNames.c_str() + rdi(shdr.sh_name)); +} + + +template<ElfFileParams> +Elf_Shdr & ElfFile<ElfFileParamNames>::findSection(const SectionName & sectionName) +{ + Elf_Shdr * shdr = findSection2(sectionName); + if (!shdr) + error("cannot find section " + sectionName); + return *shdr; +} + + +template<ElfFileParams> +Elf_Shdr * ElfFile<ElfFileParamNames>::findSection2(const SectionName & sectionName) +{ + unsigned int i = findSection3(sectionName); + return i ? &shdrs[i] : 0; +} + + +template<ElfFileParams> +unsigned int ElfFile<ElfFileParamNames>::findSection3(const SectionName & sectionName) +{ + for (unsigned int i = 1; i < rdi(hdr->e_shnum); ++i) + if (getSectionName(shdrs[i]) == sectionName) return i; + return 0; +} + + +template<ElfFileParams> +string & ElfFile<ElfFileParamNames>::replaceSection(const SectionName & sectionName, + unsigned int size) +{ + ReplacedSections::iterator i = replacedSections.find(sectionName); + string s; + + if (i != replacedSections.end()) { + s = string(i->second); + } else { + Elf_Shdr & shdr = findSection(sectionName); + s = string((char *) contents + rdi(shdr.sh_offset), rdi(shdr.sh_size)); + } + + s.resize(size); + replacedSections[sectionName] = s; + + return replacedSections[sectionName]; +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::writeReplacedSections(Elf_Off & curOff, + Elf_Addr startAddr, Elf_Off startOffset) +{ + /* Overwrite the old section contents with 'X's. Do this + *before* writing the new section contents (below) to prevent + clobbering previously written new section contents. */ + for (ReplacedSections::iterator i = replacedSections.begin(); + i != replacedSections.end(); ++i) + { + string sectionName = i->first; + Elf_Shdr & shdr = findSection(sectionName); + memset(contents + rdi(shdr.sh_offset), 'X', rdi(shdr.sh_size)); + } + + for (ReplacedSections::iterator i = replacedSections.begin(); + i != replacedSections.end(); ++i) + { + string sectionName = i->first; + Elf_Shdr & shdr = findSection(sectionName); + debug("rewriting section `%s' from offset 0x%x (size %d) to offset 0x%x (size %d)\n", + sectionName.c_str(), rdi(shdr.sh_offset), rdi(shdr.sh_size), curOff, i->second.size()); + + memcpy(contents + curOff, (unsigned char *) i->second.c_str(), + i->second.size()); + + /* Update the section header for this section. */ + wri(shdr.sh_offset, curOff); + wri(shdr.sh_addr, startAddr + (curOff - startOffset)); + wri(shdr.sh_size, i->second.size()); + wri(shdr.sh_addralign, sectionAlignment); + + /* If this is the .interp section, then the PT_INTERP segment + must be sync'ed with it. */ + if (sectionName == ".interp") { + for (unsigned int j = 0; j < phdrs.size(); ++j) + if (rdi(phdrs[j].p_type) == PT_INTERP) { + phdrs[j].p_offset = shdr.sh_offset; + phdrs[j].p_vaddr = phdrs[j].p_paddr = shdr.sh_addr; + phdrs[j].p_filesz = phdrs[j].p_memsz = shdr.sh_size; + } + } + + /* If this is the .dynamic section, then the PT_DYNAMIC segment + must be sync'ed with it. */ + if (sectionName == ".dynamic") { + for (unsigned int j = 0; j < phdrs.size(); ++j) + if (rdi(phdrs[j].p_type) == PT_DYNAMIC) { + phdrs[j].p_offset = shdr.sh_offset; + phdrs[j].p_vaddr = phdrs[j].p_paddr = shdr.sh_addr; + phdrs[j].p_filesz = phdrs[j].p_memsz = shdr.sh_size; + } + } + + curOff += roundUp(i->second.size(), sectionAlignment); + } + + replacedSections.clear(); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::rewriteSectionsLibrary() +{ + /* For dynamic libraries, we just place the replacement sections + at the end of the file. They're mapped into memory by a + PT_LOAD segment located directly after the last virtual address + page of other segments. */ + Elf_Addr startPage = 0; + for (unsigned int i = 0; i < phdrs.size(); ++i) { + Elf_Addr thisPage = roundUp(rdi(phdrs[i].p_vaddr) + rdi(phdrs[i].p_memsz), pageSize); + if (thisPage > startPage) startPage = thisPage; + } + + debug("last page is 0x%llx\n", (unsigned long long) startPage); + + + /* Compute the total space needed for the replaced sections and + the program headers. */ + off_t neededSpace = (phdrs.size() + 1) * sizeof(Elf_Phdr); + 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, pageSize); + + 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 + 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. */ + 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 % pageSize == 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. */ + 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]; + wri(phdr.p_type, PT_LOAD); + wri(phdr.p_offset, startOffset); + wri(phdr.p_vaddr, wri(phdr.p_paddr, startPage)); + wri(phdr.p_filesz, wri(phdr.p_memsz, neededSpace)); + wri(phdr.p_flags, PF_R | PF_W); + wri(phdr.p_align, pageSize); + + + /* Write out the replaced sections. */ + Elf_Off curOff = startOffset + phdrs.size() * sizeof(Elf_Phdr); + writeReplacedSections(curOff, startPage, startOffset); + assert((off_t) curOff == startOffset + neededSpace); + + + /* Move the program header to the start of the new area. */ + wri(hdr->e_phoff, startOffset); + + rewriteHeaders(startPage); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::rewriteSectionsExecutable() +{ + /* Sort the sections by offset, otherwise we won't correctly find + all the sections before the last replaced section. */ + sortShdrs(); + + + /* What is the index of the last replaced section? */ + unsigned int lastReplaced = 0; + for (unsigned int i = 1; i < rdi(hdr->e_shnum); ++i) { + string sectionName = getSectionName(shdrs[i]); + if (replacedSections.find(sectionName) != replacedSections.end()) { + debug("using replaced section `%s'\n", sectionName.c_str()); + lastReplaced = i; + } + } + + assert(lastReplaced != 0); + + debug("last replaced is %d\n", lastReplaced); + + /* Try to replace all sections before that, as far as possible. + Stop when we reach an irreplacable section (such as one of type + SHT_PROGBITS). These cannot be moved in virtual address space + since that would invalidate absolute references to them. */ + assert(lastReplaced + 1 < shdrs.size()); /* !!! I'm lazy. */ + size_t startOffset = rdi(shdrs[lastReplaced + 1].sh_offset); + Elf_Addr startAddr = rdi(shdrs[lastReplaced + 1].sh_addr); + string prevSection; + for (unsigned int i = 1; i <= lastReplaced; ++i) { + Elf_Shdr & shdr(shdrs[i]); + string sectionName = getSectionName(shdr); + debug("looking at section `%s'\n", sectionName.c_str()); + /* !!! Why do we stop after a .dynstr section? I can't + remember! */ + if ((rdi(shdr.sh_type) == SHT_PROGBITS && sectionName != ".interp") + || prevSection == ".dynstr") + { + startOffset = rdi(shdr.sh_offset); + startAddr = rdi(shdr.sh_addr); + lastReplaced = i - 1; + break; + } else { + if (replacedSections.find(sectionName) == replacedSections.end()) { + debug("replacing section `%s' which is in the way\n", sectionName.c_str()); + replaceSection(sectionName, rdi(shdr.sh_size)); + } + } + prevSection = sectionName; + } + + debug("first reserved offset/addr is 0x%x/0x%llx\n", + startOffset, (unsigned long long) startAddr); + + assert(startAddr % pageSize == startOffset % pageSize); + Elf_Addr firstPage = startAddr - startOffset; + debug("first page is 0x%llx\n", (unsigned long long) firstPage); + + /* Right now we assume that the section headers are somewhere near + the end, which appears to be the case most of the time. + Therefore they're not accidentally overwritten by the replaced + sections. !!! Fix this. */ + assert((off_t) rdi(hdr->e_shoff) >= startOffset); + + + /* Compute the total space needed for the replaced sections, the + ELF header, and the program headers. */ + size_t neededSpace = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr); + for (ReplacedSections::iterator i = replacedSections.begin(); + i != replacedSections.end(); ++i) + neededSpace += roundUp(i->second.size(), sectionAlignment); + + debug("needed space is %d\n", neededSpace); + + /* If we need more space at the start of the file, then grow the + file by the minimum number of pages and adjust internal + offsets. */ + if (neededSpace > startOffset) { + + /* We also need an additional program header, so adjust for that. */ + neededSpace += sizeof(Elf_Phdr); + debug("needed space is %d\n", neededSpace); + + unsigned int neededPages = roundUp(neededSpace - startOffset, pageSize) / pageSize; + debug("needed pages is %d\n", neededPages); + if (neededPages * pageSize > firstPage) + error("virtual address space underrun!"); + + firstPage -= neededPages * pageSize; + startOffset += neededPages * pageSize; + + shiftFile(neededPages, firstPage); + } + + + /* Clear out the free space. */ + Elf_Off curOff = sizeof(Elf_Ehdr) + phdrs.size() * sizeof(Elf_Phdr); + debug("clearing first %d bytes\n", startOffset - curOff); + memset(contents + curOff, 0, startOffset - curOff); + + + /* Write out the replaced sections. */ + writeReplacedSections(curOff, firstPage, 0); + assert((off_t) curOff == neededSpace); + + + rewriteHeaders(firstPage + rdi(hdr->e_phoff)); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::rewriteSections() +{ + if (replacedSections.empty()) return; + + for (ReplacedSections::iterator i = replacedSections.begin(); + i != replacedSections.end(); ++i) + debug("replacing section `%s' with size %d\n", + i->first.c_str(), i->second.size()); + + if (rdi(hdr->e_type) == ET_DYN) { + debug("this is a dynamic library\n"); + rewriteSectionsLibrary(); + } else if (rdi(hdr->e_type) == ET_EXEC) { + debug("this is an executable\n"); + rewriteSectionsExecutable(); + } else error("unknown ELF type"); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress) +{ + /* Rewrite the program header table. */ + + /* If there is a segment for the program header table, update it. + (According to the ELF spec, it must be the first entry.) */ + if (rdi(phdrs[0].p_type) == PT_PHDR) { + phdrs[0].p_offset = hdr->e_phoff; + wri(phdrs[0].p_vaddr, wri(phdrs[0].p_paddr, phdrAddress)); + wri(phdrs[0].p_filesz, wri(phdrs[0].p_memsz, phdrs.size() * sizeof(Elf_Phdr))); + } + + sortPhdrs(); + + for (unsigned int i = 0; i < phdrs.size(); ++i) + * ((Elf_Phdr *) (contents + rdi(hdr->e_phoff)) + i) = phdrs[i]; + + + /* Rewrite the section header table. For neatness, keep the + sections sorted. */ + assert(rdi(hdr->e_shnum) == shdrs.size()); + sortShdrs(); + for (unsigned int i = 1; i < rdi(hdr->e_shnum); ++i) + * ((Elf_Shdr *) (contents + rdi(hdr->e_shoff)) + i) = shdrs[i]; + + + /* Update all those nasty virtual addresses in the .dynamic + section. Note that not all executables have .dynamic sections + (e.g., those produced by klibc's klcc). */ + Elf_Shdr * shdrDynamic = findSection2(".dynamic"); + if (shdrDynamic) { + Elf_Dyn * dyn = (Elf_Dyn *) (contents + rdi(shdrDynamic->sh_offset)); + unsigned int d_tag; + for ( ; (d_tag = rdi(dyn->d_tag)) != DT_NULL; dyn++) + if (d_tag == DT_STRTAB) + dyn->d_un.d_ptr = findSection(".dynstr").sh_addr; + else if (d_tag == DT_STRSZ) + dyn->d_un.d_val = findSection(".dynstr").sh_size; + else if (d_tag == DT_SYMTAB) + dyn->d_un.d_ptr = findSection(".dynsym").sh_addr; + else if (d_tag == DT_HASH) + dyn->d_un.d_ptr = findSection(".hash").sh_addr; + else if (d_tag == DT_GNU_HASH) + dyn->d_un.d_ptr = findSection(".gnu.hash").sh_addr; + else if (d_tag == DT_JMPREL) { + Elf_Shdr * shdr = findSection2(".rel.plt"); + if (!shdr) shdr = findSection2(".rela.plt"); /* 64-bit Linux, x86-64 */ + if (!shdr) shdr = findSection2(".rela.IA_64.pltoff"); /* 64-bit Linux, IA-64 */ + if (!shdr) error("cannot find section corresponding to DT_JMPREL"); + dyn->d_un.d_ptr = shdr->sh_addr; + } + else if (d_tag == DT_REL) { /* !!! hack! */ + Elf_Shdr * shdr = findSection2(".rel.dyn"); + /* no idea if this makes sense, but it was needed for some + program */ + if (!shdr) shdr = findSection2(".rel.got"); + if (!shdr) error("cannot find .rel.dyn or .rel.got"); + dyn->d_un.d_ptr = shdr->sh_addr; + } + else if (d_tag == DT_RELA) + dyn->d_un.d_ptr = findSection(".rela.dyn").sh_addr; /* PPC Linux */ + else if (d_tag == DT_VERNEED) + dyn->d_un.d_ptr = findSection(".gnu.version_r").sh_addr; + else if (d_tag == DT_VERSYM) + dyn->d_un.d_ptr = findSection(".gnu.version").sh_addr; + } +} + + + +static void setSubstr(string & s, unsigned int pos, const string & t) +{ + assert(pos + t.size() <= s.size()); + copy(t.begin(), t.end(), s.begin() + pos); +} + + +template<ElfFileParams> +string ElfFile<ElfFileParamNames>::getInterpreter() +{ + Elf_Shdr & shdr = findSection(".interp"); + return string((char *) contents + rdi(shdr.sh_offset), rdi(shdr.sh_size)); +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::setInterpreter(const string & newInterpreter) +{ + string & section = replaceSection(".interp", newInterpreter.size() + 1); + setSubstr(section, 0, newInterpreter + '\0'); + changed = true; +} + + +static void concatToRPath(string & rpath, const string & path) +{ + if (!rpath.empty()) rpath += ":"; + rpath += path; +} + + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op, string newRPath) +{ + Elf_Shdr & shdrDynamic = findSection(".dynamic"); + + /* !!! We assume that the virtual address in the DT_STRTAB entry + of the dynamic section corresponds to the .dynstr section. */ + Elf_Shdr & shdrDynStr = findSection(".dynstr"); + char * strTab = (char *) contents + rdi(shdrDynStr.sh_offset); + + /* Find the DT_STRTAB entry in the dynamic section. */ + Elf_Dyn * dyn = (Elf_Dyn *) (contents + rdi(shdrDynamic.sh_offset)); + Elf_Addr strTabAddr = 0; + for ( ; rdi(dyn->d_tag) != DT_NULL; dyn++) + if (rdi(dyn->d_tag) == DT_STRTAB) strTabAddr = rdi(dyn->d_un.d_ptr); + if (!strTabAddr) error("strange: no string table"); + + assert(strTabAddr == rdi(shdrDynStr.sh_addr)); + + + /* Walk through the dynamic section, look for the RPATH/RUNPATH + entry. + + According to the ld.so docs, DT_RPATH is obsolete, we should + use DT_RUNPATH. DT_RUNPATH has two advantages: it can be + overriden by LD_LIBRARY_PATH, and it's scoped (the DT_RUNPATH + for an executable or library doesn't affect the search path for + libraries used by it). DT_RPATH is ignored if DT_RUNPATH is + present. The binutils `ld' still generates only DT_RPATH, + unless you use its `--enable-new-dtag' option, in which case it + generates a DT_RPATH and DT_RUNPATH pointing at the same + string. */ + static vector<string> neededLibs; + dyn = (Elf_Dyn *) (contents + rdi(shdrDynamic.sh_offset)); + Elf_Dyn * dynRPath = 0, * dynRunPath = 0; + char * rpath = 0; + for ( ; rdi(dyn->d_tag) != DT_NULL; dyn++) { + if (rdi(dyn->d_tag) == DT_RPATH) { + dynRPath = dyn; + /* Only use DT_RPATH if there is no DT_RUNPATH. */ + if (!dynRunPath) + rpath = strTab + rdi(dyn->d_un.d_val); + } + else if (rdi(dyn->d_tag) == DT_RUNPATH) { + dynRunPath = dyn; + rpath = strTab + rdi(dyn->d_un.d_val); + } + else if (rdi(dyn->d_tag) == DT_NEEDED) + neededLibs.push_back(string(strTab + rdi(dyn->d_un.d_val))); + } + + if (op == rpPrint) { + printf("%s\n", rpath ? rpath : ""); + return; + } + + if (op == rpShrink && !rpath) { + debug("no RPATH to shrink\n"); + return; + } + + + /* For each directory in the RPATH, check if it contains any + needed library. */ + if (op == rpShrink) { + static vector<bool> neededLibFound(neededLibs.size(), false); + + newRPath = ""; + + char * pos = rpath; + while (*pos) { + char * end = strchr(pos, ':'); + if (!end) end = strchr(pos, 0); + + /* Get the name of the directory. */ + string dirName(pos, end - pos); + if (*end == ':') ++end; + pos = end; + + /* Non-absolute entries are allowed (e.g., the special + "$ORIGIN" hack). */ + if (dirName[0] != '/') { + concatToRPath(newRPath, dirName); + continue; + } + + /* For each library that we haven't found yet, see if it + exists in this directory. */ + bool libFound = false; + for (unsigned int j = 0; j < neededLibs.size(); ++j) + if (!neededLibFound[j]) { + string libName = dirName + "/" + neededLibs[j]; + struct stat st; + if (stat(libName.c_str(), &st) == 0) { + neededLibFound[j] = true; + libFound = true; + } + } + + if (!libFound) + debug("removing directory `%s' from RPATH\n", dirName.c_str()); + else + concatToRPath(newRPath, dirName); + } + } + + + if (string(rpath ? rpath : "") == newRPath) return; + + changed = true; + + /* Zero out the previous rpath to prevent retained dependencies in + Nix. */ + unsigned int rpathSize = 0; + if (rpath) { + rpathSize = strlen(rpath); + memset(rpath, 'X', rpathSize); + } + + debug("new rpath is `%s'\n", newRPath.c_str()); + + if (!forceRPath && dynRPath && !dynRunPath) { /* convert DT_RPATH to DT_RUNPATH */ + dynRPath->d_tag = DT_RUNPATH; + dynRunPath = dynRPath; + dynRPath = 0; + } + + if (forceRPath && dynRPath && dynRunPath) { /* convert DT_RUNPATH to DT_RPATH */ + dynRunPath->d_tag = DT_IGNORE; + } + + if (newRPath.size() <= rpathSize) { + strcpy(rpath, newRPath.c_str()); + return; + } + + /* Grow the .dynstr section to make room for the new RPATH. */ + debug("rpath is too long, resizing...\n"); + + string & newDynStr = replaceSection(".dynstr", + rdi(shdrDynStr.sh_size) + newRPath.size() + 1); + setSubstr(newDynStr, rdi(shdrDynStr.sh_size), newRPath + '\0'); + + /* Update the DT_RUNPATH and DT_RPATH entries. */ + if (dynRunPath || dynRPath) { + if (dynRunPath) dynRunPath->d_un.d_val = shdrDynStr.sh_size; + if (dynRPath) dynRPath->d_un.d_val = shdrDynStr.sh_size; + } + + else { + /* There is no DT_RUNPATH entry in the .dynamic section, so we + have to grow the .dynamic section. */ + string & newDynamic = replaceSection(".dynamic", + rdi(shdrDynamic.sh_size) + sizeof(Elf_Dyn)); + + unsigned int idx = 0; + for ( ; rdi(((Elf_Dyn *) newDynamic.c_str())[idx].d_tag) != DT_NULL; idx++) ; + debug("DT_NULL index is %d\n", idx); + + /* Shift all entries down by one. */ + setSubstr(newDynamic, sizeof(Elf_Dyn), + string(newDynamic, 0, sizeof(Elf_Dyn) * (idx + 1))); + + /* Add the DT_RUNPATH entry at the top. */ + Elf_Dyn newDyn; + wri(newDyn.d_tag, forceRPath ? DT_RPATH : DT_RUNPATH); + newDyn.d_un.d_val = shdrDynStr.sh_size; + setSubstr(newDynamic, 0, string((char *) &newDyn, sizeof(Elf_Dyn))); + } +} + + +static bool printInterpreter = false; +static string newInterpreter; + +static bool shrinkRPath = false; +static bool setRPath = false; +static bool printRPath = false; +static string newRPath; + + +template<class ElfFile> +static void patchElf2(ElfFile & elfFile, mode_t fileMode) +{ + elfFile.parse(); + + + if (printInterpreter) + printf("%s\n", elfFile.getInterpreter().c_str()); + + if (newInterpreter != "") + elfFile.setInterpreter(newInterpreter); + + if (printRPath) + elfFile.modifyRPath(elfFile.rpPrint, ""); + + if (shrinkRPath) + elfFile.modifyRPath(elfFile.rpShrink, ""); + else if (setRPath) + elfFile.modifyRPath(elfFile.rpSet, newRPath); + + + if (elfFile.isChanged()){ + elfFile.rewriteSections(); + writeFile(fileName, fileMode); + } +} + + +static void patchElf() +{ + if (!printInterpreter && !printRPath) + debug("patching ELF file `%s'\n", fileName.c_str()); + + mode_t fileMode; + + readFile(fileName, &fileMode); + + + /* Check the ELF header for basic validity. */ + if (fileSize < (off_t) sizeof(Elf32_Ehdr)) error("missing ELF header"); + + if (memcmp(contents, ELFMAG, SELFMAG) != 0) + error("not an ELF executable"); + + if (contents[EI_CLASS] == ELFCLASS32 && + contents[EI_VERSION] == EV_CURRENT) + { + ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn> elfFile; + patchElf2(elfFile, fileMode); + } + else if (contents[EI_CLASS] == ELFCLASS64 && + contents[EI_VERSION] == EV_CURRENT) + { + ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn> elfFile; + patchElf2(elfFile, fileMode); + } + else { + error("ELF executable is not 32/64-bit, little/big-endian, version 1"); + } +} + + +void showHelp(const string & progName) +{ + fprintf(stderr, "syntax: %s\n\ + [--set-interpreter FILENAME]\n\ + [--print-interpreter]\n\ + [--set-rpath RPATH]\n\ + [--shrink-rpath]\n\ + [--print-rpath]\n\ + [--force-rpath]\n\ + [--debug]\n\ + [--version]\n\ + FILENAME\n", progName.c_str()); +} + + +int main(int argc, char * * argv) +{ + if (argc <= 1) { + showHelp(argv[0]); + return 1; + } + + if (getenv("PATCHELF_DEBUG") != 0) debugMode = true; + + int i; + for (i = 1; i < argc; ++i) { + string arg(argv[i]); + if (arg == "--set-interpreter" || arg == "--interpreter") { + if (++i == argc) error("missing argument"); + newInterpreter = argv[i]; + } + else if (arg == "--print-interpreter") { + printInterpreter = true; + } + else if (arg == "--shrink-rpath") { + shrinkRPath = true; + } + else if (arg == "--set-rpath") { + if (++i == argc) error("missing argument"); + setRPath = true; + newRPath = argv[i]; + } + else if (arg == "--print-rpath") { + printRPath = true; + } + else if (arg == "--force-rpath") { + /* Generally we prefer to emit DT_RUNPATH instead of + DT_RPATH, as the latter is obsolete. However, there is + a slight semantic difference: DT_RUNPATH is "scoped", + it only affects the executable or library in question, + not its recursive imports. So maybe you really want to + force the use of DT_RPATH. That's what this option + does. Without it, DT_RPATH (if encountered) is + converted to DT_RUNPATH, and if neither is present, a + DT_RUNPATH is added. With it, DT_RPATH isn't converted + to DT_RUNPATH, and if neither is present, a DT_RPATH is + added. */ + forceRPath = true; + } + else if (arg == "--debug") { + debugMode = true; + } + else if (arg == "--help") { + showHelp(argv[0]); + return 0; + } + else if (arg == "--version") { + printf("1.0\n"); + return 0; + } + else break; + } + + if (i == argc) error("missing filename"); + fileName = argv[i]; + + patchElf(); + + return 0; +} |