Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing --clean-strtab #461

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions patchelf.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
95 changes: 93 additions & 2 deletions src/patchelf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <variant>
#include <vector>

#include <cassert>
Expand Down Expand Up @@ -1903,6 +1904,88 @@ void ElfFile<ElfFileParamNames>::noDefaultLib()
changed = true;
}

template<ElfFileParams>
void ElfFile<ElfFileParamNames>::cleanStrTab()
{
std::unordered_map<std::string, unsigned> 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<Elf32_Word*, Elf64_Xword*>;
std::vector<std::pair<StrIndexPtr, unsigned*>> strRefs;

auto& strTabHdr = findSectionHeader(".dynstr");
auto strTab = getSectionSpan<char>(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<Elf_Sym>(".dynsym"))
collect(sym.st_name);

for (auto& dyn : tryGetSectionSpan<Elf_Dyn>(".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<Elf_Verdef>(*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<Elf_Verneed>(*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<ElfFileParams>
void ElfFile<ElfFileParamNames>::addDebugTag()
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -2361,9 +2448,9 @@ static void patchElf()
const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName;

if (getElfType(fileContents).is32Bit)
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Verneed, Elf32_Versym, Elf32_Rel, Elf32_Rela, 32>(fileContents), fileContents, outputFileName2);
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Versym, Elf32_Verdef, Elf32_Verdaux, Elf32_Verneed, Elf32_Vernaux, Elf32_Rel, Elf32_Rela, 32>(fileContents), fileContents, outputFileName2);
else
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym, Elf64_Rel, Elf64_Rela, 64>(fileContents), fileContents, outputFileName2);
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Versym, Elf64_Verdef, Elf64_Verdaux, Elf64_Verneed, Elf64_Vernaux, Elf64_Rel, Elf64_Rela, 64>(fileContents), fileContents, outputFileName2);
}
}

Expand Down Expand Up @@ -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\
Expand Down Expand Up @@ -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");
Expand Down
38 changes: 35 additions & 3 deletions src/patchelf.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

using FileContents = std::shared_ptr<std::vector<unsigned char>>;

#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<class T>
struct span
Expand Down Expand Up @@ -175,6 +175,8 @@ class ElfFile

void modifyExecstack(ExecstackMode op);

void cleanStrTab();

private:
struct GnuHashTable {
using BloomWord = Elf_Addr;
Expand Down Expand Up @@ -226,7 +228,6 @@ class ElfFile
void changeRelocTableSymIds(const Elf_Shdr& shdr, RemapFn&& old2newSymId)
{
static_assert(std::is_same_v<ElfRelType, Elf_Rel> || std::is_same_v<ElfRelType, Elf_Rela>);

for (auto& r : getSectionSpan<ElfRelType>(shdr))
{
auto info = rdi(r.r_info);
Expand All @@ -237,6 +238,37 @@ class ElfFile
}
}

template<class T, class U>
auto follow(U* ptr, size_t offset) -> T* {
return offset ? (T*)(((char*)ptr)+offset) : nullptr;
};

template<class VdFn, class VaFn>
void forAll_ElfVer(span<Elf_Verdef> vdspan, VdFn&& vdfn, VaFn&& vafn)
{
auto* vd = vdspan.begin();
for (; vd; vd = follow<Elf_Verdef>(vd, rdi(vd->vd_next)))
{
vdfn(*vd);
auto va = follow<Elf_Verdaux>(vd, rdi(vd->vd_aux));
for (; va; va = follow<Elf_Verdaux>(va, rdi(va->vda_next)))
vafn(*va);
}
}

template<class VnFn, class VaFn>
void forAll_ElfVer(span<Elf_Verneed> vnspan, VnFn&& vnfn, VaFn&& vafn)
{
auto* vn = vnspan.begin();
for (; vn; vn = follow<Elf_Verneed>(vn, rdi(vn->vn_next)))
{
vnfn(*vn);
auto va = follow<Elf_Vernaux>(vn, rdi(vn->vn_aux));
for (; va; va = follow<Elf_Vernaux>(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. */
Expand Down
32 changes: 32 additions & 0 deletions tests/clean-strtab.sh
Original file line number Diff line number Diff line change
@@ -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