public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [PATCH 0/3] Add support for gdb and lldb
@ 2021-08-08 21:46 Andrew Fish
  2021-08-08 21:46 ` [PATCH 1/3] efi_debugging.py: - Add debugger agnostic debugging Python Classes Andrew Fish
                   ` (4 more replies)
  0 siblings, 5 replies; 14+ messages in thread
From: Andrew Fish @ 2021-08-08 21:46 UTC (permalink / raw)
  To: devel; +Cc: 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.

Since these debugging scripts don't depend on any EFI code I was thinking
we could place them in the root of the repo to be easy to discover.

I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.

Andrew Fish (3):
  efi_debugging.py: - Add debugger agnostic debugging Python Classes
  efi_gdb.py: - Add gdb EFI commands and pretty Print
  efi_lldb.py: - Add lldb EFI commands and pretty Print

 efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
 efi_gdb.py       |  918 +++++++++++++++++++
 efi_lldb.py      | 1044 ++++++++++++++++++++++
 3 files changed, 4149 insertions(+)
 create mode 100755 efi_debugging.py
 create mode 100755 efi_gdb.py
 create mode 100755 efi_lldb.py

-- 
2.30.1 (Apple Git-130)


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

* [PATCH 1/3] efi_debugging.py: - Add debugger agnostic debugging Python Classes
  2021-08-08 21:46 [PATCH 0/3] Add support for gdb and lldb Andrew Fish
@ 2021-08-08 21:46 ` Andrew Fish
  2021-08-08 21:46 ` [PATCH 2/3] efi_gdb.py: - Add gdb EFI commands and pretty Print Andrew Fish
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 14+ messages in thread
From: Andrew Fish @ 2021-08-08 21:46 UTC (permalink / raw)
  To: devel; +Cc: Andrew Fish, Leif Lindholm, Michael D Kinney, Hao A Wu

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=y, Size: 76638 bytes --]

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

Add Python classes that take a Python file like object and provide
services to other debugger specific Python scripts.

Classes:
PeTeImage - Discovery and parse PE/COFF and TE Images. Used to load symbols
						This may end up in BaseTools eventually.
EfiConfigurationTable - Parse EFI Configuration Tables.
EfiDevicePath - Decode EFI Device Paths
EfiHob - Decode EFI HOBs
GuidNames - Manage EFI_GUID's and display the C names used in code.
EfiStatusClass - Print EFI_STATUS as text strings.
EfiBootMode - Print out the boot mode as strings.
EfiTpl - Print out the EFI_TPL relative to standard levels.

This module uses Python ctype and patches UINTN and pointer values for
32-bit EFI systems.

This module is also a PE/COFF image dumper if it is called directly:
$ ./efi_debugging.py DiskIoDxe.efi
<uuid>:X64`<path>\DiskIoDxe.pdb load = 0x00000000
EntryPoint = 0x000002c4  TextAddress = 0x000002c0 DataAddress = 0x00004f40
.text    0x000002C0 (0x0369A) flags:0x68000020
.rdata   0x00003960 (0x015E0) flags:0x48000040
.data    0x00004F40 (0x00188) flags:0xC8000040
				 0x000050E0 (0x00234) flags:0x42000040
.xdata   0x00005320 (0x001B0) flags:0x42000040
.reloc   0x000054E0 (0x0002C) flags:0x42000040

Data Directories:
	Relocation Table: 0x000054E0 0x2C
	Debug           : 0x00004DB0 0x54

Cc: Leif Lindholm <leif@nuviainc.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Hao A Wu <hao.a.wu@intel.com>
Signed-off-by: Andrew Fish <afish@apple.com>
---
 efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 2187 insertions(+)
 create mode 100755 efi_debugging.py

diff --git a/efi_debugging.py b/efi_debugging.py
new file mode 100755
index 000000000000..58c00921ec7b
--- /dev/null
+++ b/efi_debugging.py
@@ -0,0 +1,2187 @@
+#!/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: "RISKV32",
+    0x5064: "RISKV64",
+    0x5128: "RISKV128",
+}
+
+
+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, indent=''):
+        result = ''
+        if self.TeHdr:
+            debug_size = self.TeHdr.DataDirectoryDebug.Size
+            if debug_size > 0:
+                debug_offset = (self.TeAdjust
+                                + self.TeHdr.DataDirectoryDebug.VirtualAddress)
+                result += f'{indent:s}'
+                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'{indent:s}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'{indent:s}'
+                result += f'{name:16s}: 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.30.1 (Apple Git-130)


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

* [PATCH 2/3] efi_gdb.py: - Add gdb EFI commands and pretty Print
  2021-08-08 21:46 [PATCH 0/3] Add support for gdb and lldb Andrew Fish
  2021-08-08 21:46 ` [PATCH 1/3] efi_debugging.py: - Add debugger agnostic debugging Python Classes Andrew Fish
@ 2021-08-08 21:46 ` Andrew Fish
  2021-08-08 21:46 ` [PATCH 3/3] efi_lldb.py: - Add lldb " Andrew Fish
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 14+ messages in thread
From: Andrew Fish @ 2021-08-08 21:46 UTC (permalink / raw)
  To: devel; +Cc: Andrew Fish, Leif Lindholm, Michael D Kinney, Hao A Wu

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 <leif@nuviainc.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Hao A Wu <hao.a.wu@intel.com>
Signed-off-by: Andrew Fish <afish@apple.com>
---
 efi_gdb.py | 918 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 918 insertions(+)
 create mode 100755 efi_gdb.py

diff --git a/efi_gdb.py b/efi_gdb.py
new file mode 100755
index 000000000000..f3e7fd9d0c28
--- /dev/null
+++ b/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.30.1 (Apple Git-130)


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

* [PATCH 3/3] efi_lldb.py: - Add lldb EFI commands and pretty Print
  2021-08-08 21:46 [PATCH 0/3] Add support for gdb and lldb Andrew Fish
  2021-08-08 21:46 ` [PATCH 1/3] efi_debugging.py: - Add debugger agnostic debugging Python Classes Andrew Fish
  2021-08-08 21:46 ` [PATCH 2/3] efi_gdb.py: - Add gdb EFI commands and pretty Print Andrew Fish
@ 2021-08-08 21:46 ` Andrew Fish
  2021-08-11 22:11 ` [edk2-devel] [PATCH 0/3] Add support for gdb and lldb Rebecca Cran
  2021-09-14 23:47 ` Rebecca Cran
  4 siblings, 0 replies; 14+ messages in thread
From: Andrew Fish @ 2021-08-08 21:46 UTC (permalink / raw)
  To: devel; +Cc: Andrew Fish, Leif Lindholm, Michael D Kinney, Hao A Wu

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 <leif@nuviainc.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Hao A Wu <hao.a.wu@intel.com>
Signed-off-by: Andrew Fish <afish@apple.com>
---
 efi_lldb.py | 1044 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1044 insertions(+)
 create mode 100755 efi_lldb.py

diff --git a/efi_lldb.py b/efi_lldb.py
new file mode 100755
index 000000000000..6487e41bf50c
--- /dev/null
+++ b/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.30.1 (Apple Git-130)


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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-08-08 21:46 [PATCH 0/3] Add support for gdb and lldb Andrew Fish
                   ` (2 preceding siblings ...)
  2021-08-08 21:46 ` [PATCH 3/3] efi_lldb.py: - Add lldb " Andrew Fish
@ 2021-08-11 22:11 ` Rebecca Cran
  2021-08-11 23:22   ` Andrew Fish
  2021-09-14 23:47 ` Rebecca Cran
  4 siblings, 1 reply; 14+ messages in thread
From: Rebecca Cran @ 2021-08-11 22:11 UTC (permalink / raw)
  To: devel, afish

I realized the Arm gcc 10.3 aarch64 download from 
https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads 
uses Python 2.7.18 and not Python 3, so the f-Strings and likely more 
cause problems. I suspect at this point there's little point putting 
effort into supporting Python 2.


