From 0187b3943c8ac5c58c732bbfe4e45475bb8354f4 Mon Sep 17 00:00:00 2001 From: Guus Waals <_@guusw.nl> Date: Fri, 30 May 2025 23:06:14 +0800 Subject: [PATCH] Debug data generator --- .gitmodules | 3 + third_party/hooker | 1 + tooling/CMakeLists.txt | 5 +- tooling/generate_dbg_sec.cpp | 217 +++++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+), 1 deletion(-) create mode 160000 third_party/hooker create mode 100644 tooling/generate_dbg_sec.cpp diff --git a/.gitmodules b/.gitmodules index 814a8396..c29ac053 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "tooling2/third_party/tree-sitter-cpp"] path = tooling/third_party/tree-sitter-cpp url = https://github.com/guusw/tree-sitter-cpp.git +[submodule "third_party/hooker"] + path = third_party/hooker + url = https://github.com/rokups/hooker.git diff --git a/third_party/hooker b/third_party/hooker new file mode 160000 index 00000000..a2877b8f --- /dev/null +++ b/third_party/hooker @@ -0,0 +1 @@ +Subproject commit a2877b8ff301ce3fb9b671965f6e922a5c526373 diff --git a/tooling/CMakeLists.txt b/tooling/CMakeLists.txt index 227b0dee..381bbcc4 100644 --- a/tooling/CMakeLists.txt +++ b/tooling/CMakeLists.txt @@ -17,4 +17,7 @@ target_include_directories(CLI11 INTERFACE third_party/CLI11) add_executable(r3_gh_tool tool.cpp) target_link_libraries(r3_gh_tool PRIVATE spdlog::spdlog tree-sitter tree-sitter-cpp sqlite3 CLI11) -target_compile_features(r3_gh_tool PRIVATE cxx_std_23) \ No newline at end of file +target_compile_features(r3_gh_tool PRIVATE cxx_std_23) + +add_executable(generate_dbg_sec generate_dbg_sec.cpp) +target_compile_features(generate_dbg_sec PRIVATE cxx_std_23) diff --git a/tooling/generate_dbg_sec.cpp b/tooling/generate_dbg_sec.cpp new file mode 100644 index 00000000..c323fd79 --- /dev/null +++ b/tooling/generate_dbg_sec.cpp @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// RSDS debug format +typedef struct { + DWORD signature; // 'RSDS' + BYTE guid[16]; // GUID + DWORD age; // Age + // Followed by null-terminated PDB path +} RSDS_DEBUG_FORMAT; + +class PEModifier { +private: + std::vector fileData; + IMAGE_DOS_HEADER *dosHeader; + IMAGE_NT_HEADERS *ntHeaders; + IMAGE_SECTION_HEADER *sectionHeaders; + +public: + bool loadPE(const char *filename) { + std::ifstream file(filename, std::ios::binary); + if (!file) + return false; + + file.seekg(0, std::ios::end); + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + fileData.resize(fileSize); + file.read((char *)fileData.data(), fileSize); + file.close(); + + // Parse PE headers + dosHeader = (IMAGE_DOS_HEADER *)fileData.data(); + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return false; + + ntHeaders = (IMAGE_NT_HEADERS *)(fileData.data() + dosHeader->e_lfanew); + if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) + return false; + + sectionHeaders = + (IMAGE_SECTION_HEADER *)((BYTE *)&ntHeaders->OptionalHeader + + ntHeaders->FileHeader.SizeOfOptionalHeader); + + return true; + } + + IMAGE_SECTION_HEADER *findSection(const char *name) { + for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) { + if (memcmp(sectionHeaders[i].Name, name, strlen(name)) == 0) { + return §ionHeaders[i]; + } + } + return nullptr; + } + + bool addDebugDirectory(const std::string &pdbPath) { + IMAGE_SECTION_HEADER *rsrcSection = findSection(".rsrc"); + if (!rsrcSection) { + printf("Error: .rsrc section not found!\n"); + return false; + } + + // Calculate debug data size + size_t pdb_path_len = pdbPath.size() + 1; + size_t debug_data_size = sizeof(RSDS_DEBUG_FORMAT) + pdb_path_len; + + // Find end of rsrc section (aligned) + DWORD sectionAlignment = ntHeaders->OptionalHeader.SectionAlignment; + DWORD fileAlignment = ntHeaders->OptionalHeader.FileAlignment; + + // Calculate where to place debug data in the rsrc section + DWORD currentSectionEnd = + rsrcSection->PointerToRawData + rsrcSection->SizeOfRawData; + DWORD availableSpace = + rsrcSection->Misc.VirtualSize - rsrcSection->SizeOfRawData; + + if (debug_data_size > availableSpace) { + printf("Error: Not enough space in .rsrc section! Need %zu bytes, have " + "%lu\n", + debug_data_size, availableSpace); + return false; + } + + // Calculate RVA for debug data + DWORD debugDataRVA = + rsrcSection->VirtualAddress + rsrcSection->SizeOfRawData; + DWORD debugDataFileOffset = currentSectionEnd; + + // Prepare debug data + std::vector debugData(debug_data_size); + + RSDS_DEBUG_FORMAT rsds = {0}; + rsds.signature = 0x53445352; // 'RSDS' + + // Generate or use existing GUID + BYTE guid_bytes[] = {0x7E, 0x71, 0xA5, 0x7F, 0x3A, 0x25, 0xB5, 0xB9, + 0x4C, 0x4C, 0x44, 0x20, 0x50, 0x44, 0x42, 0x2E}; + memcpy(rsds.guid, guid_bytes, 16); + rsds.age = 0x00000001; + + // Copy RSDS header + memcpy(debugData.data(), &rsds, sizeof(RSDS_DEBUG_FORMAT)); + + // Copy PDB path + memcpy(debugData.data() + sizeof(RSDS_DEBUG_FORMAT), pdbPath.c_str(), + pdb_path_len); + + // Extend file data if needed + if (debugDataFileOffset + debug_data_size > fileData.size()) { + fileData.resize(debugDataFileOffset + debug_data_size); + } + + // Write debug data to rsrc section + memcpy(fileData.data() + debugDataFileOffset, debugData.data(), + debug_data_size); + + // Update rsrc section size + rsrcSection->SizeOfRawData += debug_data_size; + + // Create/update debug directory entry + IMAGE_DEBUG_DIRECTORY debugDir = {0}; + debugDir.Characteristics = 0x00000000; + debugDir.TimeDateStamp = ntHeaders->FileHeader.TimeDateStamp; + debugDir.MajorVersion = 0x0000; + debugDir.MinorVersion = 0x0002; + debugDir.Type = IMAGE_DEBUG_TYPE_CODEVIEW; + debugDir.SizeOfData = debug_data_size; + debugDir.AddressOfRawData = debugDataRVA; + debugDir.PointerToRawData = debugDataFileOffset; + + // Update debug directory in optional header + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG] + .VirtualAddress = debugDataRVA; + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size = + sizeof(IMAGE_DEBUG_DIRECTORY); + + // Actually, we need to place the debug directory entry somewhere too + // Let's put it right before the debug data + DWORD debugDirRVA = debugDataRVA - sizeof(IMAGE_DEBUG_DIRECTORY); + DWORD debugDirFileOffset = + debugDataFileOffset - sizeof(IMAGE_DEBUG_DIRECTORY); + + // Adjust our calculations + rsrcSection->SizeOfRawData += sizeof(IMAGE_DEBUG_DIRECTORY); + + // Write debug directory entry + memcpy(fileData.data() + debugDirFileOffset, &debugDir, + sizeof(IMAGE_DEBUG_DIRECTORY)); + + // Update optional header to point to debug directory + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG] + .VirtualAddress = debugDirRVA; + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size = + sizeof(IMAGE_DEBUG_DIRECTORY); + + printf("Debug directory added at RVA: 0x%08X\n", debugDirRVA); + printf("Debug data added at RVA: 0x%08X\n", debugDataRVA); + + return true; + } + + bool savePE(const char *filename) { + std::string newFilename(filename); + // Replace .exe with .debug.exe + newFilename.replace(newFilename.find_last_of(".") + 1, 3, "debug.exe"); + std::ofstream file(newFilename.c_str(), std::ios::binary); + if (!file) + return false; + + file.write((char *)fileData.data(), fileData.size()); + file.close(); + return true; + } +}; + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + const char *exePath = argv[1]; + // Pick the filename only, and replace the extension with .pdb + std::filesystem::path pdbPath(exePath); + pdbPath = pdbPath.stem().string(); + pdbPath += ".pdb"; + + PEModifier pe; + + if (!pe.loadPE(exePath)) { + printf("Error: Failed to load PE file: %s\n", exePath); + return 1; + } + + if (!pe.addDebugDirectory(pdbPath.string())) { + printf("Error: Failed to add debug directory\n"); + return 1; + } + + // Save modified PE + std::string outputPath = std::string(exePath) + ".debug"; + if (!pe.savePE(outputPath.c_str())) { + printf("Error: Failed to save modified PE file\n"); + return 1; + } + + printf("Successfully added debug directory to: %s\n", outputPath.c_str()); + return 0; +}