https://bugzilla.tianocore.org/show_bug.cgi?id=3500 Use efi_debugging.py Python Classes to implement EFI gdb commands: (gdb) help efi Commands for debugging EFI. efi <cmd> List of efi subcommands: efi devicepath -- Display an EFI device path. efi guid -- Display info about EFI GUID's. efi hob -- Dump EFI HOBs. Type 'hob -h' for more info. efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info. efi table -- Dump EFI System Tables. Type 'table -h' for more info. This module is coded against a generic gdb remote serial stub. It should work with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub. No modifications of EFI is required to load symbols. Example usage: OvmfPkg/build.sh qemu -gdb tcp::9000 gdb -ex "target remote localhost:9000" -ex "source efi_gdb.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>
		
			
				
	
	
		
			919 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			919 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/python3
 | |
| '''
 | |
| Copyright 2021 (c) Apple Inc. All rights reserved.
 | |
| SPDX-License-Identifier: BSD-2-Clause-Patent
 | |
| 
 | |
| EFI gdb commands based on efi_debugging classes.
 | |
| 
 | |
| Example usage:
 | |
| OvmfPkg/build.sh qemu -gdb tcp::9000
 | |
| gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py"
 | |
| 
 | |
| (gdb) help efi
 | |
| Commands for debugging EFI. efi <cmd>
 | |
| 
 | |
| List of efi subcommands:
 | |
| 
 | |
| efi devicepath -- Display an EFI device path.
 | |
| efi guid -- Display info about EFI GUID's.
 | |
| efi hob -- Dump EFI HOBs. Type 'hob -h' for more info.
 | |
| efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info.
 | |
| efi table -- Dump EFI System Tables. Type 'table -h' for more info.
 | |
| 
 | |
| This module is coded against a generic gdb remote serial stub. It should work
 | |
| with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub.
 | |
| 
 | |
| If you are debugging with QEMU or a JTAG hardware debugger you can insert
 | |
| a CpuDeadLoop(); in your code, attach with gdb, and then `p Index=1` to
 | |
| step past. If you have a debug stub in EFI you can use CpuBreakpoint();.
 | |
| '''
 | |
| 
 | |
| from gdb.printing import RegexpCollectionPrettyPrinter
 | |
| from gdb.printing import register_pretty_printer
 | |
| import gdb
 | |
| import os
 | |
| import sys
 | |
| import uuid
 | |
| import optparse
 | |
| import shlex
 | |
| 
 | |
| # gdb will not import from the same path as this script.
 | |
| # so lets fix that for gdb...
 | |
| sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 | |
| 
 | |
| from efi_debugging import PeTeImage, patch_ctypes            # noqa: E402
 | |
| from efi_debugging import EfiHob, GuidNames, EfiStatusClass  # noqa: E402
 | |
| from efi_debugging import EfiBootMode, EfiDevicePath         # noqa: E402
 | |
| from efi_debugging import EfiConfigurationTable, EfiTpl      # noqa: E402
 | |
| 
 | |
| 
 | |
| class GdbFileObject(object):
 | |
|     '''Provide a file like object required by efi_debugging'''
 | |
| 
 | |
|     def __init__(self):
 | |
|         self.inferior = gdb.selected_inferior()
 | |
|         self.offset = 0
 | |
| 
 | |
|     def tell(self):
 | |
|         return self.offset
 | |
| 
 | |
|     def read(self, size=-1):
 | |
|         if size == -1:
 | |
|             # arbitrary default size
 | |
|             size = 0x1000000
 | |
| 
 | |
|         try:
 | |
|             data = self.inferior.read_memory(self.offset, size)
 | |
|         except MemoryError:
 | |
|             data = bytearray(size)
 | |
|             assert False
 | |
|         if len(data) != size:
 | |
|             raise MemoryError(
 | |
|                 f'gdb could not read memory 0x{size:x}'
 | |
|                 + f' bytes from 0x{self.offset:08x}')
 | |
|         else:
 | |
|             # convert memoryview object to a bytestring.
 | |
|             return data.tobytes()
 | |
