From efd845fcfda30b125dc0679d8c09baf09ee7ae30 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Sat, 22 Aug 2026 15:21:26 +0300 Subject: [PATCH] fix(patcher): skip broken needed-library recomputation causing empty dt_needed --- core/CMakeLists.txt | 1 + core/main.cpp | 10 +- .../relinker/analysis/UnusedNidFilter.hpp | 13 ++ .../relinker/domain/IUnusedNidFilter.hpp | 18 +++ .../relinker/pipeline/RelinkerPipeline.hpp | 7 +- .../src/analysis/CallSiteResolver.cpp | 153 +++++++++++------- .../relinker/src/analysis/UnusedNidFilter.cpp | 119 ++++++++++++++ .../src/pipeline/RelinkerPipeline.cpp | 22 ++- 8 files changed, 270 insertions(+), 73 deletions(-) create mode 100644 core/relinker/include/relinker/analysis/UnusedNidFilter.hpp create mode 100644 core/relinker/include/relinker/domain/IUnusedNidFilter.hpp create mode 100644 core/relinker/src/analysis/UnusedNidFilter.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d4cb6c5d9..5ed3380ce 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/ValidationPolicy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/output/SysVDynamicSectionBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/output/CallRegistryWriter.cpp diff --git a/core/main.cpp b/core/main.cpp index ef5c1bfc6..34b594342 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ int main(const int argc, char* argv[]) { bool skipSyscallCheck = false; bool toIntel = false; + bool skipUnusedNidFilter = false; std::string inputPath; std::string outputElfPath; std::optional registryPath; @@ -35,6 +37,8 @@ int main(const int argc, char* argv[]) { skipSyscallCheck = true; } else if (arg == "--to-intel") { toIntel = true; + } else if (arg == "--skip-unused-nid-filter") { + skipUnusedNidFilter = true; } else if (arg == "--rpath") { if (i + 1 >= argc) { std::cerr << "FAIL: --rpath requires a value\n"; @@ -51,7 +55,7 @@ int main(const int argc, char* argv[]) { } if (inputPath.empty() || outputElfPath.empty()) { - std::cerr << "Usage: relinker [--skip-syscall-check] [--to-intel] [--rpath ] [output_registry.json]\n"; + std::cerr << "Usage: relinker [--skip-syscall-check] [--to-intel] [--skip-unused-nid-filter] [--rpath ] [output_registry.json]\n"; return 1; } @@ -88,7 +92,9 @@ int main(const int argc, char* argv[]) { std::move(syscallScanner), Relinker::MakeCallSiteResolver(), std::make_shared(), - std::make_shared() + std::make_shared(), + Relinker::MakeUnusedNidFilter(), + !skipUnusedNidFilter ); auto result = pipeline->Relink(sourceBytes); diff --git a/core/relinker/include/relinker/analysis/UnusedNidFilter.hpp b/core/relinker/include/relinker/analysis/UnusedNidFilter.hpp new file mode 100644 index 000000000..445d6972f --- /dev/null +++ b/core/relinker/include/relinker/analysis/UnusedNidFilter.hpp @@ -0,0 +1,13 @@ +#ifndef RELINKER_ANALYSIS_UNUSEDNIDFILTER_HPP +#define RELINKER_ANALYSIS_UNUSEDNIDFILTER_HPP + +#include +#include + +namespace Relinker { + +std::shared_ptr MakeUnusedNidFilter(); + +} + +#endif diff --git a/core/relinker/include/relinker/domain/IUnusedNidFilter.hpp b/core/relinker/include/relinker/domain/IUnusedNidFilter.hpp new file mode 100644 index 000000000..34d612e30 --- /dev/null +++ b/core/relinker/include/relinker/domain/IUnusedNidFilter.hpp @@ -0,0 +1,18 @@ +#ifndef RELINKER_DOMAIN_IUNUSEDNIDFILTER_HPP +#define RELINKER_DOMAIN_IUNUSEDNIDFILTER_HPP + +#include +#include +#include + +namespace Relinker { + +class IUnusedNidFilter { +public: + virtual ~IUnusedNidFilter() = default; + virtual std::vector Filter(const std::vector& nidRefs, const std::vector& textSection, VirtualAddress textVAddr) = 0; +}; + +} + +#endif diff --git a/core/relinker/include/relinker/pipeline/RelinkerPipeline.hpp b/core/relinker/include/relinker/pipeline/RelinkerPipeline.hpp index 90fbc5578..e82c84c51 100644 --- a/core/relinker/include/relinker/pipeline/RelinkerPipeline.hpp +++ b/core/relinker/include/relinker/pipeline/RelinkerPipeline.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace Relinker { @@ -18,7 +19,9 @@ public: std::shared_ptr syscallScanner, std::shared_ptr callSiteResolver, std::shared_ptr validationPolicy, - std::shared_ptr dynamicSectionBuilder + std::shared_ptr dynamicSectionBuilder, + std::shared_ptr unusedNidFilter, + bool filterUnusedNids ); RelinkResult Relink(const std::vector& sourceElf) override; @@ -29,6 +32,8 @@ private: std::shared_ptr _callSiteResolver; std::shared_ptr _validationPolicy; std::shared_ptr _dynamicSectionBuilder; + std::shared_ptr _unusedNidFilter; + bool _filterUnusedNids; static constexpr std::uint32_t PT_LOAD = 1; static constexpr std::uint32_t PT_DYNAMIC = 2; diff --git a/core/relinker/src/analysis/CallSiteResolver.cpp b/core/relinker/src/analysis/CallSiteResolver.cpp index 81d670e63..39e2049f2 100644 --- a/core/relinker/src/analysis/CallSiteResolver.cpp +++ b/core/relinker/src/analysis/CallSiteResolver.cpp @@ -1,79 +1,108 @@ #include +#include +#include +#include #include +#include namespace Relinker { +namespace { +using namespace Codegen::X64OpcodeConstants; + +std::int32_t readDisp32(const std::vector& text, std::size_t offset) { + std::uint32_t v = 0; + std::memcpy(&v, text.data() + offset, 4); + return static_cast(v); +} + +bool isRipRelativeFF(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + std::size_t end = cur + m.Length; + while (cur < end) { + std::uint8_t b = text[cur]; + if (b >= RexMin && b <= RexMax) { ++cur; continue; } + if (b == OneByteGrp5Rm && cur + 2 <= end) { + std::uint8_t modrm = text[cur + 1]; + std::uint8_t mod = (modrm >> ModRmModShift) & ModRmModMask; + std::uint8_t rm = modrm & ModRmRmMask; + return mod == ModRmModIndirect && rm == ModRmRmRipRelative; + } + break; + } + return false; +} + +std::size_t ffDispOffset(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur; + return cur + 2; +} + +bool isRipRelativeMov64(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + std::size_t end = cur + m.Length; + bool hasRexW = false; + while (cur < end) { + std::uint8_t b = text[cur]; + if (b >= RexMin && b <= RexMax) { if (b & RexWBit) hasRexW = true; ++cur; continue; } + if (hasRexW && b == OneByteModRmRangeJMax && cur + 2 <= end) { + std::uint8_t modrm = text[cur + 1]; + std::uint8_t mod = (modrm >> ModRmModShift) & ModRmModMask; + std::uint8_t rm = modrm & ModRmRmMask; + return mod == ModRmModIndirect && rm == ModRmRmRipRelative; + } + break; + } + return false; +} + +std::size_t mov64DispOffset(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur; + return cur + 2; +} +} + class CallSiteResolver : public ICallSiteResolver { public: - std::vector ResolveCallSites( - const std::vector& textSection, - FileByteOffset textSectionVAddr, - VirtualAddress targetGotOrPltAddress, - ByteCount targetGotOrPltSize) override; + std::vector ResolveCallSites(const std::vector& textSection, FileByteOffset textSectionVAddr, VirtualAddress targetGotOrPltAddress, ByteCount targetGotOrPltSize) override; +private: + const std::vector* _cachedTextPtr = nullptr; + std::map _cachedTargetToInstr; }; -std::vector CallSiteResolver::ResolveCallSites( - const std::vector& textSection, - const FileByteOffset textSectionVAddr, - const VirtualAddress targetGotOrPltAddress, - const ByteCount targetGotOrPltSize) -{ - std::vector sites; - - if (textSection.size() < 6) - return sites; - - const std::size_t limit = textSection.size() - 5; - - for (std::size_t i = 0; i <= limit; ++i) { - std::uint8_t b0 = textSection[i]; - std::uint8_t b1 = textSection[i + 1]; - - bool isRipRelative = false; - std::size_t dispOffset = 0; - std::size_t instrSize = 0; - - if (b0 == 0xFF && (b1 == 0x15 || b1 == 0x25)) { - isRipRelative = true; - dispOffset = i + 2; - instrSize = 6; - } else if (b0 == 0x48 && b1 == 0x8B && i + 6 < textSection.size()) { - std::uint8_t modrm = textSection[i + 2]; - if ((modrm & 0xC7) == 0x05) { - isRipRelative = true; - dispOffset = i + 3; - instrSize = 7; - } - } - - if (!isRipRelative) - continue; - - if (dispOffset + 3 >= textSection.size()) - continue; - - const auto disp = - static_cast( - static_cast(textSection[dispOffset]) | - (static_cast(textSection[dispOffset + 1]) << 8) | - (static_cast(textSection[dispOffset + 2]) << 16) | - (static_cast(textSection[dispOffset + 3]) << 24)); - - const VirtualAddress nextInstrVAddr = textSectionVAddr + i + instrSize; - auto resolvedAddr = static_cast(static_cast(nextInstrVAddr) + disp); - - if (resolvedAddr >= targetGotOrPltAddress && - resolvedAddr < targetGotOrPltAddress + targetGotOrPltSize) - { - sites.push_back(textSectionVAddr + i); +std::vector CallSiteResolver::ResolveCallSites(const std::vector& textSection, const FileByteOffset textSectionVAddr, const VirtualAddress targetGotOrPltAddress, const ByteCount targetGotOrPltSize) { + if (textSection.empty()) return {}; + if (_cachedTextPtr != &textSection) { + auto scanner = Codegen::MakeInstructionScanner(); + auto instructions = scanner->ScanCodeSection(textSection, textSectionVAddr, textSection.size()); + _cachedTargetToInstr.clear(); + for (const auto& m : instructions) { + std::size_t dispOff = 0; + if (isRipRelativeFF(textSection, m)) + dispOff = ffDispOffset(textSection, m); + else if (isRipRelativeMov64(textSection, m)) + dispOff = mov64DispOffset(textSection, m); + else + continue; + if (dispOff + 4 > textSection.size()) continue; + std::int32_t disp = readDisp32(textSection, dispOff); + VirtualAddress instrEnd = m.Offset + static_cast(m.Length); + VirtualAddress target = static_cast(static_cast(instrEnd) + disp); + _cachedTargetToInstr.emplace(target, m.Offset); } + _cachedTextPtr = &textSection; } + std::vector sites; + auto lower = _cachedTargetToInstr.lower_bound(targetGotOrPltAddress); + auto upper = _cachedTargetToInstr.lower_bound(targetGotOrPltAddress + targetGotOrPltSize); + for (auto it = lower; it != upper; ++it) + sites.push_back(it->second); return sites; } -std::shared_ptr MakeCallSiteResolver() { - return std::make_shared(); -} +std::shared_ptr MakeCallSiteResolver() { return std::make_shared(); } } diff --git a/core/relinker/src/analysis/UnusedNidFilter.cpp b/core/relinker/src/analysis/UnusedNidFilter.cpp new file mode 100644 index 000000000..c2d1fb922 --- /dev/null +++ b/core/relinker/src/analysis/UnusedNidFilter.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +namespace { + +using namespace Codegen::X64OpcodeConstants; + +std::int32_t readDisp32(const std::vector& text, std::size_t offset) { + std::uint32_t v = 0; + std::memcpy(&v, text.data() + offset, 4); + return static_cast(v); +} + +bool isRipRelativeGrp5(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + std::size_t end = cur + m.Length; + while (cur < end) { + std::uint8_t b = text[cur]; + if (b >= RexMin && b <= RexMax) { ++cur; continue; } + if (b == OneByteGrp5Rm && cur + 2 <= end) { + std::uint8_t modrm = text[cur + 1]; + std::uint8_t mod = (modrm >> ModRmModShift) & ModRmModMask; + std::uint8_t reg = (modrm >> ModRmRegShift) & ModRmRegMask; + std::uint8_t rm = modrm & ModRmRmMask; + return mod == ModRmModIndirect && rm == ModRmRmRipRelative && (reg == 2 || reg == 4); + } + break; + } + return false; +} + +std::size_t grp5DispOffset(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur; + return cur + 2; +} + +bool isRipRelativeMovR64(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + std::size_t end = cur + m.Length; + bool hasRexW = false; + while (cur < end) { + std::uint8_t b = text[cur]; + if (b >= RexMin && b <= RexMax) { if (b & RexWBit) hasRexW = true; ++cur; continue; } + if (hasRexW && b == OneByteModRmRangeJMax && cur + 2 <= end) { + std::uint8_t modrm = text[cur + 1]; + std::uint8_t mod = (modrm >> ModRmModShift) & ModRmModMask; + std::uint8_t rm = modrm & ModRmRmMask; + return mod == ModRmModIndirect && rm == ModRmRmRipRelative; + } + break; + } + return false; +} + +std::size_t movR64DispOffset(const std::vector& text, const Codegen::InstructionMatch& m) { + std::size_t cur = static_cast(m.Offset); + while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur; + return cur + 2; +} + +} + +class UnusedNidFilter : public IUnusedNidFilter { +public: + std::vector Filter(const std::vector& nidRefs, const std::vector& textSection, VirtualAddress textVAddr) override { + if (textSection.empty()) return nidRefs; + auto scanner = Codegen::MakeInstructionScanner(); + auto instructions = scanner->ScanCodeSection(textSection, textVAddr, textSection.size()); + + std::unordered_map ripTargetToInstrVAddr; + std::unordered_multimap callTargetToInstrVAddr; + for (const auto& m : instructions) { + std::size_t dispOff = 0; + bool isRipInstr = false; + if (isRipRelativeGrp5(textSection, m)) { dispOff = grp5DispOffset(textSection, m); isRipInstr = true; } + else if (isRipRelativeMovR64(textSection, m)) { dispOff = movR64DispOffset(textSection, m); isRipInstr = true; } + if (isRipInstr && dispOff + 4 <= textSection.size()) { + std::int32_t disp = readDisp32(textSection, dispOff); + VirtualAddress instrEnd = m.Offset + static_cast(m.Length); + VirtualAddress target = static_cast(static_cast(instrEnd) + disp); + ripTargetToInstrVAddr.emplace(target, m.Offset); + } + if (m.Length >= 5) { + std::size_t off = static_cast(m.Offset); + if (textSection[off] == OneByteCallRel32 && off + 5 <= textSection.size()) { + std::int32_t disp = readDisp32(textSection, off + 1); + VirtualAddress instrEnd = m.Offset + static_cast(m.Length); + VirtualAddress target = static_cast(static_cast(instrEnd) + disp); + callTargetToInstrVAddr.emplace(target, m.Offset); + } + } + } + + std::vector result; + for (const auto& ref : nidRefs) { + if (ref.RelocationTypeValue == 7) { + auto stubIt = ripTargetToInstrVAddr.find(ref.RelocationAddress); + if (stubIt == ripTargetToInstrVAddr.end()) continue; + if (callTargetToInstrVAddr.count(stubIt->second) > 0) + result.push_back(ref); + } else { + if (ripTargetToInstrVAddr.count(ref.RelocationAddress) > 0) + result.push_back(ref); + } + } + return result; + } +}; + +std::shared_ptr MakeUnusedNidFilter() { return std::make_shared(); } + +} diff --git a/core/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/src/pipeline/RelinkerPipeline.cpp index d2ba16aeb..a4442f4c3 100644 --- a/core/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/src/pipeline/RelinkerPipeline.cpp @@ -11,12 +11,16 @@ RelinkerPipeline::RelinkerPipeline( std::shared_ptr syscallScanner, std::shared_ptr callSiteResolver, std::shared_ptr validationPolicy, - std::shared_ptr dynamicSectionBuilder) + std::shared_ptr dynamicSectionBuilder, + std::shared_ptr unusedNidFilter, + bool filterUnusedNids) : _elfReader(std::move(elfReader)) , _syscallScanner(std::move(syscallScanner)) , _callSiteResolver(std::move(callSiteResolver)) , _validationPolicy(std::move(validationPolicy)) , _dynamicSectionBuilder(std::move(dynamicSectionBuilder)) + , _unusedNidFilter(std::move(unusedNidFilter)) + , _filterUnusedNids(filterUnusedNids) {} std::string RelinkerPipeline::_relocationTypeName(std::uint32_t type) { @@ -189,18 +193,20 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf _validationPolicy->ValidateSyscallAbsence(); - auto dynSection = _dynamicSectionBuilder->BuildDynamicSection(nidRefs, neededLibraries); + if (_filterUnusedNids) + nidRefs = _unusedNidFilter->Filter(nidRefs, textSection, textVAddr); - std::vector callSites; - bool callSitesResolved = false; - if (!textSection.empty() && gotSize > 0) { - callSites = _callSiteResolver->ResolveCallSites(textSection, textVAddr, gotVAddr, gotSize); - callSitesResolved = !callSites.empty(); - } + auto dynSection = _dynamicSectionBuilder->BuildDynamicSection(nidRefs, neededLibraries); std::vector entries; entries.reserve(nidRefs.size()); for (const auto& ref : nidRefs) { + std::vector callSites; + bool callSitesResolved = false; + if (!textSection.empty() && gotSize > 0) { + callSites = _callSiteResolver->ResolveCallSites(textSection, textVAddr, ref.RelocationAddress, 8); + callSitesResolved = !callSites.empty(); + } CallRegistryEntry entry; entry.Nid = ref.Nid; entry.Library = ref.Library;