Reviewed-by: Bob Feng On Tue, Mar 22, 2022 at 04:21 AM, Rebecca Cran wrote: > > https://bugzilla.tianocore.org/show_bug.cgi?id=3500 > > Use efi_debugging.py Python Classes to implement EFI gdb commands: > efi_symbols, guid, table, hob, and devicepath > > You can attach to any standard gdb or kdp remote server and get EFI > symbols. No modifications of EFI are required. > > Example usage: > OvmfPkg/build.sh qemu -gdb tcp::9000 > lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py" > > Note you may also have to teach lldb about QEMU: > -o "settings set plugin.process.gdb-remote.target-definition-file > x86_64_target_definition.py" > > Cc: Leif Lindholm > Cc: Michael D Kinney > Cc: Hao A Wu > Cc: Bob Feng > Cc: Liming Gao > Cc: Yuwei Chen > Signed-off-by: Rebecca Cran > --- > BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++ > 1 file changed, 1044 insertions(+) > > diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py > > new file mode 100755 > index 000000000000..089b6ba58ab8 > --- /dev/null > +++ b/BaseTools/Scripts/efi_lldb.py > @@ -0,0 +1,1044 @@ > +#!/usr/bin/python3 > +''' > +Copyright (c) Apple Inc. 2021 > +SPDX-License-Identifier: BSD-2-Clause-Patent > + > +Example usage: > +OvmfPkg/build.sh qemu -gdb tcp::9000 > +lldb -o "gdb-remote localhost:9000" -o "command script import > efi_lldb.py" > +''' > + > +import optparse > +import shlex > +import subprocess > +import uuid > +import sys > +import os > +from pathlib import Path > +from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl > +from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode > +from efi_debugging import PeTeImage, patch_ctypes > + > +try: > + # Just try for LLDB in case PYTHONPATH is already correctly setup > + import lldb > +except ImportError: > + try: > + env = os.environ.copy() > + env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major) > + lldb_python_path = subprocess.check_output( > + ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip() > + sys.path.append(lldb_python_path) > + import lldb > + except ValueError: > + print("Couldn't find LLDB.framework from lldb -P") > + print("PYTHONPATH should match the currently selected lldb") > + sys.exit(-1) > + > + > +class LldbFileObject(object): > + ''' > + Class that fakes out file object to abstract lldb from the generic code. > > + For lldb this is memory so we don't have a concept of the end of the > file. > + ''' > + > + def __init__(self, process): > + # _exe_ctx is lldb.SBExecutionContext > + self._process = process > + self._offset = 0 > + self._SBError = lldb.SBError() > + > + def tell(self): > + return self._offset > + > + def read(self, size=-1): > + if size == -1: > + # arbitrary default size > + size = 0x1000000 > + > + data = self._process.ReadMemory(self._offset, size, self._SBError) > + if self._SBError.fail: > + raise MemoryError( > + f'lldb could not read memory 0x{size:x} ' > + f' bytes from 0x{self._offset:08x}') > + else: > + return data > + > + def readable(self): > + return True > + > + def seek(self, offset, whence=0): > + if whence == 0: > + self._offset = offset > + elif whence == 1: > + self._offset += offset > + else: > + # whence == 2 is seek from end > + raise NotImplementedError > + > + def seekable(self): > + return True > + > + def write(self, data): > + result = self._process.WriteMemory(self._offset, data, self._SBError) > + if self._SBError.fail: > + raise MemoryError( > + f'lldb could not write memory to 0x{self._offset:08x}') > + return result > + > + def writable(self): > + return True > + > + def truncate(self, size=None): > + raise NotImplementedError > + > + def flush(self): > + raise NotImplementedError > + > + def fileno(self): > + raise NotImplementedError > + > + > +class EfiSymbols: > + """ > + Class to manage EFI Symbols > + You need to pass file, and exe_ctx to load symbols. > + You can print(EfiSymbols()) to see the currently loaded symbols > + """ > + > + loaded = {} > + stride = None > + range = None > + verbose = False > + > + def __init__(self, target=None): > + if target: > + EfiSymbols.target = target > + EfiSymbols._file = LldbFileObject(target.process) > + > + @ classmethod > + def __str__(cls): > + return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values()) > + > + @ classmethod > + def configure_search(cls, stride, range, verbose=False): > + cls.stride = stride > + cls.range = range > + cls.verbose = verbose > + > + @ classmethod > + def clear(cls): > + cls.loaded = {} > + > + @ classmethod > + def add_symbols_for_pecoff(cls, pecoff): > + '''Tell lldb the location of the .text and .data sections.''' > + > + if pecoff.LoadAddress in cls.loaded: > + return 'Already Loaded: ' > + > + module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid)) > + if not module: > + module = cls.target.AddModule(pecoff.CodeViewPdb, > + None, > + str(pecoff.CodeViewUuid)) > + if module.IsValid(): > + SBError = cls.target.SetModuleLoadAddress( > + module, pecoff.LoadAddress + pecoff.TeAdjust) > + if SBError.success: > + cls.loaded[pecoff.LoadAddress] = (pecoff, module) > + return '' > + > + return 'Symbols NOT FOUND: ' > + > + @ classmethod > + def address_to_symbols(cls, address, reprobe=False): > + ''' > + Given an address search backwards for a PE/COFF (or TE) header > + and load symbols. Return a status string. > + ''' > + if not isinstance(address, int): > + address = int(address) > + > + pecoff, _ = cls.address_in_loaded_pecoff(address) > + if not reprobe and pecoff is not None: > + # skip the probe of the remote > + return f'{pecoff} is already loaded' > + > + pecoff = PeTeImage(cls._file, None) > + if pecoff.pcToPeCoff(address, cls.stride, cls.range): > + res = cls.add_symbols_for_pecoff(pecoff) > + return f'{res}{pecoff}' > + else: > + return f'0x{address:08x} not in a PE/COFF (or TE) image' > + > + @ classmethod > + def address_in_loaded_pecoff(cls, address): > + if not isinstance(address, int): > + address = int(address) > + > + for (pecoff, module) in cls.loaded.values(): > + if (address >= pecoff.LoadAddress and > + address <= pecoff.EndLoadAddress): > + > + return pecoff, module > + > + return None, None > + > + @ classmethod > + def unload_symbols(cls, address): > + pecoff, module = cls.address_in_loaded_pecoff(address) > + if module: > + name = str(module) > + cls.target.ClearModuleLoadAddress(module) > + cls.target.RemoveModule(module) > + del cls.loaded[pecoff.LoadAddress] > + return f'{name:s} was unloaded' > + return f'0x{address:x} was not in a loaded image' > + > + > +def arg_to_address(frame, arg): > + ''' convert an lldb command arg into a memory address (addr_t)''' > + > + if arg is None: > + return None > + > + arg_str = arg if isinstance(arg, str) else str(arg) > + SBValue = frame.EvaluateExpression(arg_str) > + if SBValue.error.fail: > + return arg > + > + if (SBValue.TypeIsPointerType() or > + SBValue.value_type == lldb.eValueTypeRegister or > + SBValue.value_type == lldb.eValueTypeRegisterSet or > + SBValue.value_type == lldb.eValueTypeConstResult): > + try: > + addr = SBValue.GetValueAsAddress() > + except ValueError: > + addr = SBValue.unsigned > + else: > + try: > + addr = SBValue.address_of.GetValueAsAddress() > + except ValueError: > + addr = SBValue.address_of.unsigned > + > + return addr > + > + > +def arg_to_data(frame, arg): > + ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)''' > + if not isinstance(arg, str): > + arg_str = str(str) > + > + SBValue = frame.EvaluateExpression(arg_str) > + return SBValue.unsigned > + > + > +class EfiDevicePathCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can EFI Config Tables > +''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='devicepath', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action='store_true', > + dest='verbose', > + help='hex dump extra data', > + default=False) > + > + self.parser.add_option( > + '-n', > + '--node', > + action='store_true', > + dest='node', > + help='dump a single device path node', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI Tables" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, args) = self.parser.parse_args(command_args) > + dev_list = [] > + for arg in args: > + dev_list.append(arg_to_address(exe_ctx.frame, arg)) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + file = LldbFileObject(exe_ctx.process) > + > + for dev_addr in dev_list: > + if options.node: > + print(EfiDevicePath(file).device_path_node_str( > + dev_addr, options.verbose)) > + else: > + device_path = EfiDevicePath(file, dev_addr, options.verbose) > + if device_path.valid(): > + print(device_path) > + > + > +class EfiHobCommand: > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can EFI dump EFI HOBs''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='table', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-a', > + '--address', > + type="int", > + dest='address', > + help='Parse HOBs from address', > + default=None) > + > + self.parser.add_option( > + '-t', > + '--type', > + type="int", > + dest='type', > + help='Only dump HOBS of his type', > + default=None) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action='store_true', > + dest='verbose', > + help='hex dump extra data', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI Hobs" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, _) = self.parser.parse_args(command_args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + address = arg_to_address(exe_ctx.frame, options.address) > + > + file = LldbFileObject(exe_ctx.process) > + hob = EfiHob(file, address, options.verbose).get_hob_by_type( > + options.type) > + print(hob) > + > + > +class EfiTableCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can display EFI Config Tables > +''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='table', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI Tables" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, _) = self.parser.parse_args(command_args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + gST = exe_ctx.target.FindFirstGlobalVariable('gST') > + if gST.error.fail: > + print('Error: This command requires symbols for gST to be loaded') > + return > + > + file = LldbFileObject(exe_ctx.process) > + table = EfiConfigurationTable(file, gST.unsigned) > + if table: > + print(table, '\n') > + > + > +class EfiGuidCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = ''' > + Command that can display all EFI GUID's or give info on a > + specific GUID's > + ''' > + self.parser = optparse.OptionParser( > + description=description, > + prog='guid', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-n', > + '--new', > + action='store_true', > + dest='new', > + help='Generate a new GUID', > + default=False) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action='store_true', > + dest='verbose', > + help='Also display GUID C structure values', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI GUID's" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, args) = self.parser.parse_args(command_args) > + if len(args) >= 1: > + # guid { 0x414e6bdd, 0xe47b, 0x47cc, > + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} > + # this generates multiple args > + arg = ' '.join(args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + if options.new: > + guid = uuid.uuid4() > + print(str(guid).upper()) > + print(GuidNames.to_c_guid(guid)) > + return > + > + if len(args) > 0: > + if GuidNames.is_guid_str(arg): > + # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9 > + key = arg.lower() > + name = GuidNames.to_name(key) > + elif GuidNames.is_c_guid(arg): > + # guid { 0x414e6bdd, 0xe47b, 0x47cc, > + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} > + key = GuidNames.from_c_guid(arg) > + name = GuidNames.to_name(key) > + else: > + # guid gEfiDxeServicesTableGuid > + name = arg > + try: > + key = GuidNames.to_guid(name) > + name = GuidNames.to_name(key) > + except ValueError: > + return > + > + extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else '' > + print(f'{key}: {extra}{name}') > + > + else: > + for key, value in GuidNames._dict_.items(): > + if options.verbose: > + extra = f'{GuidNames.to_c_guid(key)}: ' > + else: > + extra = '' > + print(f'{key}: {extra}{value}') > + > + > +class EfiSymbolicateCommand(object): > + '''Class to abstract an lldb command''' > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can load EFI PE/COFF and TE image > + symbols. If you are having trouble in PEI try adding --pei. > + ''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='efi_symbols', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-a', > + '--address', > + type="int", > + dest='address', > + help='Load symbols for image at address', > + default=None) > + > + self.parser.add_option( > + '-f', > + '--frame', > + action='store_true', > + dest='frame', > + help='Load symbols for current stack frame', > + default=False) > + > + self.parser.add_option( > + '-p', > + '--pc', > + action='store_true', > + dest='pc', > + help='Load symbols for pc', > + default=False) > + > + self.parser.add_option( > + '--pei', > + action='store_true', > + dest='pei', > + help='Load symbols for PEI (searches every 4 bytes)', > + default=False) > + > + self.parser.add_option( > + '-e', > + '--extended', > + action='store_true', > + dest='extended', > + help='Try to load all symbols based on config tables.', > + default=False) > + > + self.parser.add_option( > + '-r', > + '--range', > + type="long", > + dest='range', > + help='How far to search backward for start of PE/COFF Image', > + default=None) > + > + self.parser.add_option( > + '-s', > + '--stride', > + type="long", > + dest='stride', > + help='Boundary to search for PE/COFF header', > + default=None) > + > + self.parser.add_option( > + '-t', > + '--thread', > + action='store_true', > + dest='thread', > + help='Load symbols for the frames of all threads', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return ( > + "Load symbols based on an address that is part of" > + " a PE/COFF EFI image.") > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, unused): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def lldb_print(self, lldb_str): > + # capture command out like an lldb command > + self.result.PutCString(lldb_str) > + # flush the output right away > + self.result.SetImmediateOutputFile( > + self.exe_ctx.target.debugger.GetOutputFile()) > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, _) = self.parser.parse_args(command_args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + file = LldbFileObject(exe_ctx.process) > + efi_symbols = EfiSymbols(exe_ctx.target) > + self.result = result > + self.exe_ctx = exe_ctx > + > + if options.pei: > + # XIP code ends up on a 4 byte boundary. > + options.stride = 4 > + options.range = 0x100000 > + efi_symbols.configure_search(options.stride, options.range) > + > + if not options.pc and options.address is None: > + # default to > + options.frame = True > + > + if options.frame: > + if not exe_ctx.frame.IsValid(): > + result.SetError("invalid frame") > + return > + > + threads = exe_ctx.process.threads if options.thread else [ > + exe_ctx.thread] > + > + for thread in threads: > + for frame in thread: > + res = efi_symbols.address_to_symbols(frame.pc) > + self.lldb_print(res) > + > + else: > + if options.address is not None: > + address = options.address > + elif options.pc: > + try: > + address = exe_ctx.thread.GetSelectedFrame().pc > + except ValueError: > + result.SetError("invalid pc") > + return > + else: > + address = 0 > + > + res = efi_symbols.address_to_symbols(address.pc) > + print(res) > + > + if options.extended: > + > + gST = exe_ctx.target.FindFirstGlobalVariable('gST') > + if gST.error.fail: > + print('Error: This command requires symbols to be loaded') > + else: > + table = EfiConfigurationTable(file, gST.unsigned) > + for address, _ in table.DebugImageInfo(): > + res = efi_symbols.address_to_symbols(address) > + self.lldb_print(res) > + > + # keep trying module file names until we find a GUID xref file > + for m in exe_ctx.target.modules: > + if GuidNames.add_build_guid_file(str(m.file)): > + break > + > + > +def CHAR16_TypeSummary(valobj, internal_dict): > + ''' > + Display CHAR16 as a String in the debugger. > + Note: utf-8 is returned as that is the value for the debugger. > + ''' > + SBError = lldb.SBError() > + Str = '' > + if valobj.TypeIsPointerType(): > + if valobj.GetValueAsUnsigned() == 0: > + return "NULL" > + > + # CHAR16 * max string size 1024 > + for i in range(1024): > + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0) > + if SBError.fail or Char == 0: > + break > + Str += chr(Char) > + return 'L"' + Str + '"' > + > + if valobj.num_children == 0: > + # CHAR16 > + return "L'" + chr(valobj.unsigned) + "'" > + > + else: > + # CHAR16 [] > + for i in range(valobj.num_children): > + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0) > + if Char == 0: > + break > + Str += chr(Char) > + return 'L"' + Str + '"' > + > + return Str > + > + > +def CHAR8_TypeSummary(valobj, internal_dict): > + ''' > + Display CHAR8 as a String in the debugger. > + Note: utf-8 is returned as that is the value for the debugger. > + ''' > + SBError = lldb.SBError() > + Str = '' > + if valobj.TypeIsPointerType(): > + if valobj.GetValueAsUnsigned() == 0: > + return "NULL" > + > + # CHAR8 * max string size 1024 > + for i in range(1024): > + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0) > + if SBError.fail or Char == 0: > + break > + Str += chr(Char) > + Str = '"' + Str + '"' > + return Str > + > + if valobj.num_children == 0: > + # CHAR8 > + return "'" + chr(valobj.unsigned) + "'" > + else: > + # CHAR8 [] > + for i in range(valobj.num_children): > + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0) > + if SBError.fail or Char == 0: > + break > + Str += chr(Char) > + return '"' + Str + '"' > + > + return Str > + > + > +def EFI_STATUS_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + return str(EfiStatusClass(valobj.unsigned)) > + > + > +def EFI_TPL_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + return str(EfiTpl(valobj.unsigned)) > + > + > +def EFI_GUID_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + return str(GuidNames(bytes(valobj.data.uint8))) > + > + > +def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + '''Return #define name for EFI_BOOT_MODE''' > + return str(EfiBootMode(valobj.unsigned)) > + > + > +def lldb_type_formaters(debugger, mod_name): > + '''Teach lldb about EFI types''' > + > + category = debugger.GetDefaultCategory() > + FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool) > + > + FormatHex = lldb.SBTypeFormat(lldb.eFormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier( > + "EFI_PHYSICAL_ADDRESS"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier( > + "PHYSICAL_ADDRESS"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex) > + category.AddTypeFormat( > + lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier( > + "EFI_FV_FILETYPE"), FormatHex) > + > + # > + # Smart type printing for EFI > + # > + > + debugger.HandleCommand( > + f'type summary add GUID - -python-function ' > + f'{mod_name}.EFI_GUID_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_GUID --python-function ' > + f'{mod_name}.EFI_GUID_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_STATUS --python-function ' > + f'{mod_name}.EFI_STATUS_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_TPL - -python-function ' > + f'{mod_name}.EFI_TPL_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_BOOT_MODE --python-function ' > + f'{mod_name}.EFI_BOOT_MODE_TypeSummary') > + > + debugger.HandleCommand( > + f'type summary add CHAR16 --python-function ' > + f'{mod_name}.CHAR16_TypeSummary') > + > + # W605 this is the correct escape sequence for the lldb command > + debugger.HandleCommand( > + f'type summary add --regex "CHAR16 \[[0-9]+\]" ' # noqa: W605 > + f'--python-function {mod_name}.CHAR16_TypeSummary') > + > + debugger.HandleCommand( > + f'type summary add CHAR8 --python-function ' > + f'{mod_name}.CHAR8_TypeSummary') > + > + # W605 this is the correct escape sequence for the lldb command > + debugger.HandleCommand( > + f'type summary add --regex "CHAR8 \[[0-9]+\]" ' # noqa: W605 > + f'--python-function {mod_name}.CHAR8_TypeSummary') > + > + > +class LldbWorkaround: > + needed = True > + > + @classmethod > + def activate(cls): > + if cls.needed: > + lldb.debugger.HandleCommand("process handle SIGALRM -n false") > + cls.needed = False > + > + > +def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict): > + # > + # This is an lldb breakpoint script, and assumes the breakpoint is on a > + # function with the same prototype as SecGdbScriptBreak(). The > + # argument names are important as lldb looks them up. > + # > + # VOID > + # SecGdbScriptBreak ( > + # char *FileName, > + # int FileNameLength, > + # long unsigned int LoadAddress, > + # int AddSymbolFlag > + # ) > + # { > + # return; > + # } > + # > + # When the emulator loads a PE/COFF image, it calls the stub function > with > + # the filename of the symbol file, the length of the FileName, the > + # load address and a flag to indicate if this is a load or unload > operation > + # > + LldbWorkaround().activate() > + > + symbols = EfiSymbols(frame.thread.process.target) > + LoadAddress = frame.FindVariable("LoadAddress").unsigned > + if frame.FindVariable("AddSymbolFlag").unsigned == 1: > + res = symbols.address_to_symbols(LoadAddress) > + else: > + res = symbols.unload_symbols(LoadAddress) > + print(res) > + > + # make breakpoint command continue > + return False > + > + > +def __lldb_init_module(debugger, internal_dict): > + ''' > + This initializer is being run from LLDB in the embedded command > interpreter > + ''' > + > + mod_name = Path(__file__).stem > + lldb_type_formaters(debugger, mod_name) > + > + # Add any commands contained in this module to LLDB > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiGuidCommand guid') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiTableCommand table') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiHobCommand hob') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiDevicePathCommand devicepath') > + > + print('EFI specific commands have been installed.') > + > + # patch the ctypes c_void_p values if the debuggers OS and EFI have > + # different ideas on the size of the debug. > + try: > + patch_ctypes(debugger.GetSelectedTarget().addr_size) > + except ValueError: > + # incase the script is imported and the debugger has not target > + # defaults to sizeof(UINTN) == sizeof(UINT64) > + patch_ctypes() > + > + try: > + target = debugger.GetSelectedTarget() > + if target.FindFunctions('SecGdbScriptBreak').symbols: > + breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak') > + # Set the emulator breakpoints, if we are in the emulator > + cmd = 'breakpoint command add -s python -F ' > + cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}' > + debugger.HandleCommand(cmd) > + print('Type r to run emulator.') > + else: > + raise ValueError("No Emulator Symbols") > + > + except ValueError: > + # default action when the script is imported > + debugger.HandleCommand("efi_symbols --frame --extended") > + debugger.HandleCommand("register read") > + debugger.HandleCommand("bt all") > + > + > +if __name__ == '__main__': > + pass > -- > 2.34.1