From 8a7c7c4e0df165952e989de469b3e09a4cf2721d Mon Sep 17 00:00:00 2001 From: boykopovar Date: Mon, 3 Aug 2026 20:27:55 +0300 Subject: [PATCH] feat(log): add call reg writer --- core/relinker/CMakeLists.txt | 6 +- core/relinker/src/CallRegistryWriter.cpp | 71 ++++++++ core/relinker/src/CallSiteResolver.cpp | 79 +++++++++ .../src/SysVDynamicSectionBuilder.cpp | 139 +++++++++++++++ core/relinker/src/main.cpp | 164 ++++++++++++++++++ 5 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 core/relinker/src/CallRegistryWriter.cpp create mode 100644 core/relinker/src/CallSiteResolver.cpp create mode 100644 core/relinker/src/SysVDynamicSectionBuilder.cpp create mode 100644 core/relinker/src/main.cpp diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 037ecf601..a4732b2c5 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -14,11 +14,15 @@ set(RELINKER_HEADERS ) set(RELINKER_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/src/ValidationPolicy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ValidationPolicy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ElfReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SdkRevisionProfile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SceDynlibParser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SyscallScanner.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/CallSiteResolver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SysVDynamicSectionBuilder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/CallRegistryWriter.cpp ) add_executable(relinker ${RELINKER_SOURCES} ${RELINKER_HEADERS}) diff --git a/core/relinker/src/CallRegistryWriter.cpp b/core/relinker/src/CallRegistryWriter.cpp new file mode 100644 index 000000000..3316ed07f --- /dev/null +++ b/core/relinker/src/CallRegistryWriter.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +namespace Relinker { + +static std::string _jsonString(const std::string& s) { + std::string out; + out.reserve(s.size() + 2); + out.push_back('"'); + for (char c : s) { + if (c == '"') out += "\\\""; + else if (c == '\\') out += "\\\\"; + else if (c == '\n') out += "\\n"; + else if (c == '\r') out += "\\r"; + else if (c == '\t') out += "\\t"; + else out.push_back(c); + } + out.push_back('"'); + return out; +} + +static std::string _hexOffset(Offset v) { + std::ostringstream oss; + oss << "\"0x" << std::hex << std::uppercase << v << "\""; + return oss.str(); +} + +class CallRegistryWriter : public ICallRegistryWriter { +public: + std::string WriteCallRegistry(const std::vector& Entries) override; +}; + +std::string CallRegistryWriter::WriteCallRegistry(const std::vector& Entries) { + std::ostringstream out; + out << "[\n"; + + for (std::size_t i = 0; i < Entries.size(); ++i) { + const auto& e = Entries[i]; + + out << " {\n"; + out << " \"nid\": " << _jsonString(e.Nid) << ",\n"; + out << " \"library\": " << _jsonString(e.Library) << ",\n"; + out << " \"relocationType\": " << _jsonString(e.RelocationTypeString) << ",\n"; + out << " \"relocationOffset\": " << _hexOffset(e.RelocationOffset)<< ",\n"; + out << " \"targetSection\": " << _jsonString(e.TargetSection) << ",\n"; + out << " \"targetOffset\": " << _hexOffset(e.TargetOffset) << ",\n"; + out << " \"callSites\": ["; + + for (std::size_t j = 0; j < e.CallSites.size(); ++j) { + if (j > 0) out << ", "; + out << _hexOffset(e.CallSites[j]); + } + + out << "],\n"; + out << " \"callSitesResolved\": " << (e.CallSitesResolved ? "true" : "false") << "\n"; + out << " }"; + + if (i + 1 < Entries.size()) out << ","; + out << "\n"; + } + + out << "]\n"; + return out.str(); +} + +std::shared_ptr MakeCallRegistryWriter() { + return std::make_shared(); +} + +} diff --git a/core/relinker/src/CallSiteResolver.cpp b/core/relinker/src/CallSiteResolver.cpp new file mode 100644 index 000000000..e7422d3f0 --- /dev/null +++ b/core/relinker/src/CallSiteResolver.cpp @@ -0,0 +1,79 @@ +#include +#include + +namespace Relinker { + +class CallSiteResolver : public ICallSiteResolver { +public: + std::vector ResolveCallSites( + const std::vector& TextSection, + Offset TextSectionVAddr, + Address TargetGotOrPltAddress, + Size TargetGotOrPltSize) override; +}; + +std::vector CallSiteResolver::ResolveCallSites( + const std::vector& TextSection, + Offset TextSectionVAddr, + Address TargetGotOrPltAddress, + Size TargetGotOrPltSize) +{ + std::vector sites; + + if (TextSection.size() < 6) + return sites; + + const std::size_t limit = TextSection.size() - 5; + + for (std::size_t i = 0; i <= limit; ++i) { + std::uint8_t b0 = TextSection[i]; + std::uint8_t b1 = TextSection[i + 1]; + + bool isRipRelative = false; + std::size_t dispOffset = 0; + std::size_t instrSize = 0; + + if (b0 == 0xFF && (b1 == 0x15 || b1 == 0x25)) { + isRipRelative = true; + dispOffset = i + 2; + instrSize = 6; + } else if (b0 == 0x48 && b1 == 0x8B && i + 6 < TextSection.size()) { + std::uint8_t modrm = TextSection[i + 2]; + if ((modrm & 0xC7) == 0x05) { + isRipRelative = true; + dispOffset = i + 3; + instrSize = 7; + } + } + + if (!isRipRelative) + continue; + + if (dispOffset + 3 >= TextSection.size()) + continue; + + std::int32_t disp = + static_cast( + static_cast(TextSection[dispOffset]) | + (static_cast(TextSection[dispOffset + 1]) << 8) | + (static_cast(TextSection[dispOffset + 2]) << 16) | + (static_cast(TextSection[dispOffset + 3]) << 24)); + + Address nextInstrVAddr = TextSectionVAddr + i + instrSize; + Address resolvedAddr = static_cast
(static_cast(nextInstrVAddr) + disp); + + if (resolvedAddr >= TargetGotOrPltAddress && + resolvedAddr < TargetGotOrPltAddress + TargetGotOrPltSize) + { + sites.push_back(TextSectionVAddr + i); + } + } + + return sites; +} + +std::shared_ptr MakeCallSiteResolver() { + return std::make_shared(); +} + +} diff --git a/core/relinker/src/SysVDynamicSectionBuilder.cpp b/core/relinker/src/SysVDynamicSectionBuilder.cpp new file mode 100644 index 000000000..07b79d035 --- /dev/null +++ b/core/relinker/src/SysVDynamicSectionBuilder.cpp @@ -0,0 +1,139 @@ +#include +#include + +namespace Relinker { + +static constexpr std::uint32_t STB_GLOBAL = 1; +static constexpr std::uint32_t STT_FUNC = 2; +static constexpr std::uint32_t STV_DEFAULT = 0; +static constexpr std::uint32_t R_X86_64_JUMP_SLOT = 7; +static constexpr std::uint32_t R_X86_64_GLOB_DAT = 6; + +static constexpr std::int64_t DT_NEEDED = 1; +static constexpr std::int64_t DT_SYMTAB = 6; +static constexpr std::int64_t DT_STRTAB = 5; +static constexpr std::int64_t DT_STRSZ = 10; +static constexpr std::int64_t DT_RELA = 7; +static constexpr std::int64_t DT_RELASZ = 8; +static constexpr std::int64_t DT_RELAENT = 9; +static constexpr std::int64_t DT_JMPREL = 23; +static constexpr std::int64_t DT_PLTRELSZ = 2; +static constexpr std::int64_t DT_PLTREL = 20; +static constexpr std::int64_t DT_SYMENT = 11; +static constexpr std::int64_t DT_NULL = 0; + +static void _appendU64(std::vector& buf, std::uint64_t v) { + for (int i = 0; i < 8; ++i) + buf.push_back(static_cast((v >> (i * 8)) & 0xFF)); +} + +static void _appendI64(std::vector& buf, std::int64_t v) { + _appendU64(buf, static_cast(v)); +} + +static void _appendDynEntry(std::vector& buf, std::int64_t tag, std::uint64_t val) { + _appendI64(buf, tag); + _appendU64(buf, val); +} + +static std::uint32_t _appendStr(std::vector& strtab, const std::string& s) { + auto offset = static_cast(strtab.size()); + for (char c : s) strtab.push_back(static_cast(c)); + strtab.push_back(0); + return offset; +} + +static void _appendElfSym( + std::vector& dynsym, + std::uint32_t nameOff, + std::uint8_t info, + std::uint8_t other, + std::uint16_t shndx, + std::uint64_t value, + std::uint64_t size) +{ + _appendU64(dynsym, nameOff); + dynsym.push_back(info); + dynsym.push_back(other); + dynsym.push_back(shndx & 0xFF); + dynsym.push_back((shndx >> 8) & 0xFF); + _appendU64(dynsym, value); + _appendU64(dynsym, size); +} + +static void _appendRela( + std::vector& rela, + std::uint64_t offset, + std::uint64_t info, + std::int64_t addend) +{ + _appendU64(rela, offset); + _appendU64(rela, info); + _appendI64(rela, addend); +} + +class SysVDynamicSectionBuilder : public ISysVDynamicSectionBuilder { +public: + SysVDynamicSection BuildDynamicSection( + const std::vector& NidReferences, + const std::vector& NeededLibraries) override; +}; + +SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection( + const std::vector& NidReferences, + const std::vector& NeededLibraries) +{ + SysVDynamicSection result; + + result.DynStrData.push_back(0); + + std::vector neededOffsets; + for (const auto& lib : NeededLibraries) + neededOffsets.push_back(_appendStr(result.DynStrData, lib)); + + _appendElfSym(result.DynSymData, 0, 0, 0, 0, 0, 0); + + std::uint32_t symIdx = 1; + for (const auto& ref : NidReferences) { + std::uint32_t nameOff = _appendStr(result.DynStrData, ref.Nid); + std::uint8_t info = static_cast((STB_GLOBAL << 4) | STT_FUNC); + _appendElfSym(result.DynSymData, nameOff, info, STV_DEFAULT, 0, 0, 0); + + std::uint32_t relType = ref.RelocationTypeValue; + if (relType == 0) relType = R_X86_64_JUMP_SLOT; + + std::uint64_t relaInfo = (static_cast(symIdx) << 32) | relType; + + if (relType == R_X86_64_JUMP_SLOT) + _appendRela(result.RelaPltData, ref.RelocationAddress, relaInfo, 0); + else + _appendRela(result.RelaData, ref.RelocationAddress, relaInfo, 0); + + ++symIdx; + } + + std::uint64_t strsz = result.DynStrData.size(); + + for (std::uint32_t off : neededOffsets) + _appendDynEntry(result.DynamicSegmentData, DT_NEEDED, off); + + _appendDynEntry(result.DynamicSegmentData, DT_STRTAB, 0); + _appendDynEntry(result.DynamicSegmentData, DT_STRSZ, strsz); + _appendDynEntry(result.DynamicSegmentData, DT_SYMTAB, 0); + _appendDynEntry(result.DynamicSegmentData, DT_SYMENT, 24); + _appendDynEntry(result.DynamicSegmentData, DT_RELA, 0); + _appendDynEntry(result.DynamicSegmentData, DT_RELASZ, result.RelaData.size()); + _appendDynEntry(result.DynamicSegmentData, DT_RELAENT, 24); + _appendDynEntry(result.DynamicSegmentData, DT_JMPREL, 0); + _appendDynEntry(result.DynamicSegmentData, DT_PLTRELSZ, result.RelaPltData.size()); + _appendDynEntry(result.DynamicSegmentData, DT_PLTREL, static_cast(DT_RELA)); + _appendDynEntry(result.DynamicSegmentData, DT_NULL, 0); + + return result; +} + +std::shared_ptr MakeSysVDynamicSectionBuilder() { + return std::make_shared(); +} + +} diff --git a/core/relinker/src/main.cpp b/core/relinker/src/main.cpp new file mode 100644 index 000000000..d7b960831 --- /dev/null +++ b/core/relinker/src/main.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ValidationPolicy.hpp" + +#include +#include +#include +#include +#include + +namespace Relinker { + +std::unique_ptr MakeSyscallScanner(); +std::shared_ptr MakeCallSiteResolver(); +std::shared_ptr MakeSysVDynamicSectionBuilder(); +std::shared_ptr MakeCallRegistryWriter(); + +} + +static constexpr std::uint32_t PT_LOAD = 1; +static constexpr std::uint32_t PT_DYNAMIC = 2; +static constexpr std::uint32_t PT_SCE_DYNLIBDATA = 0x61000000; +static constexpr std::uint32_t SHT_NULL = 0; + +static std::string _relocationTypeName(std::uint32_t type) { + switch (type) { + case 1: return "R_X86_64_64"; + case 6: return "R_X86_64_GLOB_DAT"; + case 7: return "R_X86_64_JUMP_SLOT"; + case 10: return "R_X86_64_32"; + default: { + std::ostringstream oss; + oss << "UNKNOWN(" << type << ")"; + return oss.str(); + } + } +} + +static void _writeFile(const std::string& path, const std::string& content) { + std::ofstream f(path); + if (!f) throw Relinker::RelinkerException("Cannot open output file: " + path); + f << content; +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + std::cerr << "Usage: relinker \n"; + return 1; + } + + const std::string inputPath = argv[1]; + const std::string registryPath = argv[2]; + + try { + auto elfReader = std::make_unique(inputPath); + + auto header = elfReader->ReadHeader(); + auto programHeaders = elfReader->ReadProgramHeaders(); + auto sectionHeaders = elfReader->ReadSectionHeaders(); + + std::vector sceDynlibData; + std::vector textSection; + Relinker::Address textVAddr = 0; + Relinker::Address gotVAddr = 0; + Relinker::Size gotSize = 0; + + for (const auto& ph : programHeaders) { + if (ph.Type == PT_SCE_DYNLIBDATA) + sceDynlibData = elfReader->ReadSegment(ph); + } + + for (const auto& sh : sectionHeaders) { + if (sh.Name == ".text") { + textSection = elfReader->ReadSection(sh); + textVAddr = sh.VirtualAddress; + } else if (sh.Name == ".got" || sh.Name == ".got.plt") { + gotVAddr = sh.VirtualAddress; + gotSize = sh.Size; + } + } + + if (sceDynlibData.empty()) + throw Relinker::RelinkerException("SCE_DYNLIBDATA segment not found"); + + auto sdkProfile = std::make_shared(sceDynlibData); + auto parser = std::make_shared(sdkProfile); + auto policy = std::make_shared(); + auto syscallScan = Relinker::MakeSyscallScanner(); + auto csResolver = Relinker::MakeCallSiteResolver(); + auto dynBuilder = Relinker::MakeSysVDynamicSectionBuilder(); + auto regWriter = Relinker::MakeCallRegistryWriter(); + + auto imports = parser->ParseLibraryImports(sceDynlibData); + auto relocations = parser->ParseRelocationTable(sceDynlibData); + auto nidRefs = parser->ExtractNidReferences(relocations, imports); + + for (const auto& imp : imports) + policy->RegisterLibraryImport(imp.LibraryName); + + for (const auto& ref : nidRefs) { + policy->ValidateRelocationTypeSupported(ref.RelocationTypeValue, ref.RelocationTableOffset); + policy->ValidateNidBelongsToLibrary(ref.Nid, ref.Library); + } + + if (!textSection.empty()) + syscallScan->ScanCodeSectionForSyscalls(textSection, textVAddr, textSection.size()); + + policy->ValidateSyscallAbsence(); + + std::vector neededLibraries; + for (const auto& imp : imports) + neededLibraries.push_back(imp.LibraryName); + + auto sysVSection = dynBuilder->BuildDynamicSection(nidRefs, neededLibraries); + + std::vector entries; + entries.reserve(nidRefs.size()); + + for (const auto& ref : nidRefs) { + std::vector callSites; + bool resolved = false; + + if (!textSection.empty() && gotSize > 0) { + callSites = csResolver->ResolveCallSites( + textSection, textVAddr, gotVAddr, gotSize); + resolved = !callSites.empty(); + } + + Relinker::CallRegistryEntry entry; + entry.Nid = ref.Nid; + entry.Library = ref.Library; + entry.RelocationTypeString = _relocationTypeName(ref.RelocationTypeValue); + entry.RelocationOffset = ref.RelocationTableOffset; + entry.TargetSection = ".got"; + entry.TargetOffset = ref.RelocationAddress; + entry.CallSites = std::move(callSites); + entry.CallSitesResolved = resolved; + entries.push_back(std::move(entry)); + } + + _writeFile(registryPath, regWriter->WriteCallRegistry(entries)); + + std::cout << "OK: " << entries.size() << " NID references processed\n"; + std::cout << "Registry: " << registryPath << "\n"; + + } catch (const Relinker::RelinkerException& e) { + std::cerr << "FAIL: " << e.what(); + if (e.FailureOffset != 0) + std::cerr << " (offset 0x" << std::hex << e.FailureOffset << ")"; + std::cerr << "\n"; + return 2; + } catch (const std::exception& e) { + std::cerr << "FAIL: " << e.what() << "\n"; + return 2; + } + + return 0; +}