feat(log): add call reg writer
This commit is contained in:
parent
657ada06b3
commit
8a7c7c4e0d
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue