|
@@ -0,0 +1,918 @@
|
|
|
+#!/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')
|