Skip to content

Commit da035d6

Browse files
bors[bot]brenoguimMic92
authored
Merge #459
459: Add feature to rename dynamic symbols r=Mic92 a=brenoguim Co-authored-by: Breno Rodrigues Guimaraes <[email protected]> Co-authored-by: Breno Rodrigues Guimarães <[email protected]> Co-authored-by: Jörg Thalheim <[email protected]>
2 parents e37f892 + aeb34c2 commit da035d6

File tree

5 files changed

+473
-7
lines changed

5 files changed

+473
-7
lines changed

patchelf.1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ Clears the executable flag of the GNU_STACK program header, or adds a new header
123123
.IP "--set-execstack"
124124
Sets the executable flag of the GNU_STACK program header, or adds a new header.
125125

126+
.IP "--rename-dynamic-symbols NAME_MAP_FILE"
127+
Renames dynamic symbols. The name map file should contain lines
128+
with the old and the new name separated by spaces like this:
129+
130+
old_name new_name
131+
132+
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.
133+
126134
.IP "--output FILE"
127135
Set the output file name. If not specified, the input will be modified in place.
128136

src/patchelf.cc

Lines changed: 272 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@
1717
*/
1818

1919
#include <algorithm>
20+
#include <fstream>
2021
#include <limits>
2122
#include <map>
2223
#include <memory>
24+
#include <optional>
2325
#include <set>
2426
#include <sstream>
2527
#include <stdexcept>
2628
#include <string>
2729
#include <string_view>
2830
#include <unordered_map>
31+
#include <unordered_set>
2932
#include <vector>
30-
#include <optional>
3133

3234
#include <cassert>
3335
#include <cerrno>
@@ -599,6 +601,27 @@ std::optional<std::reference_wrapper<const Elf_Shdr>> ElfFile<ElfFileParamNames>
599601
return {};
600602
}
601603

604+
template<ElfFileParams>
605+
template<class T>
606+
span<T> ElfFile<ElfFileParamNames>::getSectionSpan(const Elf_Shdr & shdr) const
607+
{
608+
return span((T*)(fileContents->data() + rdi(shdr.sh_offset)), rdi(shdr.sh_size)/sizeof(T));
609+
}
610+
611+
template<ElfFileParams>
612+
template<class T>
613+
span<T> ElfFile<ElfFileParamNames>::getSectionSpan(const SectionName & sectionName)
614+
{
615+
return getSectionSpan<T>(findSectionHeader(sectionName));
616+
}
617+
618+
template<ElfFileParams>
619+
template<class T>
620+
span<T> ElfFile<ElfFileParamNames>::tryGetSectionSpan(const SectionName & sectionName)
621+
{
622+
auto shdrOpt = tryFindSectionHeader(sectionName);
623+
return shdrOpt ? getSectionSpan<T>(*shdrOpt) : span<T>();
624+
}
602625

603626
template<ElfFileParams>
604627
unsigned int ElfFile<ElfFileParamNames>::getSectionIndex(const SectionName & sectionName) const
@@ -1910,6 +1933,220 @@ void ElfFile<ElfFileParamNames>::addDebugTag()
19101933
changed = true;
19111934
}
19121935

