public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [PATCH v2 0/3] BaseTools: Add support for gdb and lldb
@ 2022-03-21 20:20 Rebecca Cran
  2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw)
  To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng,
	Liming Gao, Yuwei Chen
  Cc: Rebecca Cran, Andrew Fish

This patch set adds debugging support for gdb and lldb.
It also adds generic debugging classes that use a file like object to
make it easy to import into any debugger that supports Python.

Changes from v1 to v2:
- Moved scripts from the root of the repo into BaseTools/Scripts.
- Fixed typo of "RISCV" as "RISKV".

Testing:
- Tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
- Tested gdb on openSUSE for AARCH64.

Rebecca Cran (3):
  BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes
  BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
  BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print

 BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++
 BaseTools/Scripts/efi_gdb.py       |  918 ++++++++
 BaseTools/Scripts/efi_lldb.py      | 1044 ++++++++++
 3 files changed, 4147 insertions(+)
 create mode 100755 BaseTools/Scripts/efi_debugging.py
 create mode 100755 BaseTools/Scripts/efi_gdb.py
 create mode 100755 BaseTools/Scripts/efi_lldb.py

-- 
2.34.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes
  2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
@ 2022-03-21 20:20 ` Rebecca Cran
  2022-03-26 14:04   ` Bob Feng
  2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw)
  To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng,
	Liming Gao, Yuwei Chen
  Cc: Rebecca Cran, Andrew Fish

Add Scripts/efi_debugging.py to provide debugger agnostic debugging
utility Python classes.

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>
---
 BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++
 1 file changed, 2185 insertions(+)

