feat(log): add call reg writer

This commit is contained in:
boykopovar 2026-08-03 20:27:55 +03:00
parent 657ada06b3
commit 8a7c7c4e0d
5 changed files with 458 additions and 1 deletions

View File

@ -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})

View File

@ -0,0 +1,71 @@
#include <relinker/ICallRegistryWriter.hpp>
#include <memory>
#include <sstream>
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<CallRegistryEntry>& Entries) override;
};
std::string CallRegistryWriter::WriteCallRegistry(const std::vector<CallRegistryEntry>& 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<ICallRegistryWriter> MakeCallRegistryWriter() {
return std::make_shared<CallRegistryWriter>();
}
}

View File

@ -0,0 +1,79 @@
#include <relinker/ICallSiteResolver.hpp>
#include <memory>
namespace Relinker {
class CallSiteResolver : public ICallSiteResolver {
public:
std::vector<Offset> ResolveCallSites(
const std::vector<std::uint8_t>& TextSection,
Offset TextSectionVAddr,
Address TargetGotOrPltAddress,
Size TargetGotOrPltSize) override;
};
std::vector<Offset> CallSiteResolver::ResolveCallSites(
const std::vector<std::uint8_t>& TextSection,
Offset TextSectionVAddr,
Address TargetGotOrPltAddress,
Size TargetGotOrPltSize)
{
std::vector<Offset> 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<std::int32_t>(
static_cast<std::uint32_t>(TextSection[dispOffset]) |
(static_cast<std::uint32_t>(TextSection[dispOffset + 1]) << 8) |
(static_cast<std::uint32_t>(TextSection[dispOffset + 2]) << 16) |
(static_cast<std::uint32_t>(TextSection[dispOffset + 3]) << 24));
Address nextInstrVAddr = TextSectionVAddr + i + instrSize;
Address resolvedAddr = static_cast<Address>(static_cast<std::int64_t>(nextInstrVAddr) + disp);
if (resolvedAddr >= TargetGotOrPltAddress &&
resolvedAddr < TargetGotOrPltAddress + TargetGotOrPltSize)
{
sites.push_back(TextSectionVAddr + i);
}
}
return sites;
}
std::shared_ptr<ICallSiteResolver> MakeCallSiteResolver() {
return std::make_shared<CallSiteResolver>();
}
}

View File

@ -0,0 +1,139 @@
#include <relinker/ISysVDynamicSectionBuilder.hpp>
#include <memory>
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<std::uint8_t>& buf, std::uint64_t v) {
for (int i = 0; i < 8; ++i)
buf.push_back(static_cast<std::uint8_t>((v >> (i * 8)) & 0xFF));
}
static void _appendI64(std::vector<std::uint8_t>& buf, std::int64_t v) {
_appendU64(buf, static_cast<std::uint64_t>(v));
}
static void _appendDynEntry(std::vector<std::uint8_t>& buf, std::int64_t tag, std::uint64_t val) {
_appendI64(buf, tag);
_appendU64(buf, val);
}
static std::uint32_t _appendStr(std::vector<std::uint8_t>& strtab, const std::string& s) {
auto offset = static_cast<std::uint32_t>(strtab.size());
for (char c : s) strtab.push_back(static_cast<std::uint8_t>(c));
strtab.push_back(0);
return offset;
}
static void _appendElfSym(
std::vector<std::uint8_t>& 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<std::uint8_t>& 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<NidReference>& NidReferences,
const std::vector<std::string>& NeededLibraries) override;
};
SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection(
const std::vector<NidReference>& NidReferences,
const std::vector<std::string>& NeededLibraries)
{
SysVDynamicSection result;
result.DynStrData.push_back(0);
std::vector<std::uint32_t> 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<std::uint8_t>((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<std::uint64_t>(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<std::uint64_t>(DT_RELA));
_appendDynEntry(result.DynamicSegmentData, DT_NULL, 0);
return result;
}
std::shared_ptr<ISysVDynamicSectionBuilder> MakeSysVDynamicSectionBuilder() {
return std::make_shared<SysVDynamicSectionBuilder>();
}
}

164
core/relinker/src/main.cpp Normal file
View File

@ -0,0 +1,164 @@
#include <relinker/ElfReader.hpp>
#include <relinker/SceDynlibParser.hpp>
#include <relinker/SdkRevisionProfile.hpp>
#include <relinker/ICallSiteResolver.hpp>
#include <relinker/ISysVDynamicSectionBuilder.hpp>
#include <relinker/ICallRegistryWriter.hpp>
#include <relinker/ISyscallScanner.hpp>
#include <relinker/IValidationPolicy.hpp>
#include "ValidationPolicy.hpp"
#include <iostream>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
namespace Relinker {
std::unique_ptr<ISyscallScanner> MakeSyscallScanner();
std::shared_ptr<ICallSiteResolver> MakeCallSiteResolver();
std::shared_ptr<ISysVDynamicSectionBuilder> MakeSysVDynamicSectionBuilder();
std::shared_ptr<ICallRegistryWriter> 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 <input.elf> <output_registry.json>\n";
return 1;
}
const std::string inputPath = argv[1];
const std::string registryPath = argv[2];
try {
auto elfReader = std::make_unique<Relinker::ElfReader>(inputPath);
auto header = elfReader->ReadHeader();
auto programHeaders = elfReader->ReadProgramHeaders();
auto sectionHeaders = elfReader->ReadSectionHeaders();
std::vector<std::uint8_t> sceDynlibData;
std::vector<std::uint8_t> 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<Relinker::SdkRevisionProfile>(sceDynlibData);
auto parser = std::make_shared<Relinker::SceDynlibParser>(sdkProfile);
auto policy = std::make_shared<Relinker::ValidationPolicy>();
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<std::string> neededLibraries;
for (const auto& imp : imports)
neededLibraries.push_back(imp.LibraryName);
auto sysVSection = dynBuilder->BuildDynamicSection(nidRefs, neededLibraries);
std::vector<Relinker::CallRegistryEntry> entries;
entries.reserve(nidRefs.size());
for (const auto& ref : nidRefs) {
std::vector<Relinker::Offset> 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;
}