]> git.proxmox.com Git - mirror_edk2.git/blobdiff - BaseTools/Scripts/efi_gdb.py
BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
[mirror_edk2.git] / BaseTools / Scripts / efi_gdb.py
diff --git a/BaseTools/Scripts/efi_gdb.py b/BaseTools/Scripts/efi_gdb.py
new file mode 100755 (executable)
index 0000000..f3e7fd9
--- /dev/null
@@ -0,0 +1,918 @@
+#!/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