I noticed a couple of issues though: in efi_debugging.py the Attributes 
text is partially underlined with the non-ASCII characters '——————' 
instead of '----------'.

      Attributes
      ——————----
      _dict_ : dictionay


There are also a couple of typos of 'dictionary':


_dict_ : dictionay


-- 
Rebecca Cran


On 8/8/21 3:46 PM, Andrew Fish via groups.io 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.
>
> Since these debugging scripts don't depend on any EFI code I was thinking
> we could place them in the root of the repo to be easy to discover.
>
> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>
> Andrew Fish (3):
>    efi_debugging.py: - Add debugger agnostic debugging Python Classes
>    efi_gdb.py: - Add gdb EFI commands and pretty Print
>    efi_lldb.py: - Add lldb EFI commands and pretty Print
>
>   efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>   efi_gdb.py       |  918 +++++++++++++++++++
>   efi_lldb.py      | 1044 ++++++++++++++++++++++
>   3 files changed, 4149 insertions(+)
>   create mode 100755 efi_debugging.py
>   create mode 100755 efi_gdb.py
>   create mode 100755 efi_lldb.py
>

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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-08-11 22:11 ` [edk2-devel] [PATCH 0/3] Add support for gdb and lldb Rebecca Cran
@ 2021-08-11 23:22   ` Andrew Fish
  2021-08-12  0:30     ` Rebecca Cran
  0 siblings, 1 reply; 14+ messages in thread
From: Andrew Fish @ 2021-08-11 23:22 UTC (permalink / raw)
  To: Rebecca Cran; +Cc: devel



> On Aug 11, 2021, at 3:11 PM, Rebecca Cran <rebecca@nuviainc.com> wrote:
> 
> I realized the Arm gcc 10.3 aarch64 download from https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads uses Python 2.7.18 and not Python 3, so the f-Strings and likely more cause problems. I suspect at this point there's little point putting effort into supporting Python 2.
> 

I’ve not tested on ARM yet, so please let me know if it works. I’d like to avoid Python 2.7 support is possible given it is passed its best by date. 

> 
> I noticed a couple of issues though: in efi_debugging.py the Attributes text is partially underlined with the non-ASCII characters '——————' instead of '----------'.
> 

Thanks did not notice that. 

>      Attributes
>      ——————----
>      _dict_ : dictionay
> 
> 
> There are also a couple of typos of 'dictionary':
> 
> 
> _dict_ : dictionay
> 

Shoot though I fixed those 

Thanks,

Andrew Fish

> 
> -- 
> Rebecca Cran
> 
> 
> On 8/8/21 3:46 PM, Andrew Fish via groups.io 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.
>> 
>> Since these debugging scripts don't depend on any EFI code I was thinking
>> we could place them in the root of the repo to be easy to discover.
>> 
>> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>> 
>> Andrew Fish (3):
>>   efi_debugging.py: - Add debugger agnostic debugging Python Classes
>>   efi_gdb.py: - Add gdb EFI commands and pretty Print
>>   efi_lldb.py: - Add lldb EFI commands and pretty Print
>> 
>>  efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>>  efi_gdb.py       |  918 +++++++++++++++++++
>>  efi_lldb.py      | 1044 ++++++++++++++++++++++
>>  3 files changed, 4149 insertions(+)
>>  create mode 100755 efi_debugging.py
>>  create mode 100755 efi_gdb.py
>>  create mode 100755 efi_lldb.py
>> 


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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-08-11 23:22   ` Andrew Fish
@ 2021-08-12  0:30     ` Rebecca Cran
  2021-08-12 22:56       ` Andrew Fish
  0 siblings, 1 reply; 14+ messages in thread
From: Rebecca Cran @ 2021-08-12  0:30 UTC (permalink / raw)
  To: Andrew Fish; +Cc: devel

I should note my comments are against 
https://github.com/ajfish/edk2/tree/BZ3500-gdb, not the patches you sent 
out.

AARCH64 is working great! The only thing I noticed that doesn't look 
quite right is the following message on startup when running gdb -ex 
"target remote localhost:1234" -ex "source efi_gdb.py" -ex "efi":


