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')
 |