frob said:
If you want to generate all that information, it is possible and not much more work than writing a compiler in the first place. The PDB, or program database, has all the information the debugger needs. They're quite open about the data file format, it is straightforward if you need to read or write your own.
Ok, after digging into PDB a bit deeper, I don't think that would really work in my case, as it seems that the PDB needs a matching PE/COFF-executable (and not just a blob of instructions like I'm generation), as by https://llvm.org/docs/PDB/PdbStream.html:
The executable is a PE/COFF file, and part of a PE/COFF file is the presence of number of “directories”. For our purposes here, we are interested in the “debug directory”. The exact format of a debug directory is described by the IMAGE_DEBUG_DIRECTORY structure. For this particular case, the linker emits a debug directory of type IMAGE_DEBUG_TYPE_CODEVIEW
. The format of this record is defined in llvm/DebugInfo/CodeView/CVDebugRecord.h
, but it suffices to say here only that it includes the same Guid
and Age
fields. At runtime, a debugger or tool can scan the COFF executable image for the presence of a debug directory of the correct type and verify that the Guid and Age match.
On the contrary, I did find some API-functions for supplying symbols to the debugger ad runtime, via SymLoadModuleExW/SymAddSymbolW in DbgHelp.h:
const auto process = GetCurrentProcess();
static bool hasInitialized = false;
if (!hasInitialized)
{
if (!SymInitialize(process, nullptr, false))
Log::OutErrorFmt("Failed to call SymInitialize.");
hasInitialized = true;
}
//if (!SymLoadModuleExW(process, nullptr, L"Test.dll", nullptr, (DWORD64)(pMemory + 8), DWORD(codeSize), nullptr, SLMFLAG_VIRTUAL))
// Log::OutErrorFmt("Failed to call SymLoadModuleEx.");
uint32_t index = 0;
for (const auto [target, bytecode, info] : data.vBytecodeMapping)
{
const auto end = [&]() -> uint32_t
{
const auto next = index + 1;
if (data.vBytecodeMapping.IsValidIndex(next)) [[likely]]
return data.vBytecodeMapping.At(next).target - REFERENCE_SIZE;
else
return uint32_t(codeSize);
}();
const auto code = DWORD64(pMemory + target);
const auto size = end - target;
if (!SymLoadModuleExW(process, nullptr, nullptr, nullptr, code, size, nullptr, SLMFLAG_VIRTUAL))
Log::OutErrorFmt("Failed to call SymLoadModuleEx.");
if (!SymAddSymbolW(process, code, L"Test(void)", code, size, 0))
Log::OutErrorFmt("Failed to call SymAddSymbol.");
index++;
}
This seems to work in supplying a name to the function, but its critically lacking the ability to specify the line/source-file mapping which is being enumerated by SymGetLineFromXXX-functions as well as from the debugger itself. But SymAddSymbol seems to be the only way to “add” anything to the virtual module. Am I missing something, or do I really have to create a PE/COFF-formatted executable alongside a full PDB in order for this symbols to work?