https://github.com/AOSC-Dev/liblol/blob/v0.1.4/autobuild/patches/patchelf/0001-add-remap-symvers.patch

From: Miao Wang <shankerwangmiao@gmail.com>
Date: Fri, 12 Jan 2024 16:56:07 +0800
Subject: [PATCH] add remap-symvers

--- a/src/elf.h
+++ b/src/elf.h
@@ -55,6 +55,8 @@ typedef uint16_t Elf64_Section;
 typedef Elf32_Half Elf32_Versym;
 typedef Elf64_Half Elf64_Versym;
 
+#define VERSYM_HIDDEN		0x8000
+#define VERSYM_VERSION		0x7fff
 
 /* The ELF file header.  This appears at the start of every ELF file.  */
 
--- a/src/patchelf.cc
+++ b/src/patchelf.cc
@@ -1234,8 +1234,14 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
             }
             else if (d_tag == DT_VERNEED)
                 dyn->d_un.d_ptr = findSectionHeader(".gnu.version_r").sh_addr;
+            else if (d_tag == DT_VERNEEDNUM)
+                dyn->d_un.d_val = findSectionHeader(".gnu.version_r").sh_info;
             else if (d_tag == DT_VERSYM)
                 dyn->d_un.d_ptr = findSectionHeader(".gnu.version").sh_addr;
+            else if (d_tag == DT_VERDEF)
+                dyn->d_un.d_ptr = findSectionHeader(".gnu.version_d").sh_addr;
+            else if (d_tag == DT_VERDEFNUM)
+                dyn->d_un.d_val = findSectionHeader(".gnu.version_d").sh_info;
             else if (d_tag == DT_MIPS_RLD_MAP_REL) {
                 /* the MIPS_RLD_MAP_REL tag stores the offset to the debug
                    pointer, relative to the address of the tag */
@@ -2016,7 +2022,7 @@ auto ElfFile<ElfFileParamNames>::parseGnuHashTable(span<char> sectionData) -> Gn
 }
 
 template<ElfFileParams>