1936+
static uint32_t gnuHash(std::string_view name) {
1937+
uint32_t h = 5381;
1938+
for (uint8_t c : name)
1939+
h = ((h << 5) + h) + c;
1940+
return h;
1941+
}
1942+
1943+
template<ElfFileParams>
1944+
auto ElfFile<ElfFileParamNames>::parseGnuHashTable(span<char> sectionData) -> GnuHashTable
1945+
{
1946+
auto hdr = (typename GnuHashTable::Header*)sectionData.begin();
1947+
auto bloomFilters = span((typename GnuHashTable::BloomWord*)(hdr+1), rdi(hdr->maskwords));
1948+
auto buckets = span((uint32_t*)bloomFilters.end(), rdi(hdr->numBuckets));
1949+
auto table = span(buckets.end(), ((uint32_t*)sectionData.end()) - buckets.end());
1950+
return GnuHashTable{*hdr, bloomFilters, buckets, table};
1951+
}
1952+
1953+
template<ElfFileParams>
1954+
void ElfFile<ElfFileParamNames>::rebuildGnuHashTable(span<char> strTab, span<Elf_Sym> dynsyms)
1955+
{
1956+
auto sectionData = tryGetSectionSpan<char>(".gnu.hash");
1957+
if (!sectionData)
1958+
return;
1959+
1960+
auto ght = parseGnuHashTable(sectionData);
1961+
1962+
// We can't trust the value of symndx when the hash table is empty
1963+
if (ght.m_table.size() == 0)
1964+
return;
1965+
1966+
// The hash table includes only a subset of dynsyms
1967+
auto firstSymIdx = rdi(ght.m_hdr.symndx);
1968+
dynsyms = span(&dynsyms[firstSymIdx], dynsyms.end());
1969+
1970+
// Only use the range of symbol versions that will be changed
1971+
auto versyms = tryGetSectionSpan<Elf_Versym>(".gnu.version");
1972+
if (versyms)
1973+
versyms = span(&versyms[firstSymIdx], versyms.end());
1974+
1975+
struct Entry
1976+
{
1977+
uint32_t hash, bucketIdx, originalPos;
1978+
};
1979+
1980+
std::vector<Entry> entries;
1981+
entries.reserve(dynsyms.size());
1982+
1983+
uint32_t pos = 0; // Track the original position of the symbol in the table
1984+
for (auto& sym : dynsyms)
1985+
{
1986+
Entry e;
1987+
e.hash = gnuHash(&strTab[rdi(sym.st_name)]);
1988+
e.bucketIdx = e.hash % ght.m_buckets.size();
1989+
e.originalPos = pos++;
1990+
entries.push_back(e);
1991+
}
1992+
1993+
// Sort the entries based on the buckets. This is a requirement for gnu hash table to work
1994+
std::sort(entries.begin(), entries.end(), [&] (auto& l, auto& r) {
1995+
return l.bucketIdx < r.bucketIdx;
1996+
});
1997+
1998+
// Create a map of old positions to new positions after sorting
1999+
std::vector<uint32_t> old2new(entries.size());
2000+
for (size_t i = 0; i < entries.size(); ++i)
2001+
old2new[entries[i].originalPos] = i;
2002+
2003+
// Update the symbol table with the new order and
2004+
// all tables that refer to symbols through indexes in the symbol table
2005+
auto reorderSpan = [] (auto dst, auto& old2new)
2006+
{
2007+
std::vector tmp(dst.begin(), dst.end());
2008+
for (size_t i = 0; i < tmp.size(); ++i)
2009+
dst[old2new[i]] = tmp[i];
2010+
};
2011+
2012+
reorderSpan(dynsyms, old2new);
2013+
if (versyms)
2014+
reorderSpan(versyms, old2new);
2015+
2016+
auto remapSymbolId = [&old2new, firstSymIdx] (auto& oldSymIdx)
2017+
{
2018+
return oldSymIdx >= firstSymIdx ? old2new[oldSymIdx - firstSymIdx] + firstSymIdx
2019+
: oldSymIdx;
2020+
};
2021+
2022+
for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i)
2023+
{
2024+
auto& shdr = shdrs.at(i);
2025+
auto shtype = rdi(shdr.sh_type);
2026+
if (shtype == SHT_REL)
2027+
changeRelocTableSymIds<Elf_Rel>(shdr, remapSymbolId);
2028+
else if (shtype == SHT_RELA)
2029+
changeRelocTableSymIds<Elf_Rela>(shdr, remapSymbolId);
2030+
}
2031+
2032+
// Update bloom filters
2033+
std::fill(ght.m_bloomFilters.begin(), ght.m_bloomFilters.end(), 0);
2034+
for (size_t i = 0; i < entries.size(); ++i)
2035+
{
2036+
auto h = entries[i].hash;
2037+
size_t idx = (h / ElfClass) % ght.m_bloomFilters.size();
2038+
auto val = rdi(ght.m_bloomFilters[idx]);
2039+
val |= uint64_t(1) << (h % ElfClass);
2040+
val |= uint64_t(1) << ((h >> rdi(ght.m_hdr.shift2)) % ElfClass);
2041+
wri(ght.m_bloomFilters[idx], val);
2042+
}
2043+
2044+
// Fill buckets
2045+
std::fill(ght.m_buckets.begin(), ght.m_buckets.end(), 0);
2046+
for (size_t i = 0; i < entries.size(); ++i)
2047+
{
2048+
auto symBucketIdx = entries[i].bucketIdx;
2049+
if (!ght.m_buckets[symBucketIdx])
2050+
wri(ght.m_buckets[symBucketIdx], i + firstSymIdx);
2051+
}
2052+
2053+
// Fill hash table
2054+
for (size_t i = 0; i < entries.size(); ++i)
2055+
{
2056+
auto& n = entries[i];
2057+
bool isLast = (i == entries.size() - 1) || (n.bucketIdx != entries[i+1].bucketIdx);
2058+
// Add hash with first bit indicating end of chain
2059+
wri(ght.m_table[i], isLast ? (n.hash | 1) : (n.hash & ~1));
2060+
}
2061+
}
2062+
2063+
static uint32_t sysvHash(std::string_view name) {
2064+
uint32_t h = 0;
2065+
for (uint8_t c : name)
2066+
{
2067+
h = (h << 4) + c;
2068+
uint32_t g = h & 0xf0000000;
2069+
if (g != 0)
2070+
h ^= g >> 24;
2071+
h &= ~g;
2072+
}
2073+
return h;
2074+
}
2075+
2076+
template<ElfFileParams>
2077+
auto ElfFile<ElfFileParamNames>::parseHashTable(span<char> sectionData) -> HashTable
2078+
{
2079+
auto hdr = (typename HashTable::Header*)sectionData.begin();
2080+
auto buckets = span((uint32_t*)(hdr+1), rdi(hdr->numBuckets));
2081+
auto table = span(buckets.end(), ((uint32_t*)sectionData.end()) - buckets.end());
2082+
return HashTable{*hdr, buckets, table};
2083+
}
2084+
2085+
template<ElfFileParams>
2086+
void ElfFile<ElfFileParamNames>::rebuildHashTable(span<char> strTab, span<Elf_Sym> dynsyms)
2087+
{
2088+
auto sectionData = tryGetSectionSpan<char>(".hash");
2089+
if (!sectionData)
2090+
return;
2091+
2092+
auto ht = parseHashTable(sectionData);
2093+
2094+
std::fill(ht.m_buckets.begin(), ht.m_buckets.end(), 0);
2095+
std::fill(ht.m_chain.begin(), ht.m_chain.end(), 0);
2096+
2097+
// The hash table includes only a subset of dynsyms
2098+
auto firstSymIdx = dynsyms.size() - ht.m_chain.size();
2099+
dynsyms = span(&dynsyms[firstSymIdx], dynsyms.end());
2100+
2101+
for (auto& sym : dynsyms)
2102+
{
2103+
auto name = &strTab[rdi(sym.st_name)];
2104+
uint32_t i = &sym - dynsyms.begin();
2105+
uint32_t hash = sysvHash(name) % ht.m_buckets.size();
2106+
wri(ht.m_chain[i], rdi(ht.m_buckets[hash]));
2107+
wri(ht.m_buckets[hash], i);
2108+
}
2109+
}
2110+
2111+
template<ElfFileParams>
2112+
void ElfFile<ElfFileParamNames>::renameDynamicSymbols(const std::unordered_map<std::string_view, std::string>& remap)
2113+
{
2114+
auto dynsyms = getSectionSpan<Elf_Sym>(".dynsym");
2115+
auto strTab = getSectionSpan<char>(".dynstr");
2116+
2117+
std::vector<char> extraStrings;
2118+
extraStrings.reserve(remap.size() * 30); // Just an estimate
2119+
for (auto& dynsym : dynsyms)
2120+
{
2121+
std::string_view name = &strTab[rdi(dynsym.st_name)];
2122+
auto it = remap.find(name);
2123+
if (it != remap.end())
2124+
{
2125+
wri(dynsym.st_name, strTab.size() + extraStrings.size());
2126+
auto& newName = it->second;
2127+
debug("renaming dynamic symbol %s to %s\n", name.data(), it->second.c_str());
2128+
extraStrings.insert(extraStrings.end(), newName.begin(), newName.end() + 1);
2129+
changed = true;
2130+
} else {
2131+
debug("skip renaming dynamic symbol %sn", name.data());
2132+
}
2133+
}
2134+
2135+
if (changed)
2136+
{
2137+
auto newStrTabSize = strTab.size() + extraStrings.size();
2138+
auto& newSec = replaceSection(".dynstr", newStrTabSize);
2139+
auto newStrTabSpan = span(newSec.data(), newStrTabSize);
2140+
2141+
std::copy(extraStrings.begin(), extraStrings.end(), &newStrTabSpan[strTab.size()]);
2142+
2143+
rebuildGnuHashTable(newStrTabSpan, dynsyms);
2144+
rebuildHashTable(newStrTabSpan, dynsyms);
2145+
}
2146+
2147+
this->rewriteSections();
2148+
}
2149+
19132150
template<ElfFileParams>
19142151
void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string> & syms)
19152152
{
@@ -2032,12 +2269,15 @@ static bool removeRPath = false;
20322269
static bool setRPath = false;
20332270
static bool addRPath = false;
20342271
static bool addDebugTag = false;
2272+
static bool renameDynamicSymbols = false;
20352273
static bool printRPath = false;
20362274
static std::string newRPath;
20372275
static std::set<std::string> neededLibsToRemove;
20382276
static std::map<std::string, std::string> neededLibsToReplace;
20392277
static std::set<std::string> neededLibsToAdd;
20402278
static std::set<std::string> symbolsToClearVersion;
2279+
static std::unordered_map<std::string_view, std::string> symbolsToRename;
2280+
static std::unordered_set<std::string> symbolsToRenameKeys;
20412281
static bool printNeeded = false;
20422282
static bool noDefaultLib = false;
20432283
static bool printExecstack = false;
@@ -2097,6 +2337,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
20972337
if (addDebugTag)
20982338
elfFile.addDebugTag();
20992339

2340+
if (renameDynamicSymbols)
2341+
elfFile.renameDynamicSymbols(symbolsToRename);
2342+
21002343
if (elfFile.isChanged()){
21012344
writeFile(fileName, elfFile.fileContents);
21022345
} else if (alwaysWrite) {
@@ -2116,9 +2359,9 @@ static void patchElf()
21162359
const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName;
21172360

21182361
if (getElfType(fileContents).is32Bit)
2119-
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Verneed, Elf32_Versym>(fileContents), fileContents, outputFileName2);
2362+
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);
21202363
else
2121-
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym>(fileContents), fileContents, outputFileName2);
2364+
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);
21222365
}
21232366
}
21242367

@@ -2160,6 +2403,7 @@ static void showHelp(const std::string & progName)
21602403
[--print-execstack]\t\tPrints whether the object requests an executable stack\n\
21612404
[--clear-execstack]\n\
21622405
[--set-execstack]\n\
2406+
[--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\
21632407
[--output FILE]\n\
21642408
[--debug]\n\
21652409
[--version]\n\
@@ -2291,6 +2535,31 @@ static int mainWrapped(int argc, char * * argv)
22912535
else if (arg == "--add-debug-tag") {
22922536
addDebugTag = true;
22932537
}
2538+
else if (arg == "--rename-dynamic-symbols") {
2539+
renameDynamicSymbols = true;
2540+
if (++i == argc) error("missing argument");
2541+
2542+
const char* fname = argv[i];
2543+
std::ifstream infile(fname);
2544+
if (!infile) error(fmt("Cannot open map file ", fname));
2545+
2546+
std::string line, from, to;
2547+
size_t lineCount = 1;
2548+
while (std::getline(infile, line))
2549+
{
2550+
std::istringstream iss(line);
2551+
if (!(iss >> from))
2552+
break;
2553+
if (!(iss >> to))
2554+
error(fmt(fname, ":", lineCount, ": Map file line is missing the second element"));
2555+
if (symbolsToRenameKeys.count(from))
2556+
error(fmt(fname, ":", lineCount, ": Name '", from, "' appears twice in the map file"));
2557+
if (from.find('@') != std::string_view::npos || to.find('@') != std::string_view::npos)
2558+
error(fmt(fname, ":", lineCount, ": Name pair contains version tag: ", from, " ", to));
2559+
lineCount++;
2560+
symbolsToRename[*symbolsToRenameKeys.insert(from).first] = to;
2561+
}
2562+
}
22942563
else if (arg == "--help" || arg == "-h" ) {
22952564
showHelp(argv[0]);
22962565
return 0;

0 commit comments

Comments
 (0)