diff --git a/BaseTools/Scripts/efi_debugging.py b/BaseTools/Scripts/efi_debugging.py
new file mode 100755
index 000000000000..af975f4e6ceb
--- /dev/null
+++ b/BaseTools/Scripts/efi_debugging.py
@@ -0,0 +1,2185 @@
+#!/usr/bin/python3
+'''
+Copyright (c) Apple Inc. 2021
+SPDX-License-Identifier: BSD-2-Clause-Patent
+
+Class that abstracts PE/COFF debug info parsing via a Python file like
+object. You can port this code into an arbitrary debugger by invoking
+the classes and passing in a file like object that abstracts the debugger
+reading memory.
+
+If you run this file directly it will parse the passed in PE/COFF files
+for debug info:
+python3 ./efi_pefcoff.py DxeCore.efi
+IA32`<path...>/DxeCore.dll load = 0x00000000
+EntryPoint = 0x000030d2  TextAddress = 0x00000240 DataAddress = 0x000042c0
+.text    0x00000240 (0x04080) flags:0x60000020
+.data    0x000042C0 (0x001C0) flags:0xC0000040
+.reloc   0x00004480 (0x00240) flags:0x42000040
+
+Note: PeCoffClass uses virtual addresses and not file offsets.
+       It needs to work when images are loaded into memory.
+       as long as virtual address map to file addresses this
+       code can process binary files.
+
+Note: This file can also contain generic worker functions (like GuidNames)
+      that abstract debugger agnostic services to the debugger.
+
+This file should never import debugger specific modules.
+'''
+
+import sys
+import os
+import uuid
+import struct
+import re
+from ctypes import c_char, c_uint8, c_uint16, c_uint32, c_uint64, c_void_p
+from ctypes import ARRAY, sizeof
+from ctypes import Structure, LittleEndianStructure
+
+#
+# The empty LittleEndianStructure must have _fields_ assigned prior to use or
+#  sizeof(). Anything that is size UINTN may need to get adjusted.
+#
+# The issue is ctypes matches our local machine, not the machine we are
+#  trying to debug. Call patch_ctypes() passing in the byte width from the
+#  debugger python to make sure you are in sync.
+#
+# Splitting out the _field_ from the Structure (LittleEndianStructure) class
+#  allows it to be patched.
+#
+
+
+class EFI_LOADED_IMAGE_PROTOCOL(LittleEndianStructure):
+    pass
+
+
+EFI_LOADED_IMAGE_PROTOCOL_fields_ = [
+    ('Revision',                      c_uint32),
+    ('ParentHandle',                  c_void_p),
+    ('SystemTable',                   c_void_p),
+    ('DeviceHandle',                  c_void_p),
+    ('FilePath',                      c_void_p),
+    ('Reserved',                      c_void_p),
+    ('LoadOptionsSize',               c_uint32),
+    ('LoadOptions',                   c_void_p),
+    ('ImageBase',                     c_void_p),
+    ('ImageSize',                     c_uint64),
+    ('ImageCodeType',                 c_uint32),
+    ('ImageDataType',                 c_uint32),
+    ('Unload',                        c_void_p),
+]
+
+
+class EFI_GUID(LittleEndianStructure):
+    _fields_ = [
+        ('Data1',               c_uint32),
+        ('Data2',               c_uint16),
+        ('Data3',               c_uint16),
+        ('Data4',               ARRAY(c_uint8, 8))
+    ]
+
+
+class EFI_SYSTEM_TABLE_POINTER(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',                     c_uint64),
+        ('EfiSystemTableBase',            c_uint64),
+        ('Crc32',                         c_uint32)
+    ]
+
+
+class EFI_DEBUG_IMAGE_INFO_NORMAL(LittleEndianStructure):
+    pass
+
+
+EFI_DEBUG_IMAGE_INFO_NORMAL_fields_ = [
+    ('ImageInfoType',                 c_uint32),
+    ('LoadedImageProtocolInstance',   c_void_p),
+    ('ImageHandle',                   c_void_p)
+]
+
+
+class EFI_DEBUG_IMAGE_INFO(LittleEndianStructure):
+    pass
+
+
+EFI_DEBUG_IMAGE_INFO_fields_ = [
+    ('NormalImage',                   c_void_p),
+]
+
+
+class EFI_DEBUG_IMAGE_INFO_TABLE_HEADER(LittleEndianStructure):
+    pass
+
+
+EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_ = [
+    ('UpdateStatus',                  c_uint32),
+    ('TableSize',                     c_uint32),
+    ('EfiDebugImageInfoTable',        c_void_p),
+]
+
+
+class EFI_TABLE_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',                     c_uint64),
+        ('Revision',                      c_uint32),
+        ('HeaderSize',                    c_uint32),
+        ('CRC32',                         c_uint32),
+        ('Reserved',                      c_uint32),
+    ]
+
+
+class EFI_CONFIGURATION_TABLE(LittleEndianStructure):
+    pass
+
+
+EFI_CONFIGURATION_TABLE_fields_ = [
+    ('VendorGuid',                    EFI_GUID),
+    ('VendorTable',                   c_void_p)
+]
+
+
+class EFI_SYSTEM_TABLE(LittleEndianStructure):
+    pass
+
+
+EFI_SYSTEM_TABLE_fields_ = [
+    ('Hdr',                           EFI_TABLE_HEADER),
+    ('FirmwareVendor',                c_void_p),
+    ('FirmwareRevision',              c_uint32),
+    ('ConsoleInHandle',               c_void_p),
+    ('ConIn',                         c_void_p),
+    ('ConsoleOutHandle',              c_void_p),
+    ('ConOut',                        c_void_p),
+    ('StandardErrHandle',             c_void_p),
+    ('StdErr',                        c_void_p),
+    ('RuntimeService',                c_void_p),
+    ('BootService',                   c_void_p),
+    ('NumberOfTableEntries',          c_void_p),
+    ('ConfigurationTable',            c_void_p),
+]
+
+
+class EFI_IMAGE_DATA_DIRECTORY(LittleEndianStructure):
+    _fields_ = [
+        ('VirtualAddress',       c_uint32),
+        ('Size',                 c_uint32)
+    ]
+
+
+class EFI_TE_IMAGE_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',            ARRAY(c_char, 2)),
+        ('Machine',              c_uint16),
+        ('NumberOfSections',     c_uint8),
+        ('Subsystem',            c_uint8),
+        ('StrippedSize',         c_uint16),
+        ('AddressOfEntryPoint',  c_uint32),
+        ('BaseOfCode',           c_uint32),
+        ('ImageBase',            c_uint64),
+        ('DataDirectoryBaseReloc',  EFI_IMAGE_DATA_DIRECTORY),
+        ('DataDirectoryDebug',      EFI_IMAGE_DATA_DIRECTORY)
+    ]
+
+
+class EFI_IMAGE_DOS_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('e_magic',              c_uint16),
+        ('e_cblp',               c_uint16),
+        ('e_cp',                 c_uint16),
+        ('e_crlc',               c_uint16),
+        ('e_cparhdr',            c_uint16),
+        ('e_minalloc',           c_uint16),
+        ('e_maxalloc',           c_uint16),
+        ('e_ss',                 c_uint16),
+        ('e_sp',                 c_uint16),
+        ('e_csum',               c_uint16),
+        ('e_ip',                 c_uint16),
+        ('e_cs',                 c_uint16),
+        ('e_lfarlc',             c_uint16),
+        ('e_ovno',               c_uint16),
+        ('e_res',                ARRAY(c_uint16, 4)),
+        ('e_oemid',              c_uint16),
+        ('e_oeminfo',            c_uint16),
+        ('e_res2',               ARRAY(c_uint16, 10)),
+        ('e_lfanew',             c_uint16)
+    ]
+
+
+class EFI_IMAGE_FILE_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Machine',               c_uint16),
+        ('NumberOfSections',      c_uint16),
+        ('TimeDateStamp',         c_uint32),
+        ('PointerToSymbolTable',  c_uint32),
+        ('NumberOfSymbols',       c_uint32),
+        ('SizeOfOptionalHeader',  c_uint16),
+        ('Characteristics',       c_uint16)
+    ]
+
+
+class EFI_IMAGE_OPTIONAL_HEADER32(LittleEndianStructure):
+    _fields_ = [
+        ('Magic',                         c_uint16),
+        ('MajorLinkerVersion',            c_uint8),
+        ('MinorLinkerVersion',            c_uint8),
+        ('SizeOfCode',                    c_uint32),
+        ('SizeOfInitializedData',         c_uint32),
+        ('SizeOfUninitializedData',       c_uint32),
+        ('AddressOfEntryPoint',           c_uint32),
+        ('BaseOfCode',                    c_uint32),
+        ('BaseOfData',                    c_uint32),
+        ('ImageBase',                     c_uint32),
+        ('SectionAlignment',              c_uint32),
+        ('FileAlignment',                 c_uint32),
+        ('MajorOperatingSystemVersion',   c_uint16),
+        ('MinorOperatingSystemVersion',   c_uint16),
+        ('MajorImageVersion',             c_uint16),
+        ('MinorImageVersion',             c_uint16),
+        ('MajorSubsystemVersion',         c_uint16),
+        ('MinorSubsystemVersion',         c_uint16),
+        ('Win32VersionValue',             c_uint32),
+        ('SizeOfImage',                   c_uint32),
+        ('SizeOfHeaders',                 c_uint32),
+        ('CheckSum',                 c_uint32),
+        ('Subsystem',                     c_uint16),
+        ('DllCharacteristics',            c_uint16),
+        ('SizeOfStackReserve',            c_uint32),
+        ('SizeOfStackCommit',            c_uint32),
+        ('SizeOfHeapReserve',             c_uint32),
+        ('SizeOfHeapCommit',             c_uint32),
+        ('LoaderFlags',              c_uint32),
+        ('NumberOfRvaAndSizes',           c_uint32),
+        ('DataDirectory',                 ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16))
+    ]
+
+
+class EFI_IMAGE_NT_HEADERS32(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',            c_uint32),
+        ('FileHeader',           EFI_IMAGE_FILE_HEADER),
+        ('OptionalHeader',       EFI_IMAGE_OPTIONAL_HEADER32)
+    ]
+
+
+class EFI_IMAGE_OPTIONAL_HEADER64(LittleEndianStructure):
+    _fields_ = [
+        ('Magic',                         c_uint16),
+        ('MajorLinkerVersion',            c_uint8),
+        ('MinorLinkerVersion',            c_uint8),
+        ('SizeOfCode',                    c_uint32),
+        ('SizeOfInitializedData',         c_uint32),
+        ('SizeOfUninitializedData',       c_uint32),
+        ('AddressOfEntryPoint',           c_uint32),
+        ('BaseOfCode',                    c_uint32),
+        ('BaseOfData',                    c_uint32),
+        ('ImageBase',                     c_uint32),
+        ('SectionAlignment',              c_uint32),
+        ('FileAlignment',                 c_uint32),
+        ('MajorOperatingSystemVersion',   c_uint16),
+        ('MinorOperatingSystemVersion',   c_uint16),
+        ('MajorImageVersion',             c_uint16),
+        ('MinorImageVersion',             c_uint16),
+        ('MajorSubsystemVersion',         c_uint16),
+        ('MinorSubsystemVersion',         c_uint16),
+        ('Win32VersionValue',             c_uint32),
+        ('SizeOfImage',                   c_uint32),
+        ('SizeOfHeaders',                 c_uint32),
+        ('CheckSum',                 c_uint32),
+        ('Subsystem',                     c_uint16),
+        ('DllCharacteristics',            c_uint16),
+        ('SizeOfStackReserve',            c_uint64),
+        ('SizeOfStackCommit',            c_uint64),
+        ('SizeOfHeapReserve',             c_uint64),
+        ('SizeOfHeapCommit',             c_uint64),
+        ('LoaderFlags',              c_uint32),
+        ('NumberOfRvaAndSizes',           c_uint32),
+        ('DataDirectory',                 ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16))
+    ]
+
+
+class EFI_IMAGE_NT_HEADERS64(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',            c_uint32),
+        ('FileHeader',           EFI_IMAGE_FILE_HEADER),
+        ('OptionalHeader',       EFI_IMAGE_OPTIONAL_HEADER64)
+    ]
+
+
+class EFI_IMAGE_DEBUG_DIRECTORY_ENTRY(LittleEndianStructure):
+    _fields_ = [
+        ('Characteristics',            c_uint32),
+        ('TimeDateStamp',              c_uint32),
+        ('MajorVersion',               c_uint16),
+        ('MinorVersion',               c_uint16),
+        ('Type',                       c_uint32),
+        ('SizeOfData',                 c_uint32),
+        ('RVA',                        c_uint32),
+        ('FileOffset',                 c_uint32),
+    ]
+
+
+class EFI_IMAGE_SECTION_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Name',                       ARRAY(c_char, 8)),
+        ('VirtualSize',                c_uint32),
+        ('VirtualAddress',             c_uint32),
+        ('SizeOfRawData',              c_uint32),
+        ('PointerToRawData',           c_uint32),
+        ('PointerToRelocations',       c_uint32),
+        ('PointerToLinenumbers',       c_uint32),
+        ('NumberOfRelocations',        c_uint16),
+        ('NumberOfLinenumbers',        c_uint16),
+        ('Characteristics',            c_uint32),
+    ]
+
+
+EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b
+EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
+
+DIRECTORY_DEBUG = 6
+
+
+image_machine_dict = {
+    0x014c: "IA32",
+    0x0200: "IPF",
+    0x0EBC: "EBC",
+    0x8664: "X64",
+    0x01c2: "ARM",
+    0xAA64: "AArch64",
+    0x5032: "RISC32",
+    0x5064: "RISC64",
+    0x5128: "RISCV128",
+}
+
+
+def patch_void_p_to_ctype(patch_type, to_patch):
+    '''Optionally patch c_void_p in the Structure._fields_'''
+    if patch_type is None:
+        return to_patch
+
+    result = []
+    for name, c_type in to_patch:
+        if type(c_type) == type(c_void_p):
+            result.append((name, c_uint32))
+        else:
+            result.append((name, c_type))
+    return result
+
+
+def patch_ctypes(pointer_width=8):
+    '''
+    Pass in the pointer width of the system being debugged. If it is not
+    the same as c_void_p then patch the _fields_ with the correct type.
+    For any ctypes Structure that has a c_void_p this function needs to be
+    called prior to use or sizeof() to initialize _fields_.
+    '''
+
+    if sizeof(c_void_p) == pointer_width:
+        patch_type = None
+    elif pointer_width == 16:
+        assert False
+    elif pointer_width == 8:
+        patch_type = c_uint64
+    elif pointer_width == 4:
+        patch_type = c_uint32
+    else:
+        raise Exception(f'ERROR: Unkown pointer_width = {pointer_width}')
+
+    # If you add a ctypes Structure class with a c_void_p you need to add
+    # it to this list. Note: you should use c_void_p for UINTN values.
+    EFI_LOADED_IMAGE_PROTOCOL._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_LOADED_IMAGE_PROTOCOL_fields_)
+    EFI_DEBUG_IMAGE_INFO_NORMAL._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_DEBUG_IMAGE_INFO_NORMAL_fields_)
+    EFI_DEBUG_IMAGE_INFO._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_DEBUG_IMAGE_INFO_fields_)
+    EFI_DEBUG_IMAGE_INFO_TABLE_HEADER._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_)
+    EFI_CONFIGURATION_TABLE._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_CONFIGURATION_TABLE_fields_)
+    EFI_SYSTEM_TABLE._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_SYSTEM_TABLE_fields_)
+
+    # patch up anything else that needs to know pointer_width
+    EfiStatusClass(pointer_width)
+
+
+def ctype_to_str(ctype, indent='', hide_list=[]):
+    '''
+    Given a ctype object print out as a string by walking the _fields_
+    in the cstring Class
+     '''
+    result = ''
+    for field in ctype._fields_:
+        attr = getattr(ctype, field[0])
+        tname = type(attr).__name__
+        if field[0] in hide_list:
+            continue
+
+        result += indent + f'{field[0]} = '
+        if tname == 'EFI_GUID':
+            result += GuidNames.to_name(GuidNames.to_uuid(attr)) + '\n'
+        elif issubclass(type(attr), Structure):
+            result += f'{tname}\n' + \
+                ctype_to_str(attr, indent + '  ', hide_list)
+        elif isinstance(attr, int):
+            result += f'0x{attr:x}\n'
+        else:
+            result += f'{attr}\n'
+
+    return result
+
+
+def hexline(addr, data):
+    hexstr = ''
+    printable = ''
+    for i in range(0, len(data)):
+        hexstr += f'{data[i]:02x} '
+        printable += chr(data[i]) if data[i] > 0x20 and data[i] < 0x7f else '.'
+    return f'{addr:04x}  {hexstr:48s} |{printable:s}|'
+
+
+def hexdump(data, indent=''):
+    if not isinstance(data, bytearray):
+        data = bytearray(data)
+
+    result = ''
+    for i in range(0, len(data), 16):
+        result += indent + hexline(i, data[i:i+16]) + '\n'
+    return result
+
+
+class EfiTpl:
+    ''' Return string for EFI_TPL'''
+
+    def __init__(self, tpl):
+        self.tpl = tpl
+
+    def __str__(self):
+        if self.tpl < 4:
+            result = f'{self.tpl:d}'
+        elif self.tpl < 8:
+            result = "TPL_APPLICATION"
+            if self.tpl - 4 > 0:
+                result += f' + {self.tpl - 4:d}'
+        elif self.tpl < 16:
+            result = "TPL_CALLBACK"
+            if self.tpl - 8 > 0:
+                result += f' + {self.tpl - 8:d}'
+        elif self.tpl < 31:
+            result = "TPL_NOTIFY"
+            if self.tpl - 16 > 0:
+                result += f' + {self.tpl - 16:d}'
+        elif self.tpl == 31:
+            result = "TPL_HIGH_LEVEL"
+        else:
+            result = f'Invalid TPL = {self.tpl:d}'
+        return result
+
+
+class EfiBootMode:
+    '''
+    Class to return human readable string for EFI_BOOT_MODE
+
+    Methods
+    -----------
+    to_str(boot_mode, default)
+        return string for boot_mode, and return default if there is not a
+        match.
+    '''
+
+    EFI_BOOT_MODE_dict = {
+        0x00: "BOOT_WITH_FULL_CONFIGURATION",
+        0x01: "BOOT_WITH_MINIMAL_CONFIGURATION",
+        0x02: "BOOT_ASSUMING_NO_CONFIGURATION_CHANGES",
+        0x03: "BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS",
+        0x04: "BOOT_WITH_DEFAULT_SETTINGS",
+        0x05: "BOOT_ON_S4_RESUME",
+        0x06: "BOOT_ON_S5_RESUME",
+        0x07: "BOOT_WITH_MFG_MODE_SETTINGS",
+        0x10: "BOOT_ON_S2_RESUME",
+        0x11: "BOOT_ON_S3_RESUME",
+        0x12: "BOOT_ON_FLASH_UPDATE",
+        0x20: "BOOT_IN_RECOVERY_MODE",
+    }
+
+    def __init__(self, boot_mode):
+        self._boot_mode = boot_mode
+
+    def __str__(self):
+        return self.to_str(self._boot_mode)
+
+    @classmethod
+    def to_str(cls, boot_mode, default=''):
+        return cls.EFI_BOOT_MODE_dict.get(boot_mode, default)
+
+
+class EfiStatusClass:
+    '''
+    Class to decode EFI_STATUS to a human readable string. You need to
+    pass in pointer_width to get the corret value since the EFI_STATUS
+    code values are different based on the sizeof UINTN. The default is
+    sizeof(UINTN) == 8.
+
+    Attributes
+    ——————
+    _dict_ : dictionary
+        dictionary of EFI_STATUS that has beed updated to match
+        pointer_width.
+
+    Methods
+    -----------
+    patch_dictionary(pointer_width)
+
+    to_str(status, default)
+    '''
+
+    _dict_ = {}
+    _EFI_STATUS_UINT32_dict = {
+        0: "Success",
+        1: "Warning Unknown Glyph",
+        2: "Warning Delete Failure",
+        3: "Warning Write Failure",
+        4: "Warning Buffer Too Small",
+        5: "Warning Stale Data",
+        6: "Warngin File System",
+        (0x20000000 | 0): "Warning interrupt source pending",
+        (0x20000000 | 1): "Warning interrupt source quiesced",
+
+        (0x80000000 | 1): "Load Error",
+        (0x80000000 | 2): "Invalid Parameter",
+        (0x80000000 | 3): "Unsupported",
+        (0x80000000 | 4): "Bad Buffer Size",
+        (0x80000000 | 5): "Buffer Too Small",
+        (0x80000000 | 6): "Not Ready",
+        (0x80000000 | 7): "Device Error",
+        (0x80000000 | 8): "Write Protected",
+        (0x80000000 | 9): "Out of Resources",
+        (0x80000000 | 10): "Volume Corrupt",
+        (0x80000000 | 11): "Volume Full",
+        (0x80000000 | 12): "No Media",
+        (0x80000000 | 13): "Media changed",
+        (0x80000000 | 14): "Not Found",
+        (0x80000000 | 15): "Access Denied",
+        (0x80000000 | 16): "No Response",
+        (0x80000000 | 17): "No mapping",
+        (0x80000000 | 18): "Time out",
+        (0x80000000 | 19): "Not started",
+        (0x80000000 | 20): "Already started",
+        (0x80000000 | 21): "Aborted",
+        (0x80000000 | 22): "ICMP Error",
+        (0x80000000 | 23): "TFTP Error",
+        (0x80000000 | 24): "Protocol Error",
+        (0x80000000 | 25): "Incompatible Version",
+        (0x80000000 | 26): "Security Violation",
+        (0x80000000 | 27): "CRC Error",
+        (0x80000000 | 28): "End of Media",
+        (0x80000000 | 31): "End of File",
+        (0x80000000 | 32): "Invalid Language",
+        (0x80000000 | 33): "Compromised Data",
+        (0x80000000 | 35): "HTTP Error",
+
+        (0xA0000000 | 0): "Interrupt Pending",
+    }
+
+    def __init__(self, status=None, pointer_width=8):
+        self.status = status
+        # this will convert to 64-bit version if needed
+        self.patch_dictionary(pointer_width)
+
+    def __str__(self):
+        return self.to_str(self.status)
+
+    @classmethod
+    def to_str(cls, status, default=''):
+        return cls._dict_.get(status, default)
+
+    @classmethod
+    def patch_dictionary(cls, pointer_width):
+        '''Patch UINTN upper bits like values '''
+
+        if cls._dict_:
+            # only patch the class variable once
+            return False
+
+        if pointer_width == 4:
+            cls._dict = cls._EFI_STATUS_UINT32_dict
+        elif pointer_width == 8:
+            for key, value in cls._EFI_STATUS_UINT32_dict.items():
+                mask = (key & 0xE0000000) << 32
+                new_key = (key & 0x1FFFFFFF) | mask
+                cls._dict_[new_key] = value
+            return True
+        else:
+            return False
+
+
+class GuidNames:
+    '''
+    Class to expose the C names of EFI_GUID's. The _dict_ starts with
+    common EFI System Table entry EFI_GUID's. _dict_ can get updated with the
+    build generated Guid.xref file if a path to a module is passed
+    into add_build_guid_file(). If symbols are loaded for any module
+    in the build the path the build product should imply the
+    relative location of that builds Guid.xref file.
+
+    Attributes
+    ——————----
+    _dict_ : dictionary
+        dictionary of EFI_GUID (uuid) strings to C global names
+
+    Methods
+    -------
+    to_uuid(uuid)
+        convert a hex UUID string or bytearray to a uuid.UUID
+    to_name(uuid)
+        convert a UUID string to a C global constant name.
+    to_guid(guid_name)
+        convert a C global constant EFI_GUID name to uuid hex string.
+    is_guid_str(name)
+       name is a hex UUID string.
+       Example: 49152E77-1ADA-4764-B7A2-7AFEFED95E8B
+
+    to_c_guid(value)
+        convert a uuid.UUID or UUID string to a c_guid string
+        (see is_c_guid())
+    from_c_guid(value)
+        covert a C guid string to a hex UUID string.
+    is_c_guid(name)
+        name is the C initialization value for an EFI_GUID. Example:
+        { 0x414e6bdd, 0xe47b, 0x47cc, { 0xb2, 0x44, 0xbb, 0x61,
+                                        0x02, 0x0c, 0xf5, 0x16 }}
+
+    add_build_guid_file(module_path, custom_file):
+        assume module_path is an edk2 build product and load the Guid.xref
+        file from that build to fill in _dict_. If you know the path and
+        file name of a custom Guid.xref  you can pass it in as custom_file.
+
+    '''
+    _dict_ = {  # Common EFI System Table values
+        '05AD34BA-6F02-4214-952E-4DA0398E2BB9':
+            'gEfiDxeServicesTableGuid',
+        '7739F24C-93D7-11D4-9A3A-0090273FC14D':
+            'gEfiHobListGuid',
+        '4C19049F-4137-4DD3-9C10-8B97A83FFDFA':
+            'gEfiMemoryTypeInformationGuid',
+        '49152E77-1ADA-4764-B7A2-7AFEFED95E8B':
+            'gEfiDebugImageInfoTableGuid',
+        '060CC026-4C0D-4DDA-8F41-595FEF00A502':
+            'gMemoryStatusCodeRecordGuid',
+        'EB9D2D31-2D88-11D3-9A16-0090273FC14D':
+            'gEfiSmbiosTableGuid',
+        'EB9D2D30-2D88-11D3-9A16-0090273FC14D':
+            'gEfiAcpi10TableGuid',
+        '8868E871-E4F1-11D3-BC22-0080C73C8881':
+            'gEfiAcpi20TableGuid',
+    }
+
+    guid_files = []
+
+    def __init__(self, uuid=None, pointer_width=8):
+        self.uuid = None if uuid is None else self.to_uuid(uuid)
+
+    def __str__(self):
+        if self.uuid is None:
+            result = ''
+            for key, value in GuidNames._dict_.items():
+                result += f'{key}: {value}\n'
+        else:
+            result = self.to_name(self.uuid)
+
+        return result
+
+    @classmethod
+    def to_uuid(cls, obj):
+        try:
+            return uuid.UUID(bytes_le=bytes(obj))
+        except (ValueError, TypeError):
+            try:
+                return uuid.UUID(bytes_le=obj)
+            except (ValueError, TypeError):
+                return uuid.UUID(obj)
+
+    @classmethod
+    def to_name(cls, uuid):
+        if not isinstance(uuid, str):
+            uuid = str(uuid)
+        if cls.is_c_guid(uuid):
+            uuid = cls.from_c_guid(uuid)
+        return cls._dict_.get(uuid.upper(), uuid.upper())
+
+    @classmethod
+    def to_guid(cls, guid_name):
+        for key, value in cls._dict_.items():
+            if guid_name == value:
+                return key.upper()
+        else:
+            raise KeyError(key)
+
+    @classmethod
+    def is_guid_str(cls, name):
+        if not isinstance(name, str):
+            return False
+        return name.count('-') >= 4
+
+    @classmethod
+    def to_c_guid(cls, value):
+        if isinstance(value, uuid.UUID):
+            guid = value
+        else:
+            guid = uuid.UUID(value)
+
+        (data1, data2, data3,
+         data4_0, data4_1, data4_2, data4_3,
+         data4_4, data4_5, data4_6, data4_7) = struct.unpack(
+            '<IHH8B', guid.bytes_le)
+        return (f'{{ 0x{data1:08X}, 0x{data2:04X}, 0x{data3:04X}, '
+                f'{{ 0x{data4_0:02X}, 0x{data4_1:02X}, 0x{data4_2:02X}, '
+                f'0x{data4_3:02X}, 0x{data4_4:02X}, 0x{data4_5:02X}, '
+                f'0x{data4_6:02X}, 0x{data4_7:02X} }} }}')
+
+    @ classmethod
+    def from_c_guid(cls, value):
+        try:
+            hex = [int(x, 16) for x in re.findall(r"[\w']+", value)]
+            return (f'{hex[0]:08X}-{hex[1]:04X}-{hex[2]:04X}'
+                    + f'-{hex[3]:02X}{hex[4]:02X}-{hex[5]:02X}{hex[6]:02X}'
+                    + f'{hex[7]:02X}{hex[8]:02X}{hex[9]:02X}{hex[10]:02X}')
+        except ValueError:
+            return value
+
+    @ classmethod
+    def is_c_guid(cls, name):
+        if not isinstance(name, str):
+            return False
+        return name.count('{') == 2 and name.count('}') == 2
+
+    @ classmethod
+    def add_build_guid_file(cls, module_path, custom_file=None):
+        if custom_file is not None:
+            xref = custom_file
+        else:
+            # module_path will look like:
+            # <repo>/Build/OvmfX64/DEBUG_XCODE5/X64/../DxeCore.dll
+            # Walk backwards looking for a toolchain like name.
+            # Then look for GUID database:
+            # Build/OvmfX64//DEBUG_XCODE5/FV/Guid.xref
+            for i in reversed(module_path.split(os.sep)):
+                if (i.startswith('DEBUG_') or
+                    i.startswith('RELEASE_') or
+                        i.startswith('NOOPT_')):
+                    build_root = os.path.join(
+                        module_path.rsplit(i, 1)[0], i)
+                    break
+
+            xref = os.path.join(build_root, 'FV', 'Guid.xref')
+
+        if xref in cls.guid_files:
+            # only processes the file one time
+            return True
+
+        with open(xref) as f:
+            content = f.readlines()
+            cls.guid_files.append(xref)
+
+            for lines in content:
+                try:
+                    if cls.is_guid_str(lines):
+                        # a regex would be more pedantic
+                        words = lines.split()
+                        cls._dict_[words[0].upper()] = words[1].strip('\n')
+                except ValueError:
+                    pass
+
+            return True
+
+        return False
+
+
+class EFI_HOB_GENERIC_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('HobType',             c_uint16),
+        ('HobLength',           c_uint16),
+        ('Reserved',            c_uint32)
+    ]
+
+
+class EFI_HOB_HANDOFF_INFO_TABLE(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Version',             c_uint32),
+        ('BootMode',            c_uint32),
+        ('EfiMemoryTop',        c_uint64),
+        ('EfiMemoryBottom',     c_uint64),
+        ('EfiFreeMemoryTop',    c_uint64),
+        ('EfiFreeMemoryBottom', c_uint64),
+        ('EfiEndOfHobList',     c_uint64),
+    ]
+
+
+class EFI_HOB_MEMORY_ALLOCATION(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Name',                EFI_GUID),
+        ('MemoryBaseAddress',   c_uint64),
+        ('MemoryLength',        c_uint64),
+        ('MemoryType',          c_uint32),
+        ('Reserved',            c_uint32),
+    ]
+
+
+class EFI_HOB_RESOURCE_DESCRIPTOR(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Owner',               EFI_GUID),
+        ('ResourceType',        c_uint32),
+        ('ResourceAttribute',   c_uint32),
+        ('PhysicalStart',       c_uint64),
+        ('ResourceLength',      c_uint64),
+    ]
+
+
+class EFI_HOB_GUID_TYPE(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Name',                EFI_GUID),
+    ]
+
+
+class EFI_HOB_FIRMWARE_VOLUME(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+    ]
+
+
+class EFI_HOB_CPU(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('SizeOfMemorySpace',   c_uint8),
+        ('SizeOfIoSpace',       c_uint8),
+        ('Reserved',            ARRAY(c_uint8, 6)),
+    ]
+
+
+class EFI_HOB_MEMORY_POOL(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+    ]
+
+
+class EFI_HOB_FIRMWARE_VOLUME2(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+        ('FvName',              EFI_GUID),
+        ('FileName',            EFI_GUID)
+    ]
+
+
+class EFI_HOB_FIRMWARE_VOLUME3(LittleEndianStructure):
+    _fields_ = [
+        ('HobType',             c_uint16),
+        ('HobLength',           c_uint16),
+        ('Reserved',            c_uint32),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+        ('AuthenticationStatus', c_uint32),
+        ('ExtractedFv',         c_uint8),
+        ('FvName',              EFI_GUID),
+        ('FileName',            EFI_GUID),
+    ]
+
+
+class EFI_HOB_UEFI_CAPSULE(LittleEndianStructure):
+    _fields_ = [
+        ('HobType',             c_uint16),
+        ('HobLength',           c_uint16),
+        ('Reserved',            c_uint32),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+    ]
+
+
+class EfiHob:
+    '''
+    Parse EFI Device Paths based on the edk2 C Structures defined above.
+    In the context of this class verbose means hexdump extra data.
+
+
+    Attributes
+    ——————
+    Hob : list
+        List of HOBs. Each entry contains the name, HOB type, HOB length,
+        the ctype struct for the HOB, and any extra data.
+
+    Methods
+    -----------
+    get_hob_by_type(hob_type)
+        return string that decodes the HOBs of hob_type. If hob_type is
+        None then return all HOBs.
+    '''
+
+    Hob = []
+    verbose = False
+
+    hob_dict = {
+        1: EFI_HOB_HANDOFF_INFO_TABLE,
+        2: EFI_HOB_MEMORY_ALLOCATION,
+        3: EFI_HOB_RESOURCE_DESCRIPTOR,
+        4: EFI_HOB_GUID_TYPE,
+        5: EFI_HOB_FIRMWARE_VOLUME,
+        6: EFI_HOB_CPU,
+        7: EFI_HOB_MEMORY_POOL,
+        9: EFI_HOB_FIRMWARE_VOLUME2,
+        0xb: EFI_HOB_UEFI_CAPSULE,
+        0xc: EFI_HOB_FIRMWARE_VOLUME3,
+        0xffff: EFI_HOB_GENERIC_HEADER,
+    }
+
+    def __init__(self, file, address=None, verbose=False, count=1000):
+        self._file = file
+        EfiHob.verbose = verbose
+
+        if len(EfiHob.Hob) != 0 and address is None:
+            return
+
+        if address is not None:
+            hob_ptr = address
+        else:
+            hob_ptr = EfiConfigurationTable(file).GetConfigTable(
+                '7739F24C-93D7-11D4-9A3A-0090273FC14D')
+
+        self.read_hobs(hob_ptr)
+
+    @ classmethod
+    def __str__(cls):
+        return cls.get_hob_by_type(None)
+
+    @ classmethod
+    def get_hob_by_type(cls, hob_type):
+        result = ""
+        for (Name, HobType, HobLen, chob, extra) in cls.Hob:
+            if hob_type is not None:
+                if hob_type != HobType:
+                    continue
+
+            result += f'Type: {Name:s} (0x{HobType:01x}) Len: 0x{HobLen:03x}\n'
+            result += ctype_to_str(chob, '  ', ['Reserved'])
+            if cls.verbose:
+                if extra is not None:
+                    result += hexdump(extra, '    ')
+
+        return result
+
+    def read_hobs(self, hob_ptr, count=1000):
+        if hob_ptr is None:
+            return
+
+        try:
+            for _ in range(count):  # while True
+                hdr, _ = self._ctype_read_ex(EFI_HOB_GENERIC_HEADER, hob_ptr)
+                if hdr.HobType == 0xffff:
+                    break
+
+                type_str = self.hob_dict.get(
+                    hdr.HobType, EFI_HOB_GENERIC_HEADER)
+                hob, extra = self._ctype_read_ex(
+                    type_str, hob_ptr, hdr.HobLength)
+                EfiHob.Hob.append(
+                    (type(hob).__name__,
+                     hdr.HobType,
+                     hdr.HobLength,
+                     hob,
+                     extra))
+                hob_ptr += hdr.HobLength
+        except ValueError:
+            pass
+
+    def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None):
+        if offset != 0:
+            self._file.seek(offset)
+
+        type_size = sizeof(ctype_struct)
+        size = rsize if rsize else type_size
+        data = self._file.read(size)
+        cdata = ctype_struct.from_buffer(bytearray(data))
+
+        if size > type_size:
+            return cdata, data[type_size:]
+        else:
+            return cdata, None
+
+
+class EFI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Type',                c_uint8),
+        ('SubType',             c_uint8),
+
+        # UINT8 Length[2]
+        # Cheat and use c_uint16 since we don't care about alignment
+        ('Length',              c_uint16)
+    ]
+
+
+class PCI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Function',            c_uint8),
+        ('Device',              c_uint8)
+    ]
+
+
+class PCCARD_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('FunctionNumber',      c_uint8),
+    ]
+
+
+class MEMMAP_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('StartingAddress',     c_uint64),
+        ('EndingAddress',       c_uint64),
+    ]
+
+
+class VENDOR_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Guid',                EFI_GUID),
+    ]
+
+
+class CONTROLLER_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('ControllerNumber',    c_uint32),
+    ]
+
+
+class BMC_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('InterfaceType',       c_uint8),
+        ('BaseAddress',         ARRAY(c_uint8, 8)),
+    ]
+
+
+class BBS_BBS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('DeviceType',          c_uint16),
+        ('StatusFlag',          c_uint16)
+    ]
+
+
+class ACPI_HID_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('HID',                 c_uint32),
+        ('UID',                 c_uint32)
+    ]
+
+
+class ACPI_EXTENDED_HID_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('HID',                 c_uint32),
+        ('UID',                 c_uint32),
+        ('CID',                 c_uint32)
+    ]
+
+
+class ACPI_ADR_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('ARD',                 c_uint32)
+    ]
+
+
+class ACPI_NVDIMM_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('NFITDeviceHandle',    c_uint32)
+    ]
+
+
+class ATAPI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("PrimarySecondary",    c_uint8),
+        ("SlaveMaster",         c_uint8),
+        ("Lun",                 c_uint16)
+    ]
+
+
+class SCSI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Pun",                 c_uint16),
+        ("Lun",                 c_uint16)
+    ]
+
+
+class FIBRECHANNEL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Reserved",            c_uint32),
+        ("WWN",                 c_uint64),
+        ("Lun",                 c_uint64)
+    ]
+
+
+class F1394_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Reserved",            c_uint32),
+        ("Guid",                c_uint64)
+    ]
+
+
+class USB_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("ParentPortNumber",    c_uint8),
+        ("InterfaceNumber",     c_uint8),
+    ]
+
+
+class I2O_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Tid",                 c_uint32)
+    ]
+
+
+class INFINIBAND_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("ResourceFlags",       c_uint32),
+        ("PortGid",             ARRAY(c_uint8, 16)),
+        ("ServiceId",           c_uint64),
+        ("TargetPortId",        c_uint64),
+        ("DeviceId",            c_uint64)
+    ]
+
+
+class UART_FLOW_CONTROL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Guid",                EFI_GUID),
+        ("FlowControlMap",      c_uint32)
+    ]
+
+
+class SAS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Guid",                EFI_GUID),
+        ("Reserved",            c_uint32),
+        ("SasAddress",          c_uint64),
+        ("Lun",                 c_uint64),
+        ("DeviceTopology",      c_uint16),
+        ("RelativeTargetPort",  c_uint16)
+    ]
+
+
+class EFI_MAC_ADDRESS(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ("Addr",             ARRAY(c_uint8, 32)),
+    ]
+
+
+class MAC_ADDR_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('MacAddress',          EFI_MAC_ADDRESS),
+        ('IfType',              c_uint8)
+    ]
+
+
+class IPv4_ADDRESS(LittleEndianStructure):
+    _fields_ = [
+        ("Addr",             ARRAY(c_uint8, 4)),
+    ]
+
+
+class IPv4_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('LocalIpAddress',      IPv4_ADDRESS),
+        ('RemoteIpAddress',     IPv4_ADDRESS),
+        ('LocalPort',           c_uint16),
+        ('RemotePort',          c_uint16),
+        ('Protocol',            c_uint16),
+        ('StaticIpAddress',     c_uint8),
+        ('GatewayIpAddress',    IPv4_ADDRESS),
+        ('SubnetMask',          IPv4_ADDRESS)
+    ]
+
+
+class IPv6_ADDRESS(LittleEndianStructure):
+    _fields_ = [
+        ("Addr",             ARRAY(c_uint8, 16)),
+    ]
+
+
+class IPv6_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('LocalIpAddress',      IPv6_ADDRESS),
+        ('RemoteIpAddress',     IPv6_ADDRESS),
+        ('LocalPort',           c_uint16),
+        ('RemotePort',          c_uint16),
+        ('Protocol',            c_uint16),
+        ('IpAddressOrigin',     c_uint8),
+        ('PrefixLength',        c_uint8),
+        ('GatewayIpAddress',    IPv6_ADDRESS)
+    ]
+
+
+class UART_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Reserved',            c_uint32),
+        ('BaudRate',            c_uint64),
+        ('DataBits',            c_uint8),
+        ('Parity',              c_uint8),
+        ('StopBits',            c_uint8)
+    ]
+
+
+class USB_CLASS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('VendorId',            c_uint16),
+        ('ProductId',           c_uint16),
+        ('DeviceClass',         c_uint8),
+        ('DeviceCSjblass',      c_uint8),
+        ('DeviceProtocol',      c_uint8),
+    ]
+
+
+class USB_WWID_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('InterfaceNumber',     c_uint16),
+        ('VendorId',            c_uint16),
+        ('ProductId',           c_uint16),
+    ]
+
+
+class DEVICE_LOGICAL_UNIT_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Lun',                 c_uint8)
+    ]
+
+
+class SATA_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',                      EFI_DEVICE_PATH),
+        ('HBAPortNumber',               c_uint16),
+        ('PortMultiplierPortNumber',    c_uint16),
+        ('Lun',                         c_uint16),
+    ]
+
+
+class ISCSI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',                EFI_DEVICE_PATH),
+        ('NetworkProtocol',       c_uint16),
+        ('LoginOption',           c_uint16),
+        ('Lun',                   c_uint64),
+        ('TargetPortalGroupTag',  c_uint16),
+    ]
+
+
+class VLAN_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("VlandId",             c_uint16)
+    ]
+
+
+class FIBRECHANNELEX_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Reserved",            c_uint16),
+        ("WWN",                 ARRAY(c_uint8, 8)),
+        ("Lun",                 ARRAY(c_uint8, 8)),
+    ]
+
+
+class SASEX_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SasAddress",          ARRAY(c_uint8, 8)),
+        ("Lun",                 ARRAY(c_uint8, 8)),
+        ("DeviceTopology",      c_uint16),
+        ("RelativeTargetPort",  c_uint16)
+    ]
+
+
+class NVME_NAMESPACE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("NamespaceId",         c_uint32),
+        ("NamespaceUuid",       c_uint64)
+    ]
+
+
+class DNS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("IsIPv6",              c_uint8),
+        ("DnsServerIp",         IPv6_ADDRESS)
+
+    ]
+
+
+class UFS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Pun",                 c_uint8),
+        ("Lun",                 c_uint8),
+    ]
+
+
+class SD_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SlotNumber",          c_uint8)
+    ]
+
+
+class BLUETOOTH_ADDRESS(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ("Address",             ARRAY(c_uint8, 6))
+    ]
+
+
+class BLUETOOTH_LE_ADDRESS(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ("Format",          c_uint8),
+        ("Class",           c_uint16)
+    ]
+
+
+class BLUETOOTH_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("BD_ADDR",             BLUETOOTH_ADDRESS)
+    ]
+
+
+class WIFI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SSId",                ARRAY(c_uint8, 32))
+    ]
+
+
+class EMMC_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SlotNumber",          c_uint8)
+    ]
+
+
+class BLUETOOTH_LE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("BD_ADDR",             BLUETOOTH_LE_ADDRESS)
+    ]
+
+
+class NVDIMM_NAMESPACE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Uuid",                EFI_GUID)
+    ]
+
+
+class REST_SERVICE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("RESTService",         c_uint8),
+        ("AccessMode",          c_uint8)
+    ]
+
+
+class REST_VENDOR_SERVICE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("RESTService",         c_uint8),
+        ("AccessMode",          c_uint8),
+        ("Guid",                EFI_GUID),
+    ]
+
+
+class HARDDRIVE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('PartitionNumber',     c_uint32),
+        ('PartitionStart',      c_uint64),
+        ('PartitionSize',       c_uint64),
+        ('Signature',           ARRAY(c_uint8, 16)),
+        ('MBRType',             c_uint8),
+        ('SignatureType',       c_uint8)
+    ]
+
+
+class CDROM_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('BootEntry',           c_uint32),
+        ('PartitionStart',      c_uint64),
+        ('PartitionSize',       c_uint64)
+    ]
+
+
+class MEDIA_PROTOCOL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Protocol',            EFI_GUID)
+    ]
+
+
+class MEDIA_FW_VOL_FILEPATH_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('FvFileName',          EFI_GUID)
+    ]
+
+
+class MEDIA_FW_VOL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('FvName',              EFI_GUID)
+    ]
+
+
+class MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Reserved',            c_uint32),
+        ('StartingOffset',      c_uint64),
+        ('EndingOffset',        c_uint64)
+    ]
+
+
+class MEDIA_RAM_DISK_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('StartingAddr',        c_uint64),
+        ('EndingAddr',          c_uint64),
+        ('TypeGuid',            EFI_GUID),
+        ('Instance',            c_uint16)
+    ]
+
+
+class EfiDevicePath:
+    '''
+    Parse EFI Device Paths based on the edk2 C Structures defined above.
+    In the context of this class verbose means hexdump extra data.
+
+
+    Attributes
+    ——————
+    DevicePath : list
+        List of devixe path instances. Each instance is a list of nodes
+        for the given Device Path instance.
+
+    Methods
+    -----------
+    device_path_node(address)
+        return the Device Path ctype hdr, ctype, and any extra data in
+        the Device Path node. This is just a single Device Path node,
+        not the entire Device Path.
+    device_path_node_str(address)
+        return the device path node (not the entire Device Path) as a string
+    '''
+
+    DevicePath = []
+
+    device_path_dict = {
+        # ( Type, SubType ) : Device Path C typedef
+        # HARDWARE_DEVICE_PATH
+        (1,  1): PCI_DEVICE_PATH,
+        (1,  2): PCCARD_DEVICE_PATH,
+        (1,  3): MEMMAP_DEVICE_PATH,
+        (1,  4): VENDOR_DEVICE_PATH,
+        (1,  5): CONTROLLER_DEVICE_PATH,
+        (1,  6): BMC_DEVICE_PATH,
+
+        # ACPI_DEVICE_PATH
+        (2,  1): ACPI_HID_DEVICE_PATH,
+        (2,  2): ACPI_EXTENDED_HID_DEVICE_PATH,
+        (2,  3): ACPI_ADR_DEVICE_PATH,
+        (2,  4): ACPI_NVDIMM_DEVICE_PATH,
+
+        # MESSAGING_DEVICE_PATH
+        (3,  1): ATAPI_DEVICE_PATH,
+        (3,  2): SCSI_DEVICE_PATH,
+        (3,  3): FIBRECHANNEL_DEVICE_PATH,
+        (3,  4): F1394_DEVICE_PATH,
+        (3,  5): USB_DEVICE_PATH,
+        (3,  6): I2O_DEVICE_PATH,
+
+        (3,  9): INFINIBAND_DEVICE_PATH,
+        (3, 10): VENDOR_DEVICE_PATH,
+        (3, 11): MAC_ADDR_DEVICE_PATH,
+        (3, 12): IPv4_DEVICE_PATH,
+        (3, 13): IPv6_DEVICE_PATH,
+        (3, 14): UART_DEVICE_PATH,
+        (3, 15): USB_CLASS_DEVICE_PATH,
+        (3, 16): USB_WWID_DEVICE_PATH,
+        (3, 17): DEVICE_LOGICAL_UNIT_DEVICE_PATH,
+        (3, 18): SATA_DEVICE_PATH,
+        (3, 19): ISCSI_DEVICE_PATH,
+        (3, 20): VLAN_DEVICE_PATH,
+        (3, 21): FIBRECHANNELEX_DEVICE_PATH,
+        (3, 22): SASEX_DEVICE_PATH,
+        (3, 23): NVME_NAMESPACE_DEVICE_PATH,
+        (3, 24): DNS_DEVICE_PATH,
+        (3, 25): UFS_DEVICE_PATH,
+        (3, 26): SD_DEVICE_PATH,
+        (3, 27): BLUETOOTH_DEVICE_PATH,
+        (3, 28): WIFI_DEVICE_PATH,
+        (3, 29): EMMC_DEVICE_PATH,
+        (3, 30): BLUETOOTH_LE_DEVICE_PATH,
+        (3, 31): DNS_DEVICE_PATH,
+        (3, 32): NVDIMM_NAMESPACE_DEVICE_PATH,
+
+        (3, 33): REST_SERVICE_DEVICE_PATH,
+        (3, 34): REST_VENDOR_SERVICE_DEVICE_PATH,
+
+        # MEDIA_DEVICE_PATH
+        (4,  1): HARDDRIVE_DEVICE_PATH,
+        (4,  2): CDROM_DEVICE_PATH,
+        (4,  3): VENDOR_DEVICE_PATH,
+        (4,  4): EFI_DEVICE_PATH,
+        (4,  5): MEDIA_PROTOCOL_DEVICE_PATH,
+        (4,  6): MEDIA_FW_VOL_FILEPATH_DEVICE_PATH,
+        (4,  7): MEDIA_FW_VOL_DEVICE_PATH,
+        (4,  8): MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH,
+        (4,  9): MEDIA_RAM_DISK_DEVICE_PATH,
+
+        # BBS_DEVICE_PATH
+        (5, 1): BBS_BBS_DEVICE_PATH,
+
+    }
+
+    guid_override_dict = {
+        uuid.UUID('37499A9D-542F-4C89-A026-35DA142094E4'):
+            UART_FLOW_CONTROL_DEVICE_PATH,
+        uuid.UUID('D487DDB4-008B-11D9-AFDC-001083FFCA4D'):
+            SAS_DEVICE_PATH,
+    }
+
+    def __init__(self, file, ptr=None, verbose=False, count=64):
+        '''
+        Convert ptr into a list of Device Path nodes. If verbose also hexdump
+        extra data.
+        '''
+        self._file = file
+        self._verbose = verbose
+        if ptr is None:
+            return
+
+        try:
+            instance = []
+            for _ in range(count):  # while True
+                hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, ptr)
+                if hdr.Length < sizeof(EFI_DEVICE_PATH):
+                    # Not a valid device path
+                    break
+
+                if hdr.Type == 0x7F:  # END_DEVICE_PATH_TYPE
+                    self.DevicePath.append(instance)
+                    if hdr.SubType == 0xFF:  # END_ENTIRE_DEVICE_PATH_SUBTYPE
+                        break
+                    if hdr.SubType == 0x01:  # END_INSTANCE_DEVICE_PATH_SUBTYPE
+                        # start new device path instance
+                        instance = []
+
+                type_str = self.device_path_dict.get(
+                    (hdr.Type, hdr.SubType), EFI_DEVICE_PATH)
+                node, extra = self._ctype_read_ex(type_str, ptr, hdr.Length)
+                if 'VENDOR_DEVICE_PATH' in type(node).__name__:
+                    guid_type = self.guid_override_dict.get(
+                                        GuidNames.to_uuid(node.Guid), None)
+                    if guid_type:
+                        # use the ctype associated with the GUID
+                        node, extra = self._ctype_read_ex(
+                                                guid_type, ptr, hdr.Length)
+
+                instance.append((type(node).__name__, hdr.Type,
+                                hdr.SubType, hdr.Length, node, extra))
+                ptr += hdr.Length
+        except ValueError:
+            pass
+
+    def __str__(self):
+        ''' '''
+        if not self.valid():
+            return '<class: EfiDevicePath>'
+
+        result = ""
+        for instance in self.DevicePath:
+            for (Name, Type, SubType, Length, cnode, extra) in instance:
+                result += f'{Name:s} {Type:2d}:{SubType:2d} Len: {Length:3d}\n'
+                result += ctype_to_str(cnode, '  ', ['Reserved'])
+                if self._verbose:
+                    if extra is not None:
+                        result += hexdump(extra, '    ')
+            result += '\n'
+
+        return result
+
+    def valid(self):
+        return True if self.DevicePath else False
+
+    def device_path_node(self, address):
+        try:
+            hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, address)
+            if hdr.Length < sizeof(EFI_DEVICE_PATH):
+                return None, None, None
+
+            type_str = self.device_path_dict.get(
+                (hdr.Type, hdr.SubType), EFI_DEVICE_PATH)
+            cnode, extra = self._ctype_read_ex(type_str, address, hdr.Length)
+            return hdr, cnode, extra
+        except ValueError:
+            return None, None, None
+
+    def device_path_node_str(self, address, verbose=False):
+        hdr, cnode, extra = self.device_path_node(address)
+        if hdr is None:
+            return ''
+
+        cname = type(cnode).__name__
+        result = f'{cname:s} {hdr.Type:2d}:{hdr.SubType:2d} '
+        result += f'Len: 0x{hdr.Length:03x}\n'
+        result += ctype_to_str(cnode, '  ', ['Reserved'])
+        if verbose:
+            if extra is not None:
+                result += hexdump(extra, '    ')
+
+        return result
+
+    def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None):
+        if offset != 0:
+            self._file.seek(offset)
+
+        type_size = sizeof(ctype_struct)
+        size = rsize if rsize else type_size
+        data = self._file.read(size)
+        if data is None:
+            return None, None
+
+        cdata = ctype_struct.from_buffer(bytearray(data))
+
+        if size > type_size:
+            return cdata, data[type_size:]
+        else:
+            return cdata, None
+
+
+class EfiConfigurationTable:
+    '''
+    A class to abstract EFI Configuration Tables from gST->ConfigurationTable
+    and gST->NumberOfTableEntries. Pass in the gST pointer from EFI,
+    likely you need to look up this address after you have loaded symbols
+
+    Attributes
+    ——————
+    ConfigurationTableDict : dictionary
+        dictionary of EFI Configuration Table entries
+
+    Methods
+    -----------
+    GetConfigTable(uuid)
+        pass in VendorGuid and return VendorTable from EFI System Table
+    DebugImageInfo(table)
+        return tuple of load address and size of PE/COFF images
+    '''
+
+    ConfigurationTableDict = {}
+
+    def __init__(self, file, gST_addr=None):
+        self._file = file
+        if gST_addr is None:
+            # ToDo add code to search for gST via EFI_SYSTEM_TABLE_POINTER
+            return
+
+        gST = self._ctype_read(EFI_SYSTEM_TABLE, gST_addr)
+        self.read_efi_config_table(gST.NumberOfTableEntries,
+                                   gST.ConfigurationTable,
+                                   self._ctype_read)
+
+    @ classmethod
+    def __str__(cls):
+        '''return EFI_CONFIGURATION_TABLE entries as a string'''
+        result = ""
+        for key, value in cls.ConfigurationTableDict.items():
+            result += f'{GuidNames().to_name(key):>37s}: '
+            result += f'VendorTable = 0x{value:08x}\n'
+
+        return result
+
+    def _ctype_read(self, ctype_struct, offset=0):
+        '''ctype worker function to read data'''
+        if offset != 0:
+            self._file.seek(offset)
+
+        data = self._file.read(sizeof(ctype_struct))
+        return ctype_struct.from_buffer(bytearray(data))
+
+    @ classmethod
+    def read_efi_config_table(cls, table_cnt, table_ptr, ctype_read):
+        '''Create a dictionary of EFI Configuration table entries'''
+        EmptryTables = EFI_CONFIGURATION_TABLE * table_cnt
+        Tables = ctype_read(EmptryTables, table_ptr)
+        for i in range(table_cnt):
+            cls.ConfigurationTableDict[str(GuidNames.to_uuid(
+                Tables[i].VendorGuid)).upper()] = Tables[i].VendorTable
+
+        return cls.ConfigurationTableDict
+
+    def GetConfigTable(self, uuid):
+        ''' Return VendorTable for VendorGuid (uuid.UUID) or None'''
+        return self.ConfigurationTableDict.get(uuid.upper())
+
+    def DebugImageInfo(self, table=None):
+        '''
+        Walk the debug image info table to find the LoadedImage protocols
+        for all the loaded PE/COFF images and return a list of load address
+        and image size.
+        '''
+        ImageLoad = []
+
+        if table is None:
+            table = self.GetConfigTable('49152e77-1ada-4764-b7a2-7afefed95e8b')
+
+        DbgInfoHdr = self._ctype_read(EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, table)
+        NormalImageArray = EFI_DEBUG_IMAGE_INFO * DbgInfoHdr.TableSize
+        NormalImageArray = self._ctype_read(
+            NormalImageArray, DbgInfoHdr.EfiDebugImageInfoTable)
+        for i in range(DbgInfoHdr.TableSize):
+            ImageInfo = self._ctype_read(
+                EFI_DEBUG_IMAGE_INFO_NORMAL, NormalImageArray[i].NormalImage)
+            LoadedImage = self._ctype_read(
+                EFI_LOADED_IMAGE_PROTOCOL,
+                ImageInfo.LoadedImageProtocolInstance)
+            ImageLoad.append((LoadedImage.ImageBase, LoadedImage.ImageSize))
+
+        return ImageLoad
+
+
+class PeTeImage:
+    '''
+    A class to abstract PE/COFF or TE image processing via passing in a
+    Python file like object. If you pass in an address the PE/COFF is parsed,
+    if you pass in NULL for an address then you get a class instance you can
+    use to search memory for a PE/COFF hader given a pc value.
+
+    Attributes
+    ——————
+    LoadAddress : int
+        Load address of the PE/COFF image
+    AddressOfEntryPoint : int
+        Address of the Entry point of the PE/COFF image
+    TextAddress : int
+        Start of the PE/COFF text section
+    DataAddress : int
+        Start of the PE/COFF data section
+    CodeViewPdb : str
+        File name of the symbols file
+    CodeViewUuid : uuid:UUID
+        GUID for "RSDS" Debug Directory entry, or Mach-O UUID for "MTOC"
+
+    Methods
+    -----------
+    pcToPeCoff(address, step, max_range, rom_range)
+        Given an address(pc) find the PE/COFF image it is in
+    sections_to_str()
+        return a string giving info for all the PE/COFF sections
+    '''
+
+    def __init__(self, file, address=0):
+        self._file = file
+
+        # book keeping, but public
+        self.PeHdr = None
+        self.TeHdr = None
+        self.Machine = None
+        self.Subsystem = None
+        self.CodeViewSig = None
+        self.e_lfanew = 0
+        self.NumberOfSections = 0
+        self.Sections = None
+
+        # Things debuggers may want to know
+        self.LoadAddress = 0 if address is None else address
+        self.EndLoadAddress = 0
+        self.AddressOfEntryPoint = 0
+        self.TextAddress = 0
+        self.DataAddress = 0
+        self.CodeViewPdb = None
+        self.CodeViewUuid = None
+        self.TeAdjust = 0
+
+        self.dir_name = {
+            0: 'Export Table',
+            1: 'Import Table',
+            2: 'Resource Table',
+            3: 'Exception Table',
+            4: 'Certificate Table',
+            5: 'Relocation Table',
+            6: 'Debug',
+            7: 'Architecture',
+            8: 'Global Ptr',
+            9: 'TLS Table',
+            10: 'Load Config Table',
+            11: 'Bound Import',
+            12: 'IAT',
+            13: 'Delay Import Descriptor',
+            14: 'CLR Runtime Header',
+            15: 'Reserved',
+        }
+
+        if address is not None:
+            if self.maybe():
+                self.parse()
+
+    def __str__(self):
+        if self.PeHdr is None and self.TeHdr is None:
+            # no PE/COFF header found
+            return "<class: PeTeImage>"
+
+        if self.CodeViewPdb:
+            pdb = f'{self.Machine}`{self.CodeViewPdb}'
+        else:
+            pdb = 'No Debug Info:'
+
+        if self.CodeViewUuid:
+            guid = f'{self.CodeViewUuid}:'
+        else:
+            guid = ''
+
+        slide = f'slide = {self.TeAdjust:d} ' if self.TeAdjust != 0 else ' '
+        res = guid + f'{pdb} load = 0x{self.LoadAddress:08x} ' + slide
+        return res
+
+    def _seek(self, offset):
+        """
+        seek() relative to start of PE/COFF (TE) image
+        """
+        self._file.seek(self.LoadAddress + offset)
+
+    def _read_offset(self, size, offset=None):
+        """
+        read() relative to start of PE/COFF (TE) image
+        if offset is not None then seek() before the read
+        """
+        if offset is not None:
+            self._seek(offset)
+
+        return self._file.read(size)
+
+    def _read_ctype(self, ctype_struct, offset=None):
+        data = self._read_offset(sizeof(ctype_struct), offset)
+        return ctype_struct.from_buffer(bytearray(data), 0)
+
+    def _unsigned(self, i):
+        """return a 32-bit unsigned int (UINT32) """
+        return int.from_bytes(i, byteorder='little', signed=False)
+
+    def pcToPeCoff(self,
+                   address,
+                   step=None,
+                   max_range=None,
+                   rom_range=[0xFE800000, 0xFFFFFFFF]):
+        """
+        Given an address search backwards for PE/COFF (TE) header
+        For DXE 4K is probably OK
+        For PEI you might have to search every 4 bytes.
+        """
+        if step is None:
+            step = 0x1000
+
+        if max_range is None:
+            max_range = 0x200000
+
+        if address in range(*rom_range):
+            # The XIP code in the ROM ends up 4 byte aligned.
+            step = 4
+            max_range = min(max_range, 0x100000)
+
+        # Align address to page boundary for memory image search.
+        address = address & ~(step-1)
+        # Search every step backward
+        offset_range = list(range(0, min(max_range, address), step))
+        for offset in offset_range:
+            if self.maybe(address - offset):
+                if self.parse():
+                    return True
+
+        return False
+
+    def maybe(self, offset=None):
+        """Probe to see if this offset is likely a PE/COFF or TE file """
+        self.LoadAddress = 0
+        e_magic = self._read_offset(2, offset)
+        header_ok = e_magic == b'MZ' or e_magic == b'VZ'
+        if offset is not None and header_ok:
+            self.LoadAddress = offset
+        return header_ok
+
+    def parse(self):
+        """Parse PE/COFF (TE) debug directory entry """
+        DosHdr = self._read_ctype(EFI_IMAGE_DOS_HEADER, 0)
+        if DosHdr.e_magic == self._unsigned(b'VZ'):
+            # TE image
+            self.TeHdr = self._read_ctype(EFI_TE_IMAGE_HEADER, 0)
+
+            self.TeAdjust = sizeof(self.TeHdr) - self.TeHdr.StrippedSize
+            self.Machine = image_machine_dict.get(self.TeHdr.Machine, None)
+            self.Subsystem = self.TeHdr.Subsystem
+            self.AddressOfEntryPoint = self.TeHdr.AddressOfEntryPoint
+
+            debug_dir_size = self.TeHdr.DataDirectoryDebug.Size
+            debug_dir_offset = (self.TeAdjust +
+                                self.TeHdr.DataDirectoryDebug.VirtualAddress)
+        else:
+            if DosHdr.e_magic == self._unsigned(b'MZ'):
+                self.e_lfanew = DosHdr.e_lfanew
+            else:
+                self.e_lfanew = 0
+
+            self.PeHdr = self._read_ctype(
+                EFI_IMAGE_NT_HEADERS64, self.e_lfanew)
+            if self.PeHdr.Signature != self._unsigned(b'PE\0\0'):
+                return False
+
+            if self.PeHdr.OptionalHeader.Magic == \
+                    EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
+                self.PeHdr = self._read_ctype(
+                    EFI_IMAGE_NT_HEADERS32, self.e_lfanew)
+
+            if self.PeHdr.OptionalHeader.NumberOfRvaAndSizes <= \
+                    DIRECTORY_DEBUG:
+                return False
+
+            self.Machine = image_machine_dict.get(
+                self.PeHdr.FileHeader.Machine, None)
+            self.Subsystem = self.PeHdr.OptionalHeader.Subsystem
+            self.AddressOfEntryPoint = \
+                self.PeHdr.OptionalHeader.AddressOfEntryPoint
+            self.TeAdjust = 0
+
+            debug_dir_size = self.PeHdr.OptionalHeader.DataDirectory[
+                DIRECTORY_DEBUG].Size
+            debug_dir_offset = self.PeHdr.OptionalHeader.DataDirectory[
+                DIRECTORY_DEBUG].VirtualAddress
+
+        if self.Machine is None or self.Subsystem not in [0, 10, 11, 12]:
+            return False
+
+        self.AddressOfEntryPoint += self.LoadAddress
+
+        self.sections()
+        return self.processDebugDirEntry(debug_dir_offset, debug_dir_size)
+
+    def sections(self):
+        '''Parse the PE/COFF (TE) section table'''
+        if self.Sections is not None:
+            return
+        elif self.TeHdr is not None:
+            self.NumberOfSections = self.TeHdr.NumberOfSections
+            offset = sizeof(EFI_TE_IMAGE_HEADER)
+        elif self.PeHdr is not None:
+            self.NumberOfSections = self.PeHdr.FileHeader.NumberOfSections
+            offset = sizeof(c_uint32) + \
+                sizeof(EFI_IMAGE_FILE_HEADER)
+            offset += self.PeHdr.FileHeader.SizeOfOptionalHeader
+            offset += self.e_lfanew
+        else:
+            return
+
+        self.Sections = EFI_IMAGE_SECTION_HEADER * self.NumberOfSections
+        self.Sections = self._read_ctype(self.Sections, offset)
+
+        for i in range(self.NumberOfSections):
+            name = str(self.Sections[i].Name, 'ascii', 'ignore')
+            addr = self.Sections[i].VirtualAddress
+            addr += self.LoadAddress + self.TeAdjust
+            if name == '.text':
+                self.TextAddress = addr
+            elif name == '.data':
+                self.DataAddress = addr
+
+            end_addr = addr + self.Sections[i].VirtualSize - 1
+            if end_addr > self.EndLoadAddress:
+                self.EndLoadAddress = end_addr
+
+    def sections_to_str(self):
+        # return text summary of sections
+        # name virt addr (virt size) flags:Characteristics
+        result = ''
+        for i in range(self.NumberOfSections):
+            name = str(self.Sections[i].Name, 'ascii', 'ignore')
+            result += f'{name:8s} '
+            result += f'0x{self.Sections[i].VirtualAddress:08X} '
+            result += f'(0x{self.Sections[i].VirtualSize:05X}) '
+            result += f'flags:0x{self.Sections[i].Characteristics:08X}\n'
+
+        return result
+
+    def directory_to_str(self):
+        result = ''
+        if self.TeHdr:
+            debug_size = self.TeHdr.DataDirectoryDebug.Size
+            if debug_size > 0:
+                debug_offset = (self.TeAdjust
+                                + self.TeHdr.DataDirectoryDebug.VirtualAddress)
+                result += f"Debug 0x{debug_offset:08X} 0x{debug_size}\n"
+
+            relocation_size = self.TeHdr.DataDirectoryBaseReloc.Size
+            if relocation_size > 0:
+                relocation_offset = (
+                    self.TeAdjust
+                    + self.TeHdr.DataDirectoryBaseReloc.VirtualAddress)
+                result += f'Relocation 0x{relocation_offset:08X} '
+                result += f' 0x{relocation_size}\n'
+
+        elif self.PeHdr:
+            for i in range(self.PeHdr.OptionalHeader.NumberOfRvaAndSizes):
+                size = self.PeHdr.OptionalHeader.DataDirectory[i].Size
+                if size == 0:
+                    continue
+
+                virt_addr = self.PeHdr.OptionalHeader.DataDirectory[
+                    i].VirtualAddress
+                name = self.dir_name.get(i, '?')
+                result += f'{name:s} 0x{virt_addr:08X} 0x{size:X}\n'
+
+        return result
+
+    def processDebugDirEntry(self, virt_address, virt_size):
+        """Process PE/COFF Debug Directory Entry"""
+        if (virt_address == 0 or
+                virt_size < sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)):
+            return False
+
+        data = bytearray(self._read_offset(virt_size, virt_address))
+        for offset in range(0,
+                            virt_size,
+                            sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)):
+            DirectoryEntry = EFI_IMAGE_DEBUG_DIRECTORY_ENTRY.from_buffer(
+                data[offset:])
+            if DirectoryEntry.Type != 2:
+                continue
+
+            entry = self._read_offset(
+                DirectoryEntry.SizeOfData, DirectoryEntry.RVA + self.TeAdjust)
+            self.CodeViewSig = entry[:4]
+            if self.CodeViewSig == b'MTOC':
+                self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16])
+                PdbOffset = 20
+            elif self.CodeViewSig == b'RSDS':
+                self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16])
+                PdbOffset = 24
+            elif self.CodeViewSig == b'NB10':
+                PdbOffset = 16
+            else:
+                continue
+
+            # can't find documentation about Pdb string encoding?
+            # guessing utf-8 since that will match file systems in macOS
+            # and Linux Windows is UTF-16, or ANSI adjusted for local.
+            # We might need a different value for Windows here?
+            self.CodeViewPdb = entry[PdbOffset:].split(b'\x00')[
+                0].decode('utf-8')
+            return True
+        return False
+
+
+def main():
+    '''Process arguments as PE/COFF files'''
+    for fname in sys.argv[1:]:
+        with open(fname, 'rb') as f:
+            image = PeTeImage(f)
+            print(image)
+            res = f'EntryPoint = 0x{image.AddressOfEntryPoint:08x}  '
+            res += f'TextAddress = 0x{image.TextAddress:08x} '
+            res += f'DataAddress = 0x{image.DataAddress:08x}'
+            print(res)
+            print(image.sections_to_str())
+            print('Data Directories:')
+            print(image.directory_to_str())
+
+
+if __name__ == "__main__":
+    main()
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
  2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
  2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran
