From cd0433fd82a5b5afe2628b2e9463450e1fd03bfa Mon Sep 17 00:00:00 2001 From: boykopovar Date: Tue, 13 Oct 2026 10:46:15 +0300 Subject: [PATCH] feat(relinker): report syscall scanning and import analysis progress --- .../include/codegen/IInstructionScanner.hpp | 3 +- .../codegen/src/InstructionScanner.cpp | 15 +++- core/relinker/main.cpp | 14 ++-- .../relinker/analysis/SyscallScanner.hpp | 3 +- .../relinker/analysis/UnusedNidFilter.hpp | 3 +- .../relinker/pipeline/RelinkerPipeline.hpp | 18 ++--- .../relinker/src/analysis/SyscallScanner.cpp | 22 +++--- .../UnusedNidFilter/StrictNidFilter.cpp | 13 ++-- .../src/pipeline/RelinkerPipeline.cpp | 70 ++++++++++--------- .../relinker/tests/SdkMmanSyscallTests.cpp | 5 +- .../relinker/tests/StrictNidFilterTests.cpp | 25 +++---- core/relinker/relinker/tests/TestProgress.hpp | 15 ++++ 12 files changed, 127 insertions(+), 79 deletions(-) create mode 100644 core/relinker/relinker/tests/TestProgress.hpp diff --git a/core/relinker/codegen/include/codegen/IInstructionScanner.hpp b/core/relinker/codegen/include/codegen/IInstructionScanner.hpp index 1f639525f..515ff800c 100644 --- a/core/relinker/codegen/include/codegen/IInstructionScanner.hpp +++ b/core/relinker/codegen/include/codegen/IInstructionScanner.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -19,7 +20,7 @@ public: ) const = 0; }; -std::unique_ptr MakeInstructionScanner(); +std::unique_ptr MakeInstructionScanner(std::function progress = {}); } diff --git a/core/relinker/codegen/src/InstructionScanner.cpp b/core/relinker/codegen/src/InstructionScanner.cpp index ccd03f983..37aa11e06 100644 --- a/core/relinker/codegen/src/InstructionScanner.cpp +++ b/core/relinker/codegen/src/InstructionScanner.cpp @@ -9,11 +9,15 @@ namespace Codegen { class InstructionScanner : public IInstructionScanner { public: + explicit InstructionScanner(std::function progress) : progress(std::move(progress)) {} [[nodiscard]] std::vector ScanCodeSection( const std::vector& codeSection, Domain::FileByteOffset codeSectionOffset, Domain::FileByteOffset codeSectionSize ) const override; + +private: + std::function progress; }; std::vector InstructionScanner::ScanCodeSection( @@ -27,6 +31,8 @@ std::vector InstructionScanner::ScanCodeSection( std::vector 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 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 MakeInstructionScanner() { - return std::make_unique(); +std::unique_ptr MakeInstructionScanner(std::function progress) { + return std::make_unique(std::move(progress)); } } diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 35b283b63..622521061 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -48,7 +49,7 @@ struct ConvertedExecutable { std::vector 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(sourceBytes); - const std::shared_ptr syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(args.trapSdkSyscallStubs); + const std::shared_ptr syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(progress, args.trapSdkSyscallStubs); const auto pipeline = std::make_shared( elfReader, @@ -82,8 +83,8 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, Relinker::MakeCallSiteResolver(), std::make_shared(), std::make_shared(), - 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(character + ('a' - 'A')); @@ -192,10 +194,10 @@ int Run(const int argc, char* argv[]) { } std::vector 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; diff --git a/core/relinker/relinker/include/relinker/analysis/SyscallScanner.hpp b/core/relinker/relinker/include/relinker/analysis/SyscallScanner.hpp index 29e14cf09..8b9c2d6c2 100644 --- a/core/relinker/relinker/include/relinker/analysis/SyscallScanner.hpp +++ b/core/relinker/relinker/include/relinker/analysis/SyscallScanner.hpp @@ -3,10 +3,11 @@ #include #include +#include namespace Relinker { -std::unique_ptr MakeSyscallScanner(bool trapSdkStubs = false); +std::unique_ptr MakeSyscallScanner(Domain::IProgress& progress, bool trapSdkStubs = false); std::unique_ptr MakeNullSyscallScanner(); } diff --git a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter.hpp b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter.hpp index 91ca7f306..acd0fc633 100644 --- a/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter.hpp +++ b/core/relinker/relinker/include/relinker/analysis/UnusedNidFilter.hpp @@ -3,11 +3,12 @@ #include #include +#include namespace Relinker { std::shared_ptr MakeUnusedNidFilter(); -std::shared_ptr MakeStrictUnusedNidFilter(); +std::shared_ptr MakeStrictUnusedNidFilter(Domain::IProgress& progress); } diff --git a/core/relinker/relinker/include/relinker/pipeline/RelinkerPipeline.hpp b/core/relinker/relinker/include/relinker/pipeline/RelinkerPipeline.hpp index 78554168b..43080f49a 100644 --- a/core/relinker/relinker/include/relinker/pipeline/RelinkerPipeline.hpp +++ b/core/relinker/relinker/include/relinker/pipeline/RelinkerPipeline.hpp @@ -9,25 +9,27 @@ #include #include #include +#include namespace Relinker { class RelinkerPipeline : public IRelinkerPipeline { public: - RelinkerPipeline(std::shared_ptr elfReader, std::shared_ptr syscallScanner, std::shared_ptr callSiteResolver, std::shared_ptr validationPolicy, std::shared_ptr dynamicSectionBuilder, std::shared_ptr unusedNidFilter, std::uint32_t unusedFilterLevel); + RelinkerPipeline(std::shared_ptr elfReader, std::shared_ptr syscallScanner, std::shared_ptr callSiteResolver, std::shared_ptr validationPolicy, std::shared_ptr dynamicSectionBuilder, std::shared_ptr unusedNidFilter, std::uint32_t unusedFilterLevel, Domain::IProgress& progress); RelinkResult Relink(const std::vector& sourceElf) override; private: - std::shared_ptr _elfReader; - std::shared_ptr _syscallScanner; - std::shared_ptr _callSiteResolver; - std::shared_ptr _validationPolicy; - std::shared_ptr _dynamicSectionBuilder; - std::shared_ptr _unusedNidFilter; + std::shared_ptr elfReader; + std::shared_ptr syscallScanner; + std::shared_ptr callSiteResolver; + std::shared_ptr validationPolicy; + std::shared_ptr dynamicSectionBuilder; + std::shared_ptr unusedNidFilter; std::uint32_t unusedFilterLevel; + Domain::IProgress& progress; - static std::string _relocationTypeName(std::uint32_t type); + static std::string relocationTypeName(std::uint32_t type); }; } diff --git a/core/relinker/relinker/src/analysis/SyscallScanner.cpp b/core/relinker/relinker/src/analysis/SyscallScanner.cpp index f83eb1eea..79a68d5cc 100644 --- a/core/relinker/relinker/src/analysis/SyscallScanner.cpp +++ b/core/relinker/relinker/src/analysis/SyscallScanner.cpp @@ -5,7 +5,7 @@ #include #include #include -#include + #include #include #include @@ -164,7 +164,7 @@ static std::optional 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 ScanCodeSectionForSyscalls( const std::vector& codeSection, @@ -172,7 +172,8 @@ public: FileByteOffset codeSectionSize) override; private: - bool _trapSdkStubs; + Domain::IProgress& progress; + bool trapSdkStubs; }; std::vector SyscallScanner::ScanCodeSectionForSyscalls( @@ -181,11 +182,13 @@ std::vector SyscallScanner::ScanCodeSectionForSyscalls( const FileByteOffset codeSectionSize) { const std::size_t limit = std::min(codeSection.size(), static_cast(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 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(match.Offset - codeSectionOffset); const auto opcodeOffset = Codegen::DecodedInstruction{codeSection.data() + start, match.Length}.OpcodeOffset(); @@ -197,10 +200,12 @@ std::vector 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 SyscallScanner::ScanCodeSectionForSyscalls( throw RelinkerException(msg.str(), match.Offset); } + progress.Report({Domain::ProgressStage::Syscalls, {}, matches.size(), matches.size()}); return trapped; } -std::unique_ptr MakeSyscallScanner(const bool trapSdkStubs) { - return std::make_unique(trapSdkStubs); +std::unique_ptr MakeSyscallScanner(Domain::IProgress& progress, const bool trapSdkStubs) { + return std::make_unique(progress, trapSdkStubs); } class NullSyscallScanner : public ISyscallScanner { diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp index 3eede671e..2fb2cc115 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -188,7 +188,10 @@ private: }; class StrictNidFilter : public IUnusedNidFilter { + Domain::IProgress& progress; + public: + explicit StrictNidFilter(Domain::IProgress& progress) : progress(progress) {} std::vector Filter(const std::vector& nidRefs, const std::vector& elfBytes, const std::vector& 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 MakeStrictUnusedNidFilter() { - return std::make_shared(); +std::shared_ptr MakeStrictUnusedNidFilter(Domain::IProgress& progress) { + return std::make_shared(progress); } } diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index f0ad0772a..2baf1b713 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -6,7 +6,7 @@ #include #include #include -#include + #include #include #include @@ -16,19 +16,21 @@ namespace Relinker { using namespace Elfpatcher; -RelinkerPipeline::RelinkerPipeline(std::shared_ptr elfReader, std::shared_ptr syscallScanner, std::shared_ptr callSiteResolver, std::shared_ptr validationPolicy, std::shared_ptr dynamicSectionBuilder, std::shared_ptr 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 elfReader, std::shared_ptr syscallScanner, std::shared_ptr callSiteResolver, std::shared_ptr validationPolicy, std::shared_ptr dynamicSectionBuilder, std::shared_ptr 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& sourceElf) { - auto programHeaders = _elfReader->ReadProgramHeaders(); + auto programHeaders = elfReader->ReadProgramHeaders(); std::vector textSection; VirtualAddress textVAddr = 0; @@ -53,7 +55,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& 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& sourceElf continue; hasDynamicSegment = true; - dynTags = _elfReader->ReadDynamicTags(ph); + dynTags = elfReader->ReadDynamicTags(ph); break; } @@ -121,7 +123,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& 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& sourceElf std::vector nidRefs; std::vector neededLibraries; - auto policy = std::dynamic_pointer_cast(_validationPolicy); + auto policy = std::dynamic_pointer_cast(validationPolicy); - const std::vector& raw = _elfReader->GetRawBytes(); + const std::vector& 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& 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& sourceElf std::vector 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& 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 pltRefs; std::vector nonPltRefs; @@ -343,15 +346,15 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& 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& 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 patches; auto pltCount = static_cast(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& 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& sourceElf for (const auto& [segment, segmentVAddr, segmentFileOffset] : executableSegments) { static_cast(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)}; } diff --git a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp index 264cb6057..1ede68a82 100644 --- a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp +++ b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp @@ -1,3 +1,4 @@ +#include "TestProgress.hpp" #include #include #include @@ -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{padding + fixture.syscall}, "SDK inline syscall returned the wrong patch offset"); } for (std::size_t length = fixture.syscall + 2; length < fixture.code.size(); ++length) diff --git a/core/relinker/relinker/tests/StrictNidFilterTests.cpp b/core/relinker/relinker/tests/StrictNidFilterTests.cpp index 487839560..bc31840e6 100644 --- a/core/relinker/relinker/tests/StrictNidFilterTests.cpp +++ b/core/relinker/relinker/tests/StrictNidFilterTests.cpp @@ -1,3 +1,4 @@ +#include "TestProgress.hpp" #include #include #include @@ -343,7 +344,7 @@ void filterCallbackDataImports() { const auto bytes = elfFixture(input); for (const std::uint32_t relocationType : {1, 6}) { const std::vector 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 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(exceptional, 56, 3); write(exceptional, 176, 0x6474E550); write(exceptional, 184, 0x380); write(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 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 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(overflow, 0x308, std::numeric_limits::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(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(sectionRelative, 0x358, 48); write(sectionRelative, 0x61C, 3); write(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(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(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(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"); } diff --git a/core/relinker/relinker/tests/TestProgress.hpp b/core/relinker/relinker/tests/TestProgress.hpp new file mode 100644 index 000000000..2c8c9d120 --- /dev/null +++ b/core/relinker/relinker/tests/TestProgress.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +class TestProgress final : public Domain::IProgress { +public: + std::vector 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;