diff options
author | Breno Rodrigues Guimaraes <brenorg@gmail.com> | 2023-02-16 13:58:36 -0300 |
---|---|---|
committer | Breno Rodrigues Guimaraes <brenorg@gmail.com> | 2023-02-20 18:43:01 -0300 |
commit | 0b6b66687370f2ae97310edbdbd61e7a81ac3019 (patch) | |
tree | 53f07e7aa028f30ad995df5a57ee50f0f02ebf1c | |
parent | 365e1e01868f453ee01c5aaafd78631818ddf7f0 (diff) | |
download | patchelf-0b6b66687370f2ae97310edbdbd61e7a81ac3019.tar.gz |
Add support for symbol name remapping
-rw-r--r-- | src/patchelf.cc | 272 | ||||
-rw-r--r-- | src/patchelf.h | 72 | ||||
-rw-r--r-- | tests/Makefile.am | 23 | ||||
-rwxr-xr-x | tests/rename-dynamic-symbols.sh | 84 |
4 files changed, 444 insertions, 7 deletions
diff --git a/src/patchelf.cc b/src/patchelf.cc index e91e2ab..847d4dc 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -17,16 +17,18 @@ */ #include <algorithm> +#include <fstream> #include <limits> #include <map> #include <memory> +#include <optional> #include <set> #include <sstream> #include <stdexcept> #include <string> #include <unordered_map> +#include <unordered_set> #include <vector> -#include <optional> #include <cassert> #include <cerrno> @@ -553,6 +555,27 @@ std::optional<std::reference_wrapper<Elf_Shdr>> ElfFile<ElfFileParamNames>::tryF return {}; } +template<ElfFileParams> +template<class T> +span<T> ElfFile<ElfFileParamNames>::getSectionSpan(const Elf_Shdr & shdr) const +{ + return span((T*)(fileContents->data() + rdi(shdr.sh_offset)), rdi(shdr.sh_size)/sizeof(T)); +} + +template<ElfFileParams> +template<class T> +span<T> ElfFile<ElfFileParamNames>::getSectionSpan(const SectionName & sectionName) +{ + return getSectionSpan<T>(findSectionHeader(sectionName)); +} + +template<ElfFileParams> +template<class T> +span<T> ElfFile<ElfFileParamNames>::tryGetSectionSpan(const SectionName & sectionName) +{ + auto shdrOpt = tryFindSectionHeader(sectionName); + return shdrOpt ? getSectionSpan<T>(*shdrOpt) : span<T>(); +} template<ElfFileParams> unsigned int ElfFile<ElfFileParamNames>::getSectionIndex(const SectionName & sectionName) @@ -1861,6 +1884,223 @@ void ElfFile<ElfFileParamNames>::addDebugTag() changed = true; } +static uint32_t gnuHash(std::string_view name) { + uint32_t h = 5381; + for (uint8_t c : name) + h = ((h << 5) + h) + c; + return h; +} + +template<ElfFileParams> +auto ElfFile<ElfFileParamNames>::parseGnuHashTable(span<char> sectionData) -> GnuHashTable +{ + auto hdr = (typename GnuHashTable::Header*)sectionData.begin(); + auto bloomFilters = span((typename GnuHashTable::BloomWord*)(hdr+1), rdi(hdr->maskwords)); + auto buckets = span((uint32_t*)bloomFilters.end(), rdi(hdr->numBuckets)); + auto table = span(buckets.end(), ((uint32_t*)sectionData.end()) - buckets.end()); + return GnuHashTable{*hdr, bloomFilters, buckets, table}; +} + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::rebuildGnuHashTable(const char* strTab, span<Elf_Sym> dynsyms) +{ + auto sectionData = tryGetSectionSpan<char>(".gnu.hash"); + if (!sectionData) + return; + + auto ght = parseGnuHashTable(sectionData); + + // We can't trust the value of symndx when the hash table is empty + if (ght.m_table.size() == 0) + return; + + // The hash table includes only a subset of dynsyms + auto firstSymIdx = rdi(ght.m_hdr.symndx); + dynsyms = span(dynsyms.begin() + firstSymIdx, dynsyms.end()); + + // Only use the range of symbol versions that will be changed + auto versyms = tryGetSectionSpan<Elf_Versym>(".gnu.version"); + if (versyms) + versyms = span(versyms.begin() + firstSymIdx, versyms.end()); + + struct Entry + { + uint32_t hash, bucketIdx, originalPos; + }; + + std::vector<Entry> entries; + entries.reserve(dynsyms.size()); + + uint32_t pos = 0; // Track the original position of the symbol in the table + for (auto& sym : dynsyms) + { + Entry e; + e.hash = gnuHash(strTab + rdi(sym.st_name)); + e.bucketIdx = e.hash % ght.m_buckets.size(); + e.originalPos = pos++; + entries.push_back(e); + } + + // Sort the entries based on the buckets. This is a requirement for gnu hash table to work + std::sort(entries.begin(), entries.end(), [&] (auto& l, auto& r) { + return l.bucketIdx < r.bucketIdx; + }); + + // Create a map of old positions to new positions after sorting + std::vector<uint32_t> old2new(entries.size()); + for (size_t i = 0; i < entries.size(); ++i) + old2new[entries[i].originalPos] = i; + + // Update the symbol table with the new order and + // all tables that refer to symbols through indexes in the symbol table + auto reorderSpan = [] (auto dst, auto& old2new) + { + std::vector tmp(dst.begin(), dst.end()); + for (size_t i = 0; i < tmp.size(); ++i) + dst[old2new[i]] = tmp[i]; + }; + + reorderSpan(dynsyms, old2new); + if (versyms) + reorderSpan(versyms, old2new); + + auto fixRelocationTable = [&old2new, firstSymIdx, this] <class ER> (auto& hdr) + { + auto rela = getSectionSpan<ER>(hdr); + for (auto& r : rela) + { + auto info = rdi(r.r_info); + auto oldSymIdx = rel_getSymId(info); + if (oldSymIdx >= firstSymIdx) + { + auto newSymIdx = old2new[oldSymIdx - firstSymIdx] + firstSymIdx; + if (newSymIdx != oldSymIdx) + wri(r.r_info, rel_setSymId(info, newSymIdx)); + } + } + }; + + for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i) + { + auto& shdr = shdrs.at(i); + auto shtype = rdi(shdr.sh_type); + if (shtype == SHT_REL) + fixRelocationTable.template operator()<Elf_Rel>(shdr); + else if (shtype == SHT_RELA) + fixRelocationTable.template operator()<Elf_Rela>(shdr); + } + + // Update bloom filters + std::fill(ght.m_bloomFilters.begin(), ght.m_bloomFilters.end(), 0); + for (size_t i = 0; i < entries.size(); ++i) + { + auto h = entries[i].hash; + size_t idx = (h / ElfClass) % ght.m_bloomFilters.size(); + auto val = rdi(ght.m_bloomFilters[idx]); + val |= uint64_t(1) << (h % ElfClass); + val |= uint64_t(1) << ((h >> rdi(ght.m_hdr.shift2)) % ElfClass); + wri(ght.m_bloomFilters[idx], val); + } + + // Fill buckets + std::fill(ght.m_buckets.begin(), ght.m_buckets.end(), 0); + for (size_t i = 0; i < entries.size(); ++i) + { + auto symBucketIdx = entries[i].bucketIdx; + if (!ght.m_buckets[symBucketIdx]) + wri(ght.m_buckets[symBucketIdx], i + firstSymIdx); + } + + // Fill hash table + for (size_t i = 0; i < entries.size(); ++i) + { + auto& n = entries[i]; + bool isLast = (i == entries.size() - 1) || (n.bucketIdx != entries[i+1].bucketIdx); + // Add hash with first bit indicating end of chain + wri(ght.m_table[i], isLast ? (n.hash | 1) : (n.hash & ~1)); + } +} + +static uint32_t sysvHash(std::string_view name) { + uint32_t h = 0; + for (uint8_t c : name) + { + h = (h << 4) + c; + uint32_t g = h & 0xf0000000; + if (g != 0) + h ^= g >> 24; + h &= ~g; + } + return h; +} + +template<ElfFileParams> +auto ElfFile<ElfFileParamNames>::parseHashTable(span<char> sectionData) -> HashTable +{ + auto hdr = (typename HashTable::Header*)sectionData.begin(); + auto buckets = span((uint32_t*)(hdr+1), rdi(hdr->numBuckets)); + auto table = span(buckets.end(), ((uint32_t*)sectionData.end()) - buckets.end()); + return HashTable{*hdr, buckets, table}; +} + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::rebuildHashTable(const char* strTab, span<Elf_Sym> dynsyms) +{ + auto sectionData = tryGetSectionSpan<char>(".hash"); + if (!sectionData) + return; + + auto ht = parseHashTable(sectionData); + + std::fill(ht.m_buckets.begin(), ht.m_buckets.end(), 0); + std::fill(ht.m_chain.begin(), ht.m_chain.end(), 0); + + auto symsToInsert = span(dynsyms.end() - ht.m_chain.size(), dynsyms.end()); + + for (auto& sym : symsToInsert) + { + auto name = strTab + rdi(sym.st_name); + uint32_t i = &sym - dynsyms.begin(); + uint32_t hash = sysvHash(name) % ht.m_buckets.size(); + wri(ht.m_chain[i], rdi(ht.m_buckets[hash])); + wri(ht.m_buckets[hash], i); + } +} + +template<ElfFileParams> +void ElfFile<ElfFileParamNames>::renameDynamicSymbols(const std::unordered_map<std::string_view, std::string>& remap) +{ + auto dynsyms = getSectionSpan<Elf_Sym>(".dynsym"); + auto strTab = getSectionSpan<char>(".dynstr"); + + std::vector<char> extraStrings; + extraStrings.reserve(remap.size() * 30); // Just an estimate + for (size_t i = 0; i < dynsyms.size(); i++) + { + auto& dynsym = dynsyms[i]; + std::string_view name = &strTab[rdi(dynsym.st_name)]; + auto it = remap.find(name); + if (it != remap.end()) + { + wri(dynsym.st_name, strTab.size() + extraStrings.size()); + auto& newName = it->second; + extraStrings.insert(extraStrings.end(), newName.data(), newName.data() + newName.size() + 1); + changed = true; + } + } + + if (changed) + { + auto& newSec = replaceSection(".dynstr", strTab.size() + extraStrings.size()); + std::copy(extraStrings.begin(), extraStrings.end(), newSec.begin() + strTab.size()); + + rebuildGnuHashTable(newSec.data(), dynsyms); + rebuildHashTable(newSec.data(), dynsyms); + } + + this->rewriteSections(); +} + template<ElfFileParams> void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string> & syms) { @@ -1983,12 +2223,15 @@ static bool removeRPath = false; static bool setRPath = false; static bool addRPath = false; static bool addDebugTag = false; +static bool renameDynamicSymbols = false; static bool printRPath = false; static std::string newRPath; static std::set<std::string> neededLibsToRemove; static std::map<std::string, std::string> neededLibsToReplace; static std::set<std::string> neededLibsToAdd; static std::set<std::string> symbolsToClearVersion; +static std::unordered_map<std::string_view, std::string> symbolsToRename; +static std::unordered_set<std::string> symbolsToRenameKeys; static bool printNeeded = false; static bool noDefaultLib = false; static bool printExecstack = false; @@ -2048,6 +2291,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con if (addDebugTag) elfFile.addDebugTag(); + if (renameDynamicSymbols) + elfFile.renameDynamicSymbols(symbolsToRename); + if (elfFile.isChanged()){ writeFile(fileName, elfFile.fileContents); } else if (alwaysWrite) { @@ -2067,9 +2313,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>(fileContents), fileContents, outputFileName2); + 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); else - patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym>(fileContents), fileContents, outputFileName2); + 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); } } @@ -2111,6 +2357,7 @@ void showHelp(const std::string & progName) [--print-execstack]\t\tPrints whether the object requests an executable stack\n\ [--clear-execstack]\n\ [--set-execstack]\n\ + [--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The name map file should contain two symbols (old_name new_name) per line\n\ [--output FILE]\n\ [--debug]\n\ [--version]\n\ @@ -2242,6 +2489,25 @@ int mainWrapped(int argc, char * * argv) else if (arg == "--add-debug-tag") { addDebugTag = true; } + else if (arg == "--rename-dynamic-symbols") { + renameDynamicSymbols = true; + if (++i == argc) error("missing argument"); + + std::ifstream infile(argv[i]); + if (!infile) error(fmt("Cannot open map file ", argv[i])); + + std::string from, to; + while (true) + { + if (!(infile >> from)) + break; + if (!(infile >> to)) + error("Odd number of symbols in map file"); + if (symbolsToRenameKeys.count(from)) + error(fmt("Symbol appears twice in the map file: ", from.c_str())); + symbolsToRename[*symbolsToRenameKeys.insert(from).first] = to; + } + } else if (arg == "--help" || arg == "-h" ) { showHelp(argv[0]); return 0; diff --git a/src/patchelf.h b/src/patchelf.h index c3096ff..1914d96 100644 --- a/src/patchelf.h +++ b/src/patchelf.h @@ -1,7 +1,22 @@ 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 -#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed, Elf_Versym +#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 + +template<class T> +struct span +{ + span(T* d = {}, size_t l = {}) : data(d), len(l) {} + span(T* from, T* to) : data(from), len(to-from) {} + T& operator[](std::size_t i) { return data[i]; } + T* begin() { return data; } + T* end() { return data + len; } + auto size() { return len; } + explicit operator bool() { return size() > 0; } + + T* data; + size_t len; +}; template<ElfFileParams> class ElfFile @@ -85,6 +100,10 @@ private: std::optional<std::reference_wrapper<Elf_Shdr>> tryFindSectionHeader(const SectionName & sectionName); + template<class T> span<T> getSectionSpan(const Elf_Shdr & shdr) const; + template<class T> span<T> getSectionSpan(const SectionName & sectionName); + template<class T> span<T> tryGetSectionSpan(const SectionName & sectionName); + unsigned int getSectionIndex(const SectionName & sectionName); std::string & replaceSection(const SectionName & sectionName, @@ -137,6 +156,55 @@ public: void addDebugTag(); + void renameDynamicSymbols(const std::unordered_map<std::string_view, std::string>&); + + struct GnuHashTable { + using BloomWord = Elf_Addr; + struct Header { + uint32_t numBuckets, symndx, maskwords, shift2; + } m_hdr; + span<BloomWord> m_bloomFilters; + span<uint32_t> m_buckets, m_table; + }; + GnuHashTable parseGnuHashTable(span<char> gh); + + struct HashTable { + struct Header { + uint32_t numBuckets, nchain; + } m_hdr; + span<uint32_t> m_buckets, m_chain; + }; + HashTable parseHashTable(span<char> gh); + + void rebuildGnuHashTable(const char* strTab, span<Elf_Sym> dynsyms); + void rebuildHashTable(const char* strTab, span<Elf_Sym> dynsyms); + + using Elf_Rel_Info = decltype(Elf_Rel::r_info); + + uint32_t rel_getSymId(const Elf_Rel_Info& info) const + { + if constexpr (std::is_same_v<Elf_Rel, Elf64_Rel>) + return ELF64_R_SYM(info); + else + return ELF32_R_SYM(info); + } + + Elf_Rel_Info rel_setSymId(Elf_Rel_Info info, uint32_t id) const + { + if constexpr (std::is_same_v<Elf_Rel, Elf64_Rel>) + { + constexpr Elf_Rel_Info idmask = (~Elf_Rel_Info()) << 32; + info = (info & ~idmask) | (Elf_Rel_Info(id) << 32); + } + else + { + constexpr Elf_Rel_Info idmask = (~Elf_Rel_Info()) << 8; + info = (info & ~idmask) | (Elf_Rel_Info(id) << 8); + } + return info; + } + + void clearSymbolVersions(const std::set<std::string> & syms); enum class ExecstackMode { print, set, clear }; diff --git a/tests/Makefile.am b/tests/Makefile.am index 9d36645..4a08c14 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -45,7 +45,9 @@ src_TESTS = \ add-debug-tag.sh \ empty-note.sh \ print-execstack.sh \ - modify-execstack.sh + modify-execstack.sh \ + rename-dynamic-symbols.sh \ + empty-note.sh build_TESTS = \ $(no_rpath_arch_TESTS) @@ -116,7 +118,7 @@ check_DATA = libbig-dynstr.debug # - with libtool, it is difficult to control options # - with libtool, it is not possible to compile convenience *dynamic* libraries :-( check_PROGRAMS += libfoo.so libfoo-scoped.so libbar.so libbar-scoped.so libsimple.so libsimple-execstack.so libbuildid.so libtoomanystrtab.so \ - phdr-corruption.so + phdr-corruption.so many-syms-main libmany-syms.so libbuildid_so_SOURCES = simple.c libbuildid_so_LDFLAGS = $(LDFLAGS_sharedlib) -Wl,--build-id @@ -147,6 +149,14 @@ too_many_strtab_SOURCES = too-many-strtab.c too-many-strtab2.s libtoomanystrtab_so_SOURCES = too-many-strtab.c too-many-strtab2.s libtoomanystrtab_so_LDFLAGS = $(LDFLAGS_sharedlib) +many_syms_main_SOURCES = many-syms-main.c +many_syms_main_LDFLAGS = $(LDFLAGS_local) +many_syms_main_LDADD = -lmany-syms $(AM_LDADD) +many_syms_main_DEPENDENCIES = libmany-syms.so +many_syms_main_CFLAGS = -pie +libmany_syms_so_SOURCES = many-syms.c +libmany_syms_so_LDFLAGS = $(LDFLAGS_sharedlib) + no_rpath_SOURCES = no-rpath.c # no -fpic for no-rpath.o no_rpath_CFLAGS = @@ -158,3 +168,12 @@ contiguous_note_sections_CFLAGS = -pie phdr_corruption_so_SOURCES = void.c phdr-corruption.ld phdr_corruption_so_LDFLAGS = -nostdlib -shared -Wl,-T$(srcdir)/phdr-corruption.ld phdr_corruption_so_CFLAGS = + +many-syms.c: + i=1; while [ $$i -le 2000 ]; do echo "void f$$i() {};"; i=$$(($$i + 1)); done > $@ + +many-syms-main.c: + echo "int main() {" > $@ + i=1; while [ $$i -le 2000 ]; do echo "void f$$i(); f$$i();"; i=$$(($$i + 1)); done >> $@ + echo "}" >> $@ + diff --git a/tests/rename-dynamic-symbols.sh b/tests/rename-dynamic-symbols.sh new file mode 100755 index 0000000..6fabb33 --- /dev/null +++ b/tests/rename-dynamic-symbols.sh @@ -0,0 +1,84 @@ +#!/bin/sh -e +SCRATCH=scratch/$(basename $0 .sh) +PATCHELF=$(readlink -f "../src/patchelf") + +rm -rf ${SCRATCH} +mkdir -p ${SCRATCH} + +full_main_name="${PWD}/many-syms-main" +full_lib_name="${PWD}/libmany-syms.so" +chmod -w $full_lib_name $full_main_name + +suffix="_special_suffix" + +cd ${SCRATCH} + +############################################################################### +# Test that all symbols in the dynamic symbol table will have the expected +# names after renaming. +# Also test that if we rename all symbols back, the symbols are as expected +############################################################################### + +list_symbols() { + nm -D $@ | awk '{ print $NF }' | sed '/^ *$/d' +} + +list_symbols $full_lib_name | cut -d@ -f1 | sort -u | awk "{printf \"%s %s${suffix}\n\",\$1,\$1}" > map +list_symbols $full_lib_name | cut -d@ -f1 | sort -u | awk "{printf \"%s${suffix} %s\n\",\$1,\$1}" > rmap + +${PATCHELF} --rename-dynamic-symbols map --output libmapped.so $full_lib_name +${PATCHELF} --rename-dynamic-symbols rmap --output libreversed.so libmapped.so + +list_symbols $full_lib_name | sort > orig_syms +list_symbols libmapped.so | sort > map_syms +list_symbols libreversed.so | sort > rev_syms + +diff orig_syms rev_syms > diff_orig_syms_rev_syms || exit 1 + +# Renamed symbols that match version numbers will be printed with version instead of them being ommited +# CXXABI10 is printed as CXXABI10 +# but CXXABI10_renamed is printed as CXXABI10_renamed@@CXXABI10 +# awk is used to remove these cases so that we can match the "mapped" symbols to original symbols +sed "s/${suffix}//" map_syms | awk -F @ '{ if ($1 == $2 || $1 == $3) { print $1; } else { print $0; }}' | sort > map_syms_r +diff orig_syms map_syms_r > diff_orig_syms_map_syms_r || exit 1 + +############################################################################### +# Check the relocation tables after renaming +############################################################################### + +print_relocation_table() { + readelf -W -r $1 | awk '{ printf "%s\n",$5 }' | cut -f1 -d@ +} + +print_relocation_table $full_lib_name > orig_rel +print_relocation_table libmapped.so > map_rel +print_relocation_table libreversed.so > rev_rel + +diff orig_rel rev_rel > diff_orig_rel_rev_rel || exit 1 +sed "s/${suffix}//" map_rel > map_rel_r +diff orig_rel map_rel_r > diff_orig_rel_map_rel_r || exit 1 + +############################################################################### +# Test that the hash table is correctly updated. +# For this to work, we need to rename symbols and actually use the library +# Here we: +# 1. Create a map from all symbols in libstdc++.so as "sym sym_special_suffix" +# 2. Copy Patchelf and all of its transitive library dependencies into a new directory +# 3. Rename symbols in Patchelf and all dependencies according to the map +# 4. Run patchelf with the modified dependencies +############################################################################### + +echo "# Create the map" +list_symbols --defined-only $full_lib_name | cut -d@ -f1 | sort -u | awk "{printf \"%s %s${suffix}\n\",\$1,\$1}" > map + +echo "# Copy all dependencies" +mkdir env +cd env +cp $full_lib_name $full_main_name . + +echo "# Apply renaming" +chmod +w * +${PATCHELF} --rename-dynamic-symbols ../map * + +echo "# Run the patched tool and libraries" +env LD_BIND_NOW=1 LD_LIBRARY_PATH=${PWD} ./many-syms-main |