Traceback (most recent call last):
   File "efi_gdb.py", line 804, in invoke
     self.restore_user_state()
   File "efi_gdb.py", line 690, in restore_user_state
     self.user_selected_frame.select()
gdb.error: Frame is invalid.
Traceback (most recent call last):
   File "efi_gdb.py", line 822, in invoke
     gdb.execute('efi symbols --extended')
gdb.error: Error occurred in Python: Frame is invalid.
Error occurred in Python: Error occurred in Python: Frame is invalid.


Running `bt` shows the full set of frames except for the last one which 
as expected is shown as ??.


-- 

Rebecca Cran


On 8/11/21 5:22 PM, Andrew Fish wrote:
>
>> On Aug 11, 2021, at 3:11 PM, Rebecca Cran <rebecca@nuviainc.com> wrote:
>>
>> I realized the Arm gcc 10.3 aarch64 download from https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads uses Python 2.7.18 and not Python 3, so the f-Strings and likely more cause problems. I suspect at this point there's little point putting effort into supporting Python 2.
>>
> I’ve not tested on ARM yet, so please let me know if it works. I’d like to avoid Python 2.7 support is possible given it is passed its best by date.
>
>> I noticed a couple of issues though: in efi_debugging.py the Attributes text is partially underlined with the non-ASCII characters '——————' instead of '----------'.
>>
> Thanks did not notice that.
>
>>       Attributes
>>       ——————----
>>       _dict_ : dictionay
>>
>>
>> There are also a couple of typos of 'dictionary':
>>
>>
>> _dict_ : dictionay
>>
> Shoot though I fixed those