| 
 | |
|     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):
 | |
|         self.inferior.write_memory(self.offset, data)
 | |
|         return len(data)
 | |
| 
 | |
|     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"""
 | |
| 
 | |
|     loaded = {}
 | |
|     stride = None
 | |
|     range = None
 | |
|     verbose = False
 | |
| 
 | |
|     def __init__(self, file=None):
 | |
|         EfiSymbols.file = file if file else GdbFileObject()
 | |
| 
 | |
|     @ classmethod
 | |
|     def __str__(cls):
 | |
|         return ''.join(f'{value}\n' for value in cls.loaded.values())
 | |
| 
 | |
|     @ classmethod
 | |
|     def configure_search(cls, stride, range=None, 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.TextAddress in cls.loaded:
 | |
|             return 'Already Loaded: '
 | |
|         try:
 | |
|             res = 'Loading Symbols Failed:'
 | |
|             res = gdb.execute('add-symbol-file ' + pecoff.CodeViewPdb +
 | |
|                               ' ' + hex(pecoff.TextAddress) +
 | |
|                               ' -s .data ' + hex(pecoff.DataAddress),
 | |
|                               False, True)
 | |
| 
 | |
|             cls.loaded[pecoff.TextAddress] = pecoff
 | |
|             if cls.verbose:
 | |
|                 print(f'\n{res:s}\n')
 | |
|             return ''
 | |
|         except gdb.error:
 | |
|             return res
 | |
| 
 | |
|     @ 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 value in cls.loaded.values():
 | |
|             if (address >= value.LoadAddress and
 | |
|                     address <= value.EndLoadAddress):
 | |
|                 return value
 | |
| 
 | |
|         return None
 | |
| 
 | |
|     @ classmethod
 | |
|     def unload_symbols(cls, address):
 | |
|         if not isinstance(address, int):
 | |
|             address = int(address)
 | |
| 
 | |
|         pecoff = cls.address_in_loaded_pecoff(address)
 | |
|         try:
 | |
|             res = 'Unloading Symbols Failed:'
 | |
|             res = gdb.execute(
 | |
|                 f'remove-symbol-file -a {hex(pecoff.TextAddress):s}',
 | |
|                 False, True)
 | |
|             del cls.loaded[pecoff.LoadAddress]
 | |
|             return res
 | |
|         except gdb.error:
 | |
|             return res
 | |
| 
 | |
| 
 | |
| class CHAR16_PrettyPrinter(object):
 | |
| 
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
| 
 | |
|     def to_string(self):
 | |
|         if int(self.val) < 0x20:
 | |
|             return f"L'\\x{int(self.val):02x}'"
 | |
|         else:
 | |
|             return f"L'{chr(self.val):s}'"
 | |
| 
 | |
| 
 | |
| class EFI_TPL_PrettyPrinter(object):
 | |
| 
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
| 
 | |
|     def to_string(self):
 | |
|         return str(EfiTpl(int(self.val)))
 | |
| 
 | |
| 
 | |
| class EFI_STATUS_PrettyPrinter(object):
 | |
| 
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
| 
 | |
|     def to_string(self):
 | |
|         status = int(self.val)
 | |
|         return f'{str(EfiStatusClass(status)):s} (0x{status:08x})'
 | |
| 
 | |
| 
 | |
| class EFI_BOOT_MODE_PrettyPrinter(object):
 | |
| 
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
| 
 | |
|     def to_string(self):
 | |
|         return str(EfiBootMode(int(self.val)))
 | |
| 
 | |
| 
 | |
| class EFI_GUID_PrettyPrinter(object):
 | |
|     """Print 'EFI_GUID' as 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'"""
 | |
| 
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
| 
 | |
|     def to_string(self):
 | |
|         # if we could get a byte like object of *(unsigned char (*)[16])
 | |
|         # then we could just use uuid.UUID() to convert
 | |
|         Data1 = int(self.val['Data1'])
 | |
|         Data2 = int(self.val['Data2'])
 | |
|         Data3 = int(self.val['Data3'])
 | |
