fix(patcher): skip broken needed-library recomputation causing empty dt_needed

This commit is contained in:
boykopovar 2026-08-22 15:21:26 +03:00
parent b1f51a3e4b
commit efd845fcfd
8 changed files with 270 additions and 73 deletions

View File

@ -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

View File

@ -11,6 +11,7 @@
#include <relinker/analysis/ValidationPolicy.hpp>
#include <relinker/analysis/SyscallScanner.hpp>
#include <relinker/analysis/CallSiteResolver.hpp>
#include <relinker/analysis/UnusedNidFilter.hpp>
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
#include <relinker/output/CallRegistryWriter.hpp>
#include <relinker/pipeline/RelinkerPipeline.hpp>
@ -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<std::string> 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 <path>] <input.elf> <output.elf> [output_registry.json]\n";
std::cerr << "Usage: relinker [--skip-syscall-check] [--to-intel] [--skip-unused-nid-filter] [--rpath <path>] <input.elf> <output.elf> [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<Relinker::ValidationPolicy>(),
std::make_shared<Relinker::SysVDynamicSectionBuilder>()
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
Relinker::MakeUnusedNidFilter(),
!skipUnusedNidFilter
);
auto result = pipeline->Relink(sourceBytes);

View File

@ -0,0 +1,13 @@
#ifndef RELINKER_ANALYSIS_UNUSEDNIDFILTER_HPP
#define RELINKER_ANALYSIS_UNUSEDNIDFILTER_HPP
#include <relinker/domain/IUnusedNidFilter.hpp>
#include <memory>
namespace Relinker {
std::shared_ptr<IUnusedNidFilter> MakeUnusedNidFilter();
}
#endif

View File

@ -0,0 +1,18 @@
#ifndef RELINKER_DOMAIN_IUNUSEDNIDFILTER_HPP
#define RELINKER_DOMAIN_IUNUSEDNIDFILTER_HPP
#include <relinker/domain/Types.hpp>
#include <vector>
#include <string>
namespace Relinker {
class IUnusedNidFilter {
public:
virtual ~IUnusedNidFilter() = default;
virtual std::vector<NidReference> Filter(const std::vector<NidReference>& nidRefs, const std::vector<std::uint8_t>& textSection, VirtualAddress textVAddr) = 0;
};
}
#endif

View File

@ -7,6 +7,7 @@
#include <relinker/domain/ICallSiteResolver.hpp>
#include <relinker/domain/IValidationPolicy.hpp>
#include <relinker/domain/ISysVDynamicSectionBuilder.hpp>
#include <relinker/domain/IUnusedNidFilter.hpp>
#include <memory>
namespace Relinker {
@ -18,7 +19,9 @@ public:
std::shared_ptr<ISyscallScanner> syscallScanner,
std::shared_ptr<ICallSiteResolver> callSiteResolver,
std::shared_ptr<IValidationPolicy> validationPolicy,
std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder
std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder,
std::shared_ptr<IUnusedNidFilter> unusedNidFilter,
bool filterUnusedNids
);
RelinkResult Relink(const std::vector<std::uint8_t>& sourceElf) override;
@ -29,6 +32,8 @@ private:
std::shared_ptr<ICallSiteResolver> _callSiteResolver;
std::shared_ptr<IValidationPolicy> _validationPolicy;
std::shared_ptr<ISysVDynamicSectionBuilder> _dynamicSectionBuilder;
std::shared_ptr<IUnusedNidFilter> _unusedNidFilter;
bool _filterUnusedNids;
static constexpr std::uint32_t PT_LOAD = 1;
static constexpr std::uint32_t PT_DYNAMIC = 2;

View File

