diff --git a/patchelf.1 b/patchelf.1 index 6a8a94e1..4e59b402 100644 --- a/patchelf.1 +++ b/patchelf.1 @@ -131,6 +131,11 @@ old_name new_name Symbol names do not contain version specifier that are also shown in the output of the nm -D command from binutils. So instead of the name write@GLIBC_2.2.5 it is just write. +.IP "--clean-strtab" +Regenerates the ".dynstr" section removing all unused strings. + +Notice this may actually increase the size of ths section because it will undo string sharing between "vprintf" and "printf" some linkers create. + .IP "--output FILE" Set the output file name. If not specified, the input will be modified in place. diff --git a/src/patchelf.cc b/src/patchelf.cc index ca247c12..2623429b 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -1903,6 +1904,88 @@ void ElfFile::noDefaultLib() changed = true; } +template +void ElfFile::cleanStrTab() +{ + std::unordered_map requiredStrs2Idx {{"",0}}; + + // A collection of pairs, each containing: + // - a pointer to the field that refer to str indices + // - a pointer to the new index in `requiredStrs2Idx` + using StrIndexPtr = std::variant; + std::vector> strRefs; + + auto& strTabHdr = findSectionHeader(".dynstr"); + auto strTab = getSectionSpan(strTabHdr); + + // Utility to collect a string index field from any table + auto collect = [&] (auto& idx) { + auto [it, _] = requiredStrs2Idx.emplace(&strTab[rdi(idx)], 0); + strRefs.emplace_back(&idx, &it->second); + }; + + // Iterate on tables known to store references to .dynstr + for (auto& sym : tryGetSectionSpan(".dynsym")) + collect(sym.st_name); + + for (auto& dyn : tryGetSectionSpan(".dynamic")) + switch (rdi(dyn.d_tag)) + { + case DT_NEEDED: + case DT_SONAME: + case DT_RPATH: + case DT_RUNPATH: collect(dyn.d_un.d_val); + default:; + } + + if (auto verdHdr = tryFindSectionHeader(".gnu.version_d")) + { + // Only collect fields if they use the strtab we are cleaning + if (&shdrs.at(rdi(verdHdr->get().sh_link)) == &strTabHdr) + forAll_ElfVer(getSectionSpan(*verdHdr), + [] (auto& /*vd*/) {}, + [&] (auto& vda) { collect(vda.vda_name); } + ); + } + + if (auto vernHdr = tryFindSectionHeader(".gnu.version_r")) + { + // Only collect fields if they use the strtab we are cleaning + if (&shdrs.at(rdi(vernHdr->get().sh_link)) == &strTabHdr) + forAll_ElfVer(getSectionSpan(*vernHdr), + [&] (auto& vn) { collect(vn.vn_file); }, + [&] (auto& vna) { collect(vna.vna_name); } + ); + } + + // Iterate on all required strings calculating the new position + size_t curIdx = 1; + for (auto& [str,idx] : requiredStrs2Idx) + { + idx = curIdx; + curIdx += str.size() + /*null terminator*/1; + } + + // Add required strings to the new dynstr section + auto& newStrSec = replaceSection(".dynstr", curIdx); + for (auto& [str,idx] : requiredStrs2Idx) + std::copy(str.begin(), str.end()+1, &newStrSec[idx]); + + // Iterate on all fields on all tables setting the new index value + for (auto& [oldIndexPtr, newIdxPtr_] : strRefs) + { + auto newIdxPtr = newIdxPtr_; // Some compilers complain about + // capturing structured bindings + std::visit( + [&] (auto* ptr) { wri(*ptr, *newIdxPtr); }, + oldIndexPtr + ); + } + + changed = true; + this->rewriteSections(); +} + template void ElfFile::addDebugTag() { @@ -2271,6 +2354,7 @@ static bool removeRPath = false; static bool setRPath = false; static bool addRPath = false; static bool addDebugTag = false; +static bool cleanStrTab = false; static bool renameDynamicSymbols = false; static bool printRPath = false; static std::string newRPath; @@ -2342,6 +2426,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con if (renameDynamicSymbols) elfFile.renameDynamicSymbols(symbolsToRename); + if (cleanStrTab) + elfFile.cleanStrTab(); + if (elfFile.isChanged()){ writeFile(fileName, elfFile.fileContents); } else if (alwaysWrite) { @@ -2361,9 +2448,9 @@ static void patchElf() const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName; if (getElfType(fileContents).is32Bit) - patchElf2(ElfFile(fileContents), fileContents, outputFileName2); + patchElf2(ElfFile(fileContents), fileContents, outputFileName2); else - patchElf2(ElfFile(fileContents), fileContents, outputFileName2); + patchElf2(ElfFile(fileContents), fileContents, outputFileName2); } } @@ -2406,6 +2493,7 @@ static void showHelp(const std::string & progName) [--clear-execstack]\n\ [--set-execstack]\n\ [--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\ + [--clean-strtab]\n\ [--output FILE]\n\ [--debug]\n\ [--version]\n\ @@ -2537,6 +2625,9 @@ static int mainWrapped(int argc, char * * argv) else if (arg == "--add-debug-tag") { addDebugTag = true; } + else if (arg == "--clean-strtab") { + cleanStrTab = true; + } else if (arg == "--rename-dynamic-symbols") { renameDynamicSymbols = true; if (++i == argc) error("missing argument"); diff --git a/src/patchelf.h b/src/patchelf.h index 9fab18c0..f5748fac 100644 --- a/src/patchelf.h +++ b/src/patchelf.h @@ -10,8 +10,8 @@ using FileContents = std::shared_ptr>; -#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym, class Elf_Verneed, class Elf_Versym, class Elf_Rel, class Elf_Rela, unsigned ElfClass -#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed, Elf_Versym, Elf_Rel, Elf_Rela, ElfClass +#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym, class Elf_Versym, class Elf_Verdef, class Elf_Verdaux, class Elf_Verneed, class Elf_Vernaux, class Elf_Rel, class Elf_Rela, unsigned ElfClass +#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Versym, Elf_Verdef, Elf_Verdaux, Elf_Verneed, Elf_Vernaux, Elf_Rel, Elf_Rela, ElfClass template struct span @@ -175,6 +175,8 @@ class ElfFile void modifyExecstack(ExecstackMode op); + void cleanStrTab(); + private: struct GnuHashTable { using BloomWord = Elf_Addr; @@ -226,7 +228,6 @@ class ElfFile void changeRelocTableSymIds(const Elf_Shdr& shdr, RemapFn&& old2newSymId) { static_assert(std::is_same_v || std::is_same_v); - for (auto& r : getSectionSpan(shdr)) { auto info = rdi(r.r_info); @@ -237,6 +238,37 @@ class ElfFile } } + template + auto follow(U* ptr, size_t offset) -> T* { + return offset ? (T*)(((char*)ptr)+offset) : nullptr; + }; + + template + void forAll_ElfVer(span vdspan, VdFn&& vdfn, VaFn&& vafn) + { + auto* vd = vdspan.begin(); + for (; vd; vd = follow(vd, rdi(vd->vd_next))) + { + vdfn(*vd); + auto va = follow(vd, rdi(vd->vd_aux)); + for (; va; va = follow(va, rdi(va->vda_next))) + vafn(*va); + } + } + + template + void forAll_ElfVer(span vnspan, VnFn&& vnfn, VaFn&& vafn) + { + auto* vn = vnspan.begin(); + for (; vn; vn = follow(vn, rdi(vn->vn_next))) + { + vnfn(*vn); + auto va = follow(vn, rdi(vn->vn_aux)); + for (; va; va = follow(va, rdi(va->vna_next))) + vafn(*va); + } + } + /* Convert an integer in big or little endian representation (as specified by the ELF header) to this platform's integer representation. */ diff --git a/tests/clean-strtab.sh b/tests/clean-strtab.sh new file mode 100755 index 00000000..b3ae54e7 --- /dev/null +++ b/tests/clean-strtab.sh @@ -0,0 +1,32 @@ +#! /bin/sh -e +SCRATCH=scratch/$(basename "$0" .sh) +PATCHELF=$(readlink -f "../src/patchelf") + +rm -rf "${SCRATCH}" +mkdir -p "${SCRATCH}" + +cp libfoo.so "${SCRATCH}/" + +cd "${SCRATCH}" + +the_string=VERY_SPECIFIC_STRING +check_count() { + count="$(strings libfoo.so | grep -c $the_string || true)" + expected=$1 + echo "####### Checking count. Expected: $expected" + [ "$count" = "$expected" ] || exit 1 +} + +check_count 0 + +${PATCHELF} --clean-strtab libfoo.so +check_count 0 + +${PATCHELF} --add-needed $the_string libfoo.so +check_count 1 + +${PATCHELF} --remove-needed $the_string libfoo.so +check_count 1 + +${PATCHELF} --clean-strtab libfoo.so +check_count 0