Source code for windows.debug.symbols

import os.path
import ctypes
import copy
import itertools
from collections import namedtuple

import windows
import windows.generated_def as gdef
from windows import winproxy
from windows.pycompat import basestring

DEFAULT_DBG_OPTION = gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME


[docs]def set_dbghelp_path(path): """Set the path of the ``dbghelp.dll`` file to use. It allow to configure a different version of the DLL handling PDB downloading. If ``path`` is a directory, the final ``dbghelp.dll`` will be computed as ``path\<current_process_bitness>\dbghelp.dll``. This allow to use the same script transparently in both 32b & 64b python interpreters. """ loaded_modules = [m.name.lower() for m in windows.current_process.peb.modules] if os.path.isdir(path): path = os.path.join(path, str(windows.current_process.bitness), "dbghelp.dll") if "dbghelp.dll" in loaded_modules: raise ValueError("setup_dbghelp_path should be called before any dbghelp function") # Change the DLL used by DbgHelpProxy winproxy.DbgHelpProxy.APIDLL = path return
# Load symbol config from ENV if present try: env_dbghelp_path = os.environ["PFW_DBGHELP_PATH"] # Setup the dbghelp path used by PFW set_dbghelp_path(env_dbghelp_path) except KeyError as e: pass class SymbolInfoBase(object): """Represent a Symbol. This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_ with the handling on displacement embeded into it. """ # Init on ctypes struct is not always called # resolver & displacement should be set manually CHAR_TYPE = None def __init__(self, *args, **kwargs): self.resolver = kwargs.get("resolver", None) #: POUET POUET self.displacement = kwargs.get("displacement", 0) #: POUET POUET def as_type(self): # assert self.Address == 0 ? return SymbolType(self.Index, self.ModBase, self.resolver) @property def name(self): """The name of the symbol""" if not self.NameLen: return None size = self.NameLen addr = ctypes.addressof(self) + type(self).Name.offset return (self.CHAR_TYPE * size).from_address(addr)[:] @property def fullname(self): """The fullname of the symbol in the windbg format ``mod!sym+displacement``""" return str(self) @property def addr(self): """The address of the symbol""" return self.Address + self.displacement @property def start(self): """The address of the start of the symbol If the symbol include a displacement, it is not taken into account """ return self.Address @property # Fixed ? def module(self): """The module containing the symbol :type: :class:`SymbolModule` """ return self.resolver.get_module(self.ModBase) @property def tag(self): """The Tag of the module :type: :class:`~windows.generated_def.winstructs.SymTagEnum` """ return gdef.SymTagEnum.mapper[self.Tag] def __int__(self): """An alias for ``addr``""" return self.addr def __str__(self): """The fullname of the symbol in the windbg format ``mod!sym+displacement``""" if self.displacement: return "{self.module.name}!{self.name}+{self.displacement:#x}".format(self=self) return "{self.module.name}!{self.name}".format(self=self) def __repr__(self): if self.displacement: return '<{0} name="{1}" start={2:#x} displacement={3:#x} tag={4}>'.format(type(self).__name__, self.name, self.start, self.displacement, self.tag.name) return '<{0} name="{1}" start={2:#x} tag={3}>'.format(type(self).__name__, self.name, self.start, self.tag.name)
[docs]class SymbolInfoA(gdef.SYMBOL_INFO, SymbolInfoBase): """Represent a Symbol. This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_ with the handling on displacement embeded into it.s Exemple: >>> sh = windows.debug.symbols.VirtualSymbolHandler() >>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll") >>> sym1 = sh["kernelbase!CreateFileW"] >>> sym2 = sh[int(sym1) + 3] >>> sym2 <SymbolInfoA name="CreateFileW" start=0x100f20b0 displacement=0x3 tag=SymTagPublicSymbol> >>> hex(sym2.start) '0x100f20b0L' >>> hex(sym2.addr) '0x100f20b3L' >>> hex(sym2.displacement) '0x3L' >>> str(sym2) 'kernelbase!CreateFileW+0x3' """ CHAR_TYPE = gdef.CHAR
class SymbolInfoW(gdef.SYMBOL_INFOW, SymbolInfoBase): CHAR_TYPE = gdef.WCHAR # We use the A Api in our code (for now) SymbolInfo = SymbolInfoA class SymbolType(object): def __init__(self, typeid, modbase, resolver): # Inheritance ? self.resolver = resolver self._typeid = typeid # Kind of a handle. Different of typeid property. self.modbase = modbase def _get_type_info(self, typeinfo, ires=None): res = ires if res is None: res = TST_TYPE_RES_TYPE.get(typeinfo, gdef.DWORD)() windows.winproxy.SymGetTypeInfo(self.resolver.handle, self.modbase, self._typeid, typeinfo, ctypes.byref(res)) if ires is not None: return ires newres = res.value if isinstance(res, gdef.LPWSTR): windows.winproxy.LocalFree(res) return newres @property def name(self): return self._get_type_info(gdef.TI_GET_SYMNAME) @property def size(self): return self._get_type_info(gdef.TI_GET_LENGTH) @property def tag(self): return self._get_type_info(gdef.TI_GET_SYMTAG) # Diff type/typeid ? @property def type(self): return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPE)) @property def typeid(self): return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPEID)) @property def basetype(self): return gdef.BasicType.mapper[self._get_type_info(gdef.TI_GET_BASETYPE)] @property def parent(self): return self.new_typeid(self._get_type_info(gdef.TI_GET_CLASSPARENTID)) @property def datakind(self): return gdef.DataKind.mapper[self._get_type_info(gdef.TI_GET_DATAKIND)] @property def udtkind(self): return gdef.UdtKind.mapper[self._get_type_info(gdef.TI_GET_UDTKIND)] @property def offset(self): return self._get_type_info(gdef.TI_GET_OFFSET) @property def nb_children(self): return self._get_type_info(gdef.TI_GET_CHILDRENCOUNT) @property def value(self): return self._get_type_info(gdef.TI_GET_VALUE) @property def children(self): count = self.nb_children class res_struct(ctypes.Structure): _fields_ = [("Count", gdef.ULONG), ("Start", gdef.ULONG), ("Types", (gdef.ULONG * count))] x = res_struct() x.Count = count x.Start = 0 self._get_type_info(gdef.TI_FINDCHILDREN, x) return [self.new_typeid(ch) for ch in x.Types] # Constructor @classmethod def from_symbol_info(cls, syminfo, resolver): return cls(syminfo.TypeIndex, syminfo.ModBase, resolver) # Constructor def new_typeid(self, newtypeid): return type(self)(newtypeid, self.modbase, self.resolver) def __repr__(self): if self.tag == gdef.SymTagBaseType: return '<{0} <basetype> {1!r}>'.format(type(self).__name__, self.basetype) elif self.tag == gdef.SymTagPointerType: target_type = self.type.name return '<{0} PTR TO "{1}" tag={2}>'.format(type(self).__name__, target_type, self.tag) return '<{0} name="{1}" tag={2}>'.format(type(self).__name__, self.name, self.tag)
[docs]class SymbolModule(gdef.IMAGEHLP_MODULE64): """Represent a loaded symbol module (see `MSDN IMAGEHLP_MODULE64 <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-imagehlp_module64>`_) .. note:: This represent a module in the ``symbol space`` for symbol resolution. This can be completly virtual (particularly in the case of :class:`VirtualSymbolHandler` """ # Init on ctypes struct is not always called # resolver should be set manually def __init__(self, resolver): self.resolver = resolver @property def addr(self): """The load address of the module""" return self.BaseOfImage @property def name(self): """The name of the module""" return self.ModuleName @property def path(self): """The full path and file name of the file from which symbols were loaded.""" return self.LoadedImageName @property def type(self): """The type of module (:class:`~windows.generated_def.winstructs.SYM_TYPE`), which can be one of: =========== ========================= SymCoff COFF symbols. SymCv CodeView symbols. SymDeferred Symbol loading deferred. SymDia DIA symbols. SymExport Symbols generated from a DLL export table. SymNone No symbols are loaded. SymPdb PDB symbols. SymSym .sym file. SymVirtual The virtual module created by SymLoadModuleEx with SLMFLAG_VIRTUAL. =========== ========================= """ return self.SymType @property def pdb(self): """The local path of the loaded PDB if present Exemple: >>> sh = windows.debug.symbols.VirtualSymbolHandler() >>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll") >>> mod.pdb 'd:\\symbols\\wkernelbase.pdb\\017FA9C5278235B7E6BFBA74A9A5AAD91\\wkernelbase.pdb' """ LoadedPdbName = self.LoadedPdbName if not LoadedPdbName: return None return LoadedPdbName def __repr__(self): pdb_basename = self.LoadedPdbName.split(b"\\")[-1] return '<{0} name="{1}" type={2} pdb="{3}" addr={4:#x}>'.format(type(self).__name__, self.name, self.type.value.name, pdb_basename, self.addr)
# https://docs.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization class SymbolHandler(object): """Base class of symbol handler""" def __init__(self, handle, search_path=None, invade_process=False): # https://docs.microsoft.com/en-us/windows/desktop/api/dbghelp/nf-dbghelp-syminitialize # This value should be unique and nonzero, but need not be a process handle. # be sure to use the correct handle. self.handle = handle #: The handle of the symbol handler if not engine.options_already_setup: engine.set_options(DEFAULT_DBG_OPTION) winproxy.SymInitialize(handle, search_path, invade_process) def load_module(self, file_handle=None, path=None, name=None, addr=0, size=0, data=None, flags=0): """Load a module at a given ``addr``. The module to load can be pass via a ``file_handle`` or the direct ``path`` of the file to load. :return: :class:`SymbolModule` -- The loaded module .. note:: The logic of ``SymLoadModuleEx`` seems somewhat strange about the naming of the loaded module. A custom module ``name`` is only taken into account if the file is passed via a File handle. To make it more intuitive, if this function is call with a ``path`` and ``name`` and no ``file_handle``, it will open the path and directly call ``SymLoadModuleEx`` with a file handle and a name. """ # Is that a bug in SymLoadModuleEx ? # To get a custom name for a module it use "path" # So we need to use file_handle and set a custom path # ! BUT it means we cannot get a custom name for a module where the path is not explicit and need to be searched if name is not None and file_handle is None and os.path.exists(path): try: f = open(path) file_handle = windows.utils.get_handle_from_file(f) path = name except Exception as e: pass # Expect a-string path = windows.pycompat.raw_encode(path) try: load_addr = winproxy.SymLoadModuleEx(self.handle, file_handle, path, name, addr, size, data, flags) except WindowsError as e: # if e.winerror == 0: # Already loaded ? # What if someone try to load another PE at the same BaseOfDll ? # return BaseOfDll raise return self.get_module(load_addr) def load_file(self, path, name=None, addr=0, size=0, data=None, flags=0): """Load the module ``path`` at ``addr`` :return: :class:`SymbolModule` -- The loaded module """ return self.load_module(path=path, name=name, addr=addr, size=size, data=data, flags=flags) def unload(self, addr): """Unload the module at ``addr``""" return winproxy.SymUnloadModule64(self.handle, addr) @staticmethod @ctypes.WINFUNCTYPE(gdef.BOOL, gdef.PCSTR, gdef.DWORD64, ctypes.py_object) def modules_aggregator(modname, modaddr, ctx): ctx.append(modaddr) return True @property def modules(self): """The list of loaded modules :return: [:class:`SymbolModule`] -- A list of modules """ res = [] windows.winproxy.SymEnumerateModules64(self.handle, self.modules_aggregator, res) return [self.get_module(addr) for addr in res] def get_module(self, base): modinfo = SymbolModule(self) modinfo.SizeOfStruct = ctypes.sizeof(modinfo) winproxy.SymGetModuleInfo64(self.handle, base, modinfo) return modinfo def symbol_and_displacement_from_address(self, addr): displacement = gdef.DWORD64() max_len_size = 0x1000 full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1) buff = windows.utils.BUFFER(SymbolInfo)(size=full_size) sym = buff[0] sym.SizeOfStruct = ctypes.sizeof(SymbolInfo) sym.MaxNameLen = max_len_size winproxy.SymFromAddr(self.handle, addr, displacement, buff) # SymFromAddrW ? sym.resolver = self sym.displacement = displacement.value return sym def symbol_from_name(self, name): max_len_size = 0x1000 full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1) buff = windows.utils.BUFFER(SymbolInfo)(size=full_size) sym = buff[0] sym.SizeOfStruct = ctypes.sizeof(SymbolInfo) sym.MaxNameLen = max_len_size # Expect a-string name = windows.pycompat.raw_encode(name) windows.winproxy.SymFromName(self.handle, name, buff) sym.resolver = self sym.displacement = 0 return sym def resolve(self, name_or_addr): """Resolve ``name_or_addr``. If its an int -> Return the :class:`SymbolInfo` at the address. If its a string -> Return the :class:`SymbolInfo` corresponding to the symbol name :return: :class:`SymbolInfo` .. note:: ``__getitem__`` is an alias for ``resolve()`` Exemple: >>> sh = windows.debug.symbols.VirtualSymbolHandler() >>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll") >>> mod <SymbolModule name="kernelbase" type=SymPdb pdb="wkernelbase.pdb" addr=0x10000000> >>> sh.resolve("kernelbase!CreateFileInternal") <SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction> >>> sh[0x100f2042] <SymbolInfoA name="ReadFile" addr=0x100f1ee0 displacement=0x162 tag=SymTagFunction> >>> str(sh[0x100f2042]) 'kernelbase!ReadFile+0x162' """ # Only returns None if symbol is not Found ? if isinstance(name_or_addr, windows.pycompat.anybuff): return self.symbol_from_name(name_or_addr) try: return self.symbol_and_displacement_from_address(name_or_addr) except WindowsError as e: if e.winerror != gdef.ERROR_MOD_NOT_FOUND: raise # We could not resolve and address -> return None return None __getitem__ = resolve """Alias to resolve for simpler use""" @staticmethod @ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object) def simple_aggregator(info, size, ctx): sym = info[0] fullsize = sym.SizeOfStruct + sym.NameLen cpy = windows.utils.BUFFER(SymbolInfo)(size=fullsize) ctypes.memmove(cpy, info, fullsize) ctx.append(cpy[0]) return True def search(self, mask, mod=0, tag=0, options=gdef.SYMSEARCH_ALLITEMS, callback=None): """Search the symbols matching ``mask`` (``Windbg`` like). :return: [:class:`SymbolInfo`] -- A list of :class:`SymbolInfo` >>> sh = windows.debug.symbols.VirtualSymbolHandler() >>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll") >>> sh.search("kernelbase!CreateFile*") [<SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction>, <SymbolInfoA name="CreateFileMoniker" addr=0x10117d80 tag=SymTagFunction>, <SymbolInfoA name="CreateFile2" addr=0x1011e690 tag=SymTagFunction>, ...] """ res = [] if callback is None: callback = self.simple_aggregator else: callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback) addr = getattr(mod, "addr", mod) # Retrieve mod.addr, else us the value directly # Expect A-string mask = windows.pycompat.raw_encode(mask) windows.winproxy.SymSearch(self.handle, gdef.DWORD64(addr), 0, tag, mask, 0, callback, res, options) for sym in res: sym.resolver = self sym.displacement = 0 return res def get_symbols(self, addr, callback=None): res = [] if callback is None: callback = self.simple_aggregator else: callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback) try: windows.winproxy.SymEnumSymbolsForAddr(self.handle, addr, callback, res) except WindowsError as e: if e.winerror == gdef.ERROR_MOD_NOT_FOUND: return [] raise for sym in res: sym.resolver = self sym.displacement = 0 return res # Type stuff def get_type(self, name, mod=0): max_len_size = 0x1000 full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1) buff = windows.utils.BUFFER(SymbolInfo)(size=full_size) buff[0].SizeOfStruct = ctypes.sizeof(SymbolInfo) buff[0].MaxNameLen = max_len_size windows.winproxy.SymGetTypeFromName(self.handle, mod, name, buff) return SymbolType.from_symbol_info(buff[0], resolver=self) # TODO: mets de l'huile pour w4kfu class StackWalker(object): def __init__(self, resolver, process=None, thread=None, context=None): self.resolver = resolver if process is None and thread is None: raise ValueError("At least a process or thread must be provided") if process is None: process = thread.owner self.process = process self.thread = thread self.context = context if windows.current_process.bitness == 32 and process.bitness == 64: raise NotImplementedError("StackWalking 64b does not seems to works from 32b process") def _stack_frame_generator(self): ctx, machine = self._get_effective_context_and_machine() frame = self._setup_initial_frame_from_context(ctx, machine) thread_handle = self.thread.handle if self.thread else None while True: try: windows.winproxy.StackWalkEx(machine, # dbg.current_process.handle, self.resolver.handle, thread_handle, # 0, frame, ctypes.byref(ctx), None, winproxy.resolve(winproxy.SymFunctionTableAccess64), winproxy.resolve(winproxy.SymGetModuleBase64), None, 0) except WindowsError as e: if not e.winerror: return # No_ERROR -> end of stack walking raise yield type(frame).from_buffer_copy(frame) # Make a copy ? def __iter__(self): return self._stack_frame_generator() # Autorise to force the retrieving of 32b stack when code is currently on 64b code ? def _get_effective_context_and_machine(self): ctx = self.context or self.thread.context if self.process.bitness == 32: # Process is 32b, so the context is inevitably x86 return (ctx, gdef.IMAGE_FILE_MACHINE_I386) if windows.current_process.bitness == 32: # If we are 32b, we will only be able to handle x86 stack # ctx is obligatory a 32b one, as the case us32/target64 is handled # in __init__ with a NotImplementedError return (ctx, gdef.IMAGE_FILE_MACHINE_I386) if self.process.bitness == 64: # Process is 64b, so the context is inevitably x64 return (ctx, gdef.IMAGE_FILE_MACHINE_AMD64) # Thing get a little more complicated here :) # We are a 64b process and target is 32b. # So we must find-out if we are in 32 or 64b world at the moment. # The context_syswow.SegCS give us the information # The context32.SegCs would be always 32 ctxsyswow = dbg.current_thread.context_syswow if ctxsyswow.SegCs == gdef.CS_USER_32B: return (ctx, gdef.IMAGE_FILE_MACHINE_I386) return (ctxsyswow, gdef.IMAGE_FILE_MACHINE_AMD64) def _setup_initial_frame_from_context(self, ctx, machine): frame = gdef.STACKFRAME_EX() frame.AddrPC.Mode = gdef.AddrModeFlat frame.AddrFrame.Mode = gdef.AddrModeFlat frame.AddrStack.Mode = gdef.AddrModeFlat frame.AddrPC.Offset = ctx.pc frame.AddrStack.Offset = ctx.sp if machine == gdef.IMAGE_FILE_MACHINE_I386: frame.AddrFrame.Offset = ctx.Ebp # Need RBP on 64b ? return frame
[docs]class VirtualSymbolHandler(SymbolHandler): """A SymbolHandler where its handle is not a valid process handle Allow to create/resolve symbol in a 'virtual' process But all API needing a real process handle will fail """ VIRTUAL_HANDLER_COUNTER = itertools.count(0x11223344) def __init__(self, search_path=None): handle = next(self.VIRTUAL_HANDLER_COUNTER) super(VirtualSymbolHandler, self).__init__(handle, search_path, False) # The VirtualSymbolHandler is not based on an existing process # So load() in its simplest for should just take the path of the file to load load = SymbolHandler.load_file """An alias for :func:`VirtualSymbolHandler.load_file`"""
[docs] def refresh(self): """Do nothing for a :class:`VirtualSymbolHandler`""" return False
[docs]class ProcessSymbolHandler(SymbolHandler): def __init__(self, process, search_path=None, invade_process=False): super(ProcessSymbolHandler, self).__init__(process.handle, search_path, invade_process) self.target = process # The ProcessSymbolHandler is based on an existing process # So load() in its simplest form should be able to load the symbol for an existing # module that is already loaded # Question: should be able to load other module at other address ?
[docs] def load(self, name): """Load the :class:`SymbolModule` associated with the loaded module ``name`` (as found in the PEB) :return: :class:`SymbolModule` Exemple: >>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64()) <windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30> >>> sh <windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30> >>> sh.load("kernelbase.dll") <SymbolModule name="kernelbase" type=SymDeferred pdb="" addr=0x7ffb5b090000> >>> sh["kernelbase!CreateProcessA"] <SymbolInfoA name="CreateProcessA" start=0x7ffb5b2371f0 tag=SymTagPublicSymbol> """ mods = [x for x in self.target.peb.modules if x.name == name] if not mods: raise ValueError("Could not find module <{0}>".format(name)) assert len(mods) == 1 # Load all if multiple match ? mod = mods[0] return self.load_module(addr=mod.baseaddr, path=mod.fullname)
[docs] def refresh(self): """Update the list of loaded modules to match the modules present in the target process .. note:: This function only call `SymRefreshModuleList <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symrefreshmodulelist>`_ for now. It seems that this function do not handle refreshing a 64b target from a 32b python Also, on a 32b target from a 64b python it seems to only load symbols for the 64b modules (ntdll + syswow dll) Exemple: >>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64()) >>> sh.modules [] >>> sh.refresh() 44 >>> sh.modules [<SymbolModule name="notepad" type=SymDeferred pdb="" addr=0x7ff772b80000>, <SymbolModule name="ntdll" type=SymDeferred pdb="" addr=0x7ffb5d860000>, <SymbolModule name="KERNEL32" type=SymDeferred pdb="" addr=0x7ffb5bb90000>, <SymbolModule name="KERNELBASE" type=SymDeferred pdb="" addr=0x7ffb5b090000>, ...] """ return windows.winproxy.SymRefreshModuleList(self.handle)
def stackwalk(self, ctx): pass
[docs]class SymbolEngine(object): """Represent the global symbol engine. Just a proxy to get/set global engine options Its instance can be accessed using ``windows.debug.symbols.engine`` Exemple: >>> windows.debug.symbols.engine.options 6L >>> windows.debug.symbols.engine.options = gdef.SYMOPT_UNDNAME >>> windows.debug.symbols.engine.options 2L """ def __init__(self): # use to now if we need to call the setup of options # At the first DbgHelp call self.options_already_setup = False def set_options(self, options): self.options_already_setup = True return windows.winproxy.SymSetOptions(options) def get_options(self): return windows.winproxy.SymGetOptions() options = property(get_options, set_options) """The options of the Symbol engine (`see options <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions#parameters>`_) .. note:: Default options are: ``gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME`` """
engine = SymbolEngine() """The instance of the :class:`SymbolEngine`""" TST_TYPE_RES_TYPE = { gdef.TI_GET_SYMNAME: gdef.LPWSTR, gdef.TI_GET_LENGTH: gdef.ULONG64, gdef.TI_GET_ADDRESS: gdef.ULONG64, gdef.TI_GTIEX_REQS_VALID: gdef.ULONG64, gdef.TI_GET_SYMTAG: gdef.SymTagEnum, gdef.TI_GET_VALUE: windows.com.Variant, }