>
> Thanks,
>
> Andrew Fish
>
>> -- 
>> Rebecca Cran
>>
>>
>> On 8/8/21 3:46 PM, Andrew Fish via groups.io 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.
>>>
>>> Since these debugging scripts don't depend on any EFI code I was thinking
>>> we could place them in the root of the repo to be easy to discover.
>>>
>>> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>>>
>>> Andrew Fish (3):
>>>    efi_debugging.py: - Add debugger agnostic debugging Python Classes
>>>    efi_gdb.py: - Add gdb EFI commands and pretty Print
>>>    efi_lldb.py: - Add lldb EFI commands and pretty Print
>>>
>>>   efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>>>   efi_gdb.py       |  918 +++++++++++++++++++
>>>   efi_lldb.py      | 1044 ++++++++++++++++++++++
>>>   3 files changed, 4149 insertions(+)
>>>   create mode 100755 efi_debugging.py
>>>   create mode 100755 efi_gdb.py
>>>   create mode 100755 efi_lldb.py
>>>

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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-08-12  0:30     ` Rebecca Cran
@ 2021-08-12 22:56       ` Andrew Fish
  2021-08-13  5:09         ` Rebecca Cran
  0 siblings, 1 reply; 14+ messages in thread
From: Andrew Fish @ 2021-08-12 22:56 UTC (permalink / raw)
  To: Rebecca Cran; +Cc: devel

Rebecca,

Sorry I may have been a commit or 2 behind on the private branch. 

> On Aug 11, 2021, at 5:30 PM, Rebecca Cran <rebecca@nuviainc.com> wrote:
> 
> I should note my comments are against https://github.com/ajfish/edk2/tree/BZ3500-gdb, not the patches you sent out.
> 
> AARCH64 is working great! The only thing I noticed that doesn't look quite right is the following message on startup when running gdb -ex "target remote localhost:1234" -ex "source efi_gdb.py" -ex "efi":
> 
> 
> Traceback (most recent call last):
>   File "efi_gdb.py", line 804, in invoke
>     self.restore_user_state()
>   File "efi_gdb.py", line 690, in restore_user_state
>     self.user_selected_frame.select()
> gdb.error: Frame is invalid.
> Traceback (most recent call last):
>   File "efi_gdb.py", line 822, in invoke
>     gdb.execute('efi symbols --extended')
> gdb.error: Error occurred in Python: Frame is invalid.
> Error occurred in Python: Error occurred in Python: Frame is invalid.
> 
> 

Can you try again? I think I fixed this? The `efi symbols` command is saving user context, so it can restore the context. If there is not a valid target it can fail. 

Also thanks for testing on AARCH64!!!!

> Running `bt` shows the full set of frames except for the last one which as expected is shown as ??.
> 

I noticed the stack unwind in the gdb builds can be a little wonky at times, I had to add some code to filter out the cruft. 

Thanks,

Andrew Fish

> 
> -- 
> 
> Rebecca Cran
> 
> 
> On 8/11/21 5:22 PM, Andrew Fish wrote:
>> 
>>> On Aug 11, 2021, at 3:11 PM, Rebecca Cran <rebecca@nuviainc.com> wrote:
>>> 
>>> I realized the Arm gcc 10.3 aarch64 download from https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads uses Python 2.7.18 and not Python 3, so the f-Strings and likely more cause problems. I suspect at this point there's little point putting effort into supporting Python 2.
>>> 
>> I’ve not tested on ARM yet, so please let me know if it works. I’d like to avoid Python 2.7 support is possible given it is passed its best by date.
>> 
>>> I noticed a couple of issues though: in efi_debugging.py the Attributes text is partially underlined with the non-ASCII characters '——————' instead of '----------'.
>>> 
>> Thanks did not notice that.
>> 
>>>      Attributes
>>>      ——————----
>>>      _dict_ : dictionay
>>> 
>>> 
>>> There are also a couple of typos of 'dictionary':
>>> 
>>> 
>>> _dict_ : dictionay
>>> 
>> Shoot though I fixed those
> 
> 
> 
> 
>> 
>> Thanks,
>> 
>> Andrew Fish
>> 
>>> -- 
>>> Rebecca Cran
>>> 
>>> 
>>> On 8/8/21 3:46 PM, Andrew Fish via groups.io 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.
>>>> 
>>>> Since these debugging scripts don't depend on any EFI code I was thinking
>>>> we could place them in the root of the repo to be easy to discover.
>>>> 
>>>> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>>>> 
>>>> Andrew Fish (3):
>>>>   efi_debugging.py: - Add debugger agnostic debugging Python Classes
>>>>   efi_gdb.py: - Add gdb EFI commands and pretty Print
>>>>   efi_lldb.py: - Add lldb EFI commands and pretty Print
>>>> 
>>>>  efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>>>>  efi_gdb.py       |  918 +++++++++++++++++++
>>>>  efi_lldb.py      | 1044 ++++++++++++++++++++++
>>>>  3 files changed, 4149 insertions(+)
>>>>  create mode 100755 efi_debugging.py
>>>>  create mode 100755 efi_gdb.py
>>>>  create mode 100755 efi_lldb.py
>>>> 


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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-08-12 22:56       ` Andrew Fish
@ 2021-08-13  5:09         ` Rebecca Cran
  0 siblings, 0 replies; 14+ messages in thread
From: Rebecca Cran @ 2021-08-13  5:09 UTC (permalink / raw)
  To: Andrew Fish; +Cc: devel

On 8/12/21 4:56 PM, Andrew Fish wrote:

> Can you try again? I think I fixed this? The `efi symbols` command is saving user context, so it can restore the context. If there is not a valid target it can fail.

On X64 running OVMF 'source efi_gdb.py' lists lots of DLLs and finished 
with the backtrace.

On AARCH64 running SBSA-REF, I get similar output except where the 
backtrace at the end of 'source efi_gdb.py' output should be I get the 
following output:


AArch64`/home/bcran/src/upstream/uefi/Build/SbsaQemu/DEBUG_GCC5/AARCH64/MdeModulePkg/Bus/Usb/UsbKbDxe/UsbKbDxe/DEBUG/UsbKbDxe.dll 
load = 0x101fb87d000
AArch64`/home/bcran/src/upstream/uefi/Build/SbsaQemu/DEBUG_GCC5/AARCH64/MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassStorageDxe/DEBUG/UsbMassStorageDxe.dll 
load = 0x101fb874000
AArch64`/home/bcran/src/upstream/uefi/Build/SbsaQemu/DEBUG_GCC5/AARCH64/MdeModulePkg/Application/UiApp/UiApp/DEBUG/UiApp.dll 
load = 0x101f851a000   is already loaded
Traceback (most recent call last):
   File "efi_gdb.py", line 828, in invoke
     self.restore_user_state()
   File "efi_gdb.py", line 711, in restore_user_state
     self.user_selected_frame.select()
gdb.error: Frame is invalid.


However, I can run 'bt' and get what looks like a good stack trace.

With the latest code on your private branch, I still see non-ASCII 
characters that look out of place. e.g. In efi_debugging.py line 627, 
"Attributes" is underlined with a mixture of hyphen types.


-- 
Rebecca Cran


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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-08-08 21:46 [PATCH 0/3] Add support for gdb and lldb Andrew Fish
                   ` (3 preceding siblings ...)
  2021-08-11 22:11 ` [edk2-devel] [PATCH 0/3] Add support for gdb and lldb Rebecca Cran
@ 2021-09-14 23:47 ` Rebecca Cran
  2021-09-15  0:47   ` Andrew Fish
  4 siblings, 1 reply; 14+ messages in thread
From: Rebecca Cran @ 2021-09-14 23:47 UTC (permalink / raw)
  To: devel, afish

I was wondering what your plan for committing these to the repo is? It 
would be nice to get them committed so people can start using them.


-- 
Rebecca Cran


On 8/8/21 3:46 PM, Andrew Fish via groups.io 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.
>
> Since these debugging scripts don't depend on any EFI code I was thinking
> we could place them in the root of the repo to be easy to discover.
>
> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>
> Andrew Fish (3):
>    efi_debugging.py: - Add debugger agnostic debugging Python Classes
>    efi_gdb.py: - Add gdb EFI commands and pretty Print
>    efi_lldb.py: - Add lldb EFI commands and pretty Print
>
>   efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>   efi_gdb.py       |  918 +++++++++++++++++++
>   efi_lldb.py      | 1044 ++++++++++++++++++++++
>   3 files changed, 4149 insertions(+)
>   create mode 100755 efi_debugging.py
>   create mode 100755 efi_gdb.py
>   create mode 100755 efi_lldb.py
>


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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-09-14 23:47 ` Rebecca Cran
@ 2021-09-15  0:47   ` Andrew Fish
  2021-10-23  2:07     ` Rebecca Cran
                       ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: Andrew Fish @ 2021-09-15  0:47 UTC (permalink / raw)
  To: edk2-devel-groups-io, Rebecca Cran

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

Sorry the patches stalled out. I need to push them….

Thanks,

Andrew Fish

> On Sep 14, 2021, at 4:47 PM, Rebecca Cran <rebecca@bsdio.com> wrote:
> 
> I was wondering what your plan for committing these to the repo is? It would be nice to get them committed so people can start using them.
> 
> 
> -- 
> Rebecca Cran
> 
> 
> On 8/8/21 3:46 PM, Andrew Fish via groups.io <http://groups.io/> 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.
>> 
>> Since these debugging scripts don't depend on any EFI code I was thinking
>> we could place them in the root of the repo to be easy to discover.
>> 
>> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>> 
>> Andrew Fish (3):
>>   efi_debugging.py: - Add debugger agnostic debugging Python Classes
>>   efi_gdb.py: - Add gdb EFI commands and pretty Print
>>   efi_lldb.py: - Add lldb EFI commands and pretty Print
>> 
>>  efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>>  efi_gdb.py       |  918 +++++++++++++++++++
>>  efi_lldb.py      | 1044 ++++++++++++++++++++++
>>  3 files changed, 4149 insertions(+)
>>  create mode 100755 efi_debugging.py
>>  create mode 100755 efi_gdb.py
>>  create mode 100755 efi_lldb.py
>> 
> 
> 
> 
> 


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

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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-09-15  0:47   ` Andrew Fish
@ 2021-10-23  2:07     ` Rebecca Cran
  2021-12-13 21:59     ` Rebecca Cran
       [not found]     ` <16C06F6B056C6852.12686@groups.io>
  2 siblings, 0 replies; 14+ messages in thread
From: Rebecca Cran @ 2021-10-23  2:07 UTC (permalink / raw)
  To: Andrew Fish, edk2-devel-groups-io

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

I was reminded about this work earlier today, and thought I'd remind you 
that the patches haven't been pushed yet.


-- 

Rebecca Cran


On 9/14/21 6:47 PM, Andrew Fish wrote:
> Sorry the patches stalled out. I need to push them….
>
> Thanks,
>
> Andrew Fish
>
>> On Sep 14, 2021, at 4:47 PM, Rebecca Cran <rebecca@bsdio.com 
>> <mailto:rebecca@bsdio.com>> wrote:
>>
>> I was wondering what your plan for committing these to the repo is? 
>> It would be nice to get them committed so people can start using them.
>>
>>
>> --
>> Rebecca Cran
>>
>>
>> On 8/8/21 3:46 PM, Andrew Fish viagroups.io <http://groups.io/>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.
>>>
>>> Since these debugging scripts don't depend on any EFI code I was 
>>> thinking
>>> we could place them in the root of the repo to be easy to discover.
>>>
>>> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>>>
>>> Andrew Fish (3):
>>>   efi_debugging.py: - Add debugger agnostic debugging Python Classes
>>>   efi_gdb.py: - Add gdb EFI commands and pretty Print
>>>   efi_lldb.py: - Add lldb EFI commands and pretty Print
>>>
>>>  efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>>>  efi_gdb.py       |  918 +++++++++++++++++++
>>>  efi_lldb.py      | 1044 ++++++++++++++++++++++
>>>  3 files changed, 4149 insertions(+)
>>>  create mode 100755 efi_debugging.py
>>>  create mode 100755 efi_gdb.py
>>>  create mode 100755 efi_lldb.py
>>>
>>
>>
>>
>> 
>

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

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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
  2021-09-15  0:47   ` Andrew Fish
  2021-10-23  2:07     ` Rebecca Cran
@ 2021-12-13 21:59     ` Rebecca Cran
       [not found]     ` <16C06F6B056C6852.12686@groups.io>
  2 siblings, 0 replies; 14+ messages in thread
From: Rebecca Cran @ 2021-12-13 21:59 UTC (permalink / raw)
  To: Andrew Fish, edk2-devel-groups-io; +Cc: leif, Michael D Kinney

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

(cc other TianoCore stewards)


With edk2-stable202111 just tagged, now would be a good time to get the 
patches pushed.


-- 
Rebecca Cran


On 9/14/21 18:47, Andrew Fish wrote:
> Sorry the patches stalled out. I need to push them….
>
> Thanks,
>
> Andrew Fish
>
>> On Sep 14, 2021, at 4:47 PM, Rebecca Cran <rebecca@bsdio.com> wrote:
>>
>> I was wondering what your plan for committing these to the repo is? 
>> It would be nice to get them committed so people can start using them.
>>
>>
>> --
>> Rebecca Cran
>>
>>
>> On 8/8/21 3:46 PM, Andrew Fish viagroups.io <http://groups.io/>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.
>>>
>>> Since these debugging scripts don't depend on any EFI code I was 
>>> thinking
>>> we could place them in the root of the repo to be easy to discover.
>>>
>>> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>>>
>>> Andrew Fish (3):
>>>   efi_debugging.py: - Add debugger agnostic debugging Python Classes
>>>   efi_gdb.py: - Add gdb EFI commands and pretty Print
>>>   efi_lldb.py: - Add lldb EFI commands and pretty Print
>>>
>>>  efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>>>  efi_gdb.py       |  918 +++++++++++++++++++
>>>  efi_lldb.py      | 1044 ++++++++++++++++++++++
>>>  3 files changed, 4149 insertions(+)
>>>  create mode 100755 efi_debugging.py
>>>  create mode 100755 efi_gdb.py
>>>  create mode 100755 efi_lldb.py
>>>
>>
>>
>>
>> 
>

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

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

* Re: [edk2-devel] [PATCH 0/3] Add support for gdb and lldb
       [not found]     ` <16C06F6B056C6852.12686@groups.io>
@ 2022-01-07  2:42       ` Rebecca Cran
  0 siblings, 0 replies; 14+ messages in thread
From: Rebecca Cran @ 2022-01-07  2:42 UTC (permalink / raw)
  To: Andrew Fish, edk2-devel-groups-io; +Cc: leif, Michael D Kinney

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

Since it's now the new year, I thought it might be worth pinging people 
again to try and get these patches pushed.


-- 

Rebecca Cran


On 12/13/21 14:59, Rebecca Cran wrote:
>
> (cc other TianoCore stewards)
>
>
> With edk2-stable202111 just tagged, now would be a good time to get 
> the patches pushed.
>
>
> -- 
> Rebecca Cran
>
>
> On 9/14/21 18:47, Andrew Fish wrote:
>> Sorry the patches stalled out. I need to push them….
>>
>> Thanks,
>>
>> Andrew Fish
>>
>>> On Sep 14, 2021, at 4:47 PM, Rebecca Cran <rebecca@bsdio.com> wrote:
>>>
>>> I was wondering what your plan for committing these to the repo is? 
>>> It would be nice to get them committed so people can start using them.
>>>
>>>
>>> --
>>> Rebecca Cran
>>>
>>>
>>> On 8/8/21 3:46 PM, Andrew Fish viagroups.io <http://groups.io/>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.
>>>>
>>>> Since these debugging scripts don't depend on any EFI code I was 
>>>> thinking
>>>> we could place them in the root of the repo to be easy to discover.
>>>>
>>>> I've tested gdb on Ubuntu and lldb on macOS for IA32 and X64.
>>>>
>>>> Andrew Fish (3):
>>>>   efi_debugging.py: - Add debugger agnostic debugging Python Classes
>>>>   efi_gdb.py: - Add gdb EFI commands and pretty Print
>>>>   efi_lldb.py: - Add lldb EFI commands and pretty Print
>>>>
>>>>  efi_debugging.py | 2187 ++++++++++++++++++++++++++++++++++++++++++++++
>>>>  efi_gdb.py       |  918 +++++++++++++++++++
>>>>  efi_lldb.py      | 1044 ++++++++++++++++++++++
>>>>  3 files changed, 4149 insertions(+)
>>>>  create mode 100755 efi_debugging.py
>>>>  create mode 100755 efi_gdb.py
>>>>  create mode 100755 efi_lldb.py
>>>>
>>>
>>>
>>>
>>
>
>
> 

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

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

end of thread, other threads:[~2022-01-07  2:42 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-08-08 21:46 [PATCH 0/3] Add support for gdb and lldb Andrew Fish
2021-08-08 21:46 ` [PATCH 1/3] efi_debugging.py: - Add debugger agnostic debugging Python Classes Andrew Fish
2021-08-08 21:46 ` [PATCH 2/3] efi_gdb.py: - Add gdb EFI commands and pretty Print Andrew Fish
2021-08-08 21:46 ` [PATCH 3/3] efi_lldb.py: - Add lldb " Andrew Fish
2021-08-11 22:11 ` [edk2-devel] [PATCH 0/3] Add support for gdb and lldb Rebecca Cran
2021-08-11 23:22   ` Andrew Fish
2021-08-12  0:30     ` Rebecca Cran
2021-08-12 22:56       ` Andrew Fish
2021-08-13  5:09         ` Rebecca Cran
2021-09-14 23:47 ` Rebecca Cran
2021-09-15  0:47   ` Andrew Fish
2021-10-23  2:07     ` Rebecca Cran
2021-12-13 21:59     ` Rebecca Cran
     [not found]     ` <16C06F6B056C6852.12686@groups.io>
2022-01-07  2:42       ` Rebecca Cran

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