-void ElfFile<ElfFileParamNames>::rebuildGnuHashTable(span<char> strTab, span<Elf_Sym> dynsyms)
+void ElfFile<ElfFileParamNames>::rebuildGnuHashTable(span<char> strTab, span<Elf_Sym> dynsyms, span<Elf_Versym> versyms)
 {
     auto sectionData = tryGetSectionSpan<char>(".gnu.hash");
     if (!sectionData)
@@ -2028,12 +2034,20 @@ void ElfFile<ElfFileParamNames>::rebuildGnuHashTable(span<char> strTab, span<Elf
     if (ght.m_table.size() == 0)
         return;
 
+    if (ght.m_table.size() + rdi(ght.m_hdr.symndx) < dynsyms.size()){
+        debug("gnuhash table is too small, rebuilding\n");
+        auto & newSection = replaceSection(".gnu.hash", sectionData.size() + (dynsyms.size() - ght.m_table.size() - rdi(ght.m_hdr.symndx)) * sizeof(uint32_t));
+        sectionData = span<char>(newSection.data(), newSection.size());
+        ght = parseGnuHashTable(sectionData);
+    }
+
     // The hash table includes only a subset of dynsyms
     auto firstSymIdx = rdi(ght.m_hdr.symndx);
     dynsyms = span(&dynsyms[firstSymIdx], dynsyms.end());
 
     // Only use the range of symbol versions that will be changed
-    auto versyms = tryGetSectionSpan<Elf_Versym>(".gnu.version");
+    if (!versyms)
+        versyms = tryGetSectionSpan<Elf_Versym>(".gnu.version");
     if (versyms)
         versyms = span(&versyms[firstSymIdx], versyms.end());
 
@@ -2148,7 +2162,7 @@ auto ElfFile<ElfFileParamNames>::parseHashTable(span<char> sectionData) -> HashT
 }
 
 template<ElfFileParams>
-void ElfFile<ElfFileParamNames>::rebuildHashTable(span<char> strTab, span<Elf_Sym> dynsyms)
+void ElfFile<ElfFileParamNames>::rebuildHashTable(span<char> strTab, span<Elf_Sym> dynsyms, int moreSyms)
 {
     auto sectionData = tryGetSectionSpan<char>(".hash");
     if (!sectionData)
@@ -2156,6 +2170,15 @@ void ElfFile<ElfFileParamNames>::rebuildHashTable(span<char> strTab, span<Elf_Sy
 
     auto ht = parseHashTable(sectionData);
 
+    if(moreSyms > 0)
+    {
+        auto & newSection = replaceSection(".hash", sectionData.size() + moreSyms * sizeof(uint32_t));
+        sectionData = span<char>(newSection.data(), newSection.size());
+        auto hdr = (typename HashTable::Header*)sectionData.begin();
+        wri(hdr->nchain, rdi(hdr->nchain) + moreSyms);
+        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);
 
@@ -2320,6 +2343,281 @@ void ElfFile<ElfFileParamNames>::modifyExecstack(ExecstackMode op)
     printf("execstack: %c\n", result);
 }
 
+template<ElfFileParams>
+void ElfFile<ElfFileParamNames>::remapSymvers(const std::string & mapTo, const std::vector<std::string> & mapFrom, bool alsoPatchVerNeed)
+{
+    auto shdrDynStr = findSectionHeader(".dynstr");
+    auto shdrDynsym = findSectionHeader(".dynsym");
+    auto shdrVersym = findSectionHeader(".gnu.version");
+
+    auto strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset);
+    auto strTabSize = rdi(shdrDynStr.sh_size);
+    auto dynsyms = (Elf_Sym *)(fileContents->data() + rdi(shdrDynsym.sh_offset));
+    auto versyms = (Elf_Versym *)(fileContents->data() + rdi(shdrVersym.sh_offset));
+    const size_t count = rdi(shdrDynsym.sh_size) / sizeof(Elf_Sym);
+
+    if (count != rdi(shdrVersym.sh_size) / sizeof(Elf_Versym))
+        error("versym size mismatch");
+
+    auto &shdrVerdef = findSectionHeader(".gnu.version_d");
+    auto verdef = (char *)(fileContents->data() + rdi(shdrVerdef.sh_offset));
+
+    std::map<int, std::string> verdefMap;
+
+    debug("Parsing .gnu.version_d\n");
+
+    int verdef_entries = rdi(shdrVerdef.sh_info);
+    debug(".gnu.version_d: %d entries\n", verdef_entries);
+
+    auto verdef_end = verdef + rdi(shdrVerdef.sh_size);
+    off_t curoff = 0;
+
+    int map_to_ndx = -1;
+    int max_ndx = 0;
+    off_t last_verdef_off = 0;
+    off_t mapToStrOff = 0;
+    bool mapToAdded = false;
+
+    for(int i = 0; i < verdef_entries; i++){
+        Elf_Verdef *vd = (Elf_Verdef *) (verdef + curoff);
+        if ((char *)vd + sizeof(Elf_Verdef) > verdef_end)
+            error(fmt("verdef entry overflow: idx=", i));
+        auto ndx = rdi(vd->vd_ndx);
+        if ((char *)vd + rdi(vd->vd_aux) >= verdef_end)
+            error(fmt("verdef entry aux out of bounds: idx=", i));
+        auto aux = (Elf_Verdaux *) ((char *)vd + rdi(vd->vd_aux));
+        if ((char *)aux + sizeof(Elf_Verdaux) > verdef_end)
+            error(fmt("verdef entry aux overflow: idx=", i));
+        std::string_view name = &strTab[rdi(aux->vda_name)];
+        debug("verdef entry %d: %s, ndx=%d\n", i, name.data(), ndx);
+        if (ndx > max_ndx)
+            max_ndx = ndx;
+        if (name == mapTo) {
+            map_to_ndx = ndx;
+            mapToStrOff = rdi(aux->vda_name);
+        }
+        if(ndx != 0 && ndx != 1){
+            verdefMap[ndx] = name;
+        }
+
+        if(rdi(vd->vd_next) == 0){
+            if(i == verdef_entries - 1){
+                last_verdef_off = curoff;
+                break;
+            }
+            else
+                error(fmt("verdef entry should have next entry: idx=", i));
+        }
+        if((char *)vd + rdi(vd->vd_next) >= verdef_end)
+            error(fmt("verdef entry next out of bounds: idx=", i));
+        curoff += rdi(vd->vd_next);
+    }
+    if (map_to_ndx == -1){
+        debug("no version index for %s, adding\n", mapTo.c_str());
+        auto & newDynStr = replaceSection(".dynstr", rdi(shdrDynStr.sh_size) + mapTo.size() + 1);
+        mapToStrOff = rdi(shdrDynStr.sh_size);
+        setSubstr(newDynStr, mapToStrOff, mapTo + '\0');
+        strTab = newDynStr.data();
+        strTabSize = newDynStr.size();
+    }
+    debug("parsing verneed entries\n", mapTo.c_str());
+    auto verneedhdr = tryFindSectionHeader(".gnu.version_r");
+    std::map<int, int> verneedMap;
+    if(verneedhdr){
+        auto &shdrVerNeed = verneedhdr->get();
+        auto verneed = (char *)(fileContents->data() + rdi(shdrVerNeed.sh_offset));
+
+        debug("found .gnu.version_r, parsing\n");
+        int verneed_entries = rdi(shdrVerNeed.sh_info);
+        debug(".gnu.version_r: %d entries\n", verdef_entries);
+
+        auto verneed_end = verneed + rdi(shdrVerNeed.sh_size);
+        off_t curoff = 0;
+        for(int i = 0; i < verneed_entries; i++){
+            Elf_Verneed *vn = (Elf_Verneed *) (verneed + curoff);
+            if ((char *)vn + sizeof(Elf_Verneed) > verneed_end)
+                error(fmt("verneed entry overflow: idx=", i));
+            auto aux_cnt = rdi(vn->vn_cnt);
+            debug("file: %s, %d versions\n", &strTab[rdi(vn->vn_file)], aux_cnt);
+            off_t aux_off = rdi(vn->vn_aux);
+            if ((char *)vn + aux_off >= verneed_end)
+                error(fmt("verneed entry aux out of bounds: idx=", i));
+            for(int j = 0; j < aux_cnt; j++){
+                auto aux = (Elf_Vernaux *) ((char *)vn + aux_off);
+                if ((char *)aux + sizeof(Elf_Vernaux) > verneed_end)
+                    error(fmt("verneed entry aux overflow: idx=", i, "aux idx=", j));
+                auto ndx = rdi(aux->vna_other) & VERSYM_VERSION;
+                debug("  %s, ndx=%d\n", &strTab[rdi(aux->vna_name)], ndx);
+                if(alsoPatchVerNeed){
+                    for (auto it : mapFrom){
+                        if (it == &strTab[rdi(aux->vna_name)]){
+                            debug("    found %s, changing to %s\n", it.c_str(), mapTo.c_str());
+                            wri(aux->vna_name, mapToStrOff);
+                            wri(aux->vna_hash, sysvHash(mapTo));
+                            break;
+                        }
+                    }
+                }
+                if(map_to_ndx == -1 && ndx >= max_ndx + 1){
+                    verneedMap[ndx] = ndx + 1;
+                    ndx = ndx + 1;
+                    wri(aux->vna_other, (rdi(aux->vna_other) & ~VERSYM_VERSION) | (ndx & VERSYM_VERSION));
+                    debug("    changing ndx to %d\n", ndx);
+                }
+                if (rdi(aux->vna_next) == 0){
+                    if (j == aux_cnt - 1)
+                        break;
+                    else
+                        error(fmt("verneed entry should have next entry: idx=", i, "aux idx=", j));
+                }
+                if ((char *)aux + rdi(aux->vna_next) >= verneed_end)
+                    error(fmt("verneed entry next out of bounds: idx=", i, "aux idx=", j));
+                aux_off += rdi(aux->vna_next);
+            }
+            if (rdi(vn->vn_next) == 0){
+                if (i == verneed_entries - 1)
+                    break;
+                else
+                    error(fmt("verneed entry should have next entry: idx=", i));
+            }
+            if ((char *)vn + rdi(vn->vn_next) >= verneed_end)
+                error(fmt("verneed entry next out of bounds: idx=", i));
+            curoff += rdi(vn->vn_next);
+        }
+    }else{
+        debug("no .gnu.version_r found\n");
+    }
+    if (map_to_ndx == -1){
+        map_to_ndx = max_ndx + 1;
+        debug("decided to use %d for %s\n", map_to_ndx, mapTo.c_str());
+        if(map_to_ndx > VERSYM_VERSION){
+            error(fmt("version index %d is too large", map_to_ndx));
+        }
+        verdefMap[map_to_ndx] = mapTo;
+        auto & newVerdef = replaceSection(".gnu.version_d", rdi(shdrVerdef.sh_size) + sizeof(Elf_Verdef) + sizeof(Elf_Verdaux));
+        char * newVerdefData = newVerdef.data();
+        Elf_Verdef *lastVd = (Elf_Verdef *)(newVerdefData + last_verdef_off);
+        Elf_Verdef *newVd = (Elf_Verdef *)(newVerdefData + rdi(shdrVerdef.sh_size));
+        wri(lastVd->vd_next, (char *)newVd - (char *)lastVd);
+        wri(newVd->vd_version, 1);
+        wri(newVd->vd_flags, 0);
+        wri(newVd->vd_ndx, map_to_ndx);
+        wri(newVd->vd_cnt, 1);
+        wri(newVd->vd_hash, sysvHash(mapTo));
+        wri(newVd->vd_aux, sizeof(Elf_Verdef));
+        wri(newVd->vd_next, 0);
+        Elf_Verdaux *newVda = (Elf_Verdaux *)((char *)newVd + sizeof(Elf_Verdef));
+        wri(newVda->vda_next, 0);
+        wri(((Elf_Shdr *)(&shdrVerdef))->sh_info, rdi(shdrVerdef.sh_info) + 1);
+        verdef_entries += 1;
+
+        wri(newVda->vda_name, mapToStrOff);
+        mapToAdded = true;
+    }else{
+        debug("verdef entry for %s found at ndx=%d\n", mapTo.c_str(), map_to_ndx);
+    }
+    std::map<std::string, std::map<std::string, int>> symVersionMap;
+
+    debug("Parsing .dynsym\n");
+    for(size_t i = 0; i < count; i++){
+        auto dynsym = &dynsyms[i];
+        std::string name = strTab + rdi(dynsym->st_name);
+        auto verndx = rdi(versyms[i]);
+        auto verdef_ndx = verndx & VERSYM_VERSION;
+
+        if(verneedMap.find(verdef_ndx) != verneedMap.end()){
+            debug("verneed entry remapping for %s found at ndx=%d\n", name.c_str(), verdef_ndx);
+            verdef_ndx = verneedMap[verdef_ndx];
+            wri(versyms[i], (verndx & ~VERSYM_VERSION) | (verdef_ndx & VERSYM_VERSION));
+        }
+
+        if(name.empty())
+            continue;
+        debug("dynsym entry %d: %s ", i, name.c_str());
+        auto shndx = rdi(dynsym->st_shndx);
+        if(shndx == SHN_UNDEF){
+            debug("(undefined)\n");
+            continue;
+        }else if(shndx == SHN_ABS){
+            debug("(absolute)\n");
+            continue;
+        }else if(shndx == SHN_COMMON){
+            debug("(common)\n");
+            continue;
+        }
+        if(verndx == 0){
+            debug("(local)\n");
+            continue;
+        }else if(verndx == 1){
+            debug("(global)\n");
+            continue;
+        }
+        if(verdefMap.find(verdef_ndx) == verdefMap.end()){
+            debug("(verdef %d not found)\n", verdef_ndx);
+            continue;
+        }
+        debug("(ver: %s)\n", verdefMap[verdef_ndx].c_str());
+        symVersionMap[verdefMap[verdef_ndx]][name] = i;
+    }
+
+    debug("Generating new dsyms list\n");
+    std::map<std::string, int> newDsyms;
+    for(const auto &fromVer : mapFrom){
+        if(symVersionMap.find(fromVer) == symVersionMap.end()){
+            debug("No symbols with version %s found\n", fromVer.c_str());
+            continue;
+        }
+        for(auto sym : symVersionMap[fromVer]){
+            debug("Adding %s@%s to new dsyms list\n", sym.first.c_str(), fromVer.c_str());
+            newDsyms[sym.first] = sym.second;
+        }
+    }
+    for(const auto &syms : symVersionMap[mapTo]){
+        debug("removing %s@%s from new dsyms list\n", syms.first.c_str(), mapTo.c_str());
+        newDsyms.erase(syms.first);
+    }
+    auto newDynsymSize = (newDsyms.size() + (mapToAdded ? 1 : 0)) * sizeof(Elf_Sym) + rdi(shdrDynsym.sh_size);
+    auto newVersymSize = (newDsyms.size() + (mapToAdded ? 1 : 0)) * sizeof(Elf_Versym) + rdi(shdrVersym.sh_size);
+
+    auto& newDynsym = replaceSection(".dynsym", newDynsymSize);
+    auto& newVersym = replaceSection(".gnu.version", newVersymSize);
+
+    auto newDynsymSpan = span<Elf_Sym>((Elf_Sym *)newDynsym.data(), newDynsymSize / sizeof(Elf_Sym));
+    auto newVersymSpan = span<Elf_Versym>((Elf_Versym *)newVersym.data(), newVersymSize / sizeof(Elf_Versym));
+
+    {
+        int i = count;
+        for(auto it = newDsyms.cbegin(); it != newDsyms.cend(); ++it){
+            auto sym = it->second;
+            debug("Adding %s@%s to dynsym list\n", it->first.c_str(), mapTo.c_str());
+            newDynsymSpan[i] = dynsyms[sym];
+            bool is_hidden = rdi(newVersymSpan[sym]) & VERSYM_HIDDEN;
+            wri(newVersymSpan[i], map_to_ndx | (is_hidden ? VERSYM_HIDDEN : 0));
+            wri(newVersymSpan[sym], rdi(newVersymSpan[sym]) | VERSYM_HIDDEN);
+            i += 1;
+        }
+        if(mapToAdded){
+            debug("Adding %s@%s to dynsym list\n", mapTo.c_str(), mapTo.c_str());
+            wri(newDynsymSpan[i].st_name, mapToStrOff);
+            wri(newDynsymSpan[i].st_info, STB_GLOBAL << 4 | STT_OBJECT);
+            wri(newDynsymSpan[i].st_other, STV_DEFAULT);
+            wri(newDynsymSpan[i].st_shndx, SHN_ABS);
+            wri(newDynsymSpan[i].st_value, 0);
+            wri(newDynsymSpan[i].st_size, 0);
+            wri(newVersymSpan[i], map_to_ndx);
+        }
+    }
+
+    debug("Rebuilding hash tables\n");
+
+    rebuildGnuHashTable(span(strTab, strTabSize), newDynsymSpan, newVersymSpan);
+    rebuildHashTable(span(strTab, strTabSize), newDynsymSpan, newDsyms.size() + (mapToAdded ? 1 : 0));
+
+    this->rewriteSections();
+
+    changed = true;
+}
+
 template<ElfFileParams>
 template<class StrIdxCallback>
 void ElfFile<ElfFileParamNames>::forAllStringReferences(const Elf_Shdr& strTabHdr, StrIdxCallback&& fn)
@@ -2384,6 +2682,10 @@ static bool noDefaultLib = false;
 static bool printExecstack = false;
 static bool clearExecstack = false;
 static bool setExecstack = false;
+static bool remapSymvers = false;
+static bool remapVerneed = false;
+static std::string symverMapTo;
+static std::vector<std::string> symverMapFrom;
 
 template<class ElfFile>
 static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, const std::string & fileName)
