# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from collections import namedtuple
from ctypes import *
from enum import *
from functools import reduce, partial
from .symgroup import SymbolGroup, IDebugSymbolGroup2
from .utils import *
class SymbolOptionFlags(IntFlag):
SYMOPT_CASE_INSENSITIVE = 0x00000001
SYMOPT_UNDNAME = 0x00000002
SYMOPT_DEFERRED_LOADS = 0x00000004
SYMOPT_NO_CPP = 0x00000008
SYMOPT_LOAD_LINES = 0x00000010
SYMOPT_OMAP_FIND_NEAREST = 0x00000020
SYMOPT_LOAD_ANYTHING = 0x00000040
SYMOPT_IGNORE_CVREC = 0x00000080
SYMOPT_NO_UNQUALIFIED_LOADS = 0x00000100
SYMOPT_FAIL_CRITICAL_ERRORS = 0x00000200
SYMOPT_EXACT_SYMBOLS = 0x00000400
SYMOPT_ALLOW_ABSOLUTE_SYMBOLS = 0x00000800
SYMOPT_IGNORE_NT_SYMPATH = 0x00001000
SYMOPT_INCLUDE_32BIT_MODULES = 0x00002000
SYMOPT_PUBLICS_ONLY = 0x00004000
SYMOPT_NO_PUBLICS = 0x00008000
SYMOPT_AUTO_PUBLICS = 0x00010000
SYMOPT_NO_IMAGE_SEARCH = 0x00020000
SYMOPT_SECURE = 0x00040000
SYMOPT_NO_PROMPTS = 0x00080000
SYMOPT_DEBUG = 0x80000000
class ScopeGroupFlags(IntFlag):
DEBUG_SCOPE_GROUP_ARGUMENTS = 0x00000001
DEBUG_SCOPE_GROUP_LOCALS = 0x00000002
DEBUG_SCOPE_GROUP_ALL = 0x00000003
DEBUG_SCOPE_GROUP_BY_DATAMODEL = 0x00000004
class DebugModuleNames(IntEnum):
DEBUG_MODNAME_IMAGE = 0x00000000
DEBUG_MODNAME_MODULE = 0x00000001
DEBUG_MODNAME_LOADED_IMAGE = 0x00000002
DEBUG_MODNAME_SYMBOL_FILE = 0x00000003
DEBUG_MODNAME_MAPPED_IMAGE = 0x00000004
class DebugModuleFlags(IntFlag):
DEBUG_MODULE_LOADED = 0x00000000
DEBUG_MODULE_UNLOADED = 0x00000001
DEBUG_MODULE_USER_MODE = 0x00000002
DEBUG_MODULE_EXE_MODULE = 0x00000004
DEBUG_MODULE_EXPLICIT = 0x00000008
DEBUG_MODULE_SECONDARY = 0x00000010
DEBUG_MODULE_SYNTHETIC = 0x00000020
DEBUG_MODULE_SYM_BAD_CHECKSUM = 0x00010000
class DEBUG_MODULE_PARAMETERS(Structure):
_fields_ = [
("Base", c_ulonglong),
("Size", c_ulong),
("TimeDateStamp", c_ulong),
("Checksum", c_ulong),
("Flags", c_ulong),
("SymbolType", c_ulong),
("ImageNameSize", c_ulong),
("ModuleNameSize", c_ulong),
("LoadedImageNameSize", c_ulong),
("SymbolFileNameSize", c_ulong),
("MappedImageNameSize", c_ulong),
("Reserved", c_ulonglong * 2),
]
PDEBUG_MODULE_PARAMETERS = POINTER(DEBUG_MODULE_PARAMETERS)
class DEBUG_MODULE_AND_ID(Structure):
_fields_ = [("ModuleBase", c_ulonglong), ("Id", c_ulonglong)]
PDEBUG_MODULE_AND_ID = POINTER(DEBUG_MODULE_AND_ID)
class DEBUG_SYMBOL_ENTRY(Structure):
_fields_ = [
("ModuleBase", c_ulonglong),
("Offset", c_ulonglong),
("Id", c_ulonglong),
("Arg64", c_ulonglong),
("Size", c_ulong),
("Flags", c_ulong),
("TypeId", c_ulong),
("NameSize", c_ulong),
("Token", c_ulong),
("Tag", c_ulong),
("Arg32", c_ulong),
("Reserved", c_ulong),
]
PDEBUG_SYMBOL_ENTRY = POINTER(DEBUG_SYMBOL_ENTRY)
# UUID for DebugSymbols5 interface.
DebugSymbols5IID = IID(
0xC65FA83E,
0x1E69,
0x475E,
IID_Data4_Type(0x8E, 0x0E, 0xB5, 0xD7, 0x9E, 0x9C, 0xC1, 0x7E),
)
class IDebugSymbols5(Structure):
pass
class IDebugSymbols5Vtbl(Structure):
wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugSymbols5))
ids_getsymboloptions = wrp(c_ulong_p)
ids_setsymboloptions = wrp(c_ulong)
ids_getmoduleparameters = wrp(
c_ulong, c_ulong64_p, c_ulong, PDEBUG_MODULE_PARAMETERS
)
ids_getmodulenamestring = wrp(
c_ulong, c_ulong, c_ulonglong, c_char_p, c_ulong, c_ulong_p
)
ids_getoffsetbyname = wrp(c_char_p, c_ulong64_p)
ids_getlinebyoffset = wrp(
c_ulonglong, c_ulong_p, c_char_p, c_ulong, c_ulong_p, c_ulong64_p
)
ids_getsymbolentriesbyname = wrp(
c_char_p, c_ulong, PDEBUG_MODULE_AND_ID, c_ulong, c_ulong_p
)
ids_getsymbolentrystring = wrp(
PDEBUG_MODULE_AND_ID, c_ulong, c_char_p, c_ulong, c_ulong_p
)
ids_getsymbolentryinformation = wrp(PDEBUG_MODULE_AND_ID, PDEBUG_SYMBOL_ENTRY)
ids_getcurrentscopeframeindex = wrp(c_ulong_p)
ids_getnearnamebyoffset = wrp(
c_ulonglong, c_long, c_char_p, c_ulong, c_ulong_p, c_ulong64_p
)
ids_setscopeframebyindex = wrp(c_ulong)
ids_getscopesymbolgroup2 = wrp(
c_ulong, POINTER(IDebugSymbolGroup2), POINTER(POINTER(IDebugSymbolGroup2))
)
ids_getnamebyinlinecontext = wrp(
c_ulonglong, c_ulong, c_char_p, c_ulong, c_ulong_p, c_ulong64_p
)
ids_getlinebyinlinecontext = wrp(
c_ulonglong, c_ulong, c_ulong_p, c_char_p, c_ulong, c_ulong_p, c_ulong64_p
)
_fields_ = [
("QueryInterface", c_void_p),
("AddRef", c_void_p),
("Release", c_void_p),
("GetSymbolOptions", ids_getsymboloptions),
("AddSymbolOptions", c_void_p),
("RemoveSymbolOptions", c_void_p),
("SetSymbolOptions", ids_setsymboloptions),
("GetNameByOffset", c_void_p),
("GetOffsetByName", ids_getoffsetbyname),
("GetNearNameByOffset", ids_getnearnamebyoffset),
("GetLineByOffset", ids_getlinebyoffset),
("GetOffsetByLine", c_void_p),
("GetNumberModules", c_void_p),
("GetModuleByIndex", c_void_p),
("GetModuleByModuleName", c_void_p),
("GetModuleByOffset", c_void_p),
("GetModuleNames", c_void_p),
("GetModuleParameters", ids_getmoduleparameters),
("GetSymbolModule", c_void_p),
("GetTypeName", c_void_p),
("GetTypeId", c_void_p),
("GetTypeSize", c_void_p),
("GetFieldOffset", c_void_p),
("GetSymbolTypeId", c_void_p),
("GetOffsetTypeId", c_void_p),
("ReadTypedDataVirtual", c_void_p),
("WriteTypedDataVirtual", c_void_p),
("OutputTypedDataVirtual", c_void_p),
("ReadTypedDataPhysical", c_void_p),
("WriteTypedDataPhysical", c_void_p),
("OutputTypedDataPhysical", c_void_p),
("GetScope", c_void_p),
("SetScope", c_void_p),
("ResetScope", c_void_p),
("GetScopeSymbolGroup", c_void_p),
("CreateSymbolGroup", c_void_p),
("StartSymbolMatch", c_void_p),
("GetNextSymbolMatch", c_void_p),
("EndSymbolMatch", c_void_p),
("Reload", c_void_p),
("GetSymbolPath", c_void_p),
("SetSymbolPath", c_void_p),
("AppendSymbolPath", c_void_p),
("GetImagePath", c_void_p),
("SetImagePath", c_void_p),
("AppendImagePath", c_void_p),
("GetSourcePath", c_void_p),
("GetSourcePathElement", c_void_p),
("SetSourcePath", c_void_p),
("AppendSourcePath", c_void_p),
("FindSourceFile", c_void_p),
("GetSourceFileLineOffsets", c_void_p),
("GetModuleVersionInformation", c_void_p),
("GetModuleNameString", ids_getmodulenamestring),
("GetConstantName", c_void_p),
("GetFieldName", c_void_p),
("GetTypeOptions", c_void_p),
("AddTypeOptions", c_void_p),
("RemoveTypeOptions", c_void_p),
("SetTypeOptions", c_void_p),
("GetNameByOffsetWide", c_void_p),
("GetOffsetByNameWide", c_void_p),
("GetNearNameByOffsetWide", c_void_p),
("GetLineByOffsetWide", c_void_p),
("GetOffsetByLineWide", c_void_p),
("GetModuleByModuleNameWide", c_void_p),
("GetSymbolModuleWide", c_void_p),
("GetTypeNameWide", c_void_p),
("GetTypeIdWide", c_void_p),
("GetFieldOffsetWide", c_void_p),
("GetSymbolTypeIdWide", c_void_p),
("GetScopeSymbolGroup2", ids_getscopesymbolgroup2),
("CreateSymbolGroup2", c_void_p),
("StartSymbolMatchWide", c_void_p),
("GetNextSymbolMatchWide", c_void_p),
("ReloadWide", c_void_p),
("GetSymbolPathWide", c_void_p),
("SetSymbolPathWide", c_void_p),
("AppendSymbolPathWide", c_void_p),
("GetImagePathWide", c_void_p),
("SetImagePathWide", c_void_p),
("AppendImagePathWide", c_void_p),
("GetSourcePathWide", c_void_p),
("GetSourcePathElementWide", c_void_p),
("SetSourcePathWide", c_void_p),
("AppendSourcePathWide", c_void_p),
("FindSourceFileWide", c_void_p),
("GetSourceFileLineOffsetsWide", c_void_p),
("GetModuleVersionInformationWide", c_void_p),
("GetModuleNameStringWide", c_void_p),
("GetConstantNameWide", c_void_p),
("GetFieldNameWide", c_void_p),
("IsManagedModule", c_void_p),
("GetModuleByModuleName2", c_void_p),
("GetModuleByModuleName2Wide", c_void_p),
("GetModuleByOffset2", c_void_p),
("AddSyntheticModule", c_void_p),
("AddSyntheticModuleWide", c_void_p),
("RemoveSyntheticModule", c_void_p),
("GetCurrentScopeFrameIndex", ids_getcurrentscopeframeindex),
("SetScopeFrameByIndex", ids_setscopeframebyindex),
("SetScopeFromJitDebugInfo", c_void_p),
("SetScopeFromStoredEvent", c_void_p),
("OutputSymbolByOffset", c_void_p),
("GetFunctionEntryByOffset", c_void_p),
("GetFieldTypeAndOffset", c_void_p),
("GetFieldTypeAndOffsetWide", c_void_p),
("AddSyntheticSymbol", c_void_p),
("AddSyntheticSymbolWide", c_void_p),
("RemoveSyntheticSymbol", c_void_p),
("GetSymbolEntriesByOffset", c_void_p),
("GetSymbolEntriesByName", ids_getsymbolentriesbyname),
("GetSymbolEntriesByNameWide", c_void_p),
("GetSymbolEntryByToken", c_void_p),
("GetSymbolEntryInformation", ids_getsymbolentryinformation),
("GetSymbolEntryString", ids_getsymbolentrystring),
("GetSymbolEntryStringWide", c_void_p),
("GetSymbolEntryOffsetRegions", c_void_p),
("GetSymbolEntryBySymbolEntry", c_void_p),
("GetSourceEntriesByOffset", c_void_p),
("GetSourceEntriesByLine", c_void_p),
("GetSourceEntriesByLineWide", c_void_p),
("GetSourceEntryString", c_void_p),
("GetSourceEntryStringWide", c_void_p),
("GetSourceEntryOffsetRegions", c_void_p),
("GetsourceEntryBySourceEntry", c_void_p),
("GetScopeEx", c_void_p),
("SetScopeEx", c_void_p),
("GetNameByInlineContext", ids_getnamebyinlinecontext),
("GetNameByInlineContextWide", c_void_p),
("GetLineByInlineContext", ids_getlinebyinlinecontext),
("GetLineByInlineContextWide", c_void_p),
("OutputSymbolByInlineContext", c_void_p),
("GetCurrentScopeFrameIndexEx", c_void_p),
("SetScopeFrameByIndexEx", c_void_p),
]
IDebugSymbols5._fields_ = [("lpVtbl", POINTER(IDebugSymbols5Vtbl))]
SymbolId = namedtuple("SymbolId", ["ModuleBase", "Id"])
SymbolEntry = namedtuple(
"SymbolEntry",
[
"ModuleBase",
"Offset",
"Id",
"Arg64",
"Size",
"Flags",
"TypeId",
"NameSize",
"Token",
"Tag",
"Arg32",
],
)
DebugModuleParams = namedtuple(
"DebugModuleParams",
[
"Base",
"Size",
"TimeDateStamp",
"Checksum",
"Flags",
"SymbolType",
"ImageNameSize",
"ModuleNameSize",
"LoadedImageNameSize",
"SymbolFileNameSize",
"MappedImageNameSize",
],
)
class SymTags(IntEnum):
Null = 0
Exe = 1
SymTagFunction = 5
def make_debug_module_params(cdata):
fieldvalues = map(lambda y: getattr(cdata, y), DebugModuleParams._fields)
return DebugModuleParams(*fieldvalues)
class Symbols(object):
def __init__(self, symbols):
self.ptr = symbols
self.symbols = symbols.contents
self.vt = self.symbols.lpVtbl.contents
# Keep some handy ulongs for passing into C methods.
self.ulong = c_ulong()
self.ulong64 = c_ulonglong()
def GetCurrentScopeFrameIndex(self):
res = self.vt.GetCurrentScopeFrameIndex(self.symbols, byref(self.ulong))
aborter(res, "GetCurrentScopeFrameIndex")
return self.ulong.value
def SetScopeFrameByIndex(self, idx):
res = self.vt.SetScopeFrameByIndex(self.symbols, idx)
aborter(res, "SetScopeFrameByIndex", ignore=[E_EINVAL])
return res != E_EINVAL
def GetOffsetByName(self, name):
res = self.vt.GetOffsetByName(
self.symbols, name.encode("ascii"), byref(self.ulong64)
)
aborter(res, "GetOffsetByName {}".format(name))
return self.ulong64.value
def GetNearNameByOffset(self, addr):
ptr = create_string_buffer(256)
pulong = c_ulong()
disp = c_ulonglong()
# Zero arg -> "delta" indicating how many symbols to skip
res = self.vt.GetNearNameByOffset(
self.symbols, addr, 0, ptr, 255, byref(pulong), byref(disp)
)
if res == E_NOINTERFACE:
return "{noname}"
aborter(res, "GetNearNameByOffset")
ptr[255] = "\0".encode("ascii")
return "{}+{}".format(string_at(ptr).decode("ascii"), disp.value)
def GetModuleByModuleName2(self, name):
# First zero arg -> module index to search from, second zero arg ->
# DEBUG_GETMOD_* flags, none of which we use.
res = self.vt.GetModuleByModuleName2(
self.symbols, name, 0, 0, None, byref(self.ulong64)
)
aborter(res, "GetModuleByModuleName2")
return self.ulong64.value
def GetScopeSymbolGroup2(self):
retptr = POINTER(IDebugSymbolGroup2)()
res = self.vt.GetScopeSymbolGroup2(
self.symbols, ScopeGroupFlags.DEBUG_SCOPE_GROUP_ALL, None, retptr
)
aborter(res, "GetScopeSymbolGroup2")
return SymbolGroup(retptr)
def GetSymbolEntryString(self, idx, module):
symid = DEBUG_MODULE_AND_ID()
symid.ModuleBase = module
symid.Id = idx
ptr = create_string_buffer(1024)
# Zero arg is the string index -- symbols can have multiple names, for now
# only support the first one.
res = self.vt.GetSymbolEntryString(
self.symbols, symid, 0, ptr, 1023, byref(self.ulong)
)
aborter(res, "GetSymbolEntryString")
return string_at(ptr).decode("ascii")
def GetSymbolEntryInformation(self, module, theid):
symid = DEBUG_MODULE_AND_ID()
symentry = DEBUG_SYMBOL_ENTRY()
symid.ModuleBase = module
symid.Id = theid
res = self.vt.GetSymbolEntryInformation(self.symbols, symid, symentry)
aborter(res, "GetSymbolEntryInformation")
# Fetch fields into SymbolEntry object
fields = map(lambda x: getattr(symentry, x), SymbolEntry._fields)
return SymbolEntry(*fields)
def GetSymbolEntriesByName(self, symstr):
# Initial query to find number of symbol entries
res = self.vt.GetSymbolEntriesByName(
self.symbols, symstr.encode("ascii"), 0, None, 0, byref(self.ulong)
)
aborter(res, "GetSymbolEntriesByName")
# Build a buffer and query for 'length' entries
length = self.ulong.value
symrecs = (DEBUG_MODULE_AND_ID * length)()
# Zero arg -> flags, of which there are none defined.
res = self.vt.GetSymbolEntriesByName(
self.symbols, symstr.encode("ascii"), 0, symrecs, length, byref(self.ulong)
)
aborter(res, "GetSymbolEntriesByName")
# Extract 'length' number of SymbolIds
length = self.ulong.value
def extract(x):
sym = symrecs[x]
return SymbolId(sym.ModuleBase, sym.Id)
return [extract(x) for x in range(length)]
def GetSymbolPath(self):
# Query for length of buffer to allocate
res = self.vt.GetSymbolPath(self.symbols, None, 0, byref(self.ulong))
aborter(res, "GetSymbolPath", ignore=[S_FALSE])
# Fetch 'length' length symbol path string
length = self.ulong.value
arr = create_string_buffer(length)
res = self.vt.GetSymbolPath(self.symbols, arr, length, byref(self.ulong))
aborter(res, "GetSymbolPath")
return string_at(arr).decode("ascii")
def GetSourcePath(self):
# Query for length of buffer to allocate
res = self.vt.GetSourcePath(self.symbols, None, 0, byref(self.ulong))
aborter(res, "GetSourcePath", ignore=[S_FALSE])
# Fetch a string of len 'length'
length = self.ulong.value
arr = create_string_buffer(length)
res = self.vt.GetSourcePath(self.symbols, arr, length, byref(self.ulong))
aborter(res, "GetSourcePath")
return string_at(arr).decode("ascii")
def SetSourcePath(self, string):
res = self.vt.SetSourcePath(self.symbols, string.encode("ascii"))
aborter(res, "SetSourcePath")
return
def GetModuleParameters(self, base):
self.ulong64.value = base
params = DEBUG_MODULE_PARAMETERS()
# Fetch one module params struct, starting at idx zero
res = self.vt.GetModuleParameters(
self.symbols, 1, byref(self.ulong64), 0, byref(params)
)
aborter(res, "GetModuleParameters")
return make_debug_module_params(params)
def GetSymbolOptions(self):
res = self.vt.GetSymbolOptions(self.symbols, byref(self.ulong))
aborter(res, "GetSymbolOptions")
return SymbolOptionFlags(self.ulong.value)
def SetSymbolOptions(self, opts):
assert isinstance(opts, SymbolOptionFlags)
res = self.vt.SetSymbolOptions(self.symbols, opts.value)
aborter(res, "SetSymbolOptions")
return
def GetLineByOffset(self, offs):
# Initial query for filename buffer size
res = self.vt.GetLineByOffset(
self.symbols, offs, None, None, 0, byref(self.ulong), None
)
if res == E_FAIL:
return None # Sometimes we just can't get line numbers, of course
aborter(res, "GetLineByOffset", ignore=[S_FALSE])
# Allocate filename buffer and query for line number too
filenamelen = self.ulong.value
text = create_string_buffer(filenamelen)
line = c_ulong()
res = self.vt.GetLineByOffset(
self.symbols, offs, byref(line), text, filenamelen, byref(self.ulong), None
)
aborter(res, "GetLineByOffset")
return string_at(text).decode("ascii"), line.value
def GetModuleNameString(self, whichname, base):
# Initial query for name string length
res = self.vt.GetModuleNameString(
self.symbols, whichname, DEBUG_ANY_ID, base, None, 0, byref(self.ulong)
)
aborter(res, "GetModuleNameString", ignore=[S_FALSE])
module_name_len = self.ulong.value
module_name = (c_char * module_name_len)()
res = self.vt.GetModuleNameString(
self.symbols,
whichname,
DEBUG_ANY_ID,
base,
module_name,
module_name_len,
None,
)
aborter(res, "GetModuleNameString")
return string_at(module_name).decode("ascii")
def GetNameByInlineContext(self, pc, ctx):
# None args -> ignore output name size and displacement
buf = create_string_buffer(256)
res = self.vt.GetNameByInlineContext(
self.symbols, pc, ctx, buf, 255, None, None
)
aborter(res, "GetNameByInlineContext")
return string_at(buf).decode("ascii")
def GetLineByInlineContext(self, pc, ctx):
# None args -> ignore output filename size and displacement
buf = create_string_buffer(256)
res = self.vt.GetLineByInlineContext(
self.symbols, pc, ctx, byref(self.ulong), buf, 255, None, None
)
aborter(res, "GetLineByInlineContext")
return string_at(buf).decode("ascii"), self.ulong.value
def get_all_symbols(self):
main_module_name = self.get_exefile_module_name()
idnumbers = self.GetSymbolEntriesByName("{}!*".format(main_module_name))
lst = []
for symid in idnumbers:
s = self.GetSymbolEntryString(symid.Id, symid.ModuleBase)
symentry = self.GetSymbolEntryInformation(symid.ModuleBase, symid.Id)
lst.append((s, symentry))
return lst
def get_all_functions(self):
syms = self.get_all_symbols()
return [x for x in syms if x[1].Tag == SymTags.SymTagFunction]
def get_all_modules(self):
params = DEBUG_MODULE_PARAMETERS()
idx = 0
res = 0
all_modules = []
while res != E_EINVAL:
res = self.vt.GetModuleParameters(self.symbols, 1, None, idx, byref(params))
aborter(res, "GetModuleParameters", ignore=[E_EINVAL])
all_modules.append(make_debug_module_params(params))
idx += 1
return all_modules
def get_exefile_module(self):
all_modules = self.get_all_modules()
reduce_func = (
lambda x, y: y if y.Flags & DebugModuleFlags.DEBUG_MODULE_EXE_MODULE else x
)
main_module = reduce(reduce_func, all_modules, None)
if main_module is None:
raise Exception("Couldn't find the exefile module")
return main_module
def get_module_name(self, base):
return self.GetModuleNameString(DebugModuleNames.DEBUG_MODNAME_MODULE, base)
def get_exefile_module_name(self):
return self.get_module_name(self.get_exefile_module().Base)