feat(elfpatcher): add dt_runpath libs/ support with configurable --rpath flag

This commit is contained in:
boykopovar 2026-08-20 11:01:34 +03:00
parent f786ea6576
commit dd2481951d
8 changed files with 38 additions and 9 deletions

View File

@ -4,8 +4,7 @@ Converter for native execution of PlayStation 5 ELF binaries on Linux through bi
## Status
Output ELF is accepted by the Linux kernel and handed to ld.so. Crashes during ld.so startup:
e_entry contains a file offset instead of a virtual address, causing an invalid jump at loader handoff.
Loader startup works: execution reaches `_start` and runs normally up to the first call into an external library function (from test `.prx` stubs).
## Disclaimer

View File

@ -87,6 +87,7 @@ inline constexpr std::int64_t DT_STRSZ = 10;
inline constexpr std::int64_t DT_SYMENT = 11;
inline constexpr std::int64_t DT_PLTREL = 20;
inline constexpr std::int64_t DT_JMPREL = 23;
inline constexpr std::int64_t DT_RUNPATH = 29;
inline constexpr std::uint32_t SHT_NULL = 0;
inline constexpr std::uint32_t SHT_PROGBITS = 1;

View File

@ -3,6 +3,7 @@
#include <domain/Types.hpp>
#include <cstdint>
#include <string>
#include <vector>
namespace Elfpatcher {
@ -15,7 +16,8 @@ public:
const std::vector<std::uint8_t>& sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynamicSection,
std::uint64_t originalPltGotVaddr
std::uint64_t originalPltGotVaddr,
const std::string& runPath
) = 0;
};

View File

@ -7,6 +7,7 @@
#include <elfpatcher/general/ISectionHeaderTableBuilder.hpp>
#include <io/IByteWriter.hpp>
#include <memory>
#include <string>
namespace Elfpatcher::Linux {
@ -23,7 +24,8 @@ public:
const std::vector<std::uint8_t>& sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynamicSection,
std::uint64_t originalPltGotVaddr
std::uint64_t originalPltGotVaddr,
const std::string& runPath
) override;
private:

View File

@ -7,6 +7,7 @@
#include <elfpatcher/general/ISectionHeaderTableBuilder.hpp>
#include <io/IByteWriter.hpp>
#include <memory>
#include <string>
namespace Elfpatcher::Windows {
@ -23,7 +24,8 @@ public:
const std::vector<std::uint8_t>& sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynamicSection,
std::uint64_t originalPltGotVaddr
std::uint64_t originalPltGotVaddr,
const std::string& runPath
) override;
private:

View File

@ -2,6 +2,7 @@
#include <elfpatcher/general/ElfConstants.hpp>
#include <elfpatcher/general/ProgramHeaderLayoutRequest.hpp>
#include <elfpatcher/general/SectionHeaderTableRequest.hpp>
#include <string>
namespace Elfpatcher::Linux {
@ -27,7 +28,8 @@ std::vector<std::uint8_t> LinuxElfPatcher::Patch(
const std::vector<std::uint8_t>& sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynSection,
const std::uint64_t originalPltGotVaddr)
const std::uint64_t originalPltGotVaddr,
const std::string& runPath)
{
std::vector<std::uint8_t> buf = sourceElf;
@ -51,6 +53,10 @@ std::vector<std::uint8_t> LinuxElfPatcher::Patch(
const auto dynStrOff = static_cast<std::uint64_t>(buf.size());
for (std::uint8_t b : dynSection.DynStrData)
buf.push_back(b);
const std::uint64_t runPathStrOff = static_cast<std::uint64_t>(buf.size()) - dynStrOff;
for (char c : runPath)
buf.push_back(static_cast<std::uint8_t>(c));
buf.push_back(0);
alignBuf(buf, kDynStrAlignment);
const auto dynSymOff = static_cast<std::uint64_t>(buf.size());
@ -95,6 +101,7 @@ std::vector<std::uint8_t> LinuxElfPatcher::Patch(
_appendDynEntry(dynSegBuf, DT_PLTREL, static_cast<std::uint64_t>(DT_RELA));
_appendDynEntry(dynSegBuf, DT_PLTGOT, originalPltGotVaddr);
}
_appendDynEntry(dynSegBuf, DT_RUNPATH, runPathStrOff);
_appendDynEntry(dynSegBuf, DT_NULL, 0);
const auto dynSegOff = static_cast<std::uint64_t>(buf.size());

View File

@ -20,7 +20,8 @@ std::vector<std::uint8_t> WindowsElfPatcher::Patch(
const std::vector<std::uint8_t>& sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynamicSection,
std::uint64_t originalPltGotVaddr)
std::uint64_t originalPltGotVaddr,
const std::string& runPath)
{
throw Domain::RelinkerException("Not implemented");
}

View File

@ -15,6 +15,7 @@
#include <relinker/output/CallRegistryWriter.hpp>
#include <relinker/pipeline/RelinkerPipeline.hpp>
#include <codegen/IAmd64OnlyConverter.hpp>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
@ -26,6 +27,7 @@ int main(const int argc, char* argv[]) {
std::string inputPath;
std::string outputElfPath;
std::optional<std::string> registryPath;
std::string runPath = "$ORIGIN/libs";
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
@ -33,6 +35,12 @@ int main(const int argc, char* argv[]) {
skipSyscallCheck = true;
} else if (arg == "--to-intel") {
toIntel = true;
} else if (arg == "--rpath") {
if (i + 1 >= argc) {
std::cerr << "FAIL: --rpath requires a value\n";
return 1;
}
runPath = argv[++i];
} else if (inputPath.empty()) {
inputPath = arg;
} else if (outputElfPath.empty()) {
@ -43,10 +51,17 @@ int main(const int argc, char* argv[]) {
}
if (inputPath.empty() || outputElfPath.empty()) {
std::cerr << "Usage: relinker [--skip-syscall-check] [--to-intel] <input.elf> <output.elf> [output_registry.json]\n";
std::cerr << "Usage: relinker [--skip-syscall-check] [--to-intel] [--rpath <path>] <input.elf> <output.elf> [output_registry.json]\n";
return 1;
}
const std::filesystem::path outputElfFsPath(outputElfPath);
const std::filesystem::path outputDir = outputElfFsPath.parent_path() / (outputElfFsPath.stem().string() + "_out");
std::filesystem::create_directories(outputDir);
outputElfPath = (outputDir / outputElfFsPath.filename()).string();
if (registryPath.has_value())
registryPath = (outputDir / std::filesystem::path(*registryPath).filename()).string();
try {
Io::FileReader fileReader;
Io::FileWriter fileWriter;
@ -93,7 +108,7 @@ int main(const int argc, char* argv[]) {
std::make_shared<Elfpatcher::SectionHeaderTableBuilder>(byteWriter),
byteWriter
);
auto patchedElf = elfPatcher.Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr);
auto patchedElf = elfPatcher.Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, runPath);
fileWriter.Write(outputElfPath, patchedElf);
} catch (const Domain::RelinkerException& e) {