|         Data4 = self.val['Data4']
 | |
|         guid = f'{Data1:08X}-{Data2:04X}-'
 | |
|         guid += f'{Data3:04X}-'
 | |
|         guid += f'{int(Data4[0]):02X}{int(Data4[1]):02X}-'
 | |
|         guid += f'{int(Data4[2]):02X}{int(Data4[3]):02X}'
 | |
|         guid += f'{int(Data4[4]):02X}{int(Data4[5]):02X}'
 | |
|         guid += f'{int(Data4[6]):02X}{int(Data4[7]):02X}'
 | |
|         return str(GuidNames(guid))
 | |
| 
 | |
| 
 | |
| def build_pretty_printer():
 | |
|     # Turn off via: disable pretty-printer global EFI
 | |
|     pp = RegexpCollectionPrettyPrinter("EFI")
 | |
|     # you can also tell gdb `x/sh <address>` to print CHAR16 string
 | |
|     pp.add_printer('CHAR16', '^CHAR16$', CHAR16_PrettyPrinter)
 | |
|     pp.add_printer('EFI_BOOT_MODE', '^EFI_BOOT_MODE$',
 | |
|                    EFI_BOOT_MODE_PrettyPrinter)
 | |
|     pp.add_printer('EFI_GUID', '^EFI_GUID$', EFI_GUID_PrettyPrinter)
 | |
|     pp.add_printer('EFI_STATUS', '^EFI_STATUS$', EFI_STATUS_PrettyPrinter)
 | |
|     pp.add_printer('EFI_TPL', '^EFI_TPL$', EFI_TPL_PrettyPrinter)
 | |
|     return pp
 | |
| 
 | |
| 
 | |
| class EfiDevicePathCmd (gdb.Command):
 | |
|     """Display an EFI device path. Type 'efi devicepath -h' for more info"""
 | |
| 
 | |
|     def __init__(self):
 | |
|         super(EfiDevicePathCmd, self).__init__(
 | |
|             "efi devicepath", gdb.COMMAND_NONE)
 | |
| 
 | |
|         self.file = GdbFileObject()
 | |
| 
 | |
|     def create_options(self, arg, from_tty):
 | |
|         usage = "usage: %prog [options] [arg]"
 | |
|         description = (
 | |
|             "Command that can load EFI PE/COFF and TE image symbols. ")
 | |
| 
 | |