@@ -2441,6 +2743,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
     if (renameDynamicSymbols)
         elfFile.renameDynamicSymbols(symbolsToRename);
 
+    if (remapSymvers)
+        elfFile.remapSymvers(symverMapTo, symverMapFrom, remapVerneed);
+
     if (elfFile.isChanged()){
         writeFile(fileName, elfFile.fileContents);
     } else if (alwaysWrite) {
@@ -2505,6 +2810,8 @@ 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\
+  [--remap-symvers TO=FROM1,FROM2...]\n\
+  [--also-remap-verneed]\n\
   [--output FILE]\n\
   [--debug]\n\
   [--version]\n\
@@ -2661,6 +2968,44 @@ static int mainWrapped(int argc, char * * argv)
                 symbolsToRename[*symbolsToRenameKeys.insert(from).first] = to;
             }
         }
+        else if (arg == "--remap-symvers") {
+            remapSymvers = true;
+            if (++i == argc) error("missing argument");
+
+            const char* mapping = argv[i];
+            for(int i = 0; mapping[i]; ++i)
+            {
+                if (mapping[i] == '=')
+                {
+                    char *mapto = strndup(mapping, i);
+                    symverMapTo = mapto;
+                    free(mapto);
+                    mapping += i + 1;
+                    break;
+                }
+            }
+            if (symverMapTo.empty())
+                error(fmt("Invalid symver mapping, must contains =: ", mapping));
+            for(int i = 0; mapping[i]; ++i)
+            {
+                if (mapping[i] == ',')
+                {
+                    char *mapfrom = strndup(mapping, i);
+                    if(strlen(mapfrom) != 0)
+                        symverMapFrom.push_back(mapfrom);
+                    free(mapfrom);
+                    mapping += i + 1;
+                    i = -1;
+                }
+            }
+            if (strlen(mapping) != 0)
+                symverMapFrom.push_back(mapping);
+            if (symverMapFrom.empty())
+                error(fmt("Invalid symver mapping, must contains at least one from: ", mapping));
+        }
+        else if (arg == "--also-remap-verneed") {
+            remapVerneed = true;
+        }
         else if (arg == "--help" || arg == "-h" ) {
             showHelp(argv[0]);
             return 0;
--- a/src/patchelf.h
+++ b/src/patchelf.h
@@ -175,6 +175,8 @@ public:
 
     void modifyExecstack(ExecstackMode op);
 
+    void remapSymvers(const std::string & mapTo, const std::vector<std::string> & mapFrom, bool alsoRemapVerneed);
+
 private:
     struct GnuHashTable {
         using BloomWord = Elf_Addr;
@@ -194,8 +196,8 @@ private:
     };
     HashTable parseHashTable(span<char> gh);
 
-    void rebuildGnuHashTable(span<char> strTab, span<Elf_Sym> dynsyms);
-    void rebuildHashTable(span<char> strTab, span<Elf_Sym> dynsyms);
+    void rebuildGnuHashTable(span<char> strTab, span<Elf_Sym> dynsyms, span<Elf_Versym> versyms = {nullptr, nullptr});
+    void rebuildHashTable(span<char> strTab, span<Elf_Sym> dynsyms, int moreSyms = 0);
 
     using Elf_Rel_Info = decltype(Elf_Rel::r_info);
 
-- 
2.43.0

