feat(relinker): exclude single sce_module files, tolerate a zero code tail

- --exclude-sce-module <file> (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 <noreply@anthropic.com>
This commit is contained in:
STL1te 2026-09-29 07:53:49 +03:00
parent 75a8668b5e
commit f4f8d5bbce
7 changed files with 31 additions and 4 deletions

View File

@ -1,6 +1,7 @@
#ifndef CORE_RELINKER_CLI_INCLUDE_CLI_HPP
#define CORE_RELINKER_CLI_INCLUDE_CLI_HPP
#include <set>
#include <string>
#include <cstdint>
@ -20,6 +21,7 @@ struct Args {
std::string inputPath;
std::string outputPath;
std::string runPath = "$ORIGIN/libs";
std::set<std::string> excludedSceModules;
};
Args ParseArgs(int argc, char* argv[]);

View File

@ -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 <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
"Usage: relinker [--windows] [--windows-diagnostics] [--windows-gui] [--skip-syscall-check] [--skip-sce-module] [--exclude-sce-module <file>]... [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
"Example: relinker input.elf output.elf"
);

View File

@ -35,6 +35,8 @@ std::vector<InstructionMatch> 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);
}

View File

@ -4,6 +4,7 @@
#include <codegen/x86/Sse4aLowering.hpp>
#include <codegen/x86/Sse4aOperands.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <codegen/IInstructionScanner.hpp>
#include <elfpatcher/general/EntryStubBuilder.hpp>
#include <elfpatcher/general/ProgramHeaderLayoutBuilder.hpp>
#include <elfpatcher/general/SectionHeaderTableBuilder.hpp>
@ -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';

View File

@ -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<Relinker::GuestArtifact> 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) {

View File

@ -4,6 +4,8 @@
#include <domain/Types.hpp>
#include <relinker/domain/ISyscallScanner.hpp>
#include <filesystem>
#include <set>
#include <string>
namespace Relinker {
@ -44,7 +46,7 @@ struct GuestArtifact {
class GuestModuleBuilder {
public:
std::vector<GuestArtifact> 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<GuestArtifact> 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<std::string>& excludedModules) const;
};
}

View File

@ -11,7 +11,7 @@
namespace Relinker {
std::vector<GuestArtifact> 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<GuestArtifact> 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<std::string>& 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<GuestArtifact> 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<std::filesystem::path> paths;
std::set<std::string> 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<GuestArtifact> 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<unsigned char>(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");