From f4f8d5bbce747fefdc583d15948fd9ccd9ca6977 Mon Sep 17 00:00:00 2001 From: STL1te Date: Tue, 29 Sep 2026 07:53:49 +0300 Subject: [PATCH] feat(relinker): exclude single sce_module files, tolerate a zero code tail - --exclude-sce-module (repeatable) drops one title module from guest conversion, e.g. a title libc.prx, while the rest of sce_module is still relinked as guest modules - the instruction scanner stops at zero padding that ends a code segment with an odd byte instead of failing on a truncated `add [rax], al` Co-Authored-By: Claude Opus 5.5 --- core/relinker/cli/include/Cli.hpp | 2 ++ core/relinker/cli/src/CliArgs.cpp | 9 ++++++++- core/relinker/codegen/src/InstructionScanner.cpp | 2 ++ .../relinker/codegen/tests/Amd64OnlyConverterTests.cpp | 10 ++++++++++ core/relinker/main.cpp | 3 ++- .../relinker/include/relinker/guest/GuestImage.hpp | 4 +++- .../relinker/relinker/src/guest/GuestModuleBuilder.cpp | 5 ++++- 7 files changed, 31 insertions(+), 4 deletions(-) diff --git a/core/relinker/cli/include/Cli.hpp b/core/relinker/cli/include/Cli.hpp index 7d5dc0db3..15005aa2a 100644 --- a/core/relinker/cli/include/Cli.hpp +++ b/core/relinker/cli/include/Cli.hpp @@ -1,6 +1,7 @@ #ifndef CORE_RELINKER_CLI_INCLUDE_CLI_HPP #define CORE_RELINKER_CLI_INCLUDE_CLI_HPP +#include #include #include @@ -20,6 +21,7 @@ struct Args { std::string inputPath; std::string outputPath; std::string runPath = "$ORIGIN/libs"; + std::set excludedSceModules; }; Args ParseArgs(int argc, char* argv[]); diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index 2514a24c7..212d084d8 100644 --- a/core/relinker/cli/src/CliArgs.cpp +++ b/core/relinker/cli/src/CliArgs.cpp @@ -15,6 +15,10 @@ Args ParseArgs(int argc, char* argv[]) { args.skipSyscallCheck = true; } else if (arg == "--skip-sce-module") { args.skipSceModule = true; + } else if (arg == "--exclude-sce-module") { + if (i + 1 >= argc) + throw std::runtime_error("--exclude-sce-module requires a file name"); + args.excludedSceModules.insert(argv[++i]); } else if (arg == "--to-intel") { args.toIntel = true; } else if (arg.rfind("unused-filter=", 0) == 0) { @@ -50,6 +54,9 @@ Args ParseArgs(int argc, char* argv[]) { } } + if (args.skipSceModule && !args.excludedSceModules.empty()) + throw std::runtime_error("--exclude-sce-module conflicts with --skip-sce-module"); + if (args.windowsDiagnostics && !args.toWindows) throw std::runtime_error("--windows-diagnostics requires --windows"); @@ -58,7 +65,7 @@ Args ParseArgs(int argc, char* argv[]) { if (args.inputPath.empty() || args.outputPath.empty()) throw std::runtime_error( - "Usage: relinker [--windows] [--windows-diagnostics] [--windows-gui] [--skip-syscall-check] [--skip-sce-module] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" + "Usage: relinker [--windows] [--windows-diagnostics] [--windows-gui] [--skip-syscall-check] [--skip-sce-module] [--exclude-sce-module ]... [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" "Example: relinker input.elf output.elf" ); diff --git a/core/relinker/codegen/src/InstructionScanner.cpp b/core/relinker/codegen/src/InstructionScanner.cpp index f76cfeb6f..94addfff7 100644 --- a/core/relinker/codegen/src/InstructionScanner.cpp +++ b/core/relinker/codegen/src/InstructionScanner.cpp @@ -35,6 +35,8 @@ std::vector InstructionScanner::ScanCodeSection( try { length = decoder.Decode(cursor, available); } catch (const CodegenException& e) { + // Zero padding at the segment tail decodes as `add [rax], al` pairs; an odd tail leaves one truncated byte. + if (std::all_of(cursor, cursor + available, [](const std::uint8_t b) { return b == 0; })) break; throw CodegenException(std::string("Cannot decode instruction: ") + e.what(), codeSectionOffset + i); } diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 280e82980..3d2c49ccd 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -392,6 +393,14 @@ void registerFormExecution() { void registerFormExecution() {} #endif +void scannerZeroTail() { + const auto scanner = Codegen::MakeInstructionScanner(); + const Bytes code{0xC3, 0x00, 0x00, 0x00}; + require(scanner->ScanCodeSection(code, 0, code.size()).size() == 2, "Odd zero padding at segment tail must end the scan"); + const Bytes truncated{0xC3, 0x0F}; + requireFailure([&] { (void)scanner->ScanCodeSection(truncated, 0, truncated.size()); }, "Truncated non-zero tail must still fail"); +} + int main() { try { decoderLengths(); @@ -402,6 +411,7 @@ int main() { converterSegment(); converterFailureOffsets(); linuxPlacement(); + scannerZeroTail(); std::cout << "AMD64-only converter tests passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 705c3f6b0..0802223bf 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -71,6 +71,7 @@ int main(const int argc, char* argv[]) { std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n"; std::cout << "sce_module/sce_modules processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n'; + for (const auto& name : args.excludedSceModules) std::cout << "sce_module excluded: " << name << '\n'; auto result = pipeline->Relink(sourceBytes); for (const auto& patch : result.Patches) { if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset) @@ -80,7 +81,7 @@ int main(const int argc, char* argv[]) { std::vector guestArtifacts; if (!args.skipSceModule) { - guestArtifacts = Relinker::GuestModuleBuilder().Build(args.inputPath, absPath, result.DynamicSection, args.toWindows, args.toIntel, *syscallScanner, args.lazyBinding, args.runPath); + guestArtifacts = Relinker::GuestModuleBuilder().Build(args.inputPath, absPath, result.DynamicSection, args.toWindows, args.toIntel, *syscallScanner, args.lazyBinding, args.runPath, args.excludedSceModules); } if (args.writeRegistry) { diff --git a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp index 71cca90fa..f85029698 100644 --- a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp +++ b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace Relinker { @@ -44,7 +46,7 @@ struct GuestArtifact { class GuestModuleBuilder { public: - std::vector Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, bool windows, bool toIntel, ISyscallScanner& syscallScanner, bool lazyBinding, const std::string& runPath) const; + std::vector Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, bool windows, bool toIntel, ISyscallScanner& syscallScanner, bool lazyBinding, const std::string& runPath, const std::set& excludedModules) const; }; } diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index a8655e1e4..aa68ea39d 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -11,7 +11,7 @@ namespace Relinker { -std::vector GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath) const { +std::vector GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath, const std::set& excludedModules) const { const auto root = std::filesystem::absolute(inputPath).parent_path(); const auto singular = root / "sce_module"; const auto plural = root / "sce_modules"; @@ -22,8 +22,10 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path const auto directory = hasSingular ? singular : plural; if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); std::vector paths; + std::set unmatchedExclusions = excludedModules; for (const auto& entry : std::filesystem::directory_iterator(directory)) { if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue; + if (unmatchedExclusions.erase(entry.path().filename().string()) != 0) continue; if (!entry.is_regular_file()) continue; std::ifstream stream(entry.path(), std::ios::binary); if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string()); @@ -32,6 +34,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string()); if (stream.gcount() == 4 && static_cast(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path()); } + if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded sce_module file not found: " + *unmatchedExclusions.begin()); std::sort(paths.begin(), paths.end()); if (paths.empty()) return {}; if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible");