| #!/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 |