Skip to content

Commit da7a592

Browse files
committed
Implement --clean-strtab
1 parent 69a7ae5 commit da7a592

File tree

4 files changed

+164
-5
lines changed

4 files changed

+164
-5
lines changed

patchelf.1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ old_name new_name
131131

132132
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.
133133

134+
.IP "--clean-strtab"
135+
Regenerates the ".dynstr" section removing all unused strings.
136+
137+
Notice this may actually increase the size of ths section because it will undo string sharing between "vprintf" and "printf" some linkers create.
138+
134139
.IP "--output FILE"
135140
Set the output file name. If not specified, the input will be modified in place.
136141

src/patchelf.cc

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <string_view>
3030
#include <unordered_map>
3131
#include <unordered_set>
32+
#include <variant>
3233
#include <vector>
3334

3435
#include <cassert>
@@ -1903,6 +1904,87 @@ void ElfFile<ElfFileParamNames>::noDefaultLib()
19031904
changed = true;
19041905
}
19051906

1907+
template<ElfFileParams>
1908+
void ElfFile<ElfFileParamNames>::cleanStrTab()
1909+
{
1910+
std::unordered_map<std::string, unsigned> requiredStrs2Idx {{"",0}};
1911+
1912+
// A collection of pointers to the fields that refer to str indices
1913+
// and a pointer to the new index value to be calculated
1914+
using StrIndexPtr = std::variant<Elf32_Word*, Elf64_Xword*>;
1915+
std::vector<std::pair<StrIndexPtr, unsigned*>> strRefs;
1916+
1917+
auto& strTabHdr = findSectionHeader(".dynstr");
1918+
auto strTab = getSectionSpan<char>(strTabHdr);
1919+
1920+
// Utility to collect a string index field from any table
1921+
auto collect = [&] (auto& idx) {
1922+
auto [it, _] = requiredStrs2Idx.emplace(&strTab[rdi(idx)], 0);
1923+
strRefs.emplace_back(&idx, &it->second);
1924+
};
1925+
1926+
// Iterate on tables known to store references to .dynstr
1927+
for (auto& sym : tryGetSectionSpan<Elf_Sym>(".dynsym"))
1928+
collect(sym.st_name);
1929+
1930+
for (auto& dyn : tryGetSectionSpan<Elf_Dyn>(".dynamic"))
1931+
switch (rdi(dyn.d_tag))
1932+
{
1933+
case DT_NEEDED:
1934+
case DT_SONAME:
1935+
case DT_RPATH:
1936+
case DT_RUNPATH: collect(dyn.d_un.d_val);
1937+
default:;
1938+
}
1939+
1940+
if (auto verdHdr = tryFindSectionHeader(".gnu.version_d"))
1941+
{
1942+
// Only collect fields if they use the strtab we are cleaning
1943+
if (&shdrs.at(rdi(verdHdr->get().sh_link)) == &strTabHdr)
1944+
forAll_ElfVer(getSectionSpan<Elf_Verdef>(*verdHdr),
1945+
[] (auto& /*vd*/) {},
1946+
[&] (auto& vda) { collect(vda.vda_name); }
1947+
);
1948+
}
1949+
1950+
if (auto vernHdr = tryFindSectionHeader(".gnu.version_r"))
1951+
{
1952+
// Only collect fields if they use the strtab we are cleaning
1953+
if (&shdrs.at(rdi(vernHdr->get().sh_link)) == &strTabHdr)
1954+
forAll_ElfVer(getSectionSpan<Elf_Verneed>(*vernHdr),
1955+
[&] (auto& vn) { collect(vn.vn_file); },
1956+
[&] (auto& vna) { collect(vna.vna_name); }
1957+
);
1958+
}
1959+
1960+
// Iterate on all required strings calculating the new position
1961+
size_t curIdx = 1;
1962+
for (auto& [str,idx] : requiredStrs2Idx)
1963+
{
1964+
idx = curIdx;
1965+
curIdx += str.size()+1;
1966+
}
1967+
1968+
// Add required strings to the new dynstr section
1969+
auto& newStrSec = replaceSection(".dynstr", curIdx);
1970+
for (auto& [str,idx] : requiredStrs2Idx)
1971+
std::copy(str.begin(), str.end()+1, newStrSec.begin()+idx);
1972+
1973+
// Iterate on all fields on all tables setting the new index value
1974+
for (auto& [oldIndexPtr, newIdxPtr_] : strRefs)
1975+
{
1976+
auto newIdxPtr = newIdxPtr_; // Some compilers complain about
1977+
// capturing structured bindings
1978+
std::visit(
1979+
[&] (auto* ptr) { wri(*ptr, *newIdxPtr); },
1980+
oldIndexPtr
1981+
);
1982+
}
1983+
1984+
changed = true;
1985+
this->rewriteSections();
1986+
}
1987+
19061988
template<ElfFileParams>
19071989
void ElfFile<ElfFileParamNames>::addDebugTag()
19081990
{
@@ -2271,6 +2353,7 @@ static bool removeRPath = false;
22712353
static bool setRPath = false;
22722354
static bool addRPath = false;
22732355
static bool addDebugTag = false;
2356+
static bool cleanStrTab = false;
22742357
static bool renameDynamicSymbols = false;
22752358
static bool printRPath = false;
22762359
static std::string newRPath;
@@ -2342,6 +2425,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
23422425
if (renameDynamicSymbols)
23432426
elfFile.renameDynamicSymbols(symbolsToRename);
23442427

2428+
if (cleanStrTab)
2429+
elfFile.cleanStrTab();
2430+
23452431
if (elfFile.isChanged()){
23462432
writeFile(fileName, elfFile.fileContents);
23472433
} else if (alwaysWrite) {
@@ -2361,9 +2447,9 @@ static void patchElf()
23612447
const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName;
23622448

23632449
if (getElfType(fileContents).is32Bit)
2364-
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);
2450+
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);
23652451
else
2366-
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);
2452+
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);
23672453
}
23682454
}
23692455

