From 4f4afcd28802ff8a3e78ad72e47b6acb6e24819c Mon Sep 17 00:00:00 2001 From: Rebecca Cran Date: Tue, 22 Mar 2022 04:20:48 +0800 Subject: BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print 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 Reviewed-by: Bob Feng Acked-by: Liming Gao --- BaseTools/Scripts/efi_lldb.py | 1044 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1044 insertions(+) create mode 100755 BaseTools/Scripts/efi_lldb.py (limited to 'BaseTools') diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py new file mode 100755 index 0000000000..6487e41bf5 --- /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 -- cgit v1.2.3