Skip to content

Commit c921262

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

File tree

4 files changed

+165
-5
lines changed

4 files changed

+165
-5
lines changed

patchelf.1

+5
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

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

2429+
if (cleanStrTab)
2430+
elfFile.cleanStrTab();
2431+
23452432
if (elfFile.isChanged()){
23462433
writeFile(fileName, elfFile.fileContents);
23472434
} else if (alwaysWrite) {
@@ -2361,9 +2448,9 @@ static void patchElf()
23612448
const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName;
23622449

23632450
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);
2451+
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);
23652452
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);
2453+
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);
23672454
}
23682455
}
23692456

@@ -2406,6 +2493,7 @@ static void showHelp(const std::string & progName)
24062493
[--clear-execstack]\n\
24072494
[--set-execstack]\n\
24082495
[--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\
2496+
[--clean-strtab]\n\
24092497
[--output FILE]\n\
24102498
[--debug]\n\
24112499
[--version]\n\
@@ -2537,6 +2625,9 @@ static int mainWrapped(int argc, char * * argv)
25372625
else if (arg == "--add-debug-tag") {
25382626
addDebugTag = true;
25392627
}
2628+
else if (arg == "--clean-strtab") {
2629+
cleanStrTab = true;
2630+
}
25402631
else if (arg == "--rename-dynamic-symbols") {
25412632
renameDynamicSymbols = true;
25422633
if (++i == argc) error("missing argument");

src/patchelf.h

+35-3
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

+32
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)