@ -1,79 +1,108 @@
#include <relinker/analysis/CallSiteResolver.hpp>
#include <codegen/IInstructionScanner.hpp>
#include <codegen/x86/X64OpcodeConstants.hpp>
#include <cstring>
#include <memory>
#include <map>
namespace Relinker {
namespace {
using namespace Codegen::X64OpcodeConstants;
std::int32_t readDisp32(const std::vector<std::uint8_t>& text, std::size_t offset) {
std::uint32_t v = 0;
std::memcpy(&v, text.data() + offset, 4);
return static_cast<std::int32_t>(v);
}
bool isRipRelativeFF(const std::vector<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(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<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(m.Offset);
while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur;
return cur + 2;
}
bool isRipRelativeMov64(const std::vector<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(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<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(m.Offset);
while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur;
return cur + 2;
}
}
class CallSiteResolver : public ICallSiteResolver {
public:
std::vector<FileByteOffset> ResolveCallSites(
const std::vector<std::uint8_t>& textSection,
FileByteOffset textSectionVAddr,
VirtualAddress targetGotOrPltAddress,
ByteCount targetGotOrPltSize) override;
std::vector<FileByteOffset> ResolveCallSites(const std::vector<std::uint8_t>& textSection, FileByteOffset textSectionVAddr, VirtualAddress targetGotOrPltAddress, ByteCount targetGotOrPltSize) override;
private:
const std::vector<std::uint8_t>* _cachedTextPtr = nullptr;
std::map<VirtualAddress, FileByteOffset> _cachedTargetToInstr;
};
std::vector<FileByteOffset> CallSiteResolver::ResolveCallSites(
const std::vector<std::uint8_t>& textSection,
const FileByteOffset textSectionVAddr,
const VirtualAddress targetGotOrPltAddress,
const ByteCount targetGotOrPltSize)
{
std::vector<FileByteOffset> 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<std::int32_t>(
static_cast<std::uint32_t>(textSection[dispOffset]) |
(static_cast<std::uint32_t>(textSection[dispOffset + 1]) << 8) |
(static_cast<std::uint32_t>(textSection[dispOffset + 2]) << 16) |
(static_cast<std::uint32_t>(textSection[dispOffset + 3]) << 24));
const VirtualAddress nextInstrVAddr = textSectionVAddr + i + instrSize;
auto resolvedAddr = static_cast<VirtualAddress>(static_cast<std::int64_t>(nextInstrVAddr) + disp);
if (resolvedAddr >= targetGotOrPltAddress &&
resolvedAddr < targetGotOrPltAddress + targetGotOrPltSize)
{
sites.push_back(textSectionVAddr + i);
std::vector<FileByteOffset> CallSiteResolver::ResolveCallSites(const std::vector<std::uint8_t>& 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<VirtualAddress>(m.Length);
VirtualAddress target = static_cast<VirtualAddress>(static_cast<std::int64_t>(instrEnd) + disp);
_cachedTargetToInstr.emplace(target, m.Offset);
}
_cachedTextPtr = &textSection;
}
std::vector<FileByteOffset> 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<ICallSiteResolver> MakeCallSiteResolver() {
return std::make_shared<CallSiteResolver>();
}
std::shared_ptr<ICallSiteResolver> MakeCallSiteResolver() { return std::make_shared<CallSiteResolver>(); }
}

View File

@ -0,0 +1,119 @@
#include <relinker/analysis/UnusedNidFilter.hpp>
#include <codegen/IInstructionScanner.hpp>
#include <codegen/x86/X64OpcodeConstants.hpp>
#include <cstring>
#include <unordered_map>
#include <unordered_set>
namespace Relinker {
namespace {
using namespace Codegen::X64OpcodeConstants;
std::int32_t readDisp32(const std::vector<std::uint8_t>& text, std::size_t offset) {
std::uint32_t v = 0;
std::memcpy(&v, text.data() + offset, 4);
return static_cast<std::int32_t>(v);
}
bool isRipRelativeGrp5(const std::vector<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(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<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(m.Offset);
while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur;
return cur + 2;
}
bool isRipRelativeMovR64(const std::vector<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(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<std::uint8_t>& text, const Codegen::InstructionMatch& m) {
std::size_t cur = static_cast<std::size_t>(m.Offset);
while (text[cur] >= RexMin && text[cur] <= RexMax) ++cur;
return cur + 2;
}
}
class UnusedNidFilter : public IUnusedNidFilter {
public:
std::vector<NidReference> Filter(const std::vector<NidReference>& nidRefs, const std::vector<std::uint8_t>& textSection, VirtualAddress textVAddr) override {
if (textSection.empty()) return nidRefs;
auto scanner = Codegen::MakeInstructionScanner();
auto instructions = scanner->ScanCodeSection(textSection, textVAddr, textSection.size());
std::unordered_map<VirtualAddress, VirtualAddress> ripTargetToInstrVAddr;
std::unordered_multimap<VirtualAddress, VirtualAddress> 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<VirtualAddress>(m.Length);
VirtualAddress target = static_cast<VirtualAddress>(static_cast<std::int64_t>(instrEnd) + disp);
ripTargetToInstrVAddr.emplace(target, m.Offset);
}
if (m.Length >= 5) {
std::size_t off = static_cast<std::size_t>(m.Offset);
if (textSection[off] == OneByteCallRel32 && off + 5 <= textSection.size()) {
std::int32_t disp = readDisp32(textSection, off + 1);
VirtualAddress instrEnd = m.Offset + static_cast<VirtualAddress>(m.Length);
VirtualAddress target = static_cast<VirtualAddress>(static_cast<std::int64_t>(instrEnd) + disp);
callTargetToInstrVAddr.emplace(target, m.Offset);
}
}
}
std::vector<NidReference> 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<IUnusedNidFilter> MakeUnusedNidFilter() { return std::make_shared<UnusedNidFilter>(); }
}

View File

@ -11,12 +11,16 @@ RelinkerPipeline::RelinkerPipeline(
std::shared_ptr<ISyscallScanner> syscallScanner,
std::shared_ptr<ICallSiteResolver> callSiteResolver,
std::shared_ptr<IValidationPolicy> validationPolicy,
std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder)
std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder,
std::shared_ptr<IUnusedNidFilter> 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<std::uint8_t>& sourceElf
_validationPolicy->ValidateSyscallAbsence();
auto dynSection = _dynamicSectionBuilder->BuildDynamicSection(nidRefs, neededLibraries);
if (_filterUnusedNids)
nidRefs = _unusedNidFilter->Filter(nidRefs, textSection, textVAddr);
std::vector<FileByteOffset> 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<CallRegistryEntry> entries;
entries.reserve(nidRefs.size());
for (const auto& ref : nidRefs) {
std::vector<FileByteOffset> 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;