@@ -2406,6 +2492,7 @@ static void showHelp(const std::string & progName)
24062492
[--clear-execstack]\n\
24072493
[--set-execstack]\n\
24082494
[--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\
2495+
[--clean-strtab]\n\
24092496
[--output FILE]\n\
24102497
[--debug]\n\
24112498
[--version]\n\
@@ -2537,6 +2624,9 @@ static int mainWrapped(int argc, char * * argv)
25372624
else if (arg == "--add-debug-tag") {
25382625
addDebugTag = true;
25392626
}
2627+
else if (arg == "--clean-strtab") {
2628+
cleanStrTab = true;
2629+
}
25402630
else if (arg == "--rename-dynamic-symbols") {
25412631
renameDynamicSymbols = true;
25422632
if (++i == argc) error("missing argument");

src/patchelf.h

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

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

13-
#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
14-
#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
13+
#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
14+
#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
1515

1616
template<class T>
1717
struct span
@@ -175,6 +175,8 @@ class ElfFile
175175

176176
void modifyExecstack(ExecstackMode op);
177177

178+
void cleanStrTab();
179+
178180
private:
179181
struct GnuHashTable {
180182
using BloomWord = Elf_Addr;
@@ -226,7 +228,6 @@ class ElfFile
226228
void changeRelocTableSymIds(const Elf_Shdr& shdr, RemapFn&& old2newSymId)
227229
{
228230
static_assert(std::is_same_v<ElfRelType, Elf_Rel> || std::is_same_v<ElfRelType, Elf_Rela>);
229-
230231
for (auto& r : getSectionSpan<ElfRelType>(shdr))
231232
{
232233
auto info = rdi(r.r_info);
@@ -237,6 +238,37 @@ class ElfFile
237238
}
238239
}
239240

241+
template<class T, class U>
242+
auto follow(U* ptr, size_t offset) -> T* {
243+
return offset ? (T*)(((char*)ptr)+offset) : nullptr;
244+
};
245+
246+
template<class VdFn, class VaFn>
247+
void forAll_ElfVer(span<Elf_Verdef> vdspan, VdFn&& vdfn, VaFn&& vafn)
248+
{
249+
auto* vd = vdspan.begin();
250+
for (; vd; vd = follow<Elf_Verdef>(vd, rdi(vd->vd_next)))
251+
{
252+
vdfn(*vd);
253+
auto va = follow<Elf_Verdaux>(vd, rdi(vd->vd_aux));
254+
for (; va; va = follow<Elf_Verdaux>(va, rdi(va->vda_next)))
255+
vafn(*va);
256+
}
257+
}
258+
259+
template<class VnFn, class VaFn>
260+
void forAll_ElfVer(span<Elf_Verneed> vnspan, VnFn&& vnfn, VaFn&& vafn)
261+
{
262+
auto* vn = vnspan.begin();
263+
for (; vn; vn = follow<Elf_Verneed>(vn, rdi(vn->vn_next)))
264+
{
265+
vnfn(*vn);
266+
auto va = follow<Elf_Vernaux>(vn, rdi(vn->vn_aux));
267+
for (; va; va = follow<Elf_Vernaux>(va, rdi(va->vna_next)))
268+
vafn(*va);
269+
}
270+
}
271+
240272
/* Convert an integer in big or little endian representation (as
241273
specified by the ELF header) to this platform's integer
242274
representation. */

tests/clean-strtab.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#! /bin/sh -e
2+
SCRATCH=scratch/$(basename "$0" .sh)
3+
PATCHELF=$(readlink -f "../src/patchelf")
4+
5+
rm -rf "${SCRATCH}"
6+
mkdir -p "${SCRATCH}"
7+
8+
cp libfoo.so "${SCRATCH}/"
9+
10+
cd "${SCRATCH}"
11+
12+
the_string=VERY_SPECIFIC_STRING
13+
check_count() {
14+
count="$(strings libfoo.so | grep -c $the_string || true)"
15+
expected=$1
16+
echo "####### Checking count. Expected: $expected"
17+
[ "$count" = "$expected" ] || exit 1
18+
}
19+
20+
check_count 0
21+
22+
${PATCHELF} --clean-strtab libfoo.so
23+
check_count 0
24+
25+
${PATCHELF} --add-needed $the_string libfoo.so
26+
check_count 1
27+
28+
${PATCHELF} --remove-needed $the_string libfoo.so
29+
check_count 1
30+
31+
${PATCHELF} --clean-strtab libfoo.so
32+
check_count 0

0 commit comments

Comments
 (0)