# 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)