@ 2022-03-21 20:20 ` Rebecca Cran
  2022-03-26 14:05   ` Bob Feng
  2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran
  2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
  3 siblings, 1 reply; 10+ messages in thread
From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw)
  To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng,
	Liming Gao, Yuwei Chen
  Cc: Rebecca Cran, Andrew Fish

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>
---
 BaseTools/Scripts/efi_gdb.py | 918 ++++++++++++++++++++
 1 file changed, 918 insertions(+)

diff --git a/BaseTools/Scripts/efi_gdb.py b/BaseTools/Scripts/efi_gdb.py
new file mode 100755
index 000000000000..e9bae8e9b913
--- /dev/null
+++ b/BaseTools/Scripts/efi_gdb.py
@@ -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')
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print
  2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
  2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran
  2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran
@ 2022-03-21 20:20 ` Rebecca Cran
  2022-04-09  4:16   ` [edk2-devel] " Bob Feng
  2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
  3 siblings, 1 reply; 10+ messages in thread
From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw)
  To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng,
	Liming Gao, Yuwei Chen
  Cc: Rebecca Cran, Andrew Fish

https://bugzilla.tianocore.org/show_bug.cgi?id=3500

Use efi_debugging.py Python Classes to implement EFI gdb commands:
efi_symbols, guid, table, hob, and devicepath

You can attach to any standard gdb or kdp remote server and get EFI
symbols. No modifications of EFI are required.

Example usage:
OvmfPkg/build.sh qemu -gdb tcp::9000
lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py"
Note you may also have to teach lldb about QEMU:
-o "settings set plugin.process.gdb-remote.target-definition-file
 x86_64_target_definition.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>
---
 BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++
 1 file changed, 1044 insertions(+)

diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py
new file mode 100755
index 000000000000..089b6ba58ab8
--- /dev/null
+++ b/BaseTools/Scripts/efi_lldb.py
@@ -0,0 +1,1044 @@
+#!/usr/bin/python3
+'''
+Copyright (c) Apple Inc. 2021
+SPDX-License-Identifier: BSD-2-Clause-Patent
+
+Example usage:
+OvmfPkg/build.sh qemu -gdb tcp::9000
+lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py"
+'''
+
+import optparse
+import shlex
+import subprocess
+import uuid
+import sys
+import os
+from pathlib import Path
+from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl
+from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode
+from efi_debugging import PeTeImage, patch_ctypes
+
+try:
+    # Just try for LLDB in case PYTHONPATH is already correctly setup
+    import lldb
+except ImportError:
+    try:
+        env = os.environ.copy()
+        env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
+        lldb_python_path = subprocess.check_output(
+            ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip()
+        sys.path.append(lldb_python_path)
+        import lldb
+    except ValueError:
+        print("Couldn't find LLDB.framework from lldb -P")
+        print("PYTHONPATH should match the currently selected lldb")
+        sys.exit(-1)
+
+
+class LldbFileObject(object):
+    '''
+    Class that fakes out file object to abstract lldb from the generic code.
+    For lldb this is memory so we don't have a concept of the end of the file.
+    '''
+
+    def __init__(self, process):
+        # _exe_ctx is lldb.SBExecutionContext
+        self._process = process
+        self._offset = 0
+        self._SBError = lldb.SBError()
+
+    def tell(self):
+        return self._offset
+
+    def read(self, size=-1):
+        if size == -1:
+            # arbitrary default size
+            size = 0x1000000
+
+        data = self._process.ReadMemory(self._offset, size, self._SBError)
+        if self._SBError.fail:
+            raise MemoryError(
+                f'lldb could not read memory 0x{size:x} '
+                f' bytes from 0x{self._offset:08x}')
+        else:
+            return data
+
+    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):
+        result = self._process.WriteMemory(self._offset, data, self._SBError)
+        if self._SBError.fail:
+            raise MemoryError(
+                f'lldb could not write memory to 0x{self._offset:08x}')
+        return result
+
+    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
+    You need to pass file, and exe_ctx to load symbols.
+    You can print(EfiSymbols()) to see the currently loaded symbols
+    """
+
+    loaded = {}
+    stride = None
+    range = None
+    verbose = False
+
+    def __init__(self, target=None):
+        if target:
+            EfiSymbols.target = target
+            EfiSymbols._file = LldbFileObject(target.process)
+
+    @ classmethod
+    def __str__(cls):
+        return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values())
+
+    @ classmethod
+    def configure_search(cls, stride, range, 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.LoadAddress in cls.loaded:
+            return 'Already Loaded: '
+
+        module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid))
+        if not module:
+            module = cls.target.AddModule(pecoff.CodeViewPdb,
+                                          None,
+                                          str(pecoff.CodeViewUuid))
+        if module.IsValid():
+            SBError = cls.target.SetModuleLoadAddress(
+                module, pecoff.LoadAddress + pecoff.TeAdjust)
+            if SBError.success:
+                cls.loaded[pecoff.LoadAddress] = (pecoff, module)
+                return ''
+
+        return 'Symbols NOT FOUND: '
+
+    @ 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 (pecoff, module) in cls.loaded.values():
+            if (address >= pecoff.LoadAddress and
+                    address <= pecoff.EndLoadAddress):
+
+                return pecoff, module
+
+        return None, None
+
+    @ classmethod
+    def unload_symbols(cls, address):
+        pecoff, module = cls.address_in_loaded_pecoff(address)
+        if module:
+            name = str(module)
+            cls.target.ClearModuleLoadAddress(module)
+            cls.target.RemoveModule(module)
+            del cls.loaded[pecoff.LoadAddress]
+            return f'{name:s} was unloaded'
+        return f'0x{address:x} was not in a loaded image'
+
+
+def arg_to_address(frame, arg):
+    ''' convert an lldb command arg into a memory address (addr_t)'''
+
+    if arg is None:
+        return None
+
+    arg_str = arg if isinstance(arg, str) else str(arg)
+    SBValue = frame.EvaluateExpression(arg_str)
+    if SBValue.error.fail:
+        return arg
+
+    if (SBValue.TypeIsPointerType() or
+            SBValue.value_type == lldb.eValueTypeRegister or
+            SBValue.value_type == lldb.eValueTypeRegisterSet or
+            SBValue.value_type == lldb.eValueTypeConstResult):
+        try:
+            addr = SBValue.GetValueAsAddress()
+        except ValueError:
+            addr = SBValue.unsigned
+    else:
+        try:
+            addr = SBValue.address_of.GetValueAsAddress()
+        except ValueError:
+            addr = SBValue.address_of.unsigned
+
+    return addr
+
+
+def arg_to_data(frame, arg):
+    ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)'''
+    if not isinstance(arg, str):
+        arg_str = str(str)
+
+    SBValue = frame.EvaluateExpression(arg_str)
+    return SBValue.unsigned
+
+
+class EfiDevicePathCommand:
+
+    def create_options(self):
+        ''' standard lldb command help/options parser'''
+        usage = "usage: %prog [options]"
+        description = '''Command that can EFI Config Tables
+'''
+
+        # Pass add_help_option = False, since this keeps the command in line
+        # with lldb commands, and we wire up "help command" to work by
+        # providing the long & short help methods below.
+        self.parser = optparse.OptionParser(
+            description=description,
+            prog='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)
+
+    def get_short_help(self):
+        '''standard lldb function method'''
+        return "Display EFI Tables"
+
+    def get_long_help(self):
+        '''standard lldb function method'''
+        return self.help_string
+
+    def __init__(self, debugger, internal_dict):
+        '''standard lldb function method'''
+        self.create_options()
+        self.help_string = self.parser.format_help()
+
+    def __call__(self, debugger, command, exe_ctx, result):
+        '''standard lldb function method'''
+        # Use the Shell Lexer to properly parse up command options just like a
+        # shell would
+        command_args = shlex.split(command)
+
+        try:
+            (options, args) = self.parser.parse_args(command_args)
+            dev_list = []
+            for arg in args:
+                dev_list.append(arg_to_address(exe_ctx.frame, arg))
+        except ValueError:
+            # if you don't handle exceptions, passing an incorrect argument
+            # to the OptionParser will cause LLDB to exit (courtesy of
+            # OptParse dealing with argument errors by throwing SystemExit)
+            result.SetError("option parsing failed")
+            return
+
+        if options.help:
+            self.parser.print_help()
+            return
+
+        file = LldbFileObject(exe_ctx.process)
+
+        for dev_addr in dev_list:
+            if options.node:
+                print(EfiDevicePath(file).device_path_node_str(
+                    dev_addr, options.verbose))
+            else:
+                device_path = EfiDevicePath(file, dev_addr, options.verbose)
+                if device_path.valid():
+                    print(device_path)
+
+
+class EfiHobCommand:
+    def create_options(self):
+        ''' standard lldb command help/options parser'''
+        usage = "usage: %prog [options]"
+        description = '''Command that can EFI dump EFI HOBs'''
+
+        # Pass add_help_option = False, since this keeps the command in line
+        # with lldb commands, and we wire up "help command" to work by
+        # providing the long & short help methods below.
+        self.parser = optparse.OptionParser(
+            description=description,
+            prog='table',
+            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)
+
+    def get_short_help(self):
+        '''standard lldb function method'''
+        return "Display EFI Hobs"
+
+    def get_long_help(self):
+        '''standard lldb function method'''
+        return self.help_string
+
+    def __init__(self, debugger, internal_dict):
+        '''standard lldb function method'''
+        self.create_options()
+        self.help_string = self.parser.format_help()
+
+    def __call__(self, debugger, command, exe_ctx, result):
+        '''standard lldb function method'''
+        # Use the Shell Lexer to properly parse up command options just like a
+        # shell would
+        command_args = shlex.split(command)
+
+        try:
+            (options, _) = self.parser.parse_args(command_args)
+        except ValueError:
+            # if you don't handle exceptions, passing an incorrect argument
+            # to the OptionParser will cause LLDB to exit (courtesy of
+            # OptParse dealing with argument errors by throwing SystemExit)
+            result.SetError("option parsing failed")
+            return
+
+        if options.help:
+            self.parser.print_help()
+            return
+
+        address = arg_to_address(exe_ctx.frame, options.address)
+
+        file = LldbFileObject(exe_ctx.process)
+        hob = EfiHob(file, address, options.verbose).get_hob_by_type(
+            options.type)
+        print(hob)
+
+
+class EfiTableCommand:
+
+    def create_options(self):
+        ''' standard lldb command help/options parser'''
+        usage = "usage: %prog [options]"
+        description = '''Command that can display EFI Config Tables
+'''
+
+        # Pass add_help_option = False, since this keeps the command in line
+        # with lldb commands, and we wire up "help command" to work by
+        # providing the long & short help methods below.
+        self.parser = optparse.OptionParser(
+            description=description,
+            prog='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)
+
+    def get_short_help(self):
+        '''standard lldb function method'''
+        return "Display EFI Tables"
+
+    def get_long_help(self):
+        '''standard lldb function method'''
+        return self.help_string
+
+    def __init__(self, debugger, internal_dict):
+        '''standard lldb function method'''
+        self.create_options()
+        self.help_string = self.parser.format_help()
+
+    def __call__(self, debugger, command, exe_ctx, result):
+        '''standard lldb function method'''
+        # Use the Shell Lexer to properly parse up command options just like a
+        # shell would
+        command_args = shlex.split(command)
+
+        try:
+            (options, _) = self.parser.parse_args(command_args)
+        except ValueError:
+            # if you don't handle exceptions, passing an incorrect argument
+            # to the OptionParser will cause LLDB to exit (courtesy of
+            # OptParse dealing with argument errors by throwing SystemExit)
+            result.SetError("option parsing failed")
+            return
+
+        if options.help:
+            self.parser.print_help()
+            return
+
+        gST = exe_ctx.target.FindFirstGlobalVariable('gST')
+        if gST.error.fail:
+            print('Error: This command requires symbols for gST to be loaded')
+            return
+
+        file = LldbFileObject(exe_ctx.process)
+        table = EfiConfigurationTable(file, gST.unsigned)
+        if table:
+            print(table, '\n')
+
+
+class EfiGuidCommand:
+
+    def create_options(self):
+        ''' standard lldb command help/options parser'''
+        usage = "usage: %prog [options]"
+        description = '''
+            Command that can display all EFI GUID's or give info on a
+            specific GUID's
+            '''
+        self.parser = optparse.OptionParser(
+            description=description,
+            prog='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)
+
+    def get_short_help(self):
+        '''standard lldb function method'''
+        return "Display EFI GUID's"
+
+    def get_long_help(self):
+        '''standard lldb function method'''
+        return self.help_string
+
+    def __init__(self, debugger, internal_dict):
+        '''standard lldb function method'''
+        self.create_options()
+        self.help_string = self.parser.format_help()
+
+    def __call__(self, debugger, command, exe_ctx, result):
+        '''standard lldb function method'''
+        # Use the Shell Lexer to properly parse up command options just like a
+        # shell would
+        command_args = shlex.split(command)
+
+        try:
+            (options, args) = self.parser.parse_args(command_args)
+            if len(args) >= 1:
+                # guid { 0x414e6bdd, 0xe47b, 0x47cc,
+                #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
+                # this generates multiple args
+                arg = ' '.join(args)
+        except ValueError:
+            # if you don't handle exceptions, passing an incorrect argument
+            # to the OptionParser will cause LLDB to exit (courtesy of
+            # OptParse dealing with argument errors by throwing SystemExit)
+            result.SetError("option parsing failed")
+            return
+
+        if options.help:
+            self.parser.print_help()
+            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 = arg.lower()
+                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 = arg
+                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 EfiSymbolicateCommand(object):
+    '''Class to abstract an lldb command'''
+
+    def create_options(self):
+        ''' standard lldb command help/options parser'''
+        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.
+        '''
+
+        # Pass add_help_option = False, since this keeps the command in line
+        # with lldb commands, and we wire up "help command" to work by
+        # providing the long & short help methods below.
+        self.parser = optparse.OptionParser(
+            description=description,
+            prog='efi_symbols',
+            usage=usage,
+            add_help_option=False)
+
+        self.parser.add_option(
+            '-a',
+            '--address',
+            type="int",
+            dest='address',
+            help='Load symbols for image at address',
+            default=None)
+
+        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(
+            '-h',
+            '--help',
+            action='store_true',
+            dest='help',
+            help='Show help for the command',
+            default=False)
+
+    def get_short_help(self):
+        '''standard lldb function method'''
+        return (
+            "Load symbols based on an address that is part of"
+            " a PE/COFF EFI image.")
+
+    def get_long_help(self):
+        '''standard lldb function method'''
+        return self.help_string
+
+    def __init__(self, debugger, unused):
+        '''standard lldb function method'''
+        self.create_options()
+        self.help_string = self.parser.format_help()
+
+    def lldb_print(self, lldb_str):
+        # capture command out like an lldb command
+        self.result.PutCString(lldb_str)
+        # flush the output right away
+        self.result.SetImmediateOutputFile(
+            self.exe_ctx.target.debugger.GetOutputFile())
+
+    def __call__(self, debugger, command, exe_ctx, result):
+        '''standard lldb function method'''
+        # Use the Shell Lexer to properly parse up command options just like a
+        # shell would
+        command_args = shlex.split(command)
+
+        try:
+            (options, _) = self.parser.parse_args(command_args)
+        except ValueError:
+            # if you don't handle exceptions, passing an incorrect argument
+            # to the OptionParser will cause LLDB to exit (courtesy of
+            # OptParse dealing with argument errors by throwing SystemExit)
+            result.SetError("option parsing failed")
+            return
+
+        if options.help:
+            self.parser.print_help()
+            return
+
+        file = LldbFileObject(exe_ctx.process)
+        efi_symbols = EfiSymbols(exe_ctx.target)
+        self.result = result
+        self.exe_ctx = exe_ctx
+
+        if options.pei:
+            # XIP code ends up on a 4 byte boundary.
+            options.stride = 4
+            options.range = 0x100000
+        efi_symbols.configure_search(options.stride, options.range)
+
+        if not options.pc and options.address is None:
+            # default to
+            options.frame = True
+
+        if options.frame:
+            if not exe_ctx.frame.IsValid():
+                result.SetError("invalid frame")
+                return
+
+            threads = exe_ctx.process.threads if options.thread else [
+                exe_ctx.thread]
+
+            for thread in threads:
+                for frame in thread:
+                    res = efi_symbols.address_to_symbols(frame.pc)
+                    self.lldb_print(res)
+
+        else:
+            if options.address is not None:
+                address = options.address
+            elif options.pc:
+                try:
+                    address = exe_ctx.thread.GetSelectedFrame().pc
+                except ValueError:
+                    result.SetError("invalid pc")
+                    return
+            else:
+                address = 0
+
+            res = efi_symbols.address_to_symbols(address.pc)
+            print(res)
+
+        if options.extended:
+
+            gST = exe_ctx.target.FindFirstGlobalVariable('gST')
+            if gST.error.fail:
+                print('Error: This command requires symbols to be loaded')
+            else:
+                table = EfiConfigurationTable(file, gST.unsigned)
+                for address, _ in table.DebugImageInfo():
+                    res = efi_symbols.address_to_symbols(address)
+                    self.lldb_print(res)
+
+        # keep trying module file names until we find a GUID xref file
+        for m in exe_ctx.target.modules:
+            if GuidNames.add_build_guid_file(str(m.file)):
+                break
+
+
+def CHAR16_TypeSummary(valobj, internal_dict):
+    '''
+    Display CHAR16 as a String in the debugger.
+    Note: utf-8 is returned as that is the value for the debugger.
+    '''
+    SBError = lldb.SBError()
+    Str = ''
+    if valobj.TypeIsPointerType():
+        if valobj.GetValueAsUnsigned() == 0:
+            return "NULL"
+
+        # CHAR16 *   max string size 1024
+        for i in range(1024):
+            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0)
+            if SBError.fail or Char == 0:
+                break
+            Str += chr(Char)
+        return 'L"' + Str + '"'
+
+    if valobj.num_children == 0:
+        # CHAR16
+        return "L'" + chr(valobj.unsigned) + "'"
+
+    else:
+        # CHAR16 []
+        for i in range(valobj.num_children):
+            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0)
+            if Char == 0:
+                break
+            Str += chr(Char)
+        return 'L"' + Str + '"'
+
+    return Str
+
+
+def CHAR8_TypeSummary(valobj, internal_dict):
+    '''
+    Display CHAR8 as a String in the debugger.
+    Note: utf-8 is returned as that is the value for the debugger.
+    '''
+    SBError = lldb.SBError()
+    Str = ''
+    if valobj.TypeIsPointerType():
+        if valobj.GetValueAsUnsigned() == 0:
+            return "NULL"
+
+        # CHAR8 *   max string size 1024
+        for i in range(1024):
+            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0)
+            if SBError.fail or Char == 0:
+                break
+            Str += chr(Char)
+        Str = '"' + Str + '"'
+        return Str
+
+    if valobj.num_children == 0:
+        # CHAR8
+        return "'" + chr(valobj.unsigned) + "'"
+    else:
+        # CHAR8 []
+        for i in range(valobj.num_children):
+            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0)
+            if SBError.fail or Char == 0:
+                break
+            Str += chr(Char)
+        return '"' + Str + '"'
+
+    return Str
+
+
+def EFI_STATUS_TypeSummary(valobj, internal_dict):
+    if valobj.TypeIsPointerType():
+        return ''
+    return str(EfiStatusClass(valobj.unsigned))
+
+
+def EFI_TPL_TypeSummary(valobj, internal_dict):
+    if valobj.TypeIsPointerType():
+        return ''
+    return str(EfiTpl(valobj.unsigned))
+
+
+def EFI_GUID_TypeSummary(valobj, internal_dict):
+    if valobj.TypeIsPointerType():
+        return ''
+    return str(GuidNames(bytes(valobj.data.uint8)))
+
+
+def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict):
+    if valobj.TypeIsPointerType():
+        return ''
+    '''Return #define name for EFI_BOOT_MODE'''
+    return str(EfiBootMode(valobj.unsigned))
+
+
+def lldb_type_formaters(debugger, mod_name):
+    '''Teach lldb about EFI types'''
+
+    category = debugger.GetDefaultCategory()
+    FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool)
+
+    FormatHex = lldb.SBTypeFormat(lldb.eFormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
+        "EFI_PHYSICAL_ADDRESS"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
+        "PHYSICAL_ADDRESS"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex)
+    category.AddTypeFormat(
+        lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex)
+    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
+        "EFI_FV_FILETYPE"), FormatHex)
+
+    #
+    # Smart type printing for EFI
+    #
+
+    debugger.HandleCommand(
+        f'type summary add GUID - -python-function '
+        f'{mod_name}.EFI_GUID_TypeSummary')
+    debugger.HandleCommand(
+        f'type summary add EFI_GUID --python-function '
+        f'{mod_name}.EFI_GUID_TypeSummary')
+    debugger.HandleCommand(
+        f'type summary add EFI_STATUS --python-function '
+        f'{mod_name}.EFI_STATUS_TypeSummary')
+    debugger.HandleCommand(
+        f'type summary add EFI_TPL - -python-function '
+        f'{mod_name}.EFI_TPL_TypeSummary')
+    debugger.HandleCommand(
+        f'type summary add EFI_BOOT_MODE --python-function '
+        f'{mod_name}.EFI_BOOT_MODE_TypeSummary')
+
+    debugger.HandleCommand(
+        f'type summary add CHAR16 --python-function '
+        f'{mod_name}.CHAR16_TypeSummary')
+
+    # W605 this is the correct escape sequence for the lldb command
+    debugger.HandleCommand(
+        f'type summary add --regex "CHAR16 \[[0-9]+\]" '  # noqa: W605
+        f'--python-function {mod_name}.CHAR16_TypeSummary')
+
+    debugger.HandleCommand(
+        f'type summary add CHAR8 --python-function '
+        f'{mod_name}.CHAR8_TypeSummary')
+
+    # W605 this is the correct escape sequence for the lldb command
+    debugger.HandleCommand(
+        f'type summary add --regex "CHAR8 \[[0-9]+\]"  '  # noqa: W605
+        f'--python-function {mod_name}.CHAR8_TypeSummary')
+
+
+class LldbWorkaround:
+    needed = True
+
+    @classmethod
+    def activate(cls):
+        if cls.needed:
+            lldb.debugger.HandleCommand("process handle SIGALRM -n false")
+            cls.needed = False
+
+
+def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict):
+    #
+    # This is an lldb breakpoint script, and assumes the breakpoint is on a
+    # function with the same prototype as SecGdbScriptBreak(). The
+    # argument names are important as lldb looks them up.
+    #
+    # VOID
+    # SecGdbScriptBreak (
+    #   char                *FileName,
+    #   int                 FileNameLength,
+    #   long unsigned int   LoadAddress,
+    #   int                 AddSymbolFlag
+    #   )
+    # {
+    #   return;
+    # }
+    #
+    # When the emulator loads a PE/COFF image, it calls the stub function with
+    # the filename of the symbol file, the length of the FileName, the
+    # load address and a flag to indicate if this is a load or unload operation
+    #
+    LldbWorkaround().activate()
+
+    symbols = EfiSymbols(frame.thread.process.target)
+    LoadAddress = frame.FindVariable("LoadAddress").unsigned
+    if frame.FindVariable("AddSymbolFlag").unsigned == 1:
+        res = symbols.address_to_symbols(LoadAddress)
+    else:
+        res = symbols.unload_symbols(LoadAddress)
+    print(res)
+
+    # make breakpoint command continue
+    return False
+
+
+def __lldb_init_module(debugger, internal_dict):
+    '''
+    This initializer is being run from LLDB in the embedded command interpreter
+    '''
+
+    mod_name = Path(__file__).stem
+    lldb_type_formaters(debugger, mod_name)
+
+    # Add any commands contained in this module to LLDB
+    debugger.HandleCommand(
+        f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols')
+    debugger.HandleCommand(
+        f'command script add -c {mod_name}.EfiGuidCommand guid')
+    debugger.HandleCommand(
+        f'command script add -c {mod_name}.EfiTableCommand table')
+    debugger.HandleCommand(
+        f'command script add -c {mod_name}.EfiHobCommand hob')
+    debugger.HandleCommand(
+        f'command script add -c {mod_name}.EfiDevicePathCommand devicepath')
+
+    print('EFI specific commands have been installed.')
+
+    # patch the ctypes c_void_p values if the debuggers OS and EFI have
+    # different ideas on the size of the debug.
+    try:
+        patch_ctypes(debugger.GetSelectedTarget().addr_size)
+    except ValueError:
+        # incase the script is imported and the debugger has not target
+        # defaults to sizeof(UINTN) == sizeof(UINT64)
+        patch_ctypes()
+
+    try:
+        target = debugger.GetSelectedTarget()
+        if target.FindFunctions('SecGdbScriptBreak').symbols:
+            breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak')
+            # Set the emulator breakpoints, if we are in the emulator
+            cmd = 'breakpoint command add -s python -F '
+            cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}'
+            debugger.HandleCommand(cmd)
+            print('Type r to run emulator.')
+        else:
+            raise ValueError("No Emulator Symbols")
+
+    except ValueError:
+        # default action when the script is imported
+        debugger.HandleCommand("efi_symbols --frame --extended")
+        debugger.HandleCommand("register read")
+        debugger.HandleCommand("bt all")
+
+
+if __name__ == '__main__':
+    pass
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes
  2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran
@ 2022-03-26 14:04   ` Bob Feng
  0 siblings, 0 replies; 10+ messages in thread
From: Bob Feng @ 2022-03-26 14:04 UTC (permalink / raw)
  To: Rebecca Cran, devel@edk2.groups.io, Leif Lindholm,
	Kinney, Michael D, Wu, Hao A, Gao, Liming, Chen, Christine
  Cc: Andrew Fish

Reviewed-by: Bob Feng<bob.c.feng@intel.com>

-----Original Message-----
From: Rebecca Cran <quic_rcran@quicinc.com> 
Sent: Tuesday, March 22, 2022 4:21 AM
To: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>; Kinney, Michael D <michael.d.kinney@intel.com>; Wu, Hao A <hao.a.wu@intel.com>; Feng, Bob C <bob.c.feng@intel.com>; Gao, Liming <gaoliming@byosoft.com.cn>; Chen, Christine <yuwei.chen@intel.com>
Cc: Rebecca Cran <quic_rcran@quicinc.com>; Andrew Fish <afish@apple.com>
Subject: [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes

Add Scripts/efi_debugging.py to provide debugger agnostic debugging
utility Python classes.

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>
---
 BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++
 1 file changed, 2185 insertions(+)

diff --git a/BaseTools/Scripts/efi_debugging.py b/BaseTools/Scripts/efi_debugging.py
new file mode 100755
index 000000000000..af975f4e6ceb
--- /dev/null
+++ b/BaseTools/Scripts/efi_debugging.py
@@ -0,0 +1,2185 @@
+#!/usr/bin/python3
+'''
+Copyright (c) Apple Inc. 2021
+SPDX-License-Identifier: BSD-2-Clause-Patent
+
+Class that abstracts PE/COFF debug info parsing via a Python file like
+object. You can port this code into an arbitrary debugger by invoking
+the classes and passing in a file like object that abstracts the debugger
+reading memory.
+
+If you run this file directly it will parse the passed in PE/COFF files
+for debug info:
+python3 ./efi_pefcoff.py DxeCore.efi
+IA32`<path...>/DxeCore.dll load = 0x00000000
+EntryPoint = 0x000030d2  TextAddress = 0x00000240 DataAddress = 0x000042c0
+.text    0x00000240 (0x04080) flags:0x60000020
+.data    0x000042C0 (0x001C0) flags:0xC0000040
+.reloc   0x00004480 (0x00240) flags:0x42000040
+
+Note: PeCoffClass uses virtual addresses and not file offsets.
+       It needs to work when images are loaded into memory.
+       as long as virtual address map to file addresses this
+       code can process binary files.
+
+Note: This file can also contain generic worker functions (like GuidNames)
+      that abstract debugger agnostic services to the debugger.
+
+This file should never import debugger specific modules.
+'''
+
+import sys
+import os
+import uuid
+import struct
+import re
+from ctypes import c_char, c_uint8, c_uint16, c_uint32, c_uint64, c_void_p
+from ctypes import ARRAY, sizeof
+from ctypes import Structure, LittleEndianStructure
+
+#
+# The empty LittleEndianStructure must have _fields_ assigned prior to use or
+#  sizeof(). Anything that is size UINTN may need to get adjusted.
+#
+# The issue is ctypes matches our local machine, not the machine we are
+#  trying to debug. Call patch_ctypes() passing in the byte width from the
+#  debugger python to make sure you are in sync.
+#
+# Splitting out the _field_ from the Structure (LittleEndianStructure) class
+#  allows it to be patched.
+#
+
+
+class EFI_LOADED_IMAGE_PROTOCOL(LittleEndianStructure):
+    pass
+
+
+EFI_LOADED_IMAGE_PROTOCOL_fields_ = [
+    ('Revision',                      c_uint32),
+    ('ParentHandle',                  c_void_p),
+    ('SystemTable',                   c_void_p),
+    ('DeviceHandle',                  c_void_p),
+    ('FilePath',                      c_void_p),
+    ('Reserved',                      c_void_p),
+    ('LoadOptionsSize',               c_uint32),
+    ('LoadOptions',                   c_void_p),
+    ('ImageBase',                     c_void_p),
+    ('ImageSize',                     c_uint64),
+    ('ImageCodeType',                 c_uint32),
+    ('ImageDataType',                 c_uint32),
+    ('Unload',                        c_void_p),
+]
+
+
+class EFI_GUID(LittleEndianStructure):
+    _fields_ = [
+        ('Data1',               c_uint32),
+        ('Data2',               c_uint16),
+        ('Data3',               c_uint16),
+        ('Data4',               ARRAY(c_uint8, 8))
+    ]
+
+
+class EFI_SYSTEM_TABLE_POINTER(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',                     c_uint64),
+        ('EfiSystemTableBase',            c_uint64),
+        ('Crc32',                         c_uint32)
+    ]
+
+
+class EFI_DEBUG_IMAGE_INFO_NORMAL(LittleEndianStructure):
+    pass
+
+
+EFI_DEBUG_IMAGE_INFO_NORMAL_fields_ = [
+    ('ImageInfoType',                 c_uint32),
+    ('LoadedImageProtocolInstance',   c_void_p),
+    ('ImageHandle',                   c_void_p)
+]
+
+
+class EFI_DEBUG_IMAGE_INFO(LittleEndianStructure):
+    pass
+
+
+EFI_DEBUG_IMAGE_INFO_fields_ = [
+    ('NormalImage',                   c_void_p),
+]
+
+
+class EFI_DEBUG_IMAGE_INFO_TABLE_HEADER(LittleEndianStructure):
+    pass
+
+
+EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_ = [
+    ('UpdateStatus',                  c_uint32),
+    ('TableSize',                     c_uint32),
+    ('EfiDebugImageInfoTable',        c_void_p),
+]
+
+
+class EFI_TABLE_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',                     c_uint64),
+        ('Revision',                      c_uint32),
+        ('HeaderSize',                    c_uint32),
+        ('CRC32',                         c_uint32),
+        ('Reserved',                      c_uint32),
+    ]
+
+
+class EFI_CONFIGURATION_TABLE(LittleEndianStructure):
+    pass
+
+
+EFI_CONFIGURATION_TABLE_fields_ = [
+    ('VendorGuid',                    EFI_GUID),
+    ('VendorTable',                   c_void_p)
+]
+
+
+class EFI_SYSTEM_TABLE(LittleEndianStructure):
+    pass
+
+
+EFI_SYSTEM_TABLE_fields_ = [
+    ('Hdr',                           EFI_TABLE_HEADER),
+    ('FirmwareVendor',                c_void_p),
+    ('FirmwareRevision',              c_uint32),
+    ('ConsoleInHandle',               c_void_p),
+    ('ConIn',                         c_void_p),
+    ('ConsoleOutHandle',              c_void_p),
+    ('ConOut',                        c_void_p),
+    ('StandardErrHandle',             c_void_p),
+    ('StdErr',                        c_void_p),
+    ('RuntimeService',                c_void_p),
+    ('BootService',                   c_void_p),
+    ('NumberOfTableEntries',          c_void_p),
+    ('ConfigurationTable',            c_void_p),
+]
+
+
+class EFI_IMAGE_DATA_DIRECTORY(LittleEndianStructure):
+    _fields_ = [
+        ('VirtualAddress',       c_uint32),
+        ('Size',                 c_uint32)
+    ]
+
+
+class EFI_TE_IMAGE_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',            ARRAY(c_char, 2)),
+        ('Machine',              c_uint16),
+        ('NumberOfSections',     c_uint8),
+        ('Subsystem',            c_uint8),
+        ('StrippedSize',         c_uint16),
+        ('AddressOfEntryPoint',  c_uint32),
+        ('BaseOfCode',           c_uint32),
+        ('ImageBase',            c_uint64),
+        ('DataDirectoryBaseReloc',  EFI_IMAGE_DATA_DIRECTORY),
+        ('DataDirectoryDebug',      EFI_IMAGE_DATA_DIRECTORY)
+    ]
+
+
+class EFI_IMAGE_DOS_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('e_magic',              c_uint16),
+        ('e_cblp',               c_uint16),
+        ('e_cp',                 c_uint16),
+        ('e_crlc',               c_uint16),
+        ('e_cparhdr',            c_uint16),
+        ('e_minalloc',           c_uint16),
+        ('e_maxalloc',           c_uint16),
+        ('e_ss',                 c_uint16),
+        ('e_sp',                 c_uint16),
+        ('e_csum',               c_uint16),
+        ('e_ip',                 c_uint16),
+        ('e_cs',                 c_uint16),
+        ('e_lfarlc',             c_uint16),
+        ('e_ovno',               c_uint16),
+        ('e_res',                ARRAY(c_uint16, 4)),
+        ('e_oemid',              c_uint16),
+        ('e_oeminfo',            c_uint16),
+        ('e_res2',               ARRAY(c_uint16, 10)),
+        ('e_lfanew',             c_uint16)
+    ]
+
+
+class EFI_IMAGE_FILE_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Machine',               c_uint16),
+        ('NumberOfSections',      c_uint16),
+        ('TimeDateStamp',         c_uint32),
+        ('PointerToSymbolTable',  c_uint32),
+        ('NumberOfSymbols',       c_uint32),
+        ('SizeOfOptionalHeader',  c_uint16),
+        ('Characteristics',       c_uint16)
+    ]
+
+
+class EFI_IMAGE_OPTIONAL_HEADER32(LittleEndianStructure):
+    _fields_ = [
+        ('Magic',                         c_uint16),
+        ('MajorLinkerVersion',            c_uint8),
+        ('MinorLinkerVersion',            c_uint8),
+        ('SizeOfCode',                    c_uint32),
+        ('SizeOfInitializedData',         c_uint32),
+        ('SizeOfUninitializedData',       c_uint32),
+        ('AddressOfEntryPoint',           c_uint32),
+        ('BaseOfCode',                    c_uint32),
+        ('BaseOfData',                    c_uint32),
+        ('ImageBase',                     c_uint32),
+        ('SectionAlignment',              c_uint32),
+        ('FileAlignment',                 c_uint32),
+        ('MajorOperatingSystemVersion',   c_uint16),
+        ('MinorOperatingSystemVersion',   c_uint16),
+        ('MajorImageVersion',             c_uint16),
+        ('MinorImageVersion',             c_uint16),
+        ('MajorSubsystemVersion',         c_uint16),
+        ('MinorSubsystemVersion',         c_uint16),
+        ('Win32VersionValue',             c_uint32),
+        ('SizeOfImage',                   c_uint32),
+        ('SizeOfHeaders',                 c_uint32),
+        ('CheckSum',                 c_uint32),
+        ('Subsystem',                     c_uint16),
+        ('DllCharacteristics',            c_uint16),
+        ('SizeOfStackReserve',            c_uint32),
+        ('SizeOfStackCommit',            c_uint32),
+        ('SizeOfHeapReserve',             c_uint32),
+        ('SizeOfHeapCommit',             c_uint32),
+        ('LoaderFlags',              c_uint32),
+        ('NumberOfRvaAndSizes',           c_uint32),
+        ('DataDirectory',                 ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16))
+    ]
+
+
+class EFI_IMAGE_NT_HEADERS32(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',            c_uint32),
+        ('FileHeader',           EFI_IMAGE_FILE_HEADER),
+        ('OptionalHeader',       EFI_IMAGE_OPTIONAL_HEADER32)
+    ]
+
+
+class EFI_IMAGE_OPTIONAL_HEADER64(LittleEndianStructure):
+    _fields_ = [
+        ('Magic',                         c_uint16),
+        ('MajorLinkerVersion',            c_uint8),
+        ('MinorLinkerVersion',            c_uint8),
+        ('SizeOfCode',                    c_uint32),
+        ('SizeOfInitializedData',         c_uint32),
+        ('SizeOfUninitializedData',       c_uint32),
+        ('AddressOfEntryPoint',           c_uint32),
+        ('BaseOfCode',                    c_uint32),
+        ('BaseOfData',                    c_uint32),
+        ('ImageBase',                     c_uint32),
+        ('SectionAlignment',              c_uint32),
+        ('FileAlignment',                 c_uint32),
+        ('MajorOperatingSystemVersion',   c_uint16),
+        ('MinorOperatingSystemVersion',   c_uint16),
+        ('MajorImageVersion',             c_uint16),
+        ('MinorImageVersion',             c_uint16),
+        ('MajorSubsystemVersion',         c_uint16),
+        ('MinorSubsystemVersion',         c_uint16),
+        ('Win32VersionValue',             c_uint32),
+        ('SizeOfImage',                   c_uint32),
+        ('SizeOfHeaders',                 c_uint32),
+        ('CheckSum',                 c_uint32),
+        ('Subsystem',                     c_uint16),
+        ('DllCharacteristics',            c_uint16),
+        ('SizeOfStackReserve',            c_uint64),
+        ('SizeOfStackCommit',            c_uint64),
+        ('SizeOfHeapReserve',             c_uint64),
+        ('SizeOfHeapCommit',             c_uint64),
+        ('LoaderFlags',              c_uint32),
+        ('NumberOfRvaAndSizes',           c_uint32),
+        ('DataDirectory',                 ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16))
+    ]
+
+
+class EFI_IMAGE_NT_HEADERS64(LittleEndianStructure):
+    _fields_ = [
+        ('Signature',            c_uint32),
+        ('FileHeader',           EFI_IMAGE_FILE_HEADER),
+        ('OptionalHeader',       EFI_IMAGE_OPTIONAL_HEADER64)
+    ]
+
+
+class EFI_IMAGE_DEBUG_DIRECTORY_ENTRY(LittleEndianStructure):
+    _fields_ = [
+        ('Characteristics',            c_uint32),
+        ('TimeDateStamp',              c_uint32),
+        ('MajorVersion',               c_uint16),
+        ('MinorVersion',               c_uint16),
+        ('Type',                       c_uint32),
+        ('SizeOfData',                 c_uint32),
+        ('RVA',                        c_uint32),
+        ('FileOffset',                 c_uint32),
+    ]
+
+
+class EFI_IMAGE_SECTION_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('Name',                       ARRAY(c_char, 8)),
+        ('VirtualSize',                c_uint32),
+        ('VirtualAddress',             c_uint32),
+        ('SizeOfRawData',              c_uint32),
+        ('PointerToRawData',           c_uint32),
+        ('PointerToRelocations',       c_uint32),
+        ('PointerToLinenumbers',       c_uint32),
+        ('NumberOfRelocations',        c_uint16),
+        ('NumberOfLinenumbers',        c_uint16),
+        ('Characteristics',            c_uint32),
+    ]
+
+
+EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b
+EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
+
+DIRECTORY_DEBUG = 6
+
+
+image_machine_dict = {
+    0x014c: "IA32",
+    0x0200: "IPF",
+    0x0EBC: "EBC",
+    0x8664: "X64",
+    0x01c2: "ARM",
+    0xAA64: "AArch64",
+    0x5032: "RISC32",
+    0x5064: "RISC64",
+    0x5128: "RISCV128",
+}
+
+
+def patch_void_p_to_ctype(patch_type, to_patch):
+    '''Optionally patch c_void_p in the Structure._fields_'''
+    if patch_type is None:
+        return to_patch
+
+    result = []
+    for name, c_type in to_patch:
+        if type(c_type) == type(c_void_p):
+            result.append((name, c_uint32))
+        else:
+            result.append((name, c_type))
+    return result
+
+
+def patch_ctypes(pointer_width=8):
+    '''
+    Pass in the pointer width of the system being debugged. If it is not
+    the same as c_void_p then patch the _fields_ with the correct type.
+    For any ctypes Structure that has a c_void_p this function needs to be
+    called prior to use or sizeof() to initialize _fields_.
+    '''
+
+    if sizeof(c_void_p) == pointer_width:
+        patch_type = None
+    elif pointer_width == 16:
+        assert False
+    elif pointer_width == 8:
+        patch_type = c_uint64
+    elif pointer_width == 4:
+        patch_type = c_uint32
+    else:
+        raise Exception(f'ERROR: Unkown pointer_width = {pointer_width}')
+
+    # If you add a ctypes Structure class with a c_void_p you need to add
+    # it to this list. Note: you should use c_void_p for UINTN values.
+    EFI_LOADED_IMAGE_PROTOCOL._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_LOADED_IMAGE_PROTOCOL_fields_)
+    EFI_DEBUG_IMAGE_INFO_NORMAL._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_DEBUG_IMAGE_INFO_NORMAL_fields_)
+    EFI_DEBUG_IMAGE_INFO._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_DEBUG_IMAGE_INFO_fields_)
+    EFI_DEBUG_IMAGE_INFO_TABLE_HEADER._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_)
+    EFI_CONFIGURATION_TABLE._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_CONFIGURATION_TABLE_fields_)
+    EFI_SYSTEM_TABLE._fields_ = patch_void_p_to_ctype(
+        patch_type, EFI_SYSTEM_TABLE_fields_)
+
+    # patch up anything else that needs to know pointer_width
+    EfiStatusClass(pointer_width)
+
+
+def ctype_to_str(ctype, indent='', hide_list=[]):
+    '''
+    Given a ctype object print out as a string by walking the _fields_
+    in the cstring Class
+     '''
+    result = ''
+    for field in ctype._fields_:
+        attr = getattr(ctype, field[0])
+        tname = type(attr).__name__
+        if field[0] in hide_list:
+            continue
+
+        result += indent + f'{field[0]} = '
+        if tname == 'EFI_GUID':
+            result += GuidNames.to_name(GuidNames.to_uuid(attr)) + '\n'
+        elif issubclass(type(attr), Structure):
+            result += f'{tname}\n' + \
+                ctype_to_str(attr, indent + '  ', hide_list)
+        elif isinstance(attr, int):
+            result += f'0x{attr:x}\n'
+        else:
+            result += f'{attr}\n'
+
+    return result
+
+
+def hexline(addr, data):
+    hexstr = ''
+    printable = ''
+    for i in range(0, len(data)):
+        hexstr += f'{data[i]:02x} '
+        printable += chr(data[i]) if data[i] > 0x20 and data[i] < 0x7f else '.'
+    return f'{addr:04x}  {hexstr:48s} |{printable:s}|'
+
+
+def hexdump(data, indent=''):
+    if not isinstance(data, bytearray):
+        data = bytearray(data)
+
+    result = ''
+    for i in range(0, len(data), 16):
+        result += indent + hexline(i, data[i:i+16]) + '\n'
+    return result
+
+
+class EfiTpl:
+    ''' Return string for EFI_TPL'''
+
+    def __init__(self, tpl):
+        self.tpl = tpl
+
+    def __str__(self):
+        if self.tpl < 4:
+            result = f'{self.tpl:d}'
+        elif self.tpl < 8:
+            result = "TPL_APPLICATION"
+            if self.tpl - 4 > 0:
+                result += f' + {self.tpl - 4:d}'
+        elif self.tpl < 16:
+            result = "TPL_CALLBACK"
+            if self.tpl - 8 > 0:
+                result += f' + {self.tpl - 8:d}'
+        elif self.tpl < 31:
+            result = "TPL_NOTIFY"
+            if self.tpl - 16 > 0:
+                result += f' + {self.tpl - 16:d}'
+        elif self.tpl == 31:
+            result = "TPL_HIGH_LEVEL"
+        else:
+            result = f'Invalid TPL = {self.tpl:d}'
+        return result
+
+
+class EfiBootMode:
+    '''
+    Class to return human readable string for EFI_BOOT_MODE
+
+    Methods
+    -----------
+    to_str(boot_mode, default)
+        return string for boot_mode, and return default if there is not a
+        match.
+    '''
+
+    EFI_BOOT_MODE_dict = {
+        0x00: "BOOT_WITH_FULL_CONFIGURATION",
+        0x01: "BOOT_WITH_MINIMAL_CONFIGURATION",
+        0x02: "BOOT_ASSUMING_NO_CONFIGURATION_CHANGES",
+        0x03: "BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS",
+        0x04: "BOOT_WITH_DEFAULT_SETTINGS",
+        0x05: "BOOT_ON_S4_RESUME",
+        0x06: "BOOT_ON_S5_RESUME",
+        0x07: "BOOT_WITH_MFG_MODE_SETTINGS",
+        0x10: "BOOT_ON_S2_RESUME",
+        0x11: "BOOT_ON_S3_RESUME",
+        0x12: "BOOT_ON_FLASH_UPDATE",
+        0x20: "BOOT_IN_RECOVERY_MODE",
+    }
+
+    def __init__(self, boot_mode):
+        self._boot_mode = boot_mode
+
+    def __str__(self):
+        return self.to_str(self._boot_mode)
+
+    @classmethod
+    def to_str(cls, boot_mode, default=''):
+        return cls.EFI_BOOT_MODE_dict.get(boot_mode, default)
+
+
+class EfiStatusClass:
+    '''
+    Class to decode EFI_STATUS to a human readable string. You need to
+    pass in pointer_width to get the corret value since the EFI_STATUS
+    code values are different based on the sizeof UINTN. The default is
+    sizeof(UINTN) == 8.
+
+    Attributes
+    ——————
+    _dict_ : dictionary
+        dictionary of EFI_STATUS that has beed updated to match
+        pointer_width.
+
+    Methods
+    -----------
+    patch_dictionary(pointer_width)
+
+    to_str(status, default)
+    '''
+
+    _dict_ = {}
+    _EFI_STATUS_UINT32_dict = {
+        0: "Success",
+        1: "Warning Unknown Glyph",
+        2: "Warning Delete Failure",
+        3: "Warning Write Failure",
+        4: "Warning Buffer Too Small",
+        5: "Warning Stale Data",
+        6: "Warngin File System",
+        (0x20000000 | 0): "Warning interrupt source pending",
+        (0x20000000 | 1): "Warning interrupt source quiesced",
+
+        (0x80000000 | 1): "Load Error",
+        (0x80000000 | 2): "Invalid Parameter",
+        (0x80000000 | 3): "Unsupported",
+        (0x80000000 | 4): "Bad Buffer Size",
+        (0x80000000 | 5): "Buffer Too Small",
+        (0x80000000 | 6): "Not Ready",
+        (0x80000000 | 7): "Device Error",
+        (0x80000000 | 8): "Write Protected",
+        (0x80000000 | 9): "Out of Resources",
+        (0x80000000 | 10): "Volume Corrupt",
+        (0x80000000 | 11): "Volume Full",
+        (0x80000000 | 12): "No Media",
+        (0x80000000 | 13): "Media changed",
+        (0x80000000 | 14): "Not Found",
+        (0x80000000 | 15): "Access Denied",
+        (0x80000000 | 16): "No Response",
+        (0x80000000 | 17): "No mapping",
+        (0x80000000 | 18): "Time out",
+        (0x80000000 | 19): "Not started",
+        (0x80000000 | 20): "Already started",
+        (0x80000000 | 21): "Aborted",
+        (0x80000000 | 22): "ICMP Error",
+        (0x80000000 | 23): "TFTP Error",
+        (0x80000000 | 24): "Protocol Error",
+        (0x80000000 | 25): "Incompatible Version",
+        (0x80000000 | 26): "Security Violation",
+        (0x80000000 | 27): "CRC Error",
+        (0x80000000 | 28): "End of Media",
+        (0x80000000 | 31): "End of File",
+        (0x80000000 | 32): "Invalid Language",
+        (0x80000000 | 33): "Compromised Data",
+        (0x80000000 | 35): "HTTP Error",
+
+        (0xA0000000 | 0): "Interrupt Pending",
+    }
+
+    def __init__(self, status=None, pointer_width=8):
+        self.status = status
+        # this will convert to 64-bit version if needed
+        self.patch_dictionary(pointer_width)
+
+    def __str__(self):
+        return self.to_str(self.status)
+
+    @classmethod
+    def to_str(cls, status, default=''):
+        return cls._dict_.get(status, default)
+
+    @classmethod
+    def patch_dictionary(cls, pointer_width):
+        '''Patch UINTN upper bits like values '''
+
+        if cls._dict_:
+            # only patch the class variable once
+            return False
+
+        if pointer_width == 4:
+            cls._dict = cls._EFI_STATUS_UINT32_dict
+        elif pointer_width == 8:
+            for key, value in cls._EFI_STATUS_UINT32_dict.items():
+                mask = (key & 0xE0000000) << 32
+                new_key = (key & 0x1FFFFFFF) | mask
+                cls._dict_[new_key] = value
+            return True
+        else:
+            return False
+
+
+class GuidNames:
+    '''
+    Class to expose the C names of EFI_GUID's. The _dict_ starts with
+    common EFI System Table entry EFI_GUID's. _dict_ can get updated with the
+    build generated Guid.xref file if a path to a module is passed
+    into add_build_guid_file(). If symbols are loaded for any module
+    in the build the path the build product should imply the
+    relative location of that builds Guid.xref file.
+
+    Attributes
+    ——————----
+    _dict_ : dictionary
+        dictionary of EFI_GUID (uuid) strings to C global names
+
+    Methods
+    -------
+    to_uuid(uuid)
+        convert a hex UUID string or bytearray to a uuid.UUID
+    to_name(uuid)
+        convert a UUID string to a C global constant name.
+    to_guid(guid_name)
+        convert a C global constant EFI_GUID name to uuid hex string.
+    is_guid_str(name)
+       name is a hex UUID string.
+       Example: 49152E77-1ADA-4764-B7A2-7AFEFED95E8B
+
+    to_c_guid(value)
+        convert a uuid.UUID or UUID string to a c_guid string
+        (see is_c_guid())
+    from_c_guid(value)
+        covert a C guid string to a hex UUID string.
+    is_c_guid(name)
+        name is the C initialization value for an EFI_GUID. Example:
+        { 0x414e6bdd, 0xe47b, 0x47cc, { 0xb2, 0x44, 0xbb, 0x61,
+                                        0x02, 0x0c, 0xf5, 0x16 }}
+
+    add_build_guid_file(module_path, custom_file):
+        assume module_path is an edk2 build product and load the Guid.xref
+        file from that build to fill in _dict_. If you know the path and
+        file name of a custom Guid.xref  you can pass it in as custom_file.
+
+    '''
+    _dict_ = {  # Common EFI System Table values
+        '05AD34BA-6F02-4214-952E-4DA0398E2BB9':
+            'gEfiDxeServicesTableGuid',
+        '7739F24C-93D7-11D4-9A3A-0090273FC14D':
+            'gEfiHobListGuid',
+        '4C19049F-4137-4DD3-9C10-8B97A83FFDFA':
+            'gEfiMemoryTypeInformationGuid',
+        '49152E77-1ADA-4764-B7A2-7AFEFED95E8B':
+            'gEfiDebugImageInfoTableGuid',
+        '060CC026-4C0D-4DDA-8F41-595FEF00A502':
+            'gMemoryStatusCodeRecordGuid',
+        'EB9D2D31-2D88-11D3-9A16-0090273FC14D':
+            'gEfiSmbiosTableGuid',
+        'EB9D2D30-2D88-11D3-9A16-0090273FC14D':
+            'gEfiAcpi10TableGuid',
+        '8868E871-E4F1-11D3-BC22-0080C73C8881':
+            'gEfiAcpi20TableGuid',
+    }
+
+    guid_files = []
+
+    def __init__(self, uuid=None, pointer_width=8):
+        self.uuid = None if uuid is None else self.to_uuid(uuid)
+
+    def __str__(self):
+        if self.uuid is None:
+            result = ''
+            for key, value in GuidNames._dict_.items():
+                result += f'{key}: {value}\n'
+        else:
+            result = self.to_name(self.uuid)
+
+        return result
+
+    @classmethod
+    def to_uuid(cls, obj):
+        try:
+            return uuid.UUID(bytes_le=bytes(obj))
+        except (ValueError, TypeError):
+            try:
+                return uuid.UUID(bytes_le=obj)
+            except (ValueError, TypeError):
+                return uuid.UUID(obj)
+
+    @classmethod
+    def to_name(cls, uuid):
+        if not isinstance(uuid, str):
+            uuid = str(uuid)
+        if cls.is_c_guid(uuid):
+            uuid = cls.from_c_guid(uuid)
+        return cls._dict_.get(uuid.upper(), uuid.upper())
+
+    @classmethod
+    def to_guid(cls, guid_name):
+        for key, value in cls._dict_.items():
+            if guid_name == value:
+                return key.upper()
+        else:
+            raise KeyError(key)
+
+    @classmethod
+    def is_guid_str(cls, name):
+        if not isinstance(name, str):
+            return False
+        return name.count('-') >= 4
+
+    @classmethod
+    def to_c_guid(cls, value):
+        if isinstance(value, uuid.UUID):
+            guid = value
+        else:
+            guid = uuid.UUID(value)
+
+        (data1, data2, data3,
+         data4_0, data4_1, data4_2, data4_3,
+         data4_4, data4_5, data4_6, data4_7) = struct.unpack(
+            '<IHH8B', guid.bytes_le)
+        return (f'{{ 0x{data1:08X}, 0x{data2:04X}, 0x{data3:04X}, '
+                f'{{ 0x{data4_0:02X}, 0x{data4_1:02X}, 0x{data4_2:02X}, '
+                f'0x{data4_3:02X}, 0x{data4_4:02X}, 0x{data4_5:02X}, '
+                f'0x{data4_6:02X}, 0x{data4_7:02X} }} }}')
+
+    @ classmethod
+    def from_c_guid(cls, value):
+        try:
+            hex = [int(x, 16) for x in re.findall(r"[\w']+", value)]
+            return (f'{hex[0]:08X}-{hex[1]:04X}-{hex[2]:04X}'
+                    + f'-{hex[3]:02X}{hex[4]:02X}-{hex[5]:02X}{hex[6]:02X}'
+                    + f'{hex[7]:02X}{hex[8]:02X}{hex[9]:02X}{hex[10]:02X}')
+        except ValueError:
+            return value
+
+    @ classmethod
+    def is_c_guid(cls, name):
+        if not isinstance(name, str):
+            return False
+        return name.count('{') == 2 and name.count('}') == 2
+
+    @ classmethod
+    def add_build_guid_file(cls, module_path, custom_file=None):
+        if custom_file is not None:
+            xref = custom_file
+        else:
+            # module_path will look like:
+            # <repo>/Build/OvmfX64/DEBUG_XCODE5/X64/../DxeCore.dll
+            # Walk backwards looking for a toolchain like name.
+            # Then look for GUID database:
+            # Build/OvmfX64//DEBUG_XCODE5/FV/Guid.xref
+            for i in reversed(module_path.split(os.sep)):
+                if (i.startswith('DEBUG_') or
+                    i.startswith('RELEASE_') or
+                        i.startswith('NOOPT_')):
+                    build_root = os.path.join(
+                        module_path.rsplit(i, 1)[0], i)
+                    break
+
+            xref = os.path.join(build_root, 'FV', 'Guid.xref')
+
+        if xref in cls.guid_files:
+            # only processes the file one time
+            return True
+
+        with open(xref) as f:
+            content = f.readlines()
+            cls.guid_files.append(xref)
+
+            for lines in content:
+                try:
+                    if cls.is_guid_str(lines):
+                        # a regex would be more pedantic
+                        words = lines.split()
+                        cls._dict_[words[0].upper()] = words[1].strip('\n')
+                except ValueError:
+                    pass
+
+            return True
+
+        return False
+
+
+class EFI_HOB_GENERIC_HEADER(LittleEndianStructure):
+    _fields_ = [
+        ('HobType',             c_uint16),
+        ('HobLength',           c_uint16),
+        ('Reserved',            c_uint32)
+    ]
+
+
+class EFI_HOB_HANDOFF_INFO_TABLE(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Version',             c_uint32),
+        ('BootMode',            c_uint32),
+        ('EfiMemoryTop',        c_uint64),
+        ('EfiMemoryBottom',     c_uint64),
+        ('EfiFreeMemoryTop',    c_uint64),
+        ('EfiFreeMemoryBottom', c_uint64),
+        ('EfiEndOfHobList',     c_uint64),
+    ]
+
+
+class EFI_HOB_MEMORY_ALLOCATION(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Name',                EFI_GUID),
+        ('MemoryBaseAddress',   c_uint64),
+        ('MemoryLength',        c_uint64),
+        ('MemoryType',          c_uint32),
+        ('Reserved',            c_uint32),
+    ]
+
+
+class EFI_HOB_RESOURCE_DESCRIPTOR(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Owner',               EFI_GUID),
+        ('ResourceType',        c_uint32),
+        ('ResourceAttribute',   c_uint32),
+        ('PhysicalStart',       c_uint64),
+        ('ResourceLength',      c_uint64),
+    ]
+
+
+class EFI_HOB_GUID_TYPE(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('Name',                EFI_GUID),
+    ]
+
+
+class EFI_HOB_FIRMWARE_VOLUME(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+    ]
+
+
+class EFI_HOB_CPU(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('SizeOfMemorySpace',   c_uint8),
+        ('SizeOfIoSpace',       c_uint8),
+        ('Reserved',            ARRAY(c_uint8, 6)),
+    ]
+
+
+class EFI_HOB_MEMORY_POOL(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+    ]
+
+
+class EFI_HOB_FIRMWARE_VOLUME2(LittleEndianStructure):
+    _fields_ = [
+        ('Header',              EFI_HOB_GENERIC_HEADER),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+        ('FvName',              EFI_GUID),
+        ('FileName',            EFI_GUID)
+    ]
+
+
+class EFI_HOB_FIRMWARE_VOLUME3(LittleEndianStructure):
+    _fields_ = [
+        ('HobType',             c_uint16),
+        ('HobLength',           c_uint16),
+        ('Reserved',            c_uint32),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+        ('AuthenticationStatus', c_uint32),
+        ('ExtractedFv',         c_uint8),
+        ('FvName',              EFI_GUID),
+        ('FileName',            EFI_GUID),
+    ]
+
+
+class EFI_HOB_UEFI_CAPSULE(LittleEndianStructure):
+    _fields_ = [
+        ('HobType',             c_uint16),
+        ('HobLength',           c_uint16),
+        ('Reserved',            c_uint32),
+        ('BaseAddress',         c_uint64),
+        ('Length',              c_uint64),
+    ]
+
+
+class EfiHob:
+    '''
+    Parse EFI Device Paths based on the edk2 C Structures defined above.
+    In the context of this class verbose means hexdump extra data.
+
+
+    Attributes
+    ——————
+    Hob : list
+        List of HOBs. Each entry contains the name, HOB type, HOB length,
+        the ctype struct for the HOB, and any extra data.
+
+    Methods
+    -----------
+    get_hob_by_type(hob_type)
+        return string that decodes the HOBs of hob_type. If hob_type is
+        None then return all HOBs.
+    '''
+
+    Hob = []
+    verbose = False
+
+    hob_dict = {
+        1: EFI_HOB_HANDOFF_INFO_TABLE,
+        2: EFI_HOB_MEMORY_ALLOCATION,
+        3: EFI_HOB_RESOURCE_DESCRIPTOR,
+        4: EFI_HOB_GUID_TYPE,
+        5: EFI_HOB_FIRMWARE_VOLUME,
+        6: EFI_HOB_CPU,
+        7: EFI_HOB_MEMORY_POOL,
+        9: EFI_HOB_FIRMWARE_VOLUME2,
+        0xb: EFI_HOB_UEFI_CAPSULE,
+        0xc: EFI_HOB_FIRMWARE_VOLUME3,
+        0xffff: EFI_HOB_GENERIC_HEADER,
+    }
+
+    def __init__(self, file, address=None, verbose=False, count=1000):
+        self._file = file
+        EfiHob.verbose = verbose
+
+        if len(EfiHob.Hob) != 0 and address is None:
+            return
+
+        if address is not None:
+            hob_ptr = address
+        else:
+            hob_ptr = EfiConfigurationTable(file).GetConfigTable(
+                '7739F24C-93D7-11D4-9A3A-0090273FC14D')
+
+        self.read_hobs(hob_ptr)
+
+    @ classmethod
+    def __str__(cls):
+        return cls.get_hob_by_type(None)
+
+    @ classmethod
+    def get_hob_by_type(cls, hob_type):
+        result = ""
+        for (Name, HobType, HobLen, chob, extra) in cls.Hob:
+            if hob_type is not None:
+                if hob_type != HobType:
+                    continue
+
+            result += f'Type: {Name:s} (0x{HobType:01x}) Len: 0x{HobLen:03x}\n'
+            result += ctype_to_str(chob, '  ', ['Reserved'])
+            if cls.verbose:
+                if extra is not None:
+                    result += hexdump(extra, '    ')
+
+        return result
+
+    def read_hobs(self, hob_ptr, count=1000):
+        if hob_ptr is None:
+            return
+
+        try:
+            for _ in range(count):  # while True
+                hdr, _ = self._ctype_read_ex(EFI_HOB_GENERIC_HEADER, hob_ptr)
+                if hdr.HobType == 0xffff:
+                    break
+
+                type_str = self.hob_dict.get(
+                    hdr.HobType, EFI_HOB_GENERIC_HEADER)
+                hob, extra = self._ctype_read_ex(
+                    type_str, hob_ptr, hdr.HobLength)
+                EfiHob.Hob.append(
+                    (type(hob).__name__,
+                     hdr.HobType,
+                     hdr.HobLength,
+                     hob,
+                     extra))
+                hob_ptr += hdr.HobLength
+        except ValueError:
+            pass
+
+    def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None):
+        if offset != 0:
+            self._file.seek(offset)
+
+        type_size = sizeof(ctype_struct)
+        size = rsize if rsize else type_size
+        data = self._file.read(size)
+        cdata = ctype_struct.from_buffer(bytearray(data))
+
+        if size > type_size:
+            return cdata, data[type_size:]
+        else:
+            return cdata, None
+
+
+class EFI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Type',                c_uint8),
+        ('SubType',             c_uint8),
+
+        # UINT8 Length[2]
+        # Cheat and use c_uint16 since we don't care about alignment
+        ('Length',              c_uint16)
+    ]
+
+
+class PCI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Function',            c_uint8),
+        ('Device',              c_uint8)
+    ]
+
+
+class PCCARD_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('FunctionNumber',      c_uint8),
+    ]
+
+
+class MEMMAP_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('StartingAddress',     c_uint64),
+        ('EndingAddress',       c_uint64),
+    ]
+
+
+class VENDOR_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Guid',                EFI_GUID),
+    ]
+
+
+class CONTROLLER_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('ControllerNumber',    c_uint32),
+    ]
+
+
+class BMC_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('InterfaceType',       c_uint8),
+        ('BaseAddress',         ARRAY(c_uint8, 8)),
+    ]
+
+
+class BBS_BBS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('DeviceType',          c_uint16),
+        ('StatusFlag',          c_uint16)
+    ]
+
+
+class ACPI_HID_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('HID',                 c_uint32),
+        ('UID',                 c_uint32)
+    ]
+
+
+class ACPI_EXTENDED_HID_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('HID',                 c_uint32),
+        ('UID',                 c_uint32),
+        ('CID',                 c_uint32)
+    ]
+
+
+class ACPI_ADR_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('ARD',                 c_uint32)
+    ]
+
+
+class ACPI_NVDIMM_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('NFITDeviceHandle',    c_uint32)
+    ]
+
+
+class ATAPI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("PrimarySecondary",    c_uint8),
+        ("SlaveMaster",         c_uint8),
+        ("Lun",                 c_uint16)
+    ]
+
+
+class SCSI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Pun",                 c_uint16),
+        ("Lun",                 c_uint16)
+    ]
+
+
+class FIBRECHANNEL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Reserved",            c_uint32),
+        ("WWN",                 c_uint64),
+        ("Lun",                 c_uint64)
+    ]
+
+
+class F1394_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Reserved",            c_uint32),
+        ("Guid",                c_uint64)
+    ]
+
+
+class USB_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("ParentPortNumber",    c_uint8),
+        ("InterfaceNumber",     c_uint8),
+    ]
+
+
+class I2O_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Tid",                 c_uint32)
+    ]
+
+
+class INFINIBAND_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("ResourceFlags",       c_uint32),
+        ("PortGid",             ARRAY(c_uint8, 16)),
+        ("ServiceId",           c_uint64),
+        ("TargetPortId",        c_uint64),
+        ("DeviceId",            c_uint64)
+    ]
+
+
+class UART_FLOW_CONTROL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Guid",                EFI_GUID),
+        ("FlowControlMap",      c_uint32)
+    ]
+
+
+class SAS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Guid",                EFI_GUID),
+        ("Reserved",            c_uint32),
+        ("SasAddress",          c_uint64),
+        ("Lun",                 c_uint64),
+        ("DeviceTopology",      c_uint16),
+        ("RelativeTargetPort",  c_uint16)
+    ]
+
+
+class EFI_MAC_ADDRESS(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ("Addr",             ARRAY(c_uint8, 32)),
+    ]
+
+
+class MAC_ADDR_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('MacAddress',          EFI_MAC_ADDRESS),
+        ('IfType',              c_uint8)
+    ]
+
+
+class IPv4_ADDRESS(LittleEndianStructure):
+    _fields_ = [
+        ("Addr",             ARRAY(c_uint8, 4)),
+    ]
+
+
+class IPv4_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('LocalIpAddress',      IPv4_ADDRESS),
+        ('RemoteIpAddress',     IPv4_ADDRESS),
+        ('LocalPort',           c_uint16),
+        ('RemotePort',          c_uint16),
+        ('Protocol',            c_uint16),
+        ('StaticIpAddress',     c_uint8),
+        ('GatewayIpAddress',    IPv4_ADDRESS),
+        ('SubnetMask',          IPv4_ADDRESS)
+    ]
+
+
+class IPv6_ADDRESS(LittleEndianStructure):
+    _fields_ = [
+        ("Addr",             ARRAY(c_uint8, 16)),
+    ]
+
+
+class IPv6_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('LocalIpAddress',      IPv6_ADDRESS),
+        ('RemoteIpAddress',     IPv6_ADDRESS),
+        ('LocalPort',           c_uint16),
+        ('RemotePort',          c_uint16),
+        ('Protocol',            c_uint16),
+        ('IpAddressOrigin',     c_uint8),
+        ('PrefixLength',        c_uint8),
+        ('GatewayIpAddress',    IPv6_ADDRESS)
+    ]
+
+
+class UART_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Reserved',            c_uint32),
+        ('BaudRate',            c_uint64),
+        ('DataBits',            c_uint8),
+        ('Parity',              c_uint8),
+        ('StopBits',            c_uint8)
+    ]
+
+
+class USB_CLASS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('VendorId',            c_uint16),
+        ('ProductId',           c_uint16),
+        ('DeviceClass',         c_uint8),
+        ('DeviceCSjblass',      c_uint8),
+        ('DeviceProtocol',      c_uint8),
+    ]
+
+
+class USB_WWID_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('InterfaceNumber',     c_uint16),
+        ('VendorId',            c_uint16),
+        ('ProductId',           c_uint16),
+    ]
+
+
+class DEVICE_LOGICAL_UNIT_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Lun',                 c_uint8)
+    ]
+
+
+class SATA_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',                      EFI_DEVICE_PATH),
+        ('HBAPortNumber',               c_uint16),
+        ('PortMultiplierPortNumber',    c_uint16),
+        ('Lun',                         c_uint16),
+    ]
+
+
+class ISCSI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',                EFI_DEVICE_PATH),
+        ('NetworkProtocol',       c_uint16),
+        ('LoginOption',           c_uint16),
+        ('Lun',                   c_uint64),
+        ('TargetPortalGroupTag',  c_uint16),
+    ]
+
+
+class VLAN_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("VlandId",             c_uint16)
+    ]
+
+
+class FIBRECHANNELEX_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Reserved",            c_uint16),
+        ("WWN",                 ARRAY(c_uint8, 8)),
+        ("Lun",                 ARRAY(c_uint8, 8)),
+    ]
+
+
+class SASEX_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SasAddress",          ARRAY(c_uint8, 8)),
+        ("Lun",                 ARRAY(c_uint8, 8)),
+        ("DeviceTopology",      c_uint16),
+        ("RelativeTargetPort",  c_uint16)
+    ]
+
+
+class NVME_NAMESPACE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("NamespaceId",         c_uint32),
+        ("NamespaceUuid",       c_uint64)
+    ]
+
+
+class DNS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("IsIPv6",              c_uint8),
+        ("DnsServerIp",         IPv6_ADDRESS)
+
+    ]
+
+
+class UFS_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Pun",                 c_uint8),
+        ("Lun",                 c_uint8),
+    ]
+
+
+class SD_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SlotNumber",          c_uint8)
+    ]
+
+
+class BLUETOOTH_ADDRESS(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ("Address",             ARRAY(c_uint8, 6))
+    ]
+
+
+class BLUETOOTH_LE_ADDRESS(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ("Format",          c_uint8),
+        ("Class",           c_uint16)
+    ]
+
+
+class BLUETOOTH_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("BD_ADDR",             BLUETOOTH_ADDRESS)
+    ]
+
+
+class WIFI_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SSId",                ARRAY(c_uint8, 32))
+    ]
+
+
+class EMMC_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("SlotNumber",          c_uint8)
+    ]
+
+
+class BLUETOOTH_LE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("BD_ADDR",             BLUETOOTH_LE_ADDRESS)
+    ]
+
+
+class NVDIMM_NAMESPACE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("Uuid",                EFI_GUID)
+    ]
+
+
+class REST_SERVICE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("RESTService",         c_uint8),
+        ("AccessMode",          c_uint8)
+    ]
+
+
+class REST_VENDOR_SERVICE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ("RESTService",         c_uint8),
+        ("AccessMode",          c_uint8),
+        ("Guid",                EFI_GUID),
+    ]
+
+
+class HARDDRIVE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('PartitionNumber',     c_uint32),
+        ('PartitionStart',      c_uint64),
+        ('PartitionSize',       c_uint64),
+        ('Signature',           ARRAY(c_uint8, 16)),
+        ('MBRType',             c_uint8),
+        ('SignatureType',       c_uint8)
+    ]
+
+
+class CDROM_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('BootEntry',           c_uint32),
+        ('PartitionStart',      c_uint64),
+        ('PartitionSize',       c_uint64)
+    ]
+
+
+class MEDIA_PROTOCOL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Protocol',            EFI_GUID)
+    ]
+
+
+class MEDIA_FW_VOL_FILEPATH_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('FvFileName',          EFI_GUID)
+    ]
+
+
+class MEDIA_FW_VOL_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('FvName',              EFI_GUID)
+    ]
+
+
+class MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('Reserved',            c_uint32),
+        ('StartingOffset',      c_uint64),
+        ('EndingOffset',        c_uint64)
+    ]
+
+
+class MEDIA_RAM_DISK_DEVICE_PATH(LittleEndianStructure):
+    _pack_ = 1
+    _fields_ = [
+        ('Header',              EFI_DEVICE_PATH),
+        ('StartingAddr',        c_uint64),
+        ('EndingAddr',          c_uint64),
+        ('TypeGuid',            EFI_GUID),
+        ('Instance',            c_uint16)
+    ]
+
+
+class EfiDevicePath:
+    '''
+    Parse EFI Device Paths based on the edk2 C Structures defined above.
+    In the context of this class verbose means hexdump extra data.
+
+
+    Attributes
+    ——————
+    DevicePath : list
+        List of devixe path instances. Each instance is a list of nodes
+        for the given Device Path instance.
+
+    Methods
+    -----------
+    device_path_node(address)
+        return the Device Path ctype hdr, ctype, and any extra data in
+        the Device Path node. This is just a single Device Path node,
+        not the entire Device Path.
+    device_path_node_str(address)
+        return the device path node (not the entire Device Path) as a string
+    '''
+
+    DevicePath = []
+
+    device_path_dict = {
+        # ( Type, SubType ) : Device Path C typedef
+        # HARDWARE_DEVICE_PATH
+        (1,  1): PCI_DEVICE_PATH,
+        (1,  2): PCCARD_DEVICE_PATH,
+        (1,  3): MEMMAP_DEVICE_PATH,
+        (1,  4): VENDOR_DEVICE_PATH,
+        (1,  5): CONTROLLER_DEVICE_PATH,
+        (1,  6): BMC_DEVICE_PATH,
+
+        # ACPI_DEVICE_PATH
+        (2,  1): ACPI_HID_DEVICE_PATH,
+        (2,  2): ACPI_EXTENDED_HID_DEVICE_PATH,
+        (2,  3): ACPI_ADR_DEVICE_PATH,
+        (2,  4): ACPI_NVDIMM_DEVICE_PATH,
+
+        # MESSAGING_DEVICE_PATH
+        (3,  1): ATAPI_DEVICE_PATH,
+        (3,  2): SCSI_DEVICE_PATH,
+        (3,  3): FIBRECHANNEL_DEVICE_PATH,
+        (3,  4): F1394_DEVICE_PATH,
+        (3,  5): USB_DEVICE_PATH,
+        (3,  6): I2O_DEVICE_PATH,
+
+        (3,  9): INFINIBAND_DEVICE_PATH,
+        (3, 10): VENDOR_DEVICE_PATH,
+        (3, 11): MAC_ADDR_DEVICE_PATH,
+        (3, 12): IPv4_DEVICE_PATH,
+        (3, 13): IPv6_DEVICE_PATH,
+        (3, 14): UART_DEVICE_PATH,
+        (3, 15): USB_CLASS_DEVICE_PATH,
+        (3, 16): USB_WWID_DEVICE_PATH,
+        (3, 17): DEVICE_LOGICAL_UNIT_DEVICE_PATH,
+        (3, 18): SATA_DEVICE_PATH,
+        (3, 19): ISCSI_DEVICE_PATH,
+        (3, 20): VLAN_DEVICE_PATH,
+        (3, 21): FIBRECHANNELEX_DEVICE_PATH,
+        (3, 22): SASEX_DEVICE_PATH,
+        (3, 23): NVME_NAMESPACE_DEVICE_PATH,
+        (3, 24): DNS_DEVICE_PATH,
+        (3, 25): UFS_DEVICE_PATH,
+        (3, 26): SD_DEVICE_PATH,
+        (3, 27): BLUETOOTH_DEVICE_PATH,
+        (3, 28): WIFI_DEVICE_PATH,
+        (3, 29): EMMC_DEVICE_PATH,
+        (3, 30): BLUETOOTH_LE_DEVICE_PATH,
+        (3, 31): DNS_DEVICE_PATH,
+        (3, 32): NVDIMM_NAMESPACE_DEVICE_PATH,
+
+        (3, 33): REST_SERVICE_DEVICE_PATH,
+        (3, 34): REST_VENDOR_SERVICE_DEVICE_PATH,
+
+        # MEDIA_DEVICE_PATH
+        (4,  1): HARDDRIVE_DEVICE_PATH,
+        (4,  2): CDROM_DEVICE_PATH,
+        (4,  3): VENDOR_DEVICE_PATH,
+        (4,  4): EFI_DEVICE_PATH,
+        (4,  5): MEDIA_PROTOCOL_DEVICE_PATH,
+        (4,  6): MEDIA_FW_VOL_FILEPATH_DEVICE_PATH,
+        (4,  7): MEDIA_FW_VOL_DEVICE_PATH,
+        (4,  8): MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH,
+        (4,  9): MEDIA_RAM_DISK_DEVICE_PATH,
+
+        # BBS_DEVICE_PATH
+        (5, 1): BBS_BBS_DEVICE_PATH,
+
+    }
+
+    guid_override_dict = {
+        uuid.UUID('37499A9D-542F-4C89-A026-35DA142094E4'):
+            UART_FLOW_CONTROL_DEVICE_PATH,
+        uuid.UUID('D487DDB4-008B-11D9-AFDC-001083FFCA4D'):
+            SAS_DEVICE_PATH,
+    }
+
+    def __init__(self, file, ptr=None, verbose=False, count=64):
+        '''
+        Convert ptr into a list of Device Path nodes. If verbose also hexdump
+        extra data.
+        '''
+        self._file = file
+        self._verbose = verbose
+        if ptr is None:
+            return
+
+        try:
+            instance = []
+            for _ in range(count):  # while True
+                hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, ptr)
+                if hdr.Length < sizeof(EFI_DEVICE_PATH):
+                    # Not a valid device path
+                    break
+
+                if hdr.Type == 0x7F:  # END_DEVICE_PATH_TYPE
+                    self.DevicePath.append(instance)
+                    if hdr.SubType == 0xFF:  # END_ENTIRE_DEVICE_PATH_SUBTYPE
+                        break
+                    if hdr.SubType == 0x01:  # END_INSTANCE_DEVICE_PATH_SUBTYPE
+                        # start new device path instance
+                        instance = []
+
+                type_str = self.device_path_dict.get(
+                    (hdr.Type, hdr.SubType), EFI_DEVICE_PATH)
+                node, extra = self._ctype_read_ex(type_str, ptr, hdr.Length)
+                if 'VENDOR_DEVICE_PATH' in type(node).__name__:
+                    guid_type = self.guid_override_dict.get(
+                                        GuidNames.to_uuid(node.Guid), None)
+                    if guid_type:
+                        # use the ctype associated with the GUID
+                        node, extra = self._ctype_read_ex(
+                                                guid_type, ptr, hdr.Length)
+
+                instance.append((type(node).__name__, hdr.Type,
+                                hdr.SubType, hdr.Length, node, extra))
+                ptr += hdr.Length
+        except ValueError:
+            pass
+
+    def __str__(self):
+        ''' '''
+        if not self.valid():
+            return '<class: EfiDevicePath>'
+
+        result = ""
+        for instance in self.DevicePath:
+            for (Name, Type, SubType, Length, cnode, extra) in instance:
+                result += f'{Name:s} {Type:2d}:{SubType:2d} Len: {Length:3d}\n'
+                result += ctype_to_str(cnode, '  ', ['Reserved'])
+                if self._verbose:
+                    if extra is not None:
+                        result += hexdump(extra, '    ')
+            result += '\n'
+
+        return result
+
+    def valid(self):
+        return True if self.DevicePath else False
+
+    def device_path_node(self, address):
+        try:
+            hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, address)
+            if hdr.Length < sizeof(EFI_DEVICE_PATH):
+                return None, None, None
+
+            type_str = self.device_path_dict.get(
+                (hdr.Type, hdr.SubType), EFI_DEVICE_PATH)
+            cnode, extra = self._ctype_read_ex(type_str, address, hdr.Length)
+            return hdr, cnode, extra
+        except ValueError:
+            return None, None, None
+
+    def device_path_node_str(self, address, verbose=False):
+        hdr, cnode, extra = self.device_path_node(address)
+        if hdr is None:
+            return ''
+
+        cname = type(cnode).__name__
+        result = f'{cname:s} {hdr.Type:2d}:{hdr.SubType:2d} '
+        result += f'Len: 0x{hdr.Length:03x}\n'
+        result += ctype_to_str(cnode, '  ', ['Reserved'])
+        if verbose:
+            if extra is not None:
+                result += hexdump(extra, '    ')
+
+        return result
+
+    def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None):
+        if offset != 0:
+            self._file.seek(offset)
+
+        type_size = sizeof(ctype_struct)
+        size = rsize if rsize else type_size
+        data = self._file.read(size)
+        if data is None:
+            return None, None
+
+        cdata = ctype_struct.from_buffer(bytearray(data))
+
+        if size > type_size:
+            return cdata, data[type_size:]
+        else:
+            return cdata, None
+
+
+class EfiConfigurationTable:
+    '''
+    A class to abstract EFI Configuration Tables from gST->ConfigurationTable
+    and gST->NumberOfTableEntries. Pass in the gST pointer from EFI,
+    likely you need to look up this address after you have loaded symbols
+
+    Attributes
+    ——————
+    ConfigurationTableDict : dictionary
+        dictionary of EFI Configuration Table entries
+
+    Methods
+    -----------
+    GetConfigTable(uuid)
+        pass in VendorGuid and return VendorTable from EFI System Table
+    DebugImageInfo(table)
+        return tuple of load address and size of PE/COFF images
+    '''
+
+    ConfigurationTableDict = {}
+
+    def __init__(self, file, gST_addr=None):
+        self._file = file
+        if gST_addr is None:
+            # ToDo add code to search for gST via EFI_SYSTEM_TABLE_POINTER
+            return
+
+        gST = self._ctype_read(EFI_SYSTEM_TABLE, gST_addr)
+        self.read_efi_config_table(gST.NumberOfTableEntries,
+                                   gST.ConfigurationTable,
+                                   self._ctype_read)
+
+    @ classmethod
+    def __str__(cls):
+        '''return EFI_CONFIGURATION_TABLE entries as a string'''
+        result = ""
+        for key, value in cls.ConfigurationTableDict.items():
+            result += f'{GuidNames().to_name(key):>37s}: '
+            result += f'VendorTable = 0x{value:08x}\n'
+
+        return result
+
+    def _ctype_read(self, ctype_struct, offset=0):
+        '''ctype worker function to read data'''
+        if offset != 0:
+            self._file.seek(offset)
+
+        data = self._file.read(sizeof(ctype_struct))
+        return ctype_struct.from_buffer(bytearray(data))
+
+    @ classmethod
+    def read_efi_config_table(cls, table_cnt, table_ptr, ctype_read):
+        '''Create a dictionary of EFI Configuration table entries'''
+        EmptryTables = EFI_CONFIGURATION_TABLE * table_cnt
+        Tables = ctype_read(EmptryTables, table_ptr)
+        for i in range(table_cnt):
+            cls.ConfigurationTableDict[str(GuidNames.to_uuid(
+                Tables[i].VendorGuid)).upper()] = Tables[i].VendorTable
+
+        return cls.ConfigurationTableDict
+
+    def GetConfigTable(self, uuid):
+        ''' Return VendorTable for VendorGuid (uuid.UUID) or None'''
+        return self.ConfigurationTableDict.get(uuid.upper())
+
+    def DebugImageInfo(self, table=None):
+        '''
+        Walk the debug image info table to find the LoadedImage protocols
+        for all the loaded PE/COFF images and return a list of load address
+        and image size.
+        '''
+        ImageLoad = []
+
+        if table is None:
+            table = self.GetConfigTable('49152e77-1ada-4764-b7a2-7afefed95e8b')
+
+        DbgInfoHdr = self._ctype_read(EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, table)
+        NormalImageArray = EFI_DEBUG_IMAGE_INFO * DbgInfoHdr.TableSize
+        NormalImageArray = self._ctype_read(
+            NormalImageArray, DbgInfoHdr.EfiDebugImageInfoTable)
+        for i in range(DbgInfoHdr.TableSize):
+            ImageInfo = self._ctype_read(
+                EFI_DEBUG_IMAGE_INFO_NORMAL, NormalImageArray[i].NormalImage)
+            LoadedImage = self._ctype_read(
+                EFI_LOADED_IMAGE_PROTOCOL,
+                ImageInfo.LoadedImageProtocolInstance)
+            ImageLoad.append((LoadedImage.ImageBase, LoadedImage.ImageSize))
+
+        return ImageLoad
+
+
+class PeTeImage:
+    '''
+    A class to abstract PE/COFF or TE image processing via passing in a
+    Python file like object. If you pass in an address the PE/COFF is parsed,
+    if you pass in NULL for an address then you get a class instance you can
+    use to search memory for a PE/COFF hader given a pc value.
+
+    Attributes
+    ——————
+    LoadAddress : int
+        Load address of the PE/COFF image
+    AddressOfEntryPoint : int
+        Address of the Entry point of the PE/COFF image
+    TextAddress : int
+        Start of the PE/COFF text section
+    DataAddress : int
+        Start of the PE/COFF data section
+    CodeViewPdb : str
+        File name of the symbols file
+    CodeViewUuid : uuid:UUID
+        GUID for "RSDS" Debug Directory entry, or Mach-O UUID for "MTOC"
+
+    Methods
+    -----------
+    pcToPeCoff(address, step, max_range, rom_range)
+        Given an address(pc) find the PE/COFF image it is in
+    sections_to_str()
+        return a string giving info for all the PE/COFF sections
+    '''
+
+    def __init__(self, file, address=0):
+        self._file = file
+
+        # book keeping, but public
+        self.PeHdr = None
+        self.TeHdr = None
+        self.Machine = None
+        self.Subsystem = None
+        self.CodeViewSig = None
+        self.e_lfanew = 0
+        self.NumberOfSections = 0
+        self.Sections = None
+
+        # Things debuggers may want to know
+        self.LoadAddress = 0 if address is None else address
+        self.EndLoadAddress = 0
+        self.AddressOfEntryPoint = 0
+        self.TextAddress = 0
+        self.DataAddress = 0
+        self.CodeViewPdb = None
+        self.CodeViewUuid = None
+        self.TeAdjust = 0
+
+        self.dir_name = {
+            0: 'Export Table',
+            1: 'Import Table',
+            2: 'Resource Table',
+            3: 'Exception Table',
+            4: 'Certificate Table',
+            5: 'Relocation Table',
+            6: 'Debug',
+            7: 'Architecture',
+            8: 'Global Ptr',
+            9: 'TLS Table',
+            10: 'Load Config Table',
+            11: 'Bound Import',
+            12: 'IAT',
+            13: 'Delay Import Descriptor',
+            14: 'CLR Runtime Header',
+            15: 'Reserved',
+        }
+
+        if address is not None:
+            if self.maybe():
+                self.parse()
+
+    def __str__(self):
+        if self.PeHdr is None and self.TeHdr is None:
+            # no PE/COFF header found
+            return "<class: PeTeImage>"
+
+        if self.CodeViewPdb:
+            pdb = f'{self.Machine}`{self.CodeViewPdb}'
+        else:
+            pdb = 'No Debug Info:'
+
+        if self.CodeViewUuid:
+            guid = f'{self.CodeViewUuid}:'
+        else:
+            guid = ''
+
+        slide = f'slide = {self.TeAdjust:d} ' if self.TeAdjust != 0 else ' '
+        res = guid + f'{pdb} load = 0x{self.LoadAddress:08x} ' + slide
+        return res
+
+    def _seek(self, offset):
+        """
+        seek() relative to start of PE/COFF (TE) image
+        """
+        self._file.seek(self.LoadAddress + offset)
+
+    def _read_offset(self, size, offset=None):
+        """
+        read() relative to start of PE/COFF (TE) image
+        if offset is not None then seek() before the read
+        """
+        if offset is not None:
+            self._seek(offset)
+
+        return self._file.read(size)
+
+    def _read_ctype(self, ctype_struct, offset=None):
+        data = self._read_offset(sizeof(ctype_struct), offset)
+        return ctype_struct.from_buffer(bytearray(data), 0)
+
+    def _unsigned(self, i):
+        """return a 32-bit unsigned int (UINT32) """
+        return int.from_bytes(i, byteorder='little', signed=False)
+
+    def pcToPeCoff(self,
+                   address,
+                   step=None,
+                   max_range=None,
+                   rom_range=[0xFE800000, 0xFFFFFFFF]):
+        """
+        Given an address search backwards for PE/COFF (TE) header
+        For DXE 4K is probably OK
+        For PEI you might have to search every 4 bytes.
+        """
+        if step is None:
+            step = 0x1000
+
+        if max_range is None:
+            max_range = 0x200000
+
+        if address in range(*rom_range):
+            # The XIP code in the ROM ends up 4 byte aligned.
+            step = 4
+            max_range = min(max_range, 0x100000)
+
+        # Align address to page boundary for memory image search.
+        address = address & ~(step-1)
+        # Search every step backward
+        offset_range = list(range(0, min(max_range, address), step))
+        for offset in offset_range:
+            if self.maybe(address - offset):
+                if self.parse():
+                    return True
+
+        return False
+
+    def maybe(self, offset=None):
+        """Probe to see if this offset is likely a PE/COFF or TE file """
+        self.LoadAddress = 0
+        e_magic = self._read_offset(2, offset)
+        header_ok = e_magic == b'MZ' or e_magic == b'VZ'
+        if offset is not None and header_ok:
+            self.LoadAddress = offset
+        return header_ok
+
+    def parse(self):
+        """Parse PE/COFF (TE) debug directory entry """
+        DosHdr = self._read_ctype(EFI_IMAGE_DOS_HEADER, 0)
+        if DosHdr.e_magic == self._unsigned(b'VZ'):
+            # TE image
+            self.TeHdr = self._read_ctype(EFI_TE_IMAGE_HEADER, 0)
+
+            self.TeAdjust = sizeof(self.TeHdr) - self.TeHdr.StrippedSize
+            self.Machine = image_machine_dict.get(self.TeHdr.Machine, None)
+            self.Subsystem = self.TeHdr.Subsystem
+            self.AddressOfEntryPoint = self.TeHdr.AddressOfEntryPoint
+
+            debug_dir_size = self.TeHdr.DataDirectoryDebug.Size
+            debug_dir_offset = (self.TeAdjust +
+                                self.TeHdr.DataDirectoryDebug.VirtualAddress)
+        else:
+            if DosHdr.e_magic == self._unsigned(b'MZ'):
+                self.e_lfanew = DosHdr.e_lfanew
+            else:
+                self.e_lfanew = 0
+
+            self.PeHdr = self._read_ctype(
+                EFI_IMAGE_NT_HEADERS64, self.e_lfanew)
+            if self.PeHdr.Signature != self._unsigned(b'PE\0\0'):
+                return False
+
+            if self.PeHdr.OptionalHeader.Magic == \
+                    EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
+                self.PeHdr = self._read_ctype(
+                    EFI_IMAGE_NT_HEADERS32, self.e_lfanew)
+
+            if self.PeHdr.OptionalHeader.NumberOfRvaAndSizes <= \
+                    DIRECTORY_DEBUG:
+                return False
+
+            self.Machine = image_machine_dict.get(
+                self.PeHdr.FileHeader.Machine, None)
+            self.Subsystem = self.PeHdr.OptionalHeader.Subsystem
+            self.AddressOfEntryPoint = \
+                self.PeHdr.OptionalHeader.AddressOfEntryPoint
+            self.TeAdjust = 0
+
+            debug_dir_size = self.PeHdr.OptionalHeader.DataDirectory[
+                DIRECTORY_DEBUG].Size
+            debug_dir_offset = self.PeHdr.OptionalHeader.DataDirectory[
+                DIRECTORY_DEBUG].VirtualAddress
+
+        if self.Machine is None or self.Subsystem not in [0, 10, 11, 12]:
+            return False
+
+        self.AddressOfEntryPoint += self.LoadAddress
+
+        self.sections()
+        return self.processDebugDirEntry(debug_dir_offset, debug_dir_size)
+
+    def sections(self):
+        '''Parse the PE/COFF (TE) section table'''
+        if self.Sections is not None:
+            return
+        elif self.TeHdr is not None:
+            self.NumberOfSections = self.TeHdr.NumberOfSections
+            offset = sizeof(EFI_TE_IMAGE_HEADER)
+        elif self.PeHdr is not None:
+            self.NumberOfSections = self.PeHdr.FileHeader.NumberOfSections
+            offset = sizeof(c_uint32) + \
+                sizeof(EFI_IMAGE_FILE_HEADER)
+            offset += self.PeHdr.FileHeader.SizeOfOptionalHeader
+            offset += self.e_lfanew
+        else:
+            return
+
+        self.Sections = EFI_IMAGE_SECTION_HEADER * self.NumberOfSections
+        self.Sections = self._read_ctype(self.Sections, offset)
+
+        for i in range(self.NumberOfSections):
+            name = str(self.Sections[i].Name, 'ascii', 'ignore')
+            addr = self.Sections[i].VirtualAddress
+            addr += self.LoadAddress + self.TeAdjust
+            if name == '.text':
+                self.TextAddress = addr
+            elif name == '.data':
+                self.DataAddress = addr
+
+            end_addr = addr + self.Sections[i].VirtualSize - 1
+            if end_addr > self.EndLoadAddress:
+                self.EndLoadAddress = end_addr
+
+    def sections_to_str(self):
+        # return text summary of sections
+        # name virt addr (virt size) flags:Characteristics
+        result = ''
+        for i in range(self.NumberOfSections):
+            name = str(self.Sections[i].Name, 'ascii', 'ignore')
+            result += f'{name:8s} '
+            result += f'0x{self.Sections[i].VirtualAddress:08X} '
+            result += f'(0x{self.Sections[i].VirtualSize:05X}) '
+            result += f'flags:0x{self.Sections[i].Characteristics:08X}\n'
+
+        return result
+
+    def directory_to_str(self):
+        result = ''
+        if self.TeHdr:
+            debug_size = self.TeHdr.DataDirectoryDebug.Size
+            if debug_size > 0:
+                debug_offset = (self.TeAdjust
+                                + self.TeHdr.DataDirectoryDebug.VirtualAddress)
+                result += f"Debug 0x{debug_offset:08X} 0x{debug_size}\n"
+
+            relocation_size = self.TeHdr.DataDirectoryBaseReloc.Size
+            if relocation_size > 0:
+                relocation_offset = (
+                    self.TeAdjust
+                    + self.TeHdr.DataDirectoryBaseReloc.VirtualAddress)
+                result += f'Relocation 0x{relocation_offset:08X} '
+                result += f' 0x{relocation_size}\n'
+
+        elif self.PeHdr:
+            for i in range(self.PeHdr.OptionalHeader.NumberOfRvaAndSizes):
+                size = self.PeHdr.OptionalHeader.DataDirectory[i].Size
+                if size == 0:
+                    continue
+
+                virt_addr = self.PeHdr.OptionalHeader.DataDirectory[
+                    i].VirtualAddress
+                name = self.dir_name.get(i, '?')
+                result += f'{name:s} 0x{virt_addr:08X} 0x{size:X}\n'
+
+        return result
+
+    def processDebugDirEntry(self, virt_address, virt_size):
+        """Process PE/COFF Debug Directory Entry"""
+        if (virt_address == 0 or
+                virt_size < sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)):
+            return False
+
+        data = bytearray(self._read_offset(virt_size, virt_address))
+        for offset in range(0,
+                            virt_size,
+                            sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)):
+            DirectoryEntry = EFI_IMAGE_DEBUG_DIRECTORY_ENTRY.from_buffer(
+                data[offset:])
+            if DirectoryEntry.Type != 2:
+                continue
+
+            entry = self._read_offset(
+                DirectoryEntry.SizeOfData, DirectoryEntry.RVA + self.TeAdjust)
+            self.CodeViewSig = entry[:4]
+            if self.CodeViewSig == b'MTOC':
+                self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16])
+                PdbOffset = 20
+            elif self.CodeViewSig == b'RSDS':
+                self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16])
+                PdbOffset = 24
+            elif self.CodeViewSig == b'NB10':
+                PdbOffset = 16
+            else:
+                continue
+
+            # can't find documentation about Pdb string encoding?
+            # guessing utf-8 since that will match file systems in macOS
+            # and Linux Windows is UTF-16, or ANSI adjusted for local.
+            # We might need a different value for Windows here?
+            self.CodeViewPdb = entry[PdbOffset:].split(b'\x00')[
+                0].decode('utf-8')
+            return True
+        return False
+
+
+def main():
+    '''Process arguments as PE/COFF files'''
+    for fname in sys.argv[1:]:
+        with open(fname, 'rb') as f:
+            image = PeTeImage(f)
+            print(image)
+            res = f'EntryPoint = 0x{image.AddressOfEntryPoint:08x}  '
+            res += f'TextAddress = 0x{image.TextAddress:08x} '
+            res += f'DataAddress = 0x{image.DataAddress:08x}'
+            print(res)
+            print(image.sections_to_str())
+            print('Data Directories:')
+            print(image.directory_to_str())
+
+
+if __name__ == "__main__":
+    main()
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
  2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran
@ 2022-03-26 14:05   ` Bob Feng
  0 siblings, 0 replies; 10+ messages in thread
From: Bob Feng @ 2022-03-26 14:05 UTC (permalink / raw)
  To: Rebecca Cran, devel@edk2.groups.io, Leif Lindholm,
	Kinney, Michael D, Wu, Hao A, Gao, Liming, Chen, Christine
  Cc: Andrew Fish

Reviewed-by: Bob Feng <bob.c.feng@intel.com>

-----Original Message-----
From: Rebecca Cran <quic_rcran@quicinc.com> 
Sent: Tuesday, March 22, 2022 4:21 AM
To: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>; Kinney, Michael D <michael.d.kinney@intel.com>; Wu, Hao A <hao.a.wu@intel.com>; Feng, Bob C <bob.c.feng@intel.com>; Gao, Liming <gaoliming@byosoft.com.cn>; Chen, Christine <yuwei.chen@intel.com>
Cc: Rebecca Cran <quic_rcran@quicinc.com>; Andrew Fish <afish@apple.com>
Subject: [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print

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>
---
 BaseTools/Scripts/efi_gdb.py | 918 ++++++++++++++++++++
 1 file changed, 918 insertions(+)

diff --git a/BaseTools/Scripts/efi_gdb.py b/BaseTools/Scripts/efi_gdb.py new file mode 100755 index 000000000000..e9bae8e9b913
--- /dev/null
+++ b/BaseTools/Scripts/efi_gdb.py
@@ -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')
--
2.34.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb
  2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
                   ` (2 preceding siblings ...)
  2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran
@ 2022-04-06 22:34 ` Rebecca Cran
  2022-04-07  5:02   ` 回复: " gaoliming
  2022-04-09  4:47   ` Bob Feng
  3 siblings, 2 replies; 10+ messages in thread
From: Rebecca Cran @ 2022-04-06 22:34 UTC (permalink / raw)
  To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng,
	Liming Gao, Yuwei Chen
  Cc: Andrew Fish

Could I have some more reviews on this please? I'd like to get this into 
the tree soon.

-- 
Rebecca Cran

On 3/21/22 14:20, Rebecca Cran wrote:
> This patch set adds debugging support for gdb and lldb.
> It also adds generic debugging classes that use a file like object to
> make it easy to import into any debugger that supports Python.
>
> Changes from v1 to v2:
> - Moved scripts from the root of the repo into BaseTools/Scripts.
> - Fixed typo of "RISCV" as "RISKV".
>
> Testing:
> - Tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
> - Tested gdb on openSUSE for AARCH64.
>
> Rebecca Cran (3):
>    BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes
>    BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
>    BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print
>
>   BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++
>   BaseTools/Scripts/efi_gdb.py       |  918 ++++++++
>   BaseTools/Scripts/efi_lldb.py      | 1044 ++++++++++
>   3 files changed, 4147 insertions(+)
>   create mode 100755 BaseTools/Scripts/efi_debugging.py
>   create mode 100755 BaseTools/Scripts/efi_gdb.py
>   create mode 100755 BaseTools/Scripts/efi_lldb.py
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* 回复: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb
  2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
@ 2022-04-07  5:02   ` gaoliming
  2022-04-09  4:47   ` Bob Feng
  1 sibling, 0 replies; 10+ messages in thread
From: gaoliming @ 2022-04-07  5:02 UTC (permalink / raw)
  To: 'Rebecca Cran', devel, 'Leif Lindholm',
	'Michael D Kinney', 'Hao A Wu',
	'Bob Feng', 'Yuwei Chen'
  Cc: 'Andrew Fish'

Acked-by: Liming Gao <gaoliming@byosoft.com.cn>

> -----邮件原件-----
> 发件人: Rebecca Cran <quic_rcran@quicinc.com>
> 发送时间: 2022年4月7日 6:34
> 收件人: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>;
> Michael D Kinney <michael.d.kinney@intel.com>; Hao A Wu
> <hao.a.wu@intel.com>; Bob Feng <bob.c.feng@intel.com>; Liming Gao
> <gaoliming@byosoft.com.cn>; Yuwei Chen <yuwei.chen@intel.com>
> 抄送: Andrew Fish <afish@apple.com>
> 主题: Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb
> 
> Could I have some more reviews on this please? I'd like to get this into
> the tree soon.
> 
> --
> Rebecca Cran
> 
> On 3/21/22 14:20, Rebecca Cran wrote:
> > This patch set adds debugging support for gdb and lldb.
> > It also adds generic debugging classes that use a file like object to
> > make it easy to import into any debugger that supports Python.
> >
> > Changes from v1 to v2:
> > - Moved scripts from the root of the repo into BaseTools/Scripts.
> > - Fixed typo of "RISCV" as "RISKV".
> >
> > Testing:
> > - Tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
> > - Tested gdb on openSUSE for AARCH64.
> >
> > Rebecca Cran (3):
> >    BaseTools: efi_debugging.py: Add debugger agnostic dbg Python
> Classes
> >    BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
> >    BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print
> >
> >   BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++
> >   BaseTools/Scripts/efi_gdb.py       |  918 ++++++++
> >   BaseTools/Scripts/efi_lldb.py      | 1044 ++++++++++
> >   3 files changed, 4147 insertions(+)
> >   create mode 100755 BaseTools/Scripts/efi_debugging.py
> >   create mode 100755 BaseTools/Scripts/efi_gdb.py
> >   create mode 100755 BaseTools/Scripts/efi_lldb.py
> >




^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [edk2-devel] [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print
  2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran
@ 2022-04-09  4:16   ` Bob Feng
  0 siblings, 0 replies; 10+ messages in thread
From: Bob Feng @ 2022-04-09  4:16 UTC (permalink / raw)
  To: Rebecca Cran, devel

[-- Attachment #1: Type: text/plain, Size: 32626 bytes --]

Reviewed-by: Bob Feng <bob.c.feng@intel.com>

On Tue, Mar 22, 2022 at 04:21 AM, Rebecca Cran wrote:

> 
> https://bugzilla.tianocore.org/show_bug.cgi?id=3500
> 
> Use efi_debugging.py Python Classes to implement EFI gdb commands:
> efi_symbols, guid, table, hob, and devicepath
> 
> You can attach to any standard gdb or kdp remote server and get EFI
> symbols. No modifications of EFI are required.
> 
> Example usage:
> OvmfPkg/build.sh qemu -gdb tcp::9000
> lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py"
> 
> Note you may also have to teach lldb about QEMU:
> -o "settings set plugin.process.gdb-remote.target-definition-file
> x86_64_target_definition.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>
> ---
> BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++
> 1 file changed, 1044 insertions(+)
> 
> diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py
> 
> new file mode 100755
> index 000000000000..089b6ba58ab8
> --- /dev/null
> +++ b/BaseTools/Scripts/efi_lldb.py
> @@ -0,0 +1,1044 @@
> +#!/usr/bin/python3
> +'''
> +Copyright (c) Apple Inc. 2021
> +SPDX-License-Identifier: BSD-2-Clause-Patent
> +
> +Example usage:
> +OvmfPkg/build.sh qemu -gdb tcp::9000
> +lldb -o "gdb-remote localhost:9000" -o "command script import
> efi_lldb.py"
> +'''
> +
> +import optparse
> +import shlex
> +import subprocess
> +import uuid
> +import sys
> +import os
> +from pathlib import Path
> +from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl
> +from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode
> +from efi_debugging import PeTeImage, patch_ctypes
> +
> +try:
> + # Just try for LLDB in case PYTHONPATH is already correctly setup
> + import lldb
> +except ImportError:
> + try:
> + env = os.environ.copy()
> + env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
> + lldb_python_path = subprocess.check_output(
> + ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip()
> + sys.path.append(lldb_python_path)
> + import lldb
> + except ValueError:
> + print("Couldn't find LLDB.framework from lldb -P")
> + print("PYTHONPATH should match the currently selected lldb")
> + sys.exit(-1)
> +
> +
> +class LldbFileObject(object):
> + '''
> + Class that fakes out file object to abstract lldb from the generic code.
> 
> + For lldb this is memory so we don't have a concept of the end of the
> file.
> + '''
> +
> + def __init__(self, process):
> + # _exe_ctx is lldb.SBExecutionContext
> + self._process = process
> + self._offset = 0
> + self._SBError = lldb.SBError()
> +
> + def tell(self):
> + return self._offset
> +
> + def read(self, size=-1):
> + if size == -1:
> + # arbitrary default size
> + size = 0x1000000
> +
> + data = self._process.ReadMemory(self._offset, size, self._SBError)
> + if self._SBError.fail:
> + raise MemoryError(
> + f'lldb could not read memory 0x{size:x} '
> + f' bytes from 0x{self._offset:08x}')
> + else:
> + return data
> +
> + 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):
> + result = self._process.WriteMemory(self._offset, data, self._SBError)
> + if self._SBError.fail:
> + raise MemoryError(
> + f'lldb could not write memory to 0x{self._offset:08x}')
> + return result
> +
> + 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
> + You need to pass file, and exe_ctx to load symbols.
> + You can print(EfiSymbols()) to see the currently loaded symbols
> + """
> +
> + loaded = {}
> + stride = None
> + range = None
> + verbose = False
> +
> + def __init__(self, target=None):
> + if target:
> + EfiSymbols.target = target
> + EfiSymbols._file = LldbFileObject(target.process)
> +
> + @ classmethod
> + def __str__(cls):
> + return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values())
> +
> + @ classmethod
> + def configure_search(cls, stride, range, 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.LoadAddress in cls.loaded:
> + return 'Already Loaded: '
> +
> + module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid))
> + if not module:
> + module = cls.target.AddModule(pecoff.CodeViewPdb,
> + None,
> + str(pecoff.CodeViewUuid))
> + if module.IsValid():
> + SBError = cls.target.SetModuleLoadAddress(
> + module, pecoff.LoadAddress + pecoff.TeAdjust)
> + if SBError.success:
> + cls.loaded[pecoff.LoadAddress] = (pecoff, module)
> + return ''
> +
> + return 'Symbols NOT FOUND: '
> +
> + @ 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 (pecoff, module) in cls.loaded.values():
> + if (address >= pecoff.LoadAddress and
> + address <= pecoff.EndLoadAddress):
> +
> + return pecoff, module
> +
> + return None, None
> +
> + @ classmethod
> + def unload_symbols(cls, address):
> + pecoff, module = cls.address_in_loaded_pecoff(address)
> + if module:
> + name = str(module)
> + cls.target.ClearModuleLoadAddress(module)
> + cls.target.RemoveModule(module)
> + del cls.loaded[pecoff.LoadAddress]
> + return f'{name:s} was unloaded'
> + return f'0x{address:x} was not in a loaded image'
> +
> +
> +def arg_to_address(frame, arg):
> + ''' convert an lldb command arg into a memory address (addr_t)'''
> +
> + if arg is None:
> + return None
> +
> + arg_str = arg if isinstance(arg, str) else str(arg)
> + SBValue = frame.EvaluateExpression(arg_str)
> + if SBValue.error.fail:
> + return arg
> +
> + if (SBValue.TypeIsPointerType() or
> + SBValue.value_type == lldb.eValueTypeRegister or
> + SBValue.value_type == lldb.eValueTypeRegisterSet or
> + SBValue.value_type == lldb.eValueTypeConstResult):
> + try:
> + addr = SBValue.GetValueAsAddress()
> + except ValueError:
> + addr = SBValue.unsigned
> + else:
> + try:
> + addr = SBValue.address_of.GetValueAsAddress()
> + except ValueError:
> + addr = SBValue.address_of.unsigned
> +
> + return addr
> +
> +
> +def arg_to_data(frame, arg):
> + ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)'''
> + if not isinstance(arg, str):
> + arg_str = str(str)
> +
> + SBValue = frame.EvaluateExpression(arg_str)
> + return SBValue.unsigned
> +
> +
> +class EfiDevicePathCommand:
> +
> + def create_options(self):
> + ''' standard lldb command help/options parser'''
> + usage = "usage: %prog [options]"
> + description = '''Command that can EFI Config Tables
> +'''
> +
> + # Pass add_help_option = False, since this keeps the command in line
> + # with lldb commands, and we wire up "help command" to work by
> + # providing the long & short help methods below.
> + self.parser = optparse.OptionParser(
> + description=description,
> + prog='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)
> +
> + def get_short_help(self):
> + '''standard lldb function method'''
> + return "Display EFI Tables"
> +
> + def get_long_help(self):
> + '''standard lldb function method'''
> + return self.help_string
> +
> + def __init__(self, debugger, internal_dict):
> + '''standard lldb function method'''
> + self.create_options()
> + self.help_string = self.parser.format_help()
> +
> + def __call__(self, debugger, command, exe_ctx, result):
> + '''standard lldb function method'''
> + # Use the Shell Lexer to properly parse up command options just like a
> + # shell would
> + command_args = shlex.split(command)
> +
> + try:
> + (options, args) = self.parser.parse_args(command_args)
> + dev_list = []
> + for arg in args:
> + dev_list.append(arg_to_address(exe_ctx.frame, arg))
> + except ValueError:
> + # if you don't handle exceptions, passing an incorrect argument
> + # to the OptionParser will cause LLDB to exit (courtesy of
> + # OptParse dealing with argument errors by throwing SystemExit)
> + result.SetError("option parsing failed")
> + return
> +
> + if options.help:
> + self.parser.print_help()
> + return
> +
> + file = LldbFileObject(exe_ctx.process)
> +
> + for dev_addr in dev_list:
> + if options.node:
> + print(EfiDevicePath(file).device_path_node_str(
> + dev_addr, options.verbose))
> + else:
> + device_path = EfiDevicePath(file, dev_addr, options.verbose)
> + if device_path.valid():
> + print(device_path)
> +
> +
> +class EfiHobCommand:
> + def create_options(self):
> + ''' standard lldb command help/options parser'''
> + usage = "usage: %prog [options]"
> + description = '''Command that can EFI dump EFI HOBs'''
> +
> + # Pass add_help_option = False, since this keeps the command in line
> + # with lldb commands, and we wire up "help command" to work by
> + # providing the long & short help methods below.
> + self.parser = optparse.OptionParser(
> + description=description,
> + prog='table',
> + 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)
> +
> + def get_short_help(self):
> + '''standard lldb function method'''
> + return "Display EFI Hobs"
> +
> + def get_long_help(self):
> + '''standard lldb function method'''
> + return self.help_string
> +
> + def __init__(self, debugger, internal_dict):
> + '''standard lldb function method'''
> + self.create_options()
> + self.help_string = self.parser.format_help()
> +
> + def __call__(self, debugger, command, exe_ctx, result):
> + '''standard lldb function method'''
> + # Use the Shell Lexer to properly parse up command options just like a
> + # shell would
> + command_args = shlex.split(command)
> +
> + try:
> + (options, _) = self.parser.parse_args(command_args)
> + except ValueError:
> + # if you don't handle exceptions, passing an incorrect argument
> + # to the OptionParser will cause LLDB to exit (courtesy of
> + # OptParse dealing with argument errors by throwing SystemExit)
> + result.SetError("option parsing failed")
> + return
> +
> + if options.help:
> + self.parser.print_help()
> + return
> +
> + address = arg_to_address(exe_ctx.frame, options.address)
> +
> + file = LldbFileObject(exe_ctx.process)
> + hob = EfiHob(file, address, options.verbose).get_hob_by_type(
> + options.type)
> + print(hob)
> +
> +
> +class EfiTableCommand:
> +
> + def create_options(self):
> + ''' standard lldb command help/options parser'''
> + usage = "usage: %prog [options]"
> + description = '''Command that can display EFI Config Tables
> +'''
> +
> + # Pass add_help_option = False, since this keeps the command in line
> + # with lldb commands, and we wire up "help command" to work by
> + # providing the long & short help methods below.
> + self.parser = optparse.OptionParser(
> + description=description,
> + prog='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)
> +
> + def get_short_help(self):
> + '''standard lldb function method'''
> + return "Display EFI Tables"
> +
> + def get_long_help(self):
> + '''standard lldb function method'''
> + return self.help_string
> +
> + def __init__(self, debugger, internal_dict):
> + '''standard lldb function method'''
> + self.create_options()
> + self.help_string = self.parser.format_help()
> +
> + def __call__(self, debugger, command, exe_ctx, result):
> + '''standard lldb function method'''
> + # Use the Shell Lexer to properly parse up command options just like a
> + # shell would
> + command_args = shlex.split(command)
> +
> + try:
> + (options, _) = self.parser.parse_args(command_args)
> + except ValueError:
> + # if you don't handle exceptions, passing an incorrect argument
> + # to the OptionParser will cause LLDB to exit (courtesy of
> + # OptParse dealing with argument errors by throwing SystemExit)
> + result.SetError("option parsing failed")
> + return
> +
> + if options.help:
> + self.parser.print_help()
> + return
> +
> + gST = exe_ctx.target.FindFirstGlobalVariable('gST')
> + if gST.error.fail:
> + print('Error: This command requires symbols for gST to be loaded')
> + return
> +
> + file = LldbFileObject(exe_ctx.process)
> + table = EfiConfigurationTable(file, gST.unsigned)
> + if table:
> + print(table, '\n')
> +
> +
> +class EfiGuidCommand:
> +
> + def create_options(self):
> + ''' standard lldb command help/options parser'''
> + usage = "usage: %prog [options]"
> + description = '''
> + Command that can display all EFI GUID's or give info on a
> + specific GUID's
> + '''
> + self.parser = optparse.OptionParser(
> + description=description,
> + prog='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)
> +
> + def get_short_help(self):
> + '''standard lldb function method'''
> + return "Display EFI GUID's"
> +
> + def get_long_help(self):
> + '''standard lldb function method'''
> + return self.help_string
> +
> + def __init__(self, debugger, internal_dict):
> + '''standard lldb function method'''
> + self.create_options()
> + self.help_string = self.parser.format_help()
> +
> + def __call__(self, debugger, command, exe_ctx, result):
> + '''standard lldb function method'''
> + # Use the Shell Lexer to properly parse up command options just like a
> + # shell would
> + command_args = shlex.split(command)
> +
> + try:
> + (options, args) = self.parser.parse_args(command_args)
> + if len(args) >= 1:
> + # guid { 0x414e6bdd, 0xe47b, 0x47cc,
> + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
> + # this generates multiple args
> + arg = ' '.join(args)
> + except ValueError:
> + # if you don't handle exceptions, passing an incorrect argument
> + # to the OptionParser will cause LLDB to exit (courtesy of
> + # OptParse dealing with argument errors by throwing SystemExit)
> + result.SetError("option parsing failed")
> + return
> +
> + if options.help:
> + self.parser.print_help()
> + 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 = arg.lower()
> + 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 = arg
> + 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 EfiSymbolicateCommand(object):
> + '''Class to abstract an lldb command'''
> +
> + def create_options(self):
> + ''' standard lldb command help/options parser'''
> + 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.
> + '''
> +
> + # Pass add_help_option = False, since this keeps the command in line
> + # with lldb commands, and we wire up "help command" to work by
> + # providing the long & short help methods below.
> + self.parser = optparse.OptionParser(
> + description=description,
> + prog='efi_symbols',
> + usage=usage,
> + add_help_option=False)
> +
> + self.parser.add_option(
> + '-a',
> + '--address',
> + type="int",
> + dest='address',
> + help='Load symbols for image at address',
> + default=None)
> +
> + 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(
> + '-h',
> + '--help',
> + action='store_true',
> + dest='help',
> + help='Show help for the command',
> + default=False)
> +
> + def get_short_help(self):
> + '''standard lldb function method'''
> + return (
> + "Load symbols based on an address that is part of"
> + " a PE/COFF EFI image.")
> +
> + def get_long_help(self):
> + '''standard lldb function method'''
> + return self.help_string
> +
> + def __init__(self, debugger, unused):
> + '''standard lldb function method'''
> + self.create_options()
> + self.help_string = self.parser.format_help()
> +
> + def lldb_print(self, lldb_str):
> + # capture command out like an lldb command
> + self.result.PutCString(lldb_str)
> + # flush the output right away
> + self.result.SetImmediateOutputFile(
> + self.exe_ctx.target.debugger.GetOutputFile())
> +
> + def __call__(self, debugger, command, exe_ctx, result):
> + '''standard lldb function method'''
> + # Use the Shell Lexer to properly parse up command options just like a
> + # shell would
> + command_args = shlex.split(command)
> +
> + try:
> + (options, _) = self.parser.parse_args(command_args)
> + except ValueError:
> + # if you don't handle exceptions, passing an incorrect argument
> + # to the OptionParser will cause LLDB to exit (courtesy of
> + # OptParse dealing with argument errors by throwing SystemExit)
> + result.SetError("option parsing failed")
> + return
> +
> + if options.help:
> + self.parser.print_help()
> + return
> +
> + file = LldbFileObject(exe_ctx.process)
> + efi_symbols = EfiSymbols(exe_ctx.target)
> + self.result = result
> + self.exe_ctx = exe_ctx
> +
> + if options.pei:
> + # XIP code ends up on a 4 byte boundary.
> + options.stride = 4
> + options.range = 0x100000
> + efi_symbols.configure_search(options.stride, options.range)
> +
> + if not options.pc and options.address is None:
> + # default to
> + options.frame = True
> +
> + if options.frame:
> + if not exe_ctx.frame.IsValid():
> + result.SetError("invalid frame")
> + return
> +
> + threads = exe_ctx.process.threads if options.thread else [
> + exe_ctx.thread]
> +
> + for thread in threads:
> + for frame in thread:
> + res = efi_symbols.address_to_symbols(frame.pc)
> + self.lldb_print(res)
> +
> + else:
> + if options.address is not None:
> + address = options.address
> + elif options.pc:
> + try:
> + address = exe_ctx.thread.GetSelectedFrame().pc
> + except ValueError:
> + result.SetError("invalid pc")
> + return
> + else:
> + address = 0
> +
> + res = efi_symbols.address_to_symbols(address.pc)
> + print(res)
> +
> + if options.extended:
> +
> + gST = exe_ctx.target.FindFirstGlobalVariable('gST')
> + if gST.error.fail:
> + print('Error: This command requires symbols to be loaded')
> + else:
> + table = EfiConfigurationTable(file, gST.unsigned)
> + for address, _ in table.DebugImageInfo():
> + res = efi_symbols.address_to_symbols(address)
> + self.lldb_print(res)
> +
> + # keep trying module file names until we find a GUID xref file
> + for m in exe_ctx.target.modules:
> + if GuidNames.add_build_guid_file(str(m.file)):
> + break
> +
> +
> +def CHAR16_TypeSummary(valobj, internal_dict):
> + '''
> + Display CHAR16 as a String in the debugger.
> + Note: utf-8 is returned as that is the value for the debugger.
> + '''
> + SBError = lldb.SBError()
> + Str = ''
> + if valobj.TypeIsPointerType():
> + if valobj.GetValueAsUnsigned() == 0:
> + return "NULL"
> +
> + # CHAR16 * max string size 1024
> + for i in range(1024):
> + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0)
> + if SBError.fail or Char == 0:
> + break
> + Str += chr(Char)
> + return 'L"' + Str + '"'
> +
> + if valobj.num_children == 0:
> + # CHAR16
> + return "L'" + chr(valobj.unsigned) + "'"
> +
> + else:
> + # CHAR16 []
> + for i in range(valobj.num_children):
> + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0)
> + if Char == 0:
> + break
> + Str += chr(Char)
> + return 'L"' + Str + '"'
> +
> + return Str
> +
> +
> +def CHAR8_TypeSummary(valobj, internal_dict):
> + '''
> + Display CHAR8 as a String in the debugger.
> + Note: utf-8 is returned as that is the value for the debugger.
> + '''
> + SBError = lldb.SBError()
> + Str = ''
> + if valobj.TypeIsPointerType():
> + if valobj.GetValueAsUnsigned() == 0:
> + return "NULL"
> +
> + # CHAR8 * max string size 1024
> + for i in range(1024):
> + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0)
> + if SBError.fail or Char == 0:
> + break
> + Str += chr(Char)
> + Str = '"' + Str + '"'
> + return Str
> +
> + if valobj.num_children == 0:
> + # CHAR8
> + return "'" + chr(valobj.unsigned) + "'"
> + else:
> + # CHAR8 []
> + for i in range(valobj.num_children):
> + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0)
> + if SBError.fail or Char == 0:
> + break
> + Str += chr(Char)
> + return '"' + Str + '"'
> +
> + return Str
> +
> +
> +def EFI_STATUS_TypeSummary(valobj, internal_dict):
> + if valobj.TypeIsPointerType():
> + return ''
> + return str(EfiStatusClass(valobj.unsigned))
> +
> +
> +def EFI_TPL_TypeSummary(valobj, internal_dict):
> + if valobj.TypeIsPointerType():
> + return ''
> + return str(EfiTpl(valobj.unsigned))
> +
> +
> +def EFI_GUID_TypeSummary(valobj, internal_dict):
> + if valobj.TypeIsPointerType():
> + return ''
> + return str(GuidNames(bytes(valobj.data.uint8)))
> +
> +
> +def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict):
> + if valobj.TypeIsPointerType():
> + return ''
> + '''Return #define name for EFI_BOOT_MODE'''
> + return str(EfiBootMode(valobj.unsigned))
> +
> +
> +def lldb_type_formaters(debugger, mod_name):
> + '''Teach lldb about EFI types'''
> +
> + category = debugger.GetDefaultCategory()
> + FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool)
> +
> + FormatHex = lldb.SBTypeFormat(lldb.eFormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier(
> + "EFI_PHYSICAL_ADDRESS"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier(
> + "PHYSICAL_ADDRESS"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex)
> + category.AddTypeFormat(
> + lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex)
> + category.AddTypeFormat(lldb.SBTypeNameSpecifier(
> + "EFI_FV_FILETYPE"), FormatHex)
> +
> + #
> + # Smart type printing for EFI
> + #
> +
> + debugger.HandleCommand(
> + f'type summary add GUID - -python-function '
> + f'{mod_name}.EFI_GUID_TypeSummary')
> + debugger.HandleCommand(
> + f'type summary add EFI_GUID --python-function '
> + f'{mod_name}.EFI_GUID_TypeSummary')
> + debugger.HandleCommand(
> + f'type summary add EFI_STATUS --python-function '
> + f'{mod_name}.EFI_STATUS_TypeSummary')
> + debugger.HandleCommand(
> + f'type summary add EFI_TPL - -python-function '
> + f'{mod_name}.EFI_TPL_TypeSummary')
> + debugger.HandleCommand(
> + f'type summary add EFI_BOOT_MODE --python-function '
> + f'{mod_name}.EFI_BOOT_MODE_TypeSummary')
> +
> + debugger.HandleCommand(
> + f'type summary add CHAR16 --python-function '
> + f'{mod_name}.CHAR16_TypeSummary')
> +
> + # W605 this is the correct escape sequence for the lldb command
> + debugger.HandleCommand(
> + f'type summary add --regex "CHAR16 \[[0-9]+\]" ' # noqa: W605
> + f'--python-function {mod_name}.CHAR16_TypeSummary')
> +
> + debugger.HandleCommand(
> + f'type summary add CHAR8 --python-function '
> + f'{mod_name}.CHAR8_TypeSummary')
> +
> + # W605 this is the correct escape sequence for the lldb command
> + debugger.HandleCommand(
> + f'type summary add --regex "CHAR8 \[[0-9]+\]" ' # noqa: W605
> + f'--python-function {mod_name}.CHAR8_TypeSummary')
> +
> +
> +class LldbWorkaround:
> + needed = True
> +
> + @classmethod
> + def activate(cls):
> + if cls.needed:
> + lldb.debugger.HandleCommand("process handle SIGALRM -n false")
> + cls.needed = False
> +
> +
> +def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict):
> + #
> + # This is an lldb breakpoint script, and assumes the breakpoint is on a
> + # function with the same prototype as SecGdbScriptBreak(). The
> + # argument names are important as lldb looks them up.
> + #
> + # VOID
> + # SecGdbScriptBreak (
> + # char *FileName,
> + # int FileNameLength,
> + # long unsigned int LoadAddress,
> + # int AddSymbolFlag
> + # )
> + # {
> + # return;
> + # }
> + #
> + # When the emulator loads a PE/COFF image, it calls the stub function
> with
> + # the filename of the symbol file, the length of the FileName, the
> + # load address and a flag to indicate if this is a load or unload
> operation
> + #
> + LldbWorkaround().activate()
> +
> + symbols = EfiSymbols(frame.thread.process.target)
> + LoadAddress = frame.FindVariable("LoadAddress").unsigned
> + if frame.FindVariable("AddSymbolFlag").unsigned == 1:
> + res = symbols.address_to_symbols(LoadAddress)
> + else:
> + res = symbols.unload_symbols(LoadAddress)
> + print(res)
> +
> + # make breakpoint command continue
> + return False
> +
> +
> +def __lldb_init_module(debugger, internal_dict):
> + '''
> + This initializer is being run from LLDB in the embedded command
> interpreter
> + '''
> +
> + mod_name = Path(__file__).stem
> + lldb_type_formaters(debugger, mod_name)
> +
> + # Add any commands contained in this module to LLDB
> + debugger.HandleCommand(
> + f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols')
> + debugger.HandleCommand(
> + f'command script add -c {mod_name}.EfiGuidCommand guid')
> + debugger.HandleCommand(
> + f'command script add -c {mod_name}.EfiTableCommand table')
> + debugger.HandleCommand(
> + f'command script add -c {mod_name}.EfiHobCommand hob')
> + debugger.HandleCommand(
> + f'command script add -c {mod_name}.EfiDevicePathCommand devicepath')
> +
> + print('EFI specific commands have been installed.')
> +
> + # patch the ctypes c_void_p values if the debuggers OS and EFI have
> + # different ideas on the size of the debug.
> + try:
> + patch_ctypes(debugger.GetSelectedTarget().addr_size)
> + except ValueError:
> + # incase the script is imported and the debugger has not target
> + # defaults to sizeof(UINTN) == sizeof(UINT64)
> + patch_ctypes()
> +
> + try:
> + target = debugger.GetSelectedTarget()
> + if target.FindFunctions('SecGdbScriptBreak').symbols:
> + breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak')
> + # Set the emulator breakpoints, if we are in the emulator
> + cmd = 'breakpoint command add -s python -F '
> + cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}'
> + debugger.HandleCommand(cmd)
> + print('Type r to run emulator.')
> + else:
> + raise ValueError("No Emulator Symbols")
> +
> + except ValueError:
> + # default action when the script is imported
> + debugger.HandleCommand("efi_symbols --frame --extended")
> + debugger.HandleCommand("register read")
> + debugger.HandleCommand("bt all")
> +
> +
> +if __name__ == '__main__':
> + pass
> --
> 2.34.1

[-- Attachment #2: Type: text/html, Size: 34958 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb
  2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
  2022-04-07  5:02   ` 回复: " gaoliming
@ 2022-04-09  4:47   ` Bob Feng
  1 sibling, 0 replies; 10+ messages in thread
From: Bob Feng @ 2022-04-09  4:47 UTC (permalink / raw)
  To: Rebecca Cran, devel@edk2.groups.io, Leif Lindholm,
	Kinney, Michael D, Wu, Hao A, Gao, Liming, Chen, Christine
  Cc: Andrew Fish

Created the PR.
https://github.com/tianocore/edk2/pull/2758

-----Original Message-----
From: Rebecca Cran <quic_rcran@quicinc.com> 
Sent: Thursday, April 7, 2022 6:34 AM
To: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>; Kinney, Michael D <michael.d.kinney@intel.com>; Wu, Hao A <hao.a.wu@intel.com>; Feng, Bob C <bob.c.feng@intel.com>; Gao, Liming <gaoliming@byosoft.com.cn>; Chen, Christine <yuwei.chen@intel.com>
Cc: Andrew Fish <afish@apple.com>
Subject: Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb

Could I have some more reviews on this please? I'd like to get this into the tree soon.

--
Rebecca Cran

On 3/21/22 14:20, Rebecca Cran wrote:
> This patch set adds debugging support for gdb and lldb.
> It also adds generic debugging classes that use a file like object to 
> make it easy to import into any debugger that supports Python.
>
> Changes from v1 to v2:
> - Moved scripts from the root of the repo into BaseTools/Scripts.
> - Fixed typo of "RISCV" as "RISKV".
>
> Testing:
> - Tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
> - Tested gdb on openSUSE for AARCH64.
>
> Rebecca Cran (3):
>    BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes
>    BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
>    BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty 
> Print
>
>   BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++
>   BaseTools/Scripts/efi_gdb.py       |  918 ++++++++
>   BaseTools/Scripts/efi_lldb.py      | 1044 ++++++++++
>   3 files changed, 4147 insertions(+)
>   create mode 100755 BaseTools/Scripts/efi_debugging.py
>   create mode 100755 BaseTools/Scripts/efi_gdb.py
>   create mode 100755 BaseTools/Scripts/efi_lldb.py
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2022-04-09  4:47 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran
2022-03-26 14:04   ` Bob Feng
2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran
2022-03-26 14:05   ` Bob Feng
2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran
2022-04-09  4:16   ` [edk2-devel] " Bob Feng
2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
2022-04-07  5:02   ` 回复: " gaoliming
2022-04-09  4:47   ` Bob Feng

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox