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