From 89d89ac0484624c95fe4a3dbf1f9b38b290cd165 Mon Sep 17 00:00:00 2001 From: MiniRML <134226498+MiniRML@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:53:17 -0700 Subject: [PATCH] fix(nid): preserve each ELF string consumer independently --- core/libs/CMakeLists.txt | 4 ++ core/libs/nid/src/ElfNidPatcher.cpp | 27 ++++------ core/libs/nid/tests/test_shared_strings.py | 58 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 core/libs/nid/tests/test_shared_strings.py diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index d094f39cf..9bbd3f25b 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -191,6 +191,10 @@ if(BUILD_TESTING) target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libc) configure_windows_unwind(guest_offline_imports_tests) add_test(NAME guest_offline_imports COMMAND guest_offline_imports_tests) + if(Python3_Interpreter_FOUND) + add_test(NAME nid_shared_strings COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_shared_strings.py $) + endif() if(WIN32) set_tests_properties(guest_offline_imports PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/nid/src/ElfNidPatcher.cpp b/core/libs/nid/src/ElfNidPatcher.cpp index 4f341bbd2..976f95428 100644 --- a/core/libs/nid/src/ElfNidPatcher.cpp +++ b/core/libs/nid/src/ElfNidPatcher.cpp @@ -427,7 +427,7 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& const auto addName = [&](std::uint32_t oldOffset, std::string value) { const auto existing = std::find_if( names.begin(), names.end(), - [&](const auto& entry) { return entry.first == oldOffset; } + [&](const auto& entry) { return entry.first == oldOffset && entry.second == value; } ); if (existing == names.end()) names.emplace_back(oldOffset, std::move(value)); }; @@ -463,8 +463,9 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& newDynStr.push_back(0u); } - std::vector> oldToNewOffset; - for (const auto& name : names) oldToNewOffset.emplace_back(name.first, valueOffsets.at(name.second)); + std::vector> preservedToNewOffset; + for (const auto offset : preservedOffsets) + preservedToNewOffset.emplace_back(offset, valueOffsets.at(ReadCStr(origDynStr, offset))); if (newDynStr.size() > dynStrSize) { throw std::runtime_error( @@ -475,25 +476,15 @@ void ElfNidPatcher::PatchNids(std::vector& elf, const std::string& newDynStr.resize(dynStrSize, 0u); std::vector newSymbolNames(symCount); - for (std::size_t i = 1u; i < symCount; ++i) { - const std::size_t symOffset = dynSymOffset + i * sizeof(Elf64_Sym); - const auto sym = Read(elf, symOffset); - if (sym.st_name == 0u) continue; - - const auto mapped = std::find_if( - oldToNewOffset.begin(), oldToNewOffset.end(), - [&](const auto& entry) { return entry.first == sym.st_name; } - ); - if (mapped == oldToNewOffset.end()) continue; - - newSymbolNames[i] = ReadCStr(newDynStr, mapped->second); - Write(elf, symOffset + offsetof(Elf64_Sym, st_name), mapped->second); + for (const auto& use : uses) { + newSymbolNames[use.symIndex] = use.newValue; + Write(elf, dynSymOffset + use.symIndex * sizeof(Elf64_Sym) + offsetof(Elf64_Sym, st_name), valueOffsets.at(use.newValue)); } std::memcpy(elf.data() + dynStrOffset, newDynStr.data(), newDynStr.size()); - RemapDynamicNeededOffsets(elf, ehdr, oldToNewOffset); - RemapVerneedOffsets(elf, ehdr, oldToNewOffset); + RemapDynamicNeededOffsets(elf, ehdr, preservedToNewOffset); + RemapVerneedOffsets(elf, ehdr, preservedToNewOffset); if (gnuHashOffset != 0u) { const auto bucketCount = Read(elf, gnuHashOffset); diff --git a/core/libs/nid/tests/test_shared_strings.py b/core/libs/nid/tests/test_shared_strings.py new file mode 100644 index 000000000..3bfb64125 --- /dev/null +++ b/core/libs/nid/tests/test_shared_strings.py @@ -0,0 +1,58 @@ +import struct +import sys +import unittest +from pathlib import Path + +from test_symbol_versions import build_image, symbols, patch, SHOFF, DYNSYM_OFFSET, read_dynsym, sections, DEFINITIONS + + +class SharedStringTests(unittest.TestCase): + def test_dynamic_names_keep_original_string_when_export_shares_offset(self): + for tag in (1, 14, 15, 29): + with self.subTest(tag=tag): + data = build_image(symbols(False), False) + name_offset = struct.unpack_from("