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 <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> Reviewed-by: Bob Feng <bob.c.feng@intel.com> Acked-by: Liming Gao <gaoliming@byosoft.com.cn>
		
			
				
	
	
		
			1045 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1045 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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
 |