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