|         self.parser = optparse.OptionParser(
 | |
|             description=description,
 | |
|             prog='efi 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)
 | |
| 
 | |
|         return self.parser.parse_args(shlex.split(arg))
 | |
| 
 | |
|     def invoke(self, arg, from_tty):
 | |
|         '''gdb command to dump EFI device paths'''
 | |
| 
 | |
|         try:
 | |
|             (options, _) = self.create_options(arg, from_tty)
 | |
|             if options.help:
 | |
|                 self.parser.print_help()
 | |
|                 return
 | |
| 
 | |
|             dev_addr = int(gdb.parse_and_eval(arg))
 | |
|         except ValueError:
 | |
|             print("Invalid argument!")
 | |
|             return
 | |
| 
 | |
|         if options.node:
 | |
|             print(EfiDevicePath(
 | |
|                 self.file).device_path_node_str(dev_addr,
 | |
|                                                 options.verbose))
 | |
|         else:
 | |
|             device_path = EfiDevicePath(self.file, dev_addr, options.verbose)
 | |
|             if device_path.valid():
 | |
|                 print(device_path)
 | |
| 
 | |
| 
 | |
| class EfiGuidCmd (gdb.Command):
 | |
|     """Display info about EFI GUID's. Type 'efi guid -h' for more info"""
 | |
| 
 | |
|     def __init__(self):
 | |
|         super(EfiGuidCmd, self).__init__("efi guid",
 | |
|                                          gdb.COMMAND_NONE,
 | |
|                                          gdb.COMPLETE_EXPRESSION)
 | |
|         self.file = GdbFileObject()
 | |
| 
 | |
|     def create_options(self, arg, from_tty):
 | |
|         usage = "usage: %prog [options] [arg]"
 | |
|         description = (
 | |
|             "Show EFI_GUID values and the C name of the EFI_GUID variables"
 | |
|             "in the C code. If symbols are loaded the Guid.xref file"
 | |
|             "can be processed and the complete GUID database can be shown."
 | |
|             "This command also suports generating new GUID's, and showing"
 | |
|             "the value used to initialize the C variable.")
 | |
| 
 | |
|         self.parser = optparse.OptionParser(
 | |
|             description=description,
 | |
|             prog='efi 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)
 | |
| 
 | |
|         return self.parser.parse_args(shlex.split(arg))
 | |
| 
 | |
|     def invoke(self, arg, from_tty):
 | |
|         '''gdb command to dump EFI System Tables'''
 | |
| 
 | |
|         try:
 | |
|             (options, args) = self.create_options(arg, from_tty)
 | |
|             if options.help:
 | |
|                 self.parser.print_help()
 | |
|                 return
 | |
|             if len(args) >= 1:
 | |
|                 # guid { 0x414e6bdd, 0xe47b, 0x47cc,
 | |
|                 #        { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
 | |
|                 # this generates multiple args
 | |
|                 guid = ' '.join(args)
 | |
|         except ValueError:
 | |
|             print('bad arguments!')
 | |
|             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 = guid.upper()
 | |
|                 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 = guid
 | |
|                 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 EfiHobCmd (gdb.Command):
 | |
|     """Dump EFI HOBs. Type 'hob -h' for more info."""
 | |
| 
 | |
|     def __init__(self):
 | |
|         super(EfiHobCmd, self).__init__("efi hob", gdb.COMMAND_NONE)
 | |
|         self.file = GdbFileObject()
 | |
| 
 | |
|     def create_options(self, arg, from_tty):
 | |
|         usage = "usage: %prog [options] [arg]"
 | |
|         description = (
 | |
|             "Command that can load EFI PE/COFF and TE image symbols. ")
 | |
| 
 | |
|         self.parser = optparse.OptionParser(
 | |
|             description=description,
 | |
|             prog='efi hob',
 | |
|             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)
 | |
| 
 | |
|         return self.parser.parse_args(shlex.split(arg))
 | |
| 
 | |
|     def invoke(self, arg, from_tty):
 | |
|         '''gdb command to dump EFI System Tables'''
 | |
| 
 | |
|         try:
 | |
|             (options, _) = self.create_options(arg, from_tty)
 | |
|             if options.help:
 | |
|                 self.parser.print_help()
 | |
|                 return
 | |
|         except ValueError:
 | |
|             print('bad arguments!')
 | |
|             return
 | |
| 
 | |
|         if options.address:
 | |
|             try:
 | |
|                 value = gdb.parse_and_eval(options.address)
 | |
|                 address = int(value)
 | |
|             except ValueError:
 | |
|                 address = None
 | |
|         else:
 | |
|             address = None
 | |
| 
 | |
|         hob = EfiHob(self.file,
 | |
|                      address,
 | |
|                      options.verbose).get_hob_by_type(options.type)
 | |
|         print(hob)
 | |
| 
 | |
| 
 | |
| class EfiTablesCmd (gdb.Command):
 | |
|     """Dump EFI System Tables. Type 'table -h' for more info."""
 | |
| 
 | |
|     def __init__(self):
 | |
|         super(EfiTablesCmd, self).__init__("efi table", gdb.COMMAND_NONE)
 | |
| 
 | |
|         self.file = GdbFileObject()
 | |
| 
 | |
|     def create_options(self, arg, from_tty):
 | |
|         usage = "usage: %prog [options] [arg]"
 | |
|         description = "Dump EFI System Tables. Requires symbols to be loaded"
 | |
| 
 | |
|         self.parser = optparse.OptionParser(
 | |
|             description=description,
 | |
|             prog='efi 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)
 | |
| 
 | |
|         return self.parser.parse_args(shlex.split(arg))
 | |
| 
 | |
|     def invoke(self, arg, from_tty):
 | |
|         '''gdb command to dump EFI System Tables'''
 | |
| 
 | |
|         try:
 | |
|             (options, _) = self.create_options(arg, from_tty)
 | |
|             if options.help:
 | |
|                 self.parser.print_help()
 | |
|                 return
 | |
|         except ValueError:
 | |
|             print('bad arguments!')
 | |
|             return
 | |
| 
 | |
|         gST = gdb.lookup_global_symbol('gST')
 | |
|         if gST is None:
 | |
|             print('Error: This command requires symbols for gST to be loaded')
 | |
|             return
 | |
| 
 | |
|         table = EfiConfigurationTable(
 | |
|             self.file, int(gST.value(gdb.selected_frame())))
 | |
|         if table:
 | |
|             print(table, '\n')
 | |
| 
 | |
| 
 | |
| class EfiSymbolsCmd (gdb.Command):
 | |
|     """Load Symbols for EFI. Type 'efi symbols -h' for more info."""
 | |
| 
 | |
|     def __init__(self):
 | |
|         super(EfiSymbolsCmd, self).__init__("efi symbols",
 | |
|                                             gdb.COMMAND_NONE,
 | |
|                                             gdb.COMPLETE_EXPRESSION)
 | |
|         self.file = GdbFileObject()
 | |
|         self.gST = None
 | |
|         self.efi_symbols = EfiSymbols(self.file)
 | |
| 
 | |
|     def create_options(self, arg, from_tty):
 | |
|         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. "
 | |
|             "Given any address search backward for the PE/COFF (or TE header) "
 | |
|             "and then parse the PE/COFF image to get debug info. "
 | |
|             "The address can come from the current pc, pc values in the "
 | |
|             "frame, or an address provided to the command"
 | |
|             "")
 | |
| 
 | |
|         self.parser = optparse.OptionParser(
 | |
|             description=description,
 | |
|             prog='efi symbols',
 | |
|             usage=usage,
 | |
|             add_help_option=False)
 | |
| 
 | |
|         self.parser.add_option(
 | |
|             '-a',
 | |
|             '--address',
 | |
|             type="str",
 | |
|             dest='address',
 | |
|             help='Load symbols for image that contains address',
 | |
|             default=None)
 | |
| 
 | |
|         self.parser.add_option(
 | |
|             '-c',
 | |
|             '--clear',
 | |
|             action='store_true',
 | |
|             dest='clear',
 | |
|             help='Clear the cache of loaded images',
 | |
|             default=False)
 | |
| 
 | |
|         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(
 | |
|             '-v',
 | |
|             '--verbose',
 | |
|             action='store_true',
 | |
|             dest='verbose',
 | |
|             help='Show more info on symbols loading in gdb',
 | |
|             default=False)
 | |
| 
 | |
|         self.parser.add_option(
 | |
|             '-h',
 | |
|             '--help',
 | |
|             action='store_true',
 | |
|             dest='help',
 | |
|             help='Show help for the command',
 | |
|             default=False)
 | |
| 
 | |
|         return self.parser.parse_args(shlex.split(arg))
 | |
| 
 | |
|     def save_user_state(self):
 | |
|         self.pagination = gdb.parameter("pagination")
 | |
|         if self.pagination:
 | |
|             gdb.execute("set pagination off")
 | |
| 
 | |
|         self.user_selected_thread = gdb.selected_thread()
 | |
|         self.user_selected_frame = gdb.selected_frame()
 | |
| 
 | |
|     def restore_user_state(self):
 | |
|         self.user_selected_thread.switch()
 | |
|         self.user_selected_frame.select()
 | |
| 
 | |
|         if self.pagination:
 | |
|             gdb.execute("set pagination on")
 | |
| 
 | |
|     def canonical_address(self, address):
 | |
|         '''
 | |
|         Scrub out 48-bit non canonical addresses
 | |
|         Raw frames in gdb can have some funky values
 | |
|         '''
 | |
| 
 | |
|         # Skip lowest 256 bytes to avoid interrupt frames
 | |
|         if address > 0xFF and address < 0x00007FFFFFFFFFFF:
 | |
|             return True
 | |
|         if address >= 0xFFFF800000000000:
 | |
|             return True
 | |
| 
 | |
|         return False
 | |
| 
 | |
|     def pc_set_for_frames(self):
 | |
|         '''Return a set for the PC's in the current frame'''
 | |
|         pc_list = []
 | |
|         frame = gdb.newest_frame()
 | |
|         while frame:
 | |
|             pc = int(frame.read_register('pc'))
 | |
|             if self.canonical_address(pc):
 | |
|                 pc_list.append(pc)
 | |
|             frame = frame.older()
 | |
| 
 | |
|         return set(pc_list)
 | |
| 
 | |
|     def invoke(self, arg, from_tty):
 | |
|         '''gdb command to symbolicate all the frames from all the threads'''
 | |
| 
 | |
|         try:
 | |
|             (options, _) = self.create_options(arg, from_tty)
 | |
|             if options.help:
 | |
|                 self.parser.print_help()
 | |
|                 return
 | |
|         except ValueError:
 | |
|             print('bad arguments!')
 | |
|             return
 | |
| 
 | |
|         self.dont_repeat()
 | |
| 
 | |
|         self.save_user_state()
 | |
| 
 | |
|         if options.clear:
 | |
|             self.efi_symbols.clear()
 | |
|             return
 | |
| 
 | |
|         if options.pei:
 | |
|             # XIP code can be 4 byte aligned in the FV
 | |
|             options.stride = 4
 | |
|             options.range = 0x100000
 | |
|         self.efi_symbols.configure_search(options.stride,
 | |
|                                           options.range,
 | |
|                                           options.verbose)
 | |
| 
 | |
|         if options.thread:
 | |
|             thread_list = gdb.selected_inferior().threads()
 | |
|         else:
 | |
|             thread_list = (gdb.selected_thread(),)
 | |
| 
 | |
|         address = None
 | |
|         if options.address:
 | |
|             value = gdb.parse_and_eval(options.address)
 | |
|             address = int(value)
 | |
|         elif options.pc:
 | |
|             address = gdb.selected_frame().pc()
 | |
| 
 | |
|         if address:
 | |
|             res = self.efi_symbols.address_to_symbols(address)
 | |
|             print(res)
 | |
|         else:
 | |
| 
 | |
|             for thread in thread_list:
 | |
|                 thread.switch()
 | |
| 
 | |
|                 # You can not iterate over frames as you load symbols. Loading
 | |
|                 # symbols changes the frames gdb can see due to inlining and
 | |
|                 # boom. So we loop adding symbols for the current frame, and
 | |
|                 # we test to see if new frames have shown up. If new frames
 | |
|                 # show up we process those new frames. Thus 1st pass is the
 | |
|                 # raw frame, and other passes are only new PC values.
 | |
|                 NewPcSet = self.pc_set_for_frames()
 | |
|                 while NewPcSet:
 | |
|                     PcSet = self.pc_set_for_frames()
 | |
|                     for pc in NewPcSet:
 | |
|                         res = self.efi_symbols.address_to_symbols(pc)
 | |
|                         print(res)
 | |
| 
 | |
|                     NewPcSet = PcSet.symmetric_difference(
 | |
|                         self.pc_set_for_frames())
 | |
| 
 | |
|         # find the EFI System tables the 1st time
 | |
|         if self.gST is None:
 | |
|             gST = gdb.lookup_global_symbol('gST')
 | |
|             if gST is not None:
 | |
|                 self.gST = int(gST.value(gdb.selected_frame()))
 | |
|                 table = EfiConfigurationTable(self.file, self.gST)
 | |
|             else:
 | |
|                 table = None
 | |
|         else:
 | |
|             table = EfiConfigurationTable(self.file, self.gST)
 | |
| 
 | |
|         if options.extended and table:
 | |
|             # load symbols from EFI System Table entry
 | |
|             for address, _ in table.DebugImageInfo():
 | |
|                 res = self.efi_symbols.address_to_symbols(address)
 | |
|                 print(res)
 | |
| 
 | |
|         # sync up the GUID database from the build output
 | |
|         for m in gdb.objfiles():
 | |
|             if GuidNames.add_build_guid_file(str(m.filename)):
 | |
|                 break
 | |
| 
 | |
|         self.restore_user_state()
 | |
| 
 | |
| 
 | |
| class EfiCmd (gdb.Command):
 | |
|     """Commands for debugging EFI. efi <cmd>"""
 | |
| 
 | |
|     def __init__(self):
 | |
|         super(EfiCmd, self).__init__("efi",
 | |
|                                      gdb.COMMAND_NONE,
 | |
|                                      gdb.COMPLETE_NONE,
 | |
|                                      True)
 | |
| 
 | |
|     def invoke(self, arg, from_tty):
 | |
|         '''default to loading symbols'''
 | |
|         if '-h' in arg or '--help' in arg:
 | |
|             gdb.execute('help efi')
 | |
|         else:
 | |
|             # default to loading all symbols
 | |
|             gdb.execute('efi symbols --extended')
 | |
| 
 | |
| 
 | |
| class LoadEmulatorEfiSymbols(gdb.Breakpoint):
 | |
|     '''
 | |
|     breakpoint for EmulatorPkg to load symbols
 | |
|     Note: make sure SecGdbScriptBreak is not optimized away!
 | |
|     Also turn off the dlopen() flow like on macOS.
 | |
|     '''
 | |
|     def stop(self):
 | |
|         symbols = EfiSymbols()
 | |
|         # Emulator adds SizeOfHeaders so we need file alignment to search
 | |
|         symbols.configure_search(0x20)
 | |
| 
 | |
|         frame = gdb.newest_frame()
 | |
| 
 | |
|         try:
 | |
|             # gdb was looking at spill address, pre spill :(
 | |
|             LoadAddress = frame.read_register('rdx')
 | |
|             AddSymbolFlag = frame.read_register('rcx')
 | |
|         except gdb.error:
 | |
|             LoadAddress = frame.read_var('LoadAddress')
 | |
|             AddSymbolFlag = frame.read_var('AddSymbolFlag')
 | |
| 
 | |
|         if AddSymbolFlag == 1:
 | |
|             res = symbols.address_to_symbols(LoadAddress)
 | |
|         else:
 | |
|             res = symbols.unload_symbols(LoadAddress)
 | |
|         print(res)
 | |
| 
 | |
|         # keep running
 | |
|         return False
 | |
| 
 | |
| 
 | |
| # Get python backtraces to debug errors in this script
 | |
| gdb.execute("set python print-stack full")
 | |
| 
 | |
| # tell efi_debugging how to walk data structures with pointers
 | |
| try:
 | |
|     pointer_width = gdb.lookup_type('int').pointer().sizeof
 | |
| except ValueError:
 | |
|     pointer_width = 8
 | |
| patch_ctypes(pointer_width)
 | |
| 
 | |
| register_pretty_printer(None, build_pretty_printer(), replace=True)
 | |
| 
 | |
| # gdb commands that we are adding
 | |
| # add `efi` prefix gdb command
 | |
| EfiCmd()
 | |
| 
 | |
| # subcommands for `efi`
 | |
| EfiSymbolsCmd()
 | |
| EfiTablesCmd()
 | |
| EfiHobCmd()
 | |
| EfiDevicePathCmd()
 | |
| EfiGuidCmd()
 | |
| 
 | |
| #
 | |
| bp = LoadEmulatorEfiSymbols('SecGdbScriptBreak', internal=True)
 | |
| if bp.pending:
 | |
|     try:
 | |
|         gdb.selected_frame()
 | |
|         # Not the emulator so do this when you attach
 | |
|         gdb.execute('efi symbols --frame --extended', True)
 | |
|         gdb.execute('bt')
 | |
|         # If you want to skip the above commands comment them out
 | |
|         pass
 | |
|     except gdb.error:
 | |
|         # If you load the script and there is no target ignore the error.
 | |
|         pass
 | |
| else:
 | |
|     # start the emulator
 | |
|     gdb.execute('run')
 |