mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-06-24 13:25:34 +00:00
Resolve crash stack traces to PDB symbol names
- Add PdbParser::BuildAddressIndex() to build a sorted RVA->name table from public and module symbols before closing the PDB file - Add PdbParser::FindNameByRVA() for O(log n) reverse lookup at crash time - Update crash handler to resolve Minecraft.Client.exe addresses to decorated function names (e.g. Minecraft.Client.exe!?tick@Minecraft+0x30) - Resolve the faulting address itself in the crash report header
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "CrashHandler.h"
|
||||
#include "LogUtil.h"
|
||||
#include "PdbParser.h"
|
||||
#include <Windows.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <cstdio>
|
||||
@@ -7,6 +8,7 @@
|
||||
#include <ctime>
|
||||
|
||||
static HMODULE s_runtimeModule = nullptr;
|
||||
static uintptr_t s_gameBase = 0;
|
||||
static volatile LONG s_handling = 0;
|
||||
|
||||
static const char* ExceptionCodeToString(DWORD code)
|
||||
@@ -102,10 +104,30 @@ static void WalkStack(CONTEXT* ctx)
|
||||
modName = slash ? slash + 1 : modPath;
|
||||
}
|
||||
|
||||
LogUtil::LogCrash(" [%2d] 0x%016llX %s+0x%llX", frame, rip, modName, rip - modBase);
|
||||
char symName[512] = {0};
|
||||
uint32_t symOff = 0;
|
||||
if (s_gameBase != 0 && modBase == static_cast<DWORD64>(s_gameBase))
|
||||
{
|
||||
uint32_t rva = static_cast<uint32_t>(rip - modBase);
|
||||
if (PdbParser::FindNameByRVA(rva, symName, sizeof(symName), &symOff))
|
||||
{
|
||||
LogUtil::LogCrash(" [%2d] 0x%016llX %s!%s+0x%X",
|
||||
frame, rip, modName, symName, symOff);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogUtil::LogCrash(" [%2d] 0x%016llX %s+0x%llX",
|
||||
frame, rip, modName, rip - modBase);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogUtil::LogCrash(" [%2d] 0x%016llX %s+0x%llX",
|
||||
frame, rip, modName, rip - modBase);
|
||||
}
|
||||
|
||||
frame++;
|
||||
|
||||
// Use RtlLookupFunctionEntry + RtlVirtualUnwind for x64 stack walking
|
||||
DWORD64 imageBase = 0;
|
||||
PRUNTIME_FUNCTION pFunc = RtlLookupFunctionEntry(rip, &imageBase, nullptr);
|
||||
if (!pFunc)
|
||||
@@ -162,14 +184,24 @@ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* ep)
|
||||
LogUtil::LogCrash("Fault: %s of address 0x%016llX", op, er->ExceptionInformation[1]);
|
||||
}
|
||||
|
||||
// Module containing the faulting address
|
||||
// Module containing the faulting address + PDB symbol resolution
|
||||
{
|
||||
DWORD64 faultAddr = reinterpret_cast<DWORD64>(er->ExceptionAddress);
|
||||
char modPath[MAX_PATH] = {0};
|
||||
DWORD64 modBase = 0;
|
||||
GetModuleForAddr(reinterpret_cast<DWORD64>(er->ExceptionAddress), modPath, sizeof(modPath), &modBase);
|
||||
GetModuleForAddr(faultAddr, modPath, sizeof(modPath), &modBase);
|
||||
if (modPath[0])
|
||||
LogUtil::LogCrash("Module: %s (base: 0x%016llX, offset: +0x%llX)",
|
||||
modPath, modBase, reinterpret_cast<DWORD64>(er->ExceptionAddress) - modBase);
|
||||
modPath, modBase, faultAddr - modBase);
|
||||
|
||||
char symName[512] = {0};
|
||||
uint32_t symOff = 0;
|
||||
if (s_gameBase != 0 && modBase == static_cast<DWORD64>(s_gameBase))
|
||||
{
|
||||
uint32_t rva = static_cast<uint32_t>(faultAddr - modBase);
|
||||
if (PdbParser::FindNameByRVA(rva, symName, sizeof(symName), &symOff))
|
||||
LogUtil::LogCrash("Symbol: %s+0x%X", symName, symOff);
|
||||
}
|
||||
}
|
||||
|
||||
LogUtil::LogCrash("");
|
||||
@@ -228,4 +260,10 @@ void Install(HMODULE runtimeModule)
|
||||
AddVectoredExceptionHandler(1, VectoredHandler);
|
||||
}
|
||||
|
||||
void SetGameBase(uintptr_t base)
|
||||
{
|
||||
s_gameBase = base;
|
||||
LogUtil::Log("[LegacyForge] Crash handler: game base set to 0x%016llX", (DWORD64)base);
|
||||
}
|
||||
|
||||
} // namespace CrashHandler
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace CrashHandler
|
||||
{
|
||||
// Installs the vectored exception handler. Safe to call from DllMain.
|
||||
void Install(HMODULE runtimeModule);
|
||||
|
||||
// Store the game exe's base address so we can compute RVAs for PDB lookups.
|
||||
void SetGameBase(uintptr_t base);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include "LogUtil.h"
|
||||
#include <Windows.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "PDB.h"
|
||||
#include "PDB_RawFile.h"
|
||||
@@ -14,6 +17,14 @@
|
||||
#include "PDB_ModuleInfoStream.h"
|
||||
#include "PDB_ModuleSymbolStream.h"
|
||||
|
||||
struct SymEntry
|
||||
{
|
||||
uint32_t rva;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<SymEntry> s_addrIndex;
|
||||
|
||||
struct MappedFile
|
||||
{
|
||||
HANDLE hFile = INVALID_HANDLE_VALUE;
|
||||
@@ -361,6 +372,121 @@ void DumpMatching(const char* substring)
|
||||
LogUtil::Log("[LegacyForge] PdbParser: found %d matching symbols", count);
|
||||
}
|
||||
|
||||
void BuildAddressIndex()
|
||||
{
|
||||
if (!s_open) return;
|
||||
|
||||
s_addrIndex.clear();
|
||||
|
||||
// Collect all public symbols (S_PUB32) -- these cover exported and
|
||||
// non-static functions/data with their decorated names.
|
||||
{
|
||||
const PDB::ArrayView<PDB::HashRecord> records = s_publicStream->GetRecords();
|
||||
s_addrIndex.reserve(records.GetLength());
|
||||
for (const PDB::HashRecord& hashRecord : records)
|
||||
{
|
||||
const PDB::CodeView::DBI::Record* record =
|
||||
s_publicStream->GetRecord(*s_symbolRecords, hashRecord);
|
||||
if (record->header.kind != PDB::CodeView::DBI::SymbolRecordKind::S_PUB32)
|
||||
continue;
|
||||
|
||||
uint32_t rva = s_sectionStream->ConvertSectionOffsetToRVA(
|
||||
record->data.S_PUB32.section, record->data.S_PUB32.offset);
|
||||
if (rva != 0)
|
||||
s_addrIndex.push_back({ rva, record->data.S_PUB32.name });
|
||||
}
|
||||
}
|
||||
|
||||
// Also pull in per-module procedure symbols (S_GPROC32/S_LPROC32) which
|
||||
// include internal/static functions not in the public stream.
|
||||
{
|
||||
const PDB::ArrayView<PDB::ModuleInfoStream::Module> modules = s_moduleStream->GetModules();
|
||||
for (const PDB::ModuleInfoStream::Module& mod : modules)
|
||||
{
|
||||
if (!mod.HasSymbolStream()) continue;
|
||||
const PDB::ModuleSymbolStream modSymStream = mod.CreateSymbolStream(*s_rawFile);
|
||||
modSymStream.ForEachSymbol([&](const PDB::CodeView::DBI::Record* record)
|
||||
{
|
||||
const char* name = nullptr;
|
||||
uint16_t section = 0;
|
||||
uint32_t offset = 0;
|
||||
|
||||
switch (record->header.kind)
|
||||
{
|
||||
case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32:
|
||||
name = record->data.S_LPROC32.name;
|
||||
section = record->data.S_LPROC32.section;
|
||||
offset = record->data.S_LPROC32.offset;
|
||||
break;
|
||||
case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32:
|
||||
name = record->data.S_GPROC32.name;
|
||||
section = record->data.S_GPROC32.section;
|
||||
offset = record->data.S_GPROC32.offset;
|
||||
break;
|
||||
case PDB::CodeView::DBI::SymbolRecordKind::S_LPROC32_ID:
|
||||
name = record->data.S_LPROC32_ID.name;
|
||||
section = record->data.S_LPROC32_ID.section;
|
||||
offset = record->data.S_LPROC32_ID.offset;
|
||||
break;
|
||||
case PDB::CodeView::DBI::SymbolRecordKind::S_GPROC32_ID:
|
||||
name = record->data.S_GPROC32_ID.name;
|
||||
section = record->data.S_GPROC32_ID.section;
|
||||
offset = record->data.S_GPROC32_ID.offset;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name) return;
|
||||
uint32_t rva = s_sectionStream->ConvertSectionOffsetToRVA(section, offset);
|
||||
if (rva != 0)
|
||||
s_addrIndex.push_back({ rva, name });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by RVA and deduplicate
|
||||
std::sort(s_addrIndex.begin(), s_addrIndex.end(),
|
||||
[](const SymEntry& a, const SymEntry& b) { return a.rva < b.rva; });
|
||||
|
||||
// Remove duplicates (same RVA), keeping the first entry
|
||||
auto last = std::unique(s_addrIndex.begin(), s_addrIndex.end(),
|
||||
[](const SymEntry& a, const SymEntry& b) { return a.rva == b.rva; });
|
||||
s_addrIndex.erase(last, s_addrIndex.end());
|
||||
|
||||
LogUtil::Log("[LegacyForge] PdbParser: built address index with %zu symbols", s_addrIndex.size());
|
||||
}
|
||||
|
||||
bool FindNameByRVA(uint32_t rva, char* outName, size_t nameSize, uint32_t* outOffset)
|
||||
{
|
||||
if (s_addrIndex.empty() || rva == 0)
|
||||
return false;
|
||||
|
||||
// Binary search for the largest RVA <= target
|
||||
SymEntry key = { rva, {} };
|
||||
auto it = std::upper_bound(s_addrIndex.begin(), s_addrIndex.end(), key,
|
||||
[](const SymEntry& a, const SymEntry& b) { return a.rva < b.rva; });
|
||||
|
||||
if (it == s_addrIndex.begin())
|
||||
return false;
|
||||
|
||||
--it;
|
||||
|
||||
// Sanity: don't report symbols more than 1MB away
|
||||
if (rva - it->rva > 0x100000)
|
||||
return false;
|
||||
|
||||
if (outName && nameSize > 0)
|
||||
{
|
||||
strncpy(outName, it->name.c_str(), nameSize - 1);
|
||||
outName[nameSize - 1] = '\0';
|
||||
}
|
||||
if (outOffset)
|
||||
*outOffset = rva - it->rva;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Close()
|
||||
{
|
||||
delete s_moduleStream; s_moduleStream = nullptr;
|
||||
@@ -372,6 +498,8 @@ void Close()
|
||||
delete s_rawFile; s_rawFile = nullptr;
|
||||
CloseMappedFile(s_mapped);
|
||||
s_open = false;
|
||||
// Note: s_addrIndex intentionally NOT cleared -- it survives Close()
|
||||
// so the crash handler can resolve addresses after PDB is released.
|
||||
}
|
||||
|
||||
} // namespace PdbParser
|
||||
|
||||
@@ -11,5 +11,14 @@ namespace PdbParser
|
||||
// Logs all symbols whose name contains the given substring (for debugging).
|
||||
void DumpMatching(const char* substring);
|
||||
|
||||
// Builds a sorted index of all symbols for reverse RVA->name lookup.
|
||||
// Must be called while PDB is open. The index survives Close().
|
||||
void BuildAddressIndex();
|
||||
|
||||
// Reverse lookup: given an RVA, find the nearest symbol at or before it.
|
||||
// Returns true if found. outName receives the symbol name, outOffset
|
||||
// the byte distance from the symbol's start address.
|
||||
bool FindNameByRVA(uint32_t rva, char* outName, size_t nameSize, uint32_t* outOffset);
|
||||
|
||||
void Close();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <string>
|
||||
#include "LogUtil.h"
|
||||
#include "CrashHandler.h"
|
||||
#include "PdbParser.h"
|
||||
#include "SymbolResolver.h"
|
||||
#include "HookManager.h"
|
||||
#include "DotNetHost.h"
|
||||
@@ -59,6 +60,11 @@ DWORD WINAPI InitThread(LPVOID lpParam)
|
||||
}
|
||||
LogUtil::Log("[LegacyForge] Hooks installed");
|
||||
|
||||
// Build the RVA->name index before releasing the PDB.
|
||||
// This index survives PdbParser::Close() and is used by the crash handler.
|
||||
PdbParser::BuildAddressIndex();
|
||||
CrashHandler::SetGameBase(reinterpret_cast<uintptr_t>(GetModuleHandleA(nullptr)));
|
||||
|
||||
symbols.Cleanup();
|
||||
|
||||
if (!DotNetHost::Initialize())
|
||||
|
||||
Reference in New Issue
Block a user