feat(relinker): report syscall scanning and import analysis progress

This commit is contained in:
boykopovar 2026-10-13 10:46:15 +03:00
parent 04ca1721e5
commit cd0433fd82
12 changed files with 127 additions and 79 deletions

View File

@ -3,6 +3,7 @@
#include <codegen/CodegenTypes.hpp>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
@ -19,7 +20,7 @@ public:
) const = 0;
};
std::unique_ptr<IInstructionScanner> MakeInstructionScanner();
std::unique_ptr<IInstructionScanner> MakeInstructionScanner(std::function<void(std::uint64_t, std::uint64_t)> progress = {});
}

View File

@ -9,11 +9,15 @@ namespace Codegen {
class InstructionScanner : public IInstructionScanner {
public:
explicit InstructionScanner(std::function<void(std::uint64_t, std::uint64_t)> progress) : progress(std::move(progress)) {}
[[nodiscard]] std::vector<InstructionMatch> ScanCodeSection(
const std::vector<std::uint8_t>& codeSection,
Domain::FileByteOffset codeSectionOffset,
Domain::FileByteOffset codeSectionSize
) const override;
private:
std::function<void(std::uint64_t, std::uint64_t)> progress;
};
std::vector<InstructionMatch> InstructionScanner::ScanCodeSection(
@ -27,6 +31,8 @@ std::vector<InstructionMatch> InstructionScanner::ScanCodeSection(
std::vector<InstructionMatch> matches;
std::size_t i = 0;
if (progress) progress(0, limit);
std::size_t nextReport = 65536;
while (i < limit) {
const std::uint8_t* cursor = codeSection.data() + i;
const std::size_t available = limit - i;
@ -42,13 +48,18 @@ std::vector<InstructionMatch> InstructionScanner::ScanCodeSection(
matches.push_back({codeSectionOffset + i, length});
i += length;
if (progress && i >= nextReport) {
progress(i, limit);
nextReport = i + 65536;
}
}
if (progress) progress(limit, limit);
return matches;
}
std::unique_ptr<IInstructionScanner> MakeInstructionScanner() {
return std::make_unique<InstructionScanner>();
std::unique_ptr<IInstructionScanner> MakeInstructionScanner(std::function<void(std::uint64_t, std::uint64_t)> progress) {
return std::make_unique<InstructionScanner>(std::move(progress));
}
}

View File

@ -1,3 +1,4 @@
#include <ConsoleProgress.hpp>
#include <Cli.hpp>
#include <domain/Types.hpp>
#include <io/FileReader.hpp>
@ -48,7 +49,7 @@ struct ConvertedExecutable {
std::vector<std::uint8_t> Bytes;
};
ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable) {
ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, Domain::IProgress& progress) {
Io::FileReader fileReader;
auto sourceBytes = fileReader.Read(inputPath);
@ -74,7 +75,7 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath,
}
auto elfReader = std::make_shared<Relinker::ElfReader>(sourceBytes);
const std::shared_ptr<Relinker::ISyscallScanner> syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(args.trapSdkSyscallStubs);
const std::shared_ptr<Relinker::ISyscallScanner> syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(progress, args.trapSdkSyscallStubs);
const auto pipeline = std::make_shared<Relinker::RelinkerPipeline>(
elfReader,
@ -82,8 +83,8 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath,
Relinker::MakeCallSiteResolver(),
std::make_shared<Relinker::ValidationPolicy>(),
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
args.unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter() : Relinker::MakeUnusedNidFilter(),
args.unusedFilterLevel
args.unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter(progress) : Relinker::MakeUnusedNidFilter(),
args.unusedFilterLevel, progress
);
if (mainExecutable) {
@ -148,6 +149,7 @@ int Run(const int argc, char* argv[]) {
return 0;
}
Cli::ConsoleProgress progress(std::cout);
try {
auto extension = Io::Utf8Path(Io::NativePath(args.outputPath).extension());
for (auto& character : extension) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
@ -192,10 +194,10 @@ int Run(const int argc, char* argv[]) {
}
std::vector<ConvertedExecutable> executables;
executables.push_back(Convert(args, args.inputPath, absPath, true));
executables.push_back(Convert(args, args.inputPath, absPath, true, progress));
for (const auto& [executable, relative, input, output] : extraExecutables) {
std::cout << "Executable: " << relative.generic_string() << '\n';
auto converted = Convert(args, Io::Utf8Path(input), Io::Utf8Path(output), false);
auto converted = Convert(args, Io::Utf8Path(input), Io::Utf8Path(output), false, progress);
const auto& mainArtifacts = executables.front().GuestArtifacts;
const bool shared = converted.GuestArtifacts.size() == mainArtifacts.size() && std::equal(mainArtifacts.begin(), mainArtifacts.end(), converted.GuestArtifacts.begin(), [](const auto& left, const auto& right) {
return left.Path == right.Path && left.Bytes == right.Bytes;

View File

@ -3,10 +3,11 @@
#include <relinker/domain/ISyscallScanner.hpp>
#include <memory>
#include <domain/IProgress.hpp>
namespace Relinker {
std::unique_ptr<ISyscallScanner> MakeSyscallScanner(bool trapSdkStubs = false);
std::unique_ptr<ISyscallScanner> MakeSyscallScanner(Domain::IProgress& progress, bool trapSdkStubs = false);
std::unique_ptr<ISyscallScanner> MakeNullSyscallScanner();
}

View File

@ -3,11 +3,12 @@
#include <relinker/domain/IUnusedNidFilter.hpp>
#include <memory>
#include <domain/IProgress.hpp>
namespace Relinker {
std::shared_ptr<IUnusedNidFilter> MakeUnusedNidFilter();
std::shared_ptr<IUnusedNidFilter> MakeStrictUnusedNidFilter();
std::shared_ptr<IUnusedNidFilter> MakeStrictUnusedNidFilter(Domain::IProgress& progress);
}

View File

@ -9,25 +9,27 @@
#include <relinker/domain/ISysVDynamicSectionBuilder.hpp>
#include <relinker/domain/IUnusedNidFilter.hpp>
#include <memory>
#include <domain/IProgress.hpp>
namespace Relinker {
class RelinkerPipeline : public IRelinkerPipeline {
public:
RelinkerPipeline(std::shared_ptr<IElfReader> elfReader, std::shared_ptr<ISyscallScanner> syscallScanner, std::shared_ptr<ICallSiteResolver> callSiteResolver, std::shared_ptr<IValidationPolicy> validationPolicy, std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder, std::shared_ptr<IUnusedNidFilter> unusedNidFilter, std::uint32_t unusedFilterLevel);
RelinkerPipeline(std::shared_ptr<IElfReader> elfReader, std::shared_ptr<ISyscallScanner> syscallScanner, std::shared_ptr<ICallSiteResolver> callSiteResolver, std::shared_ptr<IValidationPolicy> validationPolicy, std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder, std::shared_ptr<IUnusedNidFilter> unusedNidFilter, std::uint32_t unusedFilterLevel, Domain::IProgress& progress);
RelinkResult Relink(const std::vector<std::uint8_t>& sourceElf) override;
private:
std::shared_ptr<IElfReader> _elfReader;
std::shared_ptr<ISyscallScanner> _syscallScanner;
std::shared_ptr<ICallSiteResolver> _callSiteResolver;
std::shared_ptr<IValidationPolicy> _validationPolicy;
std::shared_ptr<ISysVDynamicSectionBuilder> _dynamicSectionBuilder;
std::shared_ptr<IUnusedNidFilter> _unusedNidFilter;
std::shared_ptr<IElfReader> elfReader;
std::shared_ptr<ISyscallScanner> syscallScanner;
std::shared_ptr<ICallSiteResolver> callSiteResolver;
std::shared_ptr<IValidationPolicy> validationPolicy;
std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder;
std::shared_ptr<IUnusedNidFilter> unusedNidFilter;
std::uint32_t unusedFilterLevel;
Domain::IProgress& progress;
static std::string _relocationTypeName(std::uint32_t type);
static std::string relocationTypeName(std::uint32_t type);
};
}

View File

@ -5,7 +5,7 @@
#include <array>
#include <cstddef>
#include <cstring>
#include <iostream>
#include <memory>
#include <optional>
#include <sstream>
@ -164,7 +164,7 @@ static std::optional<std::uint32_t> ConstantSyscallNumber(
class SyscallScanner : public ISyscallScanner {
public:
explicit SyscallScanner(const bool trapSdkStubs) : _trapSdkStubs(trapSdkStubs) {}
SyscallScanner(Domain::IProgress& progress, const bool trapSdkStubs) : progress(progress), trapSdkStubs(trapSdkStubs) {}
std::vector<std::size_t> ScanCodeSectionForSyscalls(
const std::vector<std::uint8_t>& codeSection,
@ -172,7 +172,8 @@ public:
FileByteOffset codeSectionSize) override;
private:
bool _trapSdkStubs;
Domain::IProgress& progress;
bool trapSdkStubs;
};
std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(
@ -181,11 +182,13 @@ std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(
const FileByteOffset codeSectionSize) {
const std::size_t limit = std::min(codeSection.size(), static_cast<std::size_t>(codeSectionSize));
const auto scanner = Codegen::MakeInstructionScanner();
const auto scanner = Codegen::MakeInstructionScanner([&](auto completed, auto total) { progress.Report({Domain::ProgressStage::Decode, {}, completed, total}); });
const auto matches = scanner->ScanCodeSection(codeSection, codeSectionOffset, codeSectionSize);
progress.Report({Domain::ProgressStage::Syscalls, {}, 0, matches.size()});
std::vector<std::size_t> trapped;
for (std::size_t index = 0; index < matches.size(); ++index) {
if (index % 4096 == 0) progress.Report({Domain::ProgressStage::Syscalls, {}, index, matches.size()});
const auto& match = matches[index];
const std::size_t start = static_cast<std::size_t>(match.Offset - codeSectionOffset);
const auto opcodeOffset = Codegen::DecodedInstruction{codeSection.data() + start, match.Length}.OpcodeOffset();
@ -197,10 +200,12 @@ std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(
if (kind == ForbiddenInstruction::None)
continue;
if (_trapSdkStubs && kind == ForbiddenInstruction::Invocation &&
if (trapSdkStubs && kind == ForbiddenInstruction::Invocation &&
(IsSdkSyscallStub(codeSection, opcode) || IsSdkMmanSyscallStub(codeSection, opcode, limit, matches, codeSectionOffset))) {
trapped.push_back(opcode);
std::cout << "Trapping SDK syscall instruction at code offset 0x" << std::hex << match.Offset << std::dec << '\n';
std::ostringstream message;
message << "Trapping SDK syscall instruction at code offset 0x" << std::hex << match.Offset << '\n';
progress.Message(message.str());
continue;
}
@ -213,11 +218,12 @@ std::vector<std::size_t> SyscallScanner::ScanCodeSectionForSyscalls(
throw RelinkerException(msg.str(), match.Offset);
}
progress.Report({Domain::ProgressStage::Syscalls, {}, matches.size(), matches.size()});
return trapped;
}
std::unique_ptr<ISyscallScanner> MakeSyscallScanner(const bool trapSdkStubs) {
return std::make_unique<SyscallScanner>(trapSdkStubs);
std::unique_ptr<ISyscallScanner> MakeSyscallScanner(Domain::IProgress& progress, const bool trapSdkStubs) {
return std::make_unique<SyscallScanner>(progress, trapSdkStubs);
}
class NullSyscallScanner : public ISyscallScanner {

View File

@ -3,7 +3,7 @@
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
#include <relinker/parsing/ElfReader.hpp>
#include <cstring>
#include <iostream>
#include <sstream>
#include <limits>
#include <optional>
@ -188,7 +188,10 @@ private:
};
class StrictNidFilter : public IUnusedNidFilter {
Domain::IProgress& progress;
public:
explicit StrictNidFilter(Domain::IProgress& progress) : progress(progress) {}
std::vector<NidReference> Filter(const std::vector<NidReference>& nidRefs, const std::vector<std::uint8_t>& elfBytes, const std::vector<std::uint8_t>& textSection, VirtualAddress textVAddr) override {
const StrictImage image(elfBytes);
const auto input = image.Build(nidRefs, textSection, textVAddr);
@ -197,16 +200,16 @@ public:
for (const auto& reference : nidRefs) {
if (analysis.ImportSlots.contains(reference.RelocationAddress)) result.push_back(reference);
}
std::cout << "Strict reachability: " << analysis.Instructions.size() << " instructions; conservative indirect transfers=" << analysis.IndirectTransfers << "\n";
std::cout << "Function graph: " << analysis.LiveRegions << "/" << analysis.TotalRegions << " live regions; address-taken roots=" << analysis.AddressTakenRoots << "; unwind functions=" << input.Functions.size() << "\n";
{ std::ostringstream message; message << "Strict reachability: " << analysis.Instructions.size() << " instructions; conservative indirect transfers=" << analysis.IndirectTransfers << "\n"; progress.Message(message.str()); }
{ std::ostringstream message; message << "Function graph: " << analysis.LiveRegions << "/" << analysis.TotalRegions << " live regions; address-taken roots=" << analysis.AddressTakenRoots << "; unwind functions=" << input.Functions.size() << "\n"; progress.Message(message.str()); }
return result;
}
};
}
std::shared_ptr<IUnusedNidFilter> MakeStrictUnusedNidFilter() {
return std::make_shared<StrictNidFilter>();
std::shared_ptr<IUnusedNidFilter> MakeStrictUnusedNidFilter(Domain::IProgress& progress) {
return std::make_shared<StrictNidFilter>(progress);
}
}

View File

@ -6,7 +6,7 @@
#include <algorithm>
#include <optional>
#include <sstream>
#include <iostream>
#include <cstring>
#include <limits>
#include <tuple>
@ -16,19 +16,21 @@ namespace Relinker {
using namespace Elfpatcher;
RelinkerPipeline::RelinkerPipeline(std::shared_ptr<IElfReader> elfReader, std::shared_ptr<ISyscallScanner> syscallScanner, std::shared_ptr<ICallSiteResolver> callSiteResolver, std::shared_ptr<IValidationPolicy> validationPolicy, std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder, std::shared_ptr<IUnusedNidFilter> unusedNidFilter, std::uint32_t unusedFilterLevel)
: _elfReader(std::move(elfReader))
, _syscallScanner(std::move(syscallScanner))
, _callSiteResolver(std::move(callSiteResolver))
, _validationPolicy(std::move(validationPolicy))
, _dynamicSectionBuilder(std::move(dynamicSectionBuilder))
, _unusedNidFilter(std::move(unusedNidFilter))
RelinkerPipeline::RelinkerPipeline(std::shared_ptr<IElfReader> elfReader, std::shared_ptr<ISyscallScanner> syscallScanner, std::shared_ptr<ICallSiteResolver> callSiteResolver, std::shared_ptr<IValidationPolicy> validationPolicy, std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder, std::shared_ptr<IUnusedNidFilter> unusedNidFilter, std::uint32_t unusedFilterLevel, Domain::IProgress& progress)
: elfReader(std::move(elfReader))
, syscallScanner(std::move(syscallScanner))
, callSiteResolver(std::move(callSiteResolver))
, validationPolicy(std::move(validationPolicy))
, dynamicSectionBuilder(std::move(dynamicSectionBuilder))
, unusedNidFilter(std::move(unusedNidFilter))
, unusedFilterLevel(unusedFilterLevel)
, progress(progress)
{
if (!this->elfReader || !this->syscallScanner || !this->callSiteResolver || !this->validationPolicy || !this->dynamicSectionBuilder || !this->unusedNidFilter) throw RelinkerException("Missing relinker pipeline dependency");
if (unusedFilterLevel > 2) throw RelinkerException("Unused NID filter level must be 0, 1 or 2");
}
std::string RelinkerPipeline::_relocationTypeName(std::uint32_t type) {
std::string RelinkerPipeline::relocationTypeName(std::uint32_t type) {
switch (type) {
case 1: return "R_X86_64_64";
case 6: return "R_X86_64_GLOB_DAT";
@ -43,7 +45,7 @@ std::string RelinkerPipeline::_relocationTypeName(std::uint32_t type) {
}
RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf) {
auto programHeaders = _elfReader->ReadProgramHeaders();
auto programHeaders = elfReader->ReadProgramHeaders();
std::vector<std::uint8_t> textSection;
VirtualAddress textVAddr = 0;
@ -53,7 +55,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
for (const auto& ph : programHeaders) {
if (ph.Type == PT_LOAD && (ph.Flags & PF_X) != 0) {
auto segment = _elfReader->ReadSegment(ph);
auto segment = elfReader->ReadSegment(ph);
if (!segment.empty()) {
if (textSection.empty()) {
textSection = segment;
@ -83,7 +85,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
continue;
hasDynamicSegment = true;
dynTags = _elfReader->ReadDynamicTags(ph);
dynTags = elfReader->ReadDynamicTags(ph);
break;
}
@ -121,7 +123,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
throw RelinkerException(std::string("Adding PT_SCE_DYNLIBDATA base to ") + name + " overflows", value);
return sceTableBase + value;
}
return _elfReader->TranslateVirtualAddress(getTagValue(sysvTag));
return elfReader->TranslateVirtualAddress(getTagValue(sysvTag));
};
auto readAsSize = [&](const std::int64_t osTag, const std::int64_t sysvTag, const char* name) -> ByteCount {
@ -175,9 +177,9 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
std::vector<NidReference> nidRefs;
std::vector<std::string> neededLibraries;
auto policy = std::dynamic_pointer_cast<ValidationPolicy>(_validationPolicy);
auto policy = std::dynamic_pointer_cast<ValidationPolicy>(validationPolicy);
const std::vector<std::uint8_t>& raw = _elfReader->GetRawBytes();
const std::vector<std::uint8_t>& raw = elfReader->GetRawBytes();
if (dynStrTabOffset > raw.size() || dynStrTabSize > raw.size() - dynStrTabOffset)
throw RelinkerException("Dynamic string table is out of bounds", dynStrTabOffset);
@ -270,7 +272,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
extractRela(dynJmpRelOffset, dynJmpRelSize);
for (const auto& ref : nidRefs)
_validationPolicy->ValidateRelocationTypeSupported(ref.RelocationTypeValue, ref.RelocationTableOffset);
validationPolicy->ValidateRelocationTypeSupported(ref.RelocationTypeValue, ref.RelocationTableOffset);
if (std::any_of(programHeaders.begin(), programHeaders.end(), [](const ProgramHeader& ph) { return ph.Type == PT_SCE_DYNLIBDATA; })) {
const auto functions = UnusedNidFilter::ReadExceptionFunctions(raw, programHeaders, {}, {});
@ -312,7 +314,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
std::vector<RelinkPatch> syscallPatches;
for (auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) {
for (const auto offset : _syscallScanner->ScanCodeSectionForSyscalls(segment, segmentVAddr, segment.size())) {
for (const auto offset : syscallScanner->ScanCodeSectionForSyscalls(segment, segmentVAddr, segment.size())) {
if (offset > segment.size() || segment.size() - offset < 2u)
throw RelinkerException("Syscall trap patch exceeds executable segment", segmentVAddr + offset);
segment[offset] = 0x0F;
@ -321,18 +323,19 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
}
}
_validationPolicy->ValidateSyscallAbsence();
validationPolicy->ValidateSyscallAbsence();
progress.Report({Domain::ProgressStage::Imports, {}, 0, 1});
const std::size_t originalNidCount = nidRefs.size();
const auto originalNidRefs = nidRefs;
std::cout << "NID input: " << originalNidCount << " references\n";
{ std::ostringstream message; message << "NID input: " << originalNidCount << " references\n"; progress.Message(message.str()); }
if (unusedFilterLevel == 2) {
if (executableSegments.size() > 1)
throw RelinkerException("Strict NID filtering does not support multiple executable segments");
nidRefs = _unusedNidFilter->Filter(nidRefs, raw, textSection, textVAddr);
nidRefs = unusedNidFilter->Filter(nidRefs, raw, textSection, textVAddr);
if (nidRefs.size() > originalNidCount) throw RelinkerException("Strict NID filter increased the reference count");
std::cout << "Strict filtering total: " << originalNidCount << " -> " << nidRefs.size() << "; filtered=" << originalNidCount - nidRefs.size() << "\n";
{ std::ostringstream message; message << "Strict filtering total: " << originalNidCount << " -> " << nidRefs.size() << "; filtered=" << originalNidCount - nidRefs.size() << "\n"; progress.Message(message.str()); }
} else if (unusedFilterLevel == 1) {
std::vector<NidReference> pltRefs;
std::vector<NidReference> nonPltRefs;
@ -343,15 +346,15 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
nonPltRefs.push_back(ref);
}
std::cout << "PLT preservation: " << pltRefs.size() << " -> " << pltRefs.size() << "; filtered=0\n";
{ std::ostringstream message; message << "PLT preservation: " << pltRefs.size() << " -> " << pltRefs.size() << "; filtered=0\n"; progress.Message(message.str()); }
const std::size_t nonPltCount = nonPltRefs.size();
if (executableSegments.size() > 1) {
std::cout << "CFG/GOT filtering skipped: multiple executable segments are not modeled; "
<< nonPltCount << " -> " << nonPltCount << "; filtered=0\n";
{ std::ostringstream message; message << "CFG/GOT filtering skipped: multiple executable segments are not modeled; "
<< nonPltCount << " -> " << nonPltCount << "; filtered=0\n"; progress.Message(message.str()); }
} else {
nonPltRefs = _unusedNidFilter->Filter(nonPltRefs, raw, textSection, textVAddr);
nonPltRefs = unusedNidFilter->Filter(nonPltRefs, raw, textSection, textVAddr);
if (nonPltRefs.size() > nonPltCount) throw RelinkerException("Unused NID filter increased the reference count");
std::cout << "CFG/GOT filtering: " << nonPltCount << " -> " << nonPltRefs.size() << "; filtered=" << nonPltCount - nonPltRefs.size() << "\n";
{ std::ostringstream message; message << "CFG/GOT filtering: " << nonPltCount << " -> " << nonPltRefs.size() << "; filtered=" << nonPltCount - nonPltRefs.size() << "\n"; progress.Message(message.str()); }
}
nidRefs.clear();
@ -359,22 +362,22 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
for (auto& ref : pltRefs) nidRefs.push_back(std::move(ref));
for (auto& ref : nonPltRefs) nidRefs.push_back(std::move(ref));
} else {
std::cout << "Unused NID filtering: disabled; filtered=0\n";
{ std::ostringstream message; message << "Unused NID filtering: disabled; filtered=0\n"; progress.Message(message.str()); }
}
std::cout << "NID total: " << originalNidCount << " -> " << nidRefs.size() << "; filtered=" << originalNidCount - nidRefs.size() << "\n";
{ std::ostringstream message; message << "NID total: " << originalNidCount << " -> " << nidRefs.size() << "; filtered=" << originalNidCount - nidRefs.size() << "\n"; progress.Message(message.str()); }
auto dynamicRefs = nidRefs;
std::vector<RelinkPatch> patches;
auto pltCount = static_cast<std::uint32_t>(dynJmpRelSize / relaEntSize);
if (unusedFilterLevel == 2) {
auto compacted = UnusedNidFilter::CompactPlt(originalNidRefs, nidRefs, textSection, textVAddr, _elfReader->TranslateVirtualAddress(textVAddr), dynJmpRelOffset);
auto compacted = UnusedNidFilter::CompactPlt(originalNidRefs, nidRefs, textSection, textVAddr, elfReader->TranslateVirtualAddress(textVAddr), dynJmpRelOffset);
dynamicRefs = std::move(compacted.References);
patches = std::move(compacted.Patches);
std::cout << "PLT compaction: " << pltCount << " -> " << compacted.SlotCount << "\n";
{ std::ostringstream message; message << "PLT compaction: " << pltCount << " -> " << compacted.SlotCount << "\n"; progress.Message(message.str()); }
pltCount = compacted.SlotCount;
}
auto dynSection = _dynamicSectionBuilder->BuildDynamicSection(dynamicRefs, neededLibraries, dynJmpRelOffset, pltCount);
auto dynSection = dynamicSectionBuilder->BuildDynamicSection(dynamicRefs, neededLibraries, dynJmpRelOffset, pltCount);
for (const auto& ref : dynamicRefs) {
if (!ref.Library.empty()) continue;
if (auto declared = Domain::DeclaredImportModule(ref.Nid, importModules); !declared.empty()) dynSection.UnmatchedImportModules.insert(std::move(declared));
@ -434,7 +437,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
CallRegistryEntry entry;
entry.Nid = ref.Nid;
entry.Library = ref.Library;
entry.RelocationTypeString = _relocationTypeName(ref.RelocationTypeValue);
entry.RelocationTypeString = relocationTypeName(ref.RelocationTypeValue);
entry.RelocationOffset = ref.RelocationTableOffset;
entry.TargetSection = ".got";
entry.TargetOffset = ref.RelocationAddress;
@ -445,13 +448,14 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
for (const auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) {
static_cast<void>(segmentFileOffset);
for (auto& entry : entries) {
auto segmentSites = _callSiteResolver->ResolveCallSites(segment, segmentVAddr, entry.TargetOffset, 8);
auto segmentSites = callSiteResolver->ResolveCallSites(segment, segmentVAddr, entry.TargetOffset, 8);
entry.CallSites.insert(entry.CallSites.end(), segmentSites.begin(), segmentSites.end());
entry.CallSitesResolved = !entry.CallSites.empty();
}
}
patches.insert(patches.end(), std::make_move_iterator(syscallPatches.begin()), std::make_move_iterator(syscallPatches.end()));
progress.Report({Domain::ProgressStage::Imports, {}, 1, 1});
return RelinkResult{std::move(entries), std::move(programHeaders), std::move(dynSection), gotVAddr, std::move(patches)};
}

View File

@ -1,3 +1,4 @@
#include "TestProgress.hpp"
#include <relinker/analysis/SyscallScanner.hpp>
#include <cstdint>
#include <iostream>
@ -30,7 +31,7 @@ void Require(bool value, const std::string& message) {
bool Rejected(const Bytes& code, bool trap, std::size_t size) {
try {
Relinker::MakeSyscallScanner(trap)->ScanCodeSectionForSyscalls(code, 0, size);
Relinker::MakeSyscallScanner(testProgress, trap)->ScanCodeSectionForSyscalls(code, 0, size);
return false;
} catch (const std::runtime_error&) {
return true;
@ -44,7 +45,7 @@ void CheckFixtures() {
for (const auto padding : {std::size_t{0}, std::size_t{1}, std::size_t{7}, std::size_t{64}}) {
Bytes code(padding, 0x90);
code.insert(code.end(), fixture.code.begin(), fixture.code.end());
const auto trapped = Relinker::MakeSyscallScanner(true)->ScanCodeSectionForSyscalls(code, 0x123400, code.size());
const auto trapped = Relinker::MakeSyscallScanner(testProgress, true)->ScanCodeSectionForSyscalls(code, 0x123400, code.size());
Require(trapped == std::vector<std::size_t>{padding + fixture.syscall}, "SDK inline syscall returned the wrong patch offset");
}
for (std::size_t length = fixture.syscall + 2; length < fixture.code.size(); ++length)

View File

@ -1,3 +1,4 @@
#include "TestProgress.hpp"
#include <relinker/analysis/UnusedNidFilter.hpp>
#include <relinker/analysis/UnusedNidFilter/StrictReachability.hpp>
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
@ -343,7 +344,7 @@ void filterCallbackDataImports() {
const auto bytes = elfFixture(input);
for (const std::uint32_t relocationType : {1, 6}) {
const std::vector<Relinker::NidReference> references = {{"callbackData", {}, relocationType, 0x300, 0x2000, 0}, {"deadData", {}, relocationType, 0x318, 0x2008, 0}};
const auto filtered = Relinker::MakeStrictUnusedNidFilter()->Filter(references, bytes, input.Text, input.TextVaddr);
const auto filtered = Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr);
require(filtered.size() == 1 && filtered[0].Nid == references[0].Nid, "Strict ELF filter lost callback data or retained unreachable data");
}
}
@ -354,7 +355,7 @@ void filterAndPltCompaction() {
importThunk(input, 16, 0x2008);
const auto bytes = elfFixture(input);
const std::vector<Relinker::NidReference> references = {{"live", {}, 7, 0x300, 0x2000, 0}, {"dead", {}, 7, 0x318, 0x2008, 0}};
const auto filtered = Relinker::MakeStrictUnusedNidFilter()->Filter(references, bytes, input.Text, input.TextVaddr);
const auto filtered = Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr);
require(filtered.size() == 1 && filtered[0].Nid == references[0].Nid, "Strict ELF filter did not remove only the dead PLT import");
for (std::size_t index = 0; index < references.size(); ++index) {
const auto offset = index * 16;
@ -377,13 +378,13 @@ void filterAndPltCompaction() {
require(section.RelaPltData.size() == 24 && section.DynSymData.size() == 48, "Compacted PLT retained a dead dynamic symbol");
auto truncated = bytes;
truncated.resize(70);
requireFailure([&] { Relinker::MakeStrictUnusedNidFilter()->Filter(references, truncated, input.Text, input.TextVaddr); }, "Truncated ELF was accepted");
requireFailure([&] { Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, truncated, input.Text, input.TextVaddr); }, "Truncated ELF was accepted");
auto exceptional = bytes;
write<std::uint16_t>(exceptional, 56, 3);
write<std::uint32_t>(exceptional, 176, 0x6474E550);
write<std::uint64_t>(exceptional, 184, 0x380);
write<std::uint64_t>(exceptional, 208, 8);
requireFailure([&] { Relinker::MakeStrictUnusedNidFilter()->Filter(references, exceptional, input.Text, input.TextVaddr); }, "Exception metadata was ignored");
requireFailure([&] { Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, exceptional, input.Text, input.TextVaddr); }, "Exception metadata was ignored");
}
void relativeRelocationTableBounds() {
@ -524,7 +525,7 @@ void sceDynlibDataOffsets() {
}
try {
const std::vector<Relinker::NidReference> references = {{"live", {}, 6, 0x900, 0x2000, 0}};
const auto filtered = Relinker::MakeStrictUnusedNidFilter()->Filter(references, bytes, input.Text, input.TextVaddr);
const auto filtered = Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr);
if (filtered.size() != 1) failures += "SCE relocation target did not keep its import";
} catch (const std::exception& error) {
failures += std::string("SCE strict filter: ") + error.what();
@ -537,7 +538,7 @@ void sceDynlibDataOffsets() {
require(Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(noSegment)->TargetOfSlot(0x2040) == 0x1010,
"SCE relocation without PT_SCE_DYNLIBDATA was not read at its absolute file offset");
const std::vector<Relinker::NidReference> references = {{"live", {}, 6, 0x900, 0x2000, 0}};
require(Relinker::MakeStrictUnusedNidFilter()->Filter(references, noSegment, input.Text, input.TextVaddr).size() == 1,
require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, noSegment, input.Text, input.TextVaddr).size() == 1,
"Strict filter lost an SCE relocation without PT_SCE_DYNLIBDATA");
auto jmprel = bytes;
@ -584,7 +585,7 @@ void sceDynlibDataOffsets() {
write<std::uint64_t>(overflow, 0x308, std::numeric_limits<std::uint64_t>::max());
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(overflow); },
"SCE relocation file offset overflow was accepted");
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, overflow, input.Text, input.TextVaddr); },
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, overflow, input.Text, input.TextVaddr); },
"Strict filter accepted an overflowing SCE table offset");
auto duplicate = bytes;
@ -592,7 +593,7 @@ void sceDynlibDataOffsets() {
write<std::uint32_t>(duplicate, 288, 0x61000000);
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(duplicate); },
"Duplicate PT_SCE_DYNLIBDATA segments were accepted");
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, duplicate, input.Text, input.TextVaddr); },
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, duplicate, input.Text, input.TextVaddr); },
"Strict filter accepted duplicate PT_SCE_DYNLIBDATA segments");
auto sectionRelative = bytes;
@ -606,19 +607,19 @@ void sceDynlibDataOffsets() {
write<std::uint64_t>(sectionRelative, 0x358, 48);
write<std::uint8_t>(sectionRelative, 0x61C, 3);
write<std::uint64_t>(sectionRelative, 0x908, (std::uint64_t{1} << 32) | 8);
require(Relinker::MakeStrictUnusedNidFilter()->Filter(references, sectionRelative, input.Text, input.TextVaddr).size() == 1,
require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, sectionRelative, input.Text, input.TextVaddr).size() == 1,
"Strict filter rejected a RELATIVE relocation against a zero-value section symbol");
auto valuedSection = sectionRelative;
write<std::uint64_t>(valuedSection, 0x620, 0x10);
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, valuedSection, input.Text, input.TextVaddr); },
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, valuedSection, input.Text, input.TextVaddr); },
"Strict filter accepted a RELATIVE relocation against a section symbol with a value");
auto untyped = sectionRelative;
write<std::uint8_t>(untyped, 0x61C, 0);
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, untyped, input.Text, input.TextVaddr); },
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, untyped, input.Text, input.TextVaddr); },
"Strict filter accepted a RELATIVE relocation against a non-section symbol");
auto outside = sectionRelative;
write<std::uint64_t>(outside, 0x908, (std::uint64_t{2} << 32) | 8);
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, outside, input.Text, input.TextVaddr); },
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, outside, input.Text, input.TextVaddr); },
"Strict filter accepted a RELATIVE relocation symbol outside the symbol table");
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <domain/IProgress.hpp>
#include <vector>
class TestProgress final : public Domain::IProgress {
public:
std::vector<Domain::ProgressEvent> Events;
std::string Messages;
void Report(const Domain::ProgressEvent& event) override { Events.push_back(event); }
void Message(std::string_view message) override { Messages += message; }
};
inline TestProgress testProgress;