#!/usr/bin/python3
'''
Copyright (c) Apple Inc. 2021
SPDX-License-Identifier: BSD-2-Clause-Patent

Class that abstracts PE/COFF debug info parsing via a Python file like
object. You can port this code into an arbitrary debugger by invoking
the classes and passing in a file like object that abstracts the debugger
reading memory.

If you run this file directly it will parse the passed in PE/COFF files
for debug info:
python3 ./efi_pefcoff.py DxeCore.efi
IA32`<path...>/DxeCore.dll load = 0x00000000
EntryPoint = 0x000030d2  TextAddress = 0x00000240 DataAddress = 0x000042c0
.text    0x00000240 (0x04080) flags:0x60000020
.data    0x000042C0 (0x001C0) flags:0xC0000040
.reloc   0x00004480 (0x00240) flags:0x42000040

Note: PeCoffClass uses virtual addresses and not file offsets.
       It needs to work when images are loaded into memory.
       as long as virtual address map to file addresses this
       code can process binary files.

Note: This file can also contain generic worker functions (like GuidNames)
      that abstract debugger agnostic services to the debugger.

This file should never import debugger specific modules.
'''

import sys
import os
import uuid
import struct
import re
from ctypes import c_char, c_uint8, c_uint16, c_uint32, c_uint64, c_void_p
from ctypes import ARRAY, sizeof
from ctypes import Structure, LittleEndianStructure

#
# The empty LittleEndianStructure must have _fields_ assigned prior to use or
#  sizeof(). Anything that is size UINTN may need to get adjusted.
#
# The issue is ctypes matches our local machine, not the machine we are
#  trying to debug. Call patch_ctypes() passing in the byte width from the
#  debugger python to make sure you are in sync.
#
# Splitting out the _field_ from the Structure (LittleEndianStructure) class
#  allows it to be patched.
#


class EFI_LOADED_IMAGE_PROTOCOL(LittleEndianStructure):
    pass


EFI_LOADED_IMAGE_PROTOCOL_fields_ = [
    ('Revision',                      c_uint32),
    ('ParentHandle',                  c_void_p),
    ('SystemTable',                   c_void_p),
    ('DeviceHandle',                  c_void_p),
    ('FilePath',                      c_void_p),
    ('Reserved',                      c_void_p),
    ('LoadOptionsSize',               c_uint32),
    ('LoadOptions',                   c_void_p),
    ('ImageBase',                     c_void_p),
    ('ImageSize',                     c_uint64),
    ('ImageCodeType',                 c_uint32),
    ('ImageDataType',                 c_uint32),
    ('Unload',                        c_void_p),
]


class EFI_GUID(LittleEndianStructure):
    _fields_ = [
        ('Data1',               c_uint32),
        ('Data2',               c_uint16),
        ('Data3',               c_uint16),
        ('Data4',               ARRAY(c_uint8, 8))
    ]


class EFI_SYSTEM_TABLE_POINTER(LittleEndianStructure):
    _fields_ = [
        ('Signature',                     c_uint64),
        ('EfiSystemTableBase',            c_uint64),
        ('Crc32',                         c_uint32)
    ]


class EFI_DEBUG_IMAGE_INFO_NORMAL(LittleEndianStructure):
    pass


EFI_DEBUG_IMAGE_INFO_NORMAL_fields_ = [
    ('ImageInfoType',                 c_uint32),
    ('LoadedImageProtocolInstance',   c_void_p),
    ('ImageHandle',                   c_void_p)
]


class EFI_DEBUG_IMAGE_INFO(LittleEndianStructure):
    pass


EFI_DEBUG_IMAGE_INFO_fields_ = [
    ('NormalImage',                   c_void_p),
]


class EFI_DEBUG_IMAGE_INFO_TABLE_HEADER(LittleEndianStructure):
    pass


EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_ = [
    ('UpdateStatus',                  c_uint32),
    ('TableSize',                     c_uint32),
    ('EfiDebugImageInfoTable',        c_void_p),
]


class EFI_TABLE_HEADER(LittleEndianStructure):
    _fields_ = [
        ('Signature',                     c_uint64),
        ('Revision',                      c_uint32),
        ('HeaderSize',                    c_uint32),
        ('CRC32',                         c_uint32),
        ('Reserved',                      c_uint32),
    ]


class EFI_CONFIGURATION_TABLE(LittleEndianStructure):
    pass


EFI_CONFIGURATION_TABLE_fields_ = [
    ('VendorGuid',                    EFI_GUID),
    ('VendorTable',                   c_void_p)
]


class EFI_SYSTEM_TABLE(LittleEndianStructure):
    pass


EFI_SYSTEM_TABLE_fields_ = [
    ('Hdr',                           EFI_TABLE_HEADER),
    ('FirmwareVendor',                c_void_p),
    ('FirmwareRevision',              c_uint32),
    ('ConsoleInHandle',               c_void_p),
    ('ConIn',                         c_void_p),
    ('ConsoleOutHandle',              c_void_p),
    ('ConOut',                        c_void_p),
    ('StandardErrHandle',             c_void_p),
    ('StdErr',                        c_void_p),
    ('RuntimeService',                c_void_p),
    ('BootService',                   c_void_p),
    ('NumberOfTableEntries',          c_void_p),
    ('ConfigurationTable',            c_void_p),
]


class EFI_IMAGE_DATA_DIRECTORY(LittleEndianStructure):
    _fields_ = [
        ('VirtualAddress',       c_uint32),
        ('Size',                 c_uint32)
    ]


class EFI_TE_IMAGE_HEADER(LittleEndianStructure):
    _fields_ = [
        ('Signature',            ARRAY(c_char, 2)),
        ('Machine',              c_uint16),
        ('NumberOfSections',     c_uint8),
        ('Subsystem',            c_uint8),
        ('StrippedSize',         c_uint16),
        ('AddressOfEntryPoint',  c_uint32),
        ('BaseOfCode',           c_uint32),
        ('ImageBase',            c_uint64),
        ('DataDirectoryBaseReloc',  EFI_IMAGE_DATA_DIRECTORY),
        ('DataDirectoryDebug',      EFI_IMAGE_DATA_DIRECTORY)
    ]


class EFI_IMAGE_DOS_HEADER(LittleEndianStructure):
    _fields_ = [
        ('e_magic',              c_uint16),
        ('e_cblp',               c_uint16),
        ('e_cp',                 c_uint16),
        ('e_crlc',               c_uint16),
        ('e_cparhdr',            c_uint16),
        ('e_minalloc',           c_uint16),
        ('e_maxalloc',           c_uint16),
        ('e_ss',                 c_uint16),
        ('e_sp',                 c_uint16),
        ('e_csum',               c_uint16),
        ('e_ip',                 c_uint16),
        ('e_cs',                 c_uint16),
        ('e_lfarlc',             c_uint16),
        ('e_ovno',               c_uint16),
        ('e_res',                ARRAY(c_uint16, 4)),
        ('e_oemid',              c_uint16),
        ('e_oeminfo',            c_uint16),
        ('e_res2',               ARRAY(c_uint16, 10)),
        ('e_lfanew',             c_uint16)
    ]


class EFI_IMAGE_FILE_HEADER(LittleEndianStructure):
    _fields_ = [
        ('Machine',               c_uint16),
        ('NumberOfSections',      c_uint16),
        ('TimeDateStamp',         c_uint32),
        ('PointerToSymbolTable',  c_uint32),
        ('NumberOfSymbols',       c_uint32),
        ('SizeOfOptionalHeader',  c_uint16),
        ('Characteristics',       c_uint16)
    ]


class EFI_IMAGE_OPTIONAL_HEADER32(LittleEndianStructure):
    _fields_ = [
        ('Magic',                         c_uint16),
        ('MajorLinkerVersion',            c_uint8),
        ('MinorLinkerVersion',            c_uint8),
        ('SizeOfCode',                    c_uint32),
        ('SizeOfInitializedData',         c_uint32),
        ('SizeOfUninitializedData',       c_uint32),
        ('AddressOfEntryPoint',           c_uint32),
        ('BaseOfCode',                    c_uint32),
        ('BaseOfData',                    c_uint32),
        ('ImageBase',                     c_uint32),
        ('SectionAlignment',              c_uint32),
        ('FileAlignment',                 c_uint32),
        ('MajorOperatingSystemVersion',   c_uint16),
        ('MinorOperatingSystemVersion',   c_uint16),
        ('MajorImageVersion',             c_uint16),
        ('MinorImageVersion',             c_uint16),
        ('MajorSubsystemVersion',         c_uint16),
        ('MinorSubsystemVersion',         c_uint16),
        ('Win32VersionValue',             c_uint32),
        ('SizeOfImage',                   c_uint32),
        ('SizeOfHeaders',                 c_uint32),
        ('CheckSum',                 c_uint32),
        ('Subsystem',                     c_uint16),
        ('DllCharacteristics',            c_uint16),
        ('SizeOfStackReserve',            c_uint32),
        ('SizeOfStackCommit',            c_uint32),
        ('SizeOfHeapReserve',             c_uint32),
        ('SizeOfHeapCommit',             c_uint32),
        ('LoaderFlags',              c_uint32),
        ('NumberOfRvaAndSizes',           c_uint32),
        ('DataDirectory',                 ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16))
    ]


class EFI_IMAGE_NT_HEADERS32(LittleEndianStructure):
    _fields_ = [
        ('Signature',            c_uint32),
        ('FileHeader',           EFI_IMAGE_FILE_HEADER),
        ('OptionalHeader',       EFI_IMAGE_OPTIONAL_HEADER32)
    ]


class EFI_IMAGE_OPTIONAL_HEADER64(LittleEndianStructure):
    _fields_ = [
        ('Magic',                         c_uint16),
        ('MajorLinkerVersion',            c_uint8),
        ('MinorLinkerVersion',            c_uint8),
        ('SizeOfCode',                    c_uint32),
        ('SizeOfInitializedData',         c_uint32),
        ('SizeOfUninitializedData',       c_uint32),
        ('AddressOfEntryPoint',           c_uint32),
        ('BaseOfCode',                    c_uint32),
        ('BaseOfData',                    c_uint32),
        ('ImageBase',                     c_uint32),
        ('SectionAlignment',              c_uint32),
        ('FileAlignment',                 c_uint32),
        ('MajorOperatingSystemVersion',   c_uint16),
        ('MinorOperatingSystemVersion',   c_uint16),
        ('MajorImageVersion',             c_uint16),
        ('MinorImageVersion',             c_uint16),
        ('MajorSubsystemVersion',         c_uint16),
        ('MinorSubsystemVersion',         c_uint16),
        ('Win32VersionValue',             c_uint32),
        ('SizeOfImage',                   c_uint32),
        ('SizeOfHeaders',                 c_uint32),
        ('CheckSum',                 c_uint32),
        ('Subsystem',                     c_uint16),
        ('DllCharacteristics',            c_uint16),
        ('SizeOfStackReserve',            c_uint64),
        ('SizeOfStackCommit',            c_uint64),
        ('SizeOfHeapReserve',             c_uint64),
        ('SizeOfHeapCommit',             c_uint64),
        ('LoaderFlags',              c_uint32),
        ('NumberOfRvaAndSizes',           c_uint32),
        ('DataDirectory',                 ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16))
    ]


class EFI_IMAGE_NT_HEADERS64(LittleEndianStructure):
    _fields_ = [
        ('Signature',            c_uint32),
        ('FileHeader',           EFI_IMAGE_FILE_HEADER),
        ('OptionalHeader',       EFI_IMAGE_OPTIONAL_HEADER64)
    ]


class EFI_IMAGE_DEBUG_DIRECTORY_ENTRY(LittleEndianStructure):
    _fields_ = [
        ('Characteristics',            c_uint32),
        ('TimeDateStamp',              c_uint32),
        ('MajorVersion',               c_uint16),
        ('MinorVersion',               c_uint16),
        ('Type',                       c_uint32),
        ('SizeOfData',                 c_uint32),
        ('RVA',                        c_uint32),
        ('FileOffset',                 c_uint32),
    ]


class EFI_IMAGE_SECTION_HEADER(LittleEndianStructure):
    _fields_ = [
        ('Name',                       ARRAY(c_char, 8)),
        ('VirtualSize',                c_uint32),
        ('VirtualAddress',             c_uint32),
        ('SizeOfRawData',              c_uint32),
        ('PointerToRawData',           c_uint32),
        ('PointerToRelocations',       c_uint32),
        ('PointerToLinenumbers',       c_uint32),
        ('NumberOfRelocations',        c_uint16),
        ('NumberOfLinenumbers',        c_uint16),
        ('Characteristics',            c_uint32),
    ]


EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b
EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b

DIRECTORY_DEBUG = 6


image_machine_dict = {
    0x014c: "IA32",
    0x0200: "IPF",
    0x0EBC: "EBC",
    0x8664: "X64",
    0x01c2: "ARM",
    0xAA64: "AArch64",
    0x5032: "RISC32",
    0x5064: "RISC64",
    0x5128: "RISCV128",
}


def patch_void_p_to_ctype(patch_type, to_patch):
    '''Optionally patch c_void_p in the Structure._fields_'''
    if patch_type is None:
        return to_patch

    result = []
    for name, c_type in to_patch:
        if type(c_type) == type(c_void_p):
            result.append((name, c_uint32))
        else:
            result.append((name, c_type))
    return result


def patch_ctypes(pointer_width=8):
    '''
    Pass in the pointer width of the system being debugged. If it is not
    the same as c_void_p then patch the _fields_ with the correct type.
    For any ctypes Structure that has a c_void_p this function needs to be
    called prior to use or sizeof() to initialize _fields_.
    '''

    if sizeof(c_void_p) == pointer_width:
        patch_type = None
    elif pointer_width == 16:
        assert False
    elif pointer_width == 8:
        patch_type = c_uint64
    elif pointer_width == 4:
        patch_type = c_uint32
    else:
        raise Exception(f'ERROR: Unkown pointer_width = {pointer_width}')

    # If you add a ctypes Structure class with a c_void_p you need to add
    # it to this list. Note: you should use c_void_p for UINTN values.
    EFI_LOADED_IMAGE_PROTOCOL._fields_ = patch_void_p_to_ctype(
        patch_type, EFI_LOADED_IMAGE_PROTOCOL_fields_)
    EFI_DEBUG_IMAGE_INFO_NORMAL._fields_ = patch_void_p_to_ctype(
        patch_type, EFI_DEBUG_IMAGE_INFO_NORMAL_fields_)
    EFI_DEBUG_IMAGE_INFO._fields_ = patch_void_p_to_ctype(
        patch_type, EFI_DEBUG_IMAGE_INFO_fields_)
    EFI_DEBUG_IMAGE_INFO_TABLE_HEADER._fields_ = patch_void_p_to_ctype(
        patch_type, EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_)
    EFI_CONFIGURATION_TABLE._fields_ = patch_void_p_to_ctype(
        patch_type, EFI_CONFIGURATION_TABLE_fields_)
    EFI_SYSTEM_TABLE._fields_ = patch_void_p_to_ctype(
        patch_type, EFI_SYSTEM_TABLE_fields_)

    # patch up anything else that needs to know pointer_width
    EfiStatusClass(pointer_width)


def ctype_to_str(ctype, indent='', hide_list=[]):
    '''
    Given a ctype object print out as a string by walking the _fields_
    in the cstring Class
     '''
    result = ''
    for field in ctype._fields_:
        attr = getattr(ctype, field[0])
        tname = type(attr).__name__
        if field[0] in hide_list:
            continue

        result += indent + f'{field[0]} = '
        if tname == 'EFI_GUID':
            result += GuidNames.to_name(GuidNames.to_uuid(attr)) + '\n'
        elif issubclass(type(attr), Structure):
            result += f'{tname}\n' + \
                ctype_to_str(attr, indent + '  ', hide_list)
        elif isinstance(attr, int):
            result += f'0x{attr:x}\n'
        else:
            result += f'{attr}\n'

    return result


def hexline(addr, data):
    hexstr = ''
    printable = ''
    for i in range(0, len(data)):
        hexstr += f'{data[i]:02x} '
        printable += chr(data[i]) if data[i] > 0x20 and data[i] < 0x7f else '.'
    return f'{addr:04x}  {hexstr:48s} |{printable:s}|'


def hexdump(data, indent=''):
    if not isinstance(data, bytearray):
        data = bytearray(data)

    result = ''
    for i in range(0, len(data), 16):
        result += indent + hexline(i, data[i:i+16]) + '\n'
    return result


class EfiTpl:
    ''' Return string for EFI_TPL'''

    def __init__(self, tpl):
        self.tpl = tpl

    def __str__(self):
        if self.tpl < 4:
            result = f'{self.tpl:d}'
        elif self.tpl < 8:
            result = "TPL_APPLICATION"
            if self.tpl - 4 > 0:
                result += f' + {self.tpl - 4:d}'
        elif self.tpl < 16:
            result = "TPL_CALLBACK"
            if self.tpl - 8 > 0:
                result += f' + {self.tpl - 8:d}'
        elif self.tpl < 31:
            result = "TPL_NOTIFY"
            if self.tpl - 16 > 0:
                result += f' + {self.tpl - 16:d}'
        elif self.tpl == 31:
            result = "TPL_HIGH_LEVEL"
        else:
            result = f'Invalid TPL = {self.tpl:d}'
        return result


class EfiBootMode:
    '''
    Class to return human readable string for EFI_BOOT_MODE

    Methods
    -----------
    to_str(boot_mode, default)
        return string for boot_mode, and return default if there is not a
        match.
    '''

    EFI_BOOT_MODE_dict = {
        0x00: "BOOT_WITH_FULL_CONFIGURATION",
        0x01: "BOOT_WITH_MINIMAL_CONFIGURATION",
        0x02: "BOOT_ASSUMING_NO_CONFIGURATION_CHANGES",
        0x03: "BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS",
        0x04: "BOOT_WITH_DEFAULT_SETTINGS",
        0x05: "BOOT_ON_S4_RESUME",
        0x06: "BOOT_ON_S5_RESUME",
        0x07: "BOOT_WITH_MFG_MODE_SETTINGS",
        0x10: "BOOT_ON_S2_RESUME",
        0x11: "BOOT_ON_S3_RESUME",
        0x12: "BOOT_ON_FLASH_UPDATE",
        0x20: "BOOT_IN_RECOVERY_MODE",
    }

    def __init__(self, boot_mode):
        self._boot_mode = boot_mode

    def __str__(self):
        return self.to_str(self._boot_mode)

    @classmethod
    def to_str(cls, boot_mode, default=''):
        return cls.EFI_BOOT_MODE_dict.get(boot_mode, default)


class EfiStatusClass:
    '''
    Class to decode EFI_STATUS to a human readable string. You need to
    pass in pointer_width to get the corret value since the EFI_STATUS
    code values are different based on the sizeof UINTN. The default is
    sizeof(UINTN) == 8.

    Attributes
    ??????
    _dict_ : dictionary
        dictionary of EFI_STATUS that has beed updated to match
        pointer_width.

    Methods
    -----------
    patch_dictionary(pointer_width)

    to_str(status, default)
    '''

    _dict_ = {}
    _EFI_STATUS_UINT32_dict = {
        0: "Success",
        1: "Warning Unknown Glyph",
        2: "Warning Delete Failure",
        3: "Warning Write Failure",
        4: "Warning Buffer Too Small",
        5: "Warning Stale Data",
        6: "Warngin File System",
        (0x20000000 | 0): "Warning interrupt source pending",
        (0x20000000 | 1): "Warning interrupt source quiesced",

        (0x80000000 | 1): "Load Error",
        (0x80000000 | 2): "Invalid Parameter",
        (0x80000000 | 3): "Unsupported",
        (0x80000000 | 4): "Bad Buffer Size",
        (0x80000000 | 5): "Buffer Too Small",
        (0x80000000 | 6): "Not Ready",
        (0x80000000 | 7): "Device Error",
        (0x80000000 | 8): "Write Protected",
        (0x80000000 | 9): "Out of Resources",
        (0x80000000 | 10): "Volume Corrupt",
        (0x80000000 | 11): "Volume Full",
        (0x80000000 | 12): "No Media",
        (0x80000000 | 13): "Media changed",
        (0x80000000 | 14): "Not Found",
        (0x80000000 | 15): "Access Denied",
        (0x80000000 | 16): "No Response",
        (0x80000000 | 17): "No mapping",
        (0x80000000 | 18): "Time out",
        (0x80000000 | 19): "Not started",
        (0x80000000 | 20): "Already started",
        (0x80000000 | 21): "Aborted",
        (0x80000000 | 22): "ICMP Error",
        (0x80000000 | 23): "TFTP Error",
        (0x80000000 | 24): "Protocol Error",
        (0x80000000 | 25): "Incompatible Version",
        (0x80000000 | 26): "Security Violation",
        (0x80000000 | 27): "CRC Error",
        (0x80000000 | 28): "End of Media",
        (0x80000000 | 31): "End of File",
        (0x80000000 | 32): "Invalid Language",
        (0x80000000 | 33): "Compromised Data",
        (0x80000000 | 35): "HTTP Error",

        (0xA0000000 | 0): "Interrupt Pending",
    }

    def __init__(self, status=None, pointer_width=8):
        self.status = status
        # this will convert to 64-bit version if needed
        self.patch_dictionary(pointer_width)

    def __str__(self):
        return self.to_str(self.status)

    @classmethod
    def to_str(cls, status, default=''):
        return cls._dict_.get(status, default)

    @classmethod
    def patch_dictionary(cls, pointer_width):
        '''Patch UINTN upper bits like values '''

        if cls._dict_:
            # only patch the class variable once
            return False

        if pointer_width == 4:
            cls._dict = cls._EFI_STATUS_UINT32_dict
        elif pointer_width == 8:
            for key, value in cls._EFI_STATUS_UINT32_dict.items():
                mask = (key & 0xE0000000) << 32
                new_key = (key & 0x1FFFFFFF) | mask
                cls._dict_[new_key] = value
            return True
        else:
            return False


class GuidNames:
    '''
    Class to expose the C names of EFI_GUID's. The _dict_ starts with
    common EFI System Table entry EFI_GUID's. _dict_ can get updated with the
    build generated Guid.xref file if a path to a module is passed
    into add_build_guid_file(). If symbols are loaded for any module
    in the build the path the build product should imply the
    relative location of that builds Guid.xref file.

    Attributes
    ??????----
    _dict_ : dictionary
        dictionary of EFI_GUID (uuid) strings to C global names

    Methods
    -------
    to_uuid(uuid)
        convert a hex UUID string or bytearray to a uuid.UUID
    to_name(uuid)
        convert a UUID string to a C global constant name.
    to_guid(guid_name)
        convert a C global constant EFI_GUID name to uuid hex string.
    is_guid_str(name)
       name is a hex UUID string.
       Example: 49152E77-1ADA-4764-B7A2-7AFEFED95E8B

    to_c_guid(value)
        convert a uuid.UUID or UUID string to a c_guid string
        (see is_c_guid())
    from_c_guid(value)
        covert a C guid string to a hex UUID string.
    is_c_guid(name)
        name is the C initialization value for an EFI_GUID. Example:
        { 0x414e6bdd, 0xe47b, 0x47cc, { 0xb2, 0x44, 0xbb, 0x61,
                                        0x02, 0x0c, 0xf5, 0x16 }}

    add_build_guid_file(module_path, custom_file):
        assume module_path is an edk2 build product and load the Guid.xref
        file from that build to fill in _dict_. If you know the path and
        file name of a custom Guid.xref  you can pass it in as custom_file.

    '''
    _dict_ = {  # Common EFI System Table values
        '05AD34BA-6F02-4214-952E-4DA0398E2BB9':
            'gEfiDxeServicesTableGuid',
        '7739F24C-93D7-11D4-9A3A-0090273FC14D':
            'gEfiHobListGuid',
        '4C19049F-4137-4DD3-9C10-8B97A83FFDFA':
            'gEfiMemoryTypeInformationGuid',
        '49152E77-1ADA-4764-B7A2-7AFEFED95E8B':
            'gEfiDebugImageInfoTableGuid',
        '060CC026-4C0D-4DDA-8F41-595FEF00A502':
            'gMemoryStatusCodeRecordGuid',
        'EB9D2D31-2D88-11D3-9A16-0090273FC14D':
            'gEfiSmbiosTableGuid',
        'EB9D2D30-2D88-11D3-9A16-0090273FC14D':
            'gEfiAcpi10TableGuid',
        '8868E871-E4F1-11D3-BC22-0080C73C8881':
            'gEfiAcpi20TableGuid',
    }

    guid_files = []

    def __init__(self, uuid=None, pointer_width=8):
        self.uuid = None if uuid is None else self.to_uuid(uuid)

    def __str__(self):
        if self.uuid is None:
            result = ''
            for key, value in GuidNames._dict_.items():
                result += f'{key}: {value}\n'
        else:
            result = self.to_name(self.uuid)

        return result

    @classmethod
    def to_uuid(cls, obj):
        try:
            return uuid.UUID(bytes_le=bytes(obj))
        except (ValueError, TypeError):
            try:
                return uuid.UUID(bytes_le=obj)
            except (ValueError, TypeError):
                return uuid.UUID(obj)

    @classmethod
    def to_name(cls, uuid):
        if not isinstance(uuid, str):
            uuid = str(uuid)
        if cls.is_c_guid(uuid):
            uuid = cls.from_c_guid(uuid)
        return cls._dict_.get(uuid.upper(), uuid.upper())

    @classmethod
    def to_guid(cls, guid_name):
        for key, value in cls._dict_.items():
            if guid_name == value:
                return key.upper()
        else:
            raise KeyError(key)

    @classmethod
    def is_guid_str(cls, name):
        if not isinstance(name, str):
            return False
        return name.count('-') >= 4

    @classmethod
    def to_c_guid(cls, value):
        if isinstance(value, uuid.UUID):
            guid = value
        else:
            guid = uuid.UUID(value)

        (data1, data2, data3,
         data4_0, data4_1, data4_2, data4_3,
         data4_4, data4_5, data4_6, data4_7) = struct.unpack(
            '<IHH8B', guid.bytes_le)
        return (f'{{ 0x{data1:08X}, 0x{data2:04X}, 0x{data3:04X}, '
                f'{{ 0x{data4_0:02X}, 0x{data4_1:02X}, 0x{data4_2:02X}, '
                f'0x{data4_3:02X}, 0x{data4_4:02X}, 0x{data4_5:02X}, '
                f'0x{data4_6:02X}, 0x{data4_7:02X} }} }}')

    @ classmethod
    def from_c_guid(cls, value):
        try:
            hex = [int(x, 16) for x in re.findall(r"[\w']+", value)]
            return (f'{hex[0]:08X}-{hex[1]:04X}-{hex[2]:04X}'
                    + f'-{hex[3]:02X}{hex[4]:02X}-{hex[5]:02X}{hex[6]:02X}'
                    + f'{hex[7]:02X}{hex[8]:02X}{hex[9]:02X}{hex[10]:02X}')
        except ValueError:
            return value

    @ classmethod
    def is_c_guid(cls, name):
        if not isinstance(name, str):
            return False
        return name.count('{') == 2 and name.count('}') == 2

    @ classmethod
    def add_build_guid_file(cls, module_path, custom_file=None):
        if custom_file is not None:
            xref = custom_file
        else:
            # module_path will look like:
            # <repo>/Build/OvmfX64/DEBUG_XCODE5/X64/../DxeCore.dll
            # Walk backwards looking for a toolchain like name.
            # Then look for GUID database:
            # Build/OvmfX64//DEBUG_XCODE5/FV/Guid.xref
            for i in reversed(module_path.split(os.sep)):
                if (i.startswith('DEBUG_') or
                    i.startswith('RELEASE_') or
                        i.startswith('NOOPT_')):
                    build_root = os.path.join(
                        module_path.rsplit(i, 1)[0], i)
                    break

            xref = os.path.join(build_root, 'FV', 'Guid.xref')

        if xref in cls.guid_files:
            # only processes the file one time
            return True

        with open(xref) as f:
            content = f.readlines()
            cls.guid_files.append(xref)

            for lines in content:
                try:
                    if cls.is_guid_str(lines):
                        # a regex would be more pedantic
                        words = lines.split()
                        cls._dict_[words[0].upper()] = words[1].strip('\n')
                except ValueError:
                    pass

            return True

        return False


class EFI_HOB_GENERIC_HEADER(LittleEndianStructure):
    _fields_ = [
        ('HobType',             c_uint16),
        ('HobLength',           c_uint16),
        ('Reserved',            c_uint32)
    ]


class EFI_HOB_HANDOFF_INFO_TABLE(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
        ('Version',             c_uint32),
        ('BootMode',            c_uint32),
        ('EfiMemoryTop',        c_uint64),
        ('EfiMemoryBottom',     c_uint64),
        ('EfiFreeMemoryTop',    c_uint64),
        ('EfiFreeMemoryBottom', c_uint64),
        ('EfiEndOfHobList',     c_uint64),
    ]


class EFI_HOB_MEMORY_ALLOCATION(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
        ('Name',                EFI_GUID),
        ('MemoryBaseAddress',   c_uint64),
        ('MemoryLength',        c_uint64),
        ('MemoryType',          c_uint32),
        ('Reserved',            c_uint32),
    ]


class EFI_HOB_RESOURCE_DESCRIPTOR(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
        ('Owner',               EFI_GUID),
        ('ResourceType',        c_uint32),
        ('ResourceAttribute',   c_uint32),
        ('PhysicalStart',       c_uint64),
        ('ResourceLength',      c_uint64),
    ]


class EFI_HOB_GUID_TYPE(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
        ('Name',                EFI_GUID),
    ]


class EFI_HOB_FIRMWARE_VOLUME(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
        ('BaseAddress',         c_uint64),
        ('Length',              c_uint64),
    ]


class EFI_HOB_CPU(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
        ('SizeOfMemorySpace',   c_uint8),
        ('SizeOfIoSpace',       c_uint8),
        ('Reserved',            ARRAY(c_uint8, 6)),
    ]


class EFI_HOB_MEMORY_POOL(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
    ]


class EFI_HOB_FIRMWARE_VOLUME2(LittleEndianStructure):
    _fields_ = [
        ('Header',              EFI_HOB_GENERIC_HEADER),
        ('BaseAddress',         c_uint64),
        ('Length',              c_uint64),
        ('FvName',              EFI_GUID),
        ('FileName',            EFI_GUID)
    ]


class EFI_HOB_FIRMWARE_VOLUME3(LittleEndianStructure):
    _fields_ = [
        ('HobType',             c_uint16),
        ('HobLength',           c_uint16),
        ('Reserved',            c_uint32),
        ('BaseAddress',         c_uint64),
        ('Length',              c_uint64),
        ('AuthenticationStatus', c_uint32),
        ('ExtractedFv',         c_uint8),
        ('FvName',              EFI_GUID),
        ('FileName',            EFI_GUID),
    ]


class EFI_HOB_UEFI_CAPSULE(LittleEndianStructure):
    _fields_ = [
        ('HobType',             c_uint16),
        ('HobLength',           c_uint16),
        ('Reserved',            c_uint32),
        ('BaseAddress',         c_uint64),
        ('Length',              c_uint64),
    ]


class EfiHob:
    '''
    Parse EFI Device Paths based on the edk2 C Structures defined above.
    In the context of this class verbose means hexdump extra data.


    Attributes
    ??????
    Hob : list
        List of HOBs. Each entry contains the name, HOB type, HOB length,
        the ctype struct for the HOB, and any extra data.

    Methods
    -----------
    get_hob_by_type(hob_type)
        return string that decodes the HOBs of hob_type. If hob_type is
        None then return all HOBs.
    '''

    Hob = []
    verbose = False

    hob_dict = {
        1: EFI_HOB_HANDOFF_INFO_TABLE,
        2: EFI_HOB_MEMORY_ALLOCATION,
        3: EFI_HOB_RESOURCE_DESCRIPTOR,
        4: EFI_HOB_GUID_TYPE,
        5: EFI_HOB_FIRMWARE_VOLUME,
        6: EFI_HOB_CPU,
        7: EFI_HOB_MEMORY_POOL,
        9: EFI_HOB_FIRMWARE_VOLUME2,
        0xb: EFI_HOB_UEFI_CAPSULE,
        0xc: EFI_HOB_FIRMWARE_VOLUME3,
        0xffff: EFI_HOB_GENERIC_HEADER,
    }

    def __init__(self, file, address=None, verbose=False, count=1000):
        self._file = file
        EfiHob.verbose = verbose

        if len(EfiHob.Hob) != 0 and address is None:
            return

        if address is not None:
            hob_ptr = address
        else:
            hob_ptr = EfiConfigurationTable(file).GetConfigTable(
                '7739F24C-93D7-11D4-9A3A-0090273FC14D')

        self.read_hobs(hob_ptr)

    @ classmethod
    def __str__(cls):
        return cls.get_hob_by_type(None)

    @ classmethod
    def get_hob_by_type(cls, hob_type):
        result = ""
        for (Name, HobType, HobLen, chob, extra) in cls.Hob:
            if hob_type is not None:
                if hob_type != HobType:
                    continue

            result += f'Type: {Name:s} (0x{HobType:01x}) Len: 0x{HobLen:03x}\n'
            result += ctype_to_str(chob, '  ', ['Reserved'])
            if cls.verbose:
                if extra is not None:
                    result += hexdump(extra, '    ')

        return result

    def read_hobs(self, hob_ptr, count=1000):
        if hob_ptr is None:
            return

        try:
            for _ in range(count):  # while True
                hdr, _ = self._ctype_read_ex(EFI_HOB_GENERIC_HEADER, hob_ptr)
                if hdr.HobType == 0xffff:
                    break

                type_str = self.hob_dict.get(
                    hdr.HobType, EFI_HOB_GENERIC_HEADER)
                hob, extra = self._ctype_read_ex(
                    type_str, hob_ptr, hdr.HobLength)
                EfiHob.Hob.append(
                    (type(hob).__name__,
                     hdr.HobType,
                     hdr.HobLength,
                     hob,
                     extra))
                hob_ptr += hdr.HobLength
        except ValueError:
            pass

    def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None):
        if offset != 0:
            self._file.seek(offset)

        type_size = sizeof(ctype_struct)
        size = rsize if rsize else type_size
        data = self._file.read(size)
        cdata = ctype_struct.from_buffer(bytearray(data))

        if size > type_size:
            return cdata, data[type_size:]
        else:
            return cdata, None


class EFI_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Type',                c_uint8),
        ('SubType',             c_uint8),

        # UINT8 Length[2]
        # Cheat and use c_uint16 since we don't care about alignment
        ('Length',              c_uint16)
    ]


class PCI_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('Function',            c_uint8),
        ('Device',              c_uint8)
    ]


class PCCARD_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('FunctionNumber',      c_uint8),
    ]


class MEMMAP_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('StartingAddress',     c_uint64),
        ('EndingAddress',       c_uint64),
    ]


class VENDOR_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('Guid',                EFI_GUID),
    ]


class CONTROLLER_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('ControllerNumber',    c_uint32),
    ]


class BMC_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('InterfaceType',       c_uint8),
        ('BaseAddress',         ARRAY(c_uint8, 8)),
    ]


class BBS_BBS_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('DeviceType',          c_uint16),
        ('StatusFlag',          c_uint16)
    ]


class ACPI_HID_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('HID',                 c_uint32),
        ('UID',                 c_uint32)
    ]


class ACPI_EXTENDED_HID_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('HID',                 c_uint32),
        ('UID',                 c_uint32),
        ('CID',                 c_uint32)
    ]


class ACPI_ADR_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('ARD',                 c_uint32)
    ]


class ACPI_NVDIMM_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('NFITDeviceHandle',    c_uint32)
    ]


class ATAPI_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("PrimarySecondary",    c_uint8),
        ("SlaveMaster",         c_uint8),
        ("Lun",                 c_uint16)
    ]


class SCSI_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Pun",                 c_uint16),
        ("Lun",                 c_uint16)
    ]


class FIBRECHANNEL_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Reserved",            c_uint32),
        ("WWN",                 c_uint64),
        ("Lun",                 c_uint64)
    ]


class F1394_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Reserved",            c_uint32),
        ("Guid",                c_uint64)
    ]


class USB_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("ParentPortNumber",    c_uint8),
        ("InterfaceNumber",     c_uint8),
    ]


class I2O_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Tid",                 c_uint32)
    ]


class INFINIBAND_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("ResourceFlags",       c_uint32),
        ("PortGid",             ARRAY(c_uint8, 16)),
        ("ServiceId",           c_uint64),
        ("TargetPortId",        c_uint64),
        ("DeviceId",            c_uint64)
    ]


class UART_FLOW_CONTROL_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Guid",                EFI_GUID),
        ("FlowControlMap",      c_uint32)
    ]


class SAS_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Guid",                EFI_GUID),
        ("Reserved",            c_uint32),
        ("SasAddress",          c_uint64),
        ("Lun",                 c_uint64),
        ("DeviceTopology",      c_uint16),
        ("RelativeTargetPort",  c_uint16)
    ]


class EFI_MAC_ADDRESS(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ("Addr",             ARRAY(c_uint8, 32)),
    ]


class MAC_ADDR_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('MacAddress',          EFI_MAC_ADDRESS),
        ('IfType',              c_uint8)
    ]


class IPv4_ADDRESS(LittleEndianStructure):
    _fields_ = [
        ("Addr",             ARRAY(c_uint8, 4)),
    ]


class IPv4_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('LocalIpAddress',      IPv4_ADDRESS),
        ('RemoteIpAddress',     IPv4_ADDRESS),
        ('LocalPort',           c_uint16),
        ('RemotePort',          c_uint16),
        ('Protocol',            c_uint16),
        ('StaticIpAddress',     c_uint8),
        ('GatewayIpAddress',    IPv4_ADDRESS),
        ('SubnetMask',          IPv4_ADDRESS)
    ]


class IPv6_ADDRESS(LittleEndianStructure):
    _fields_ = [
        ("Addr",             ARRAY(c_uint8, 16)),
    ]


class IPv6_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('LocalIpAddress',      IPv6_ADDRESS),
        ('RemoteIpAddress',     IPv6_ADDRESS),
        ('LocalPort',           c_uint16),
        ('RemotePort',          c_uint16),
        ('Protocol',            c_uint16),
        ('IpAddressOrigin',     c_uint8),
        ('PrefixLength',        c_uint8),
        ('GatewayIpAddress',    IPv6_ADDRESS)
    ]


class UART_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('Reserved',            c_uint32),
        ('BaudRate',            c_uint64),
        ('DataBits',            c_uint8),
        ('Parity',              c_uint8),
        ('StopBits',            c_uint8)
    ]


class USB_CLASS_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('VendorId',            c_uint16),
        ('ProductId',           c_uint16),
        ('DeviceClass',         c_uint8),
        ('DeviceCSjblass',      c_uint8),
        ('DeviceProtocol',      c_uint8),
    ]


class USB_WWID_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('InterfaceNumber',     c_uint16),
        ('VendorId',            c_uint16),
        ('ProductId',           c_uint16),
    ]


class DEVICE_LOGICAL_UNIT_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('Lun',                 c_uint8)
    ]


class SATA_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',                      EFI_DEVICE_PATH),
        ('HBAPortNumber',               c_uint16),
        ('PortMultiplierPortNumber',    c_uint16),
        ('Lun',                         c_uint16),
    ]


class ISCSI_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',                EFI_DEVICE_PATH),
        ('NetworkProtocol',       c_uint16),
        ('LoginOption',           c_uint16),
        ('Lun',                   c_uint64),
        ('TargetPortalGroupTag',  c_uint16),
    ]


class VLAN_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("VlandId",             c_uint16)
    ]


class FIBRECHANNELEX_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Reserved",            c_uint16),
        ("WWN",                 ARRAY(c_uint8, 8)),
        ("Lun",                 ARRAY(c_uint8, 8)),
    ]


class SASEX_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("SasAddress",          ARRAY(c_uint8, 8)),
        ("Lun",                 ARRAY(c_uint8, 8)),
        ("DeviceTopology",      c_uint16),
        ("RelativeTargetPort",  c_uint16)
    ]


class NVME_NAMESPACE_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("NamespaceId",         c_uint32),
        ("NamespaceUuid",       c_uint64)
    ]


class DNS_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("IsIPv6",              c_uint8),
        ("DnsServerIp",         IPv6_ADDRESS)

    ]


class UFS_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Pun",                 c_uint8),
        ("Lun",                 c_uint8),
    ]


class SD_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("SlotNumber",          c_uint8)
    ]


class BLUETOOTH_ADDRESS(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ("Address",             ARRAY(c_uint8, 6))
    ]


class BLUETOOTH_LE_ADDRESS(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ("Format",          c_uint8),
        ("Class",           c_uint16)
    ]


class BLUETOOTH_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("BD_ADDR",             BLUETOOTH_ADDRESS)
    ]


class WIFI_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("SSId",                ARRAY(c_uint8, 32))
    ]


class EMMC_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("SlotNumber",          c_uint8)
    ]


class BLUETOOTH_LE_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("BD_ADDR",             BLUETOOTH_LE_ADDRESS)
    ]


class NVDIMM_NAMESPACE_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("Uuid",                EFI_GUID)
    ]


class REST_SERVICE_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("RESTService",         c_uint8),
        ("AccessMode",          c_uint8)
    ]


class REST_VENDOR_SERVICE_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ("RESTService",         c_uint8),
        ("AccessMode",          c_uint8),
        ("Guid",                EFI_GUID),
    ]


class HARDDRIVE_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('PartitionNumber',     c_uint32),
        ('PartitionStart',      c_uint64),
        ('PartitionSize',       c_uint64),
        ('Signature',           ARRAY(c_uint8, 16)),
        ('MBRType',             c_uint8),
        ('SignatureType',       c_uint8)
    ]


class CDROM_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('BootEntry',           c_uint32),
        ('PartitionStart',      c_uint64),
        ('PartitionSize',       c_uint64)
    ]


class MEDIA_PROTOCOL_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('Protocol',            EFI_GUID)
    ]


class MEDIA_FW_VOL_FILEPATH_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('FvFileName',          EFI_GUID)
    ]


class MEDIA_FW_VOL_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('FvName',              EFI_GUID)
    ]


class MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('Reserved',            c_uint32),
        ('StartingOffset',      c_uint64),
        ('EndingOffset',        c_uint64)
    ]


class MEDIA_RAM_DISK_DEVICE_PATH(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('Header',              EFI_DEVICE_PATH),
        ('StartingAddr',        c_uint64),
        ('EndingAddr',          c_uint64),
        ('TypeGuid',            EFI_GUID),
        ('Instance',            c_uint16)
    ]


class EfiDevicePath:
    '''
    Parse EFI Device Paths based on the edk2 C Structures defined above.
    In the context of this class verbose means hexdump extra data.


    Attributes
    ??????
    DevicePath : list
        List of devixe path instances. Each instance is a list of nodes
        for the given Device Path instance.

    Methods
    -----------
    device_path_node(address)
        return the Device Path ctype hdr, ctype, and any extra data in
        the Device Path node. This is just a single Device Path node,
        not the entire Device Path.
    device_path_node_str(address)
        return the device path node (not the entire Device Path) as a string
    '''

    DevicePath = []

    device_path_dict = {
        # ( Type, SubType ) : Device Path C typedef
        # HARDWARE_DEVICE_PATH
        (1,  1): PCI_DEVICE_PATH,
        (1,  2): PCCARD_DEVICE_PATH,
        (1,  3): MEMMAP_DEVICE_PATH,
        (1,  4): VENDOR_DEVICE_PATH,
        (1,  5): CONTROLLER_DEVICE_PATH,
        (1,  6): BMC_DEVICE_PATH,

        # ACPI_DEVICE_PATH
        (2,  1): ACPI_HID_DEVICE_PATH,
        (2,  2): ACPI_EXTENDED_HID_DEVICE_PATH,
        (2,  3): ACPI_ADR_DEVICE_PATH,
        (2,  4): ACPI_NVDIMM_DEVICE_PATH,

        # MESSAGING_DEVICE_PATH
        (3,  1): ATAPI_DEVICE_PATH,
        (3,  2): SCSI_DEVICE_PATH,
        (3,  3): FIBRECHANNEL_DEVICE_PATH,
        (3,  4): F1394_DEVICE_PATH,
        (3,  5): USB_DEVICE_PATH,
        (3,  6): I2O_DEVICE_PATH,

        (3,  9): INFINIBAND_DEVICE_PATH,
        (3, 10): VENDOR_DEVICE_PATH,
        (3, 11): MAC_ADDR_DEVICE_PATH,
        (3, 12): IPv4_DEVICE_PATH,
        (3, 13): IPv6_DEVICE_PATH,
        (3, 14): UART_DEVICE_PATH,
        (3, 15): USB_CLASS_DEVICE_PATH,
        (3, 16): USB_WWID_DEVICE_PATH,
        (3, 17): DEVICE_LOGICAL_UNIT_DEVICE_PATH,
        (3, 18): SATA_DEVICE_PATH,
        (3, 19): ISCSI_DEVICE_PATH,
        (3, 20): VLAN_DEVICE_PATH,
        (3, 21): FIBRECHANNELEX_DEVICE_PATH,
        (3, 22): SASEX_DEVICE_PATH,
        (3, 23): NVME_NAMESPACE_DEVICE_PATH,
        (3, 24): DNS_DEVICE_PATH,
        (3, 25): UFS_DEVICE_PATH,
        (3, 26): SD_DEVICE_PATH,
        (3, 27): BLUETOOTH_DEVICE_PATH,
        (3, 28): WIFI_DEVICE_PATH,
        (3, 29): EMMC_DEVICE_PATH,
        (3, 30): BLUETOOTH_LE_DEVICE_PATH,
        (3, 31): DNS_DEVICE_PATH,
        (3, 32): NVDIMM_NAMESPACE_DEVICE_PATH,

        (3, 33): REST_SERVICE_DEVICE_PATH,
        (3, 34): REST_VENDOR_SERVICE_DEVICE_PATH,

        # MEDIA_DEVICE_PATH
        (4,  1): HARDDRIVE_DEVICE_PATH,
        (4,  2): CDROM_DEVICE_PATH,
        (4,  3): VENDOR_DEVICE_PATH,
        (4,  4): EFI_DEVICE_PATH,
        (4,  5): MEDIA_PROTOCOL_DEVICE_PATH,
        (4,  6): MEDIA_FW_VOL_FILEPATH_DEVICE_PATH,
        (4,  7): MEDIA_FW_VOL_DEVICE_PATH,
        (4,  8): MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH,
        (4,  9): MEDIA_RAM_DISK_DEVICE_PATH,

        # BBS_DEVICE_PATH
        (5, 1): BBS_BBS_DEVICE_PATH,

    }

    guid_override_dict = {
        uuid.UUID('37499A9D-542F-4C89-A026-35DA142094E4'):
            UART_FLOW_CONTROL_DEVICE_PATH,
        uuid.UUID('D487DDB4-008B-11D9-AFDC-001083FFCA4D'):
            SAS_DEVICE_PATH,
    }

    def __init__(self, file, ptr=None, verbose=False, count=64):
        '''
        Convert ptr into a list of Device Path nodes. If verbose also hexdump
        extra data.
        '''
        self._file = file
        self._verbose = verbose
        if ptr is None:
            return

        try:
            instance = []
            for _ in range(count):  # while True
                hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, ptr)
                if hdr.Length < sizeof(EFI_DEVICE_PATH):
                    # Not a valid device path
                    break

                if hdr.Type == 0x7F:  # END_DEVICE_PATH_TYPE
                    self.DevicePath.append(instance)
                    if hdr.SubType == 0xFF:  # END_ENTIRE_DEVICE_PATH_SUBTYPE
                        break
                    if hdr.SubType == 0x01:  # END_INSTANCE_DEVICE_PATH_SUBTYPE
                        # start new device path instance
                        instance = []

                type_str = self.device_path_dict.get(
                    (hdr.Type, hdr.SubType), EFI_DEVICE_PATH)
                node, extra = self._ctype_read_ex(type_str, ptr, hdr.Length)
                if 'VENDOR_DEVICE_PATH' in type(node).__name__:
                    guid_type = self.guid_override_dict.get(
                                        GuidNames.to_uuid(node.Guid), None)
                    if guid_type:
                        # use the ctype associated with the GUID
                        node, extra = self._ctype_read_ex(
                                                guid_type, ptr, hdr.Length)

                instance.append((type(node).__name__, hdr.Type,
                                hdr.SubType, hdr.Length, node, extra))
                ptr += hdr.Length
        except ValueError:
            pass

    def __str__(self):
        ''' '''
        if not self.valid():
            return '<class: EfiDevicePath>'

        result = ""
        for instance in self.DevicePath:
            for (Name, Type, SubType, Length, cnode, extra) in instance:
                result += f'{Name:s} {Type:2d}:{SubType:2d} Len: {Length:3d}\n'
                result += ctype_to_str(cnode, '  ', ['Reserved'])
                if self._verbose:
                    if extra is not None:
                        result += hexdump(extra, '    ')
            result += '\n'

        return result

    def valid(self):
        return True if self.DevicePath else False

    def device_path_node(self, address):
        try:
            hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, address)
            if hdr.Length < sizeof(EFI_DEVICE_PATH):
                return None, None, None

            type_str = self.device_path_dict.get(
                (hdr.Type, hdr.SubType), EFI_DEVICE_PATH)
            cnode, extra = self._ctype_read_ex(type_str, address, hdr.Length)
            return hdr, cnode, extra
        except ValueError:
            return None, None, None

    def device_path_node_str(self, address, verbose=False):
        hdr, cnode, extra = self.device_path_node(address)
        if hdr is None:
            return ''

        cname = type(cnode).__name__
        result = f'{cname:s} {hdr.Type:2d}:{hdr.SubType:2d} '
        result += f'Len: 0x{hdr.Length:03x}\n'
        result += ctype_to_str(cnode, '  ', ['Reserved'])
        if verbose:
            if extra is not None:
                result += hexdump(extra, '    ')

        return result

    def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None):
        if offset != 0:
            self._file.seek(offset)

        type_size = sizeof(ctype_struct)
        size = rsize if rsize else type_size
        data = self._file.read(size)
        if data is None:
            return None, None

        cdata = ctype_struct.from_buffer(bytearray(data))

        if size > type_size:
            return cdata, data[type_size:]
        else:
            return cdata, None


class EfiConfigurationTable:
    '''
    A class to abstract EFI Configuration Tables from gST->ConfigurationTable
    and gST->NumberOfTableEntries. Pass in the gST pointer from EFI,
    likely you need to look up this address after you have loaded symbols

    Attributes
    ??????
    ConfigurationTableDict : dictionary
        dictionary of EFI Configuration Table entries

    Methods
    -----------
    GetConfigTable(uuid)
        pass in VendorGuid and return VendorTable from EFI System Table
    DebugImageInfo(table)
        return tuple of load address and size of PE/COFF images
    '''

    ConfigurationTableDict = {}

    def __init__(self, file, gST_addr=None):
        self._file = file
        if gST_addr is None:
            # ToDo add code to search for gST via EFI_SYSTEM_TABLE_POINTER
            return

        gST = self._ctype_read(EFI_SYSTEM_TABLE, gST_addr)
        self.read_efi_config_table(gST.NumberOfTableEntries,
                                   gST.ConfigurationTable,
                                   self._ctype_read)

    @ classmethod
    def __str__(cls):
        '''return EFI_CONFIGURATION_TABLE entries as a string'''
        result = ""
        for key, value in cls.ConfigurationTableDict.items():
            result += f'{GuidNames().to_name(key):>37s}: '
            result += f'VendorTable = 0x{value:08x}\n'

        return result

    def _ctype_read(self, ctype_struct, offset=0):
        '''ctype worker function to read data'''
        if offset != 0:
            self._file.seek(offset)

        data = self._file.read(sizeof(ctype_struct))
        return ctype_struct.from_buffer(bytearray(data))

    @ classmethod
    def read_efi_config_table(cls, table_cnt, table_ptr, ctype_read):
        '''Create a dictionary of EFI Configuration table entries'''
        EmptryTables = EFI_CONFIGURATION_TABLE * table_cnt
        Tables = ctype_read(EmptryTables, table_ptr)
        for i in range(table_cnt):
            cls.ConfigurationTableDict[str(GuidNames.to_uuid(
                Tables[i].VendorGuid)).upper()] = Tables[i].VendorTable

        return cls.ConfigurationTableDict

    def GetConfigTable(self, uuid):
        ''' Return VendorTable for VendorGuid (uuid.UUID) or None'''
        return self.ConfigurationTableDict.get(uuid.upper())

    def DebugImageInfo(self, table=None):
        '''
        Walk the debug image info table to find the LoadedImage protocols
        for all the loaded PE/COFF images and return a list of load address
        and image size.
        '''
        ImageLoad = []

        if table is None:
            table = self.GetConfigTable('49152e77-1ada-4764-b7a2-7afefed95e8b')

        DbgInfoHdr = self._ctype_read(EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, table)
        NormalImageArray = EFI_DEBUG_IMAGE_INFO * DbgInfoHdr.TableSize
        NormalImageArray = self._ctype_read(
            NormalImageArray, DbgInfoHdr.EfiDebugImageInfoTable)
        for i in range(DbgInfoHdr.TableSize):
            ImageInfo = self._ctype_read(
                EFI_DEBUG_IMAGE_INFO_NORMAL, NormalImageArray[i].NormalImage)
            LoadedImage = self._ctype_read(
                EFI_LOADED_IMAGE_PROTOCOL,
                ImageInfo.LoadedImageProtocolInstance)
            ImageLoad.append((LoadedImage.ImageBase, LoadedImage.ImageSize))

        return ImageLoad


class PeTeImage:
    '''
    A class to abstract PE/COFF or TE image processing via passing in a
    Python file like object. If you pass in an address the PE/COFF is parsed,
    if you pass in NULL for an address then you get a class instance you can
    use to search memory for a PE/COFF hader given a pc value.

    Attributes
    ??????
    LoadAddress : int
        Load address of the PE/COFF image
    AddressOfEntryPoint : int
        Address of the Entry point of the PE/COFF image
    TextAddress : int
        Start of the PE/COFF text section
    DataAddress : int
        Start of the PE/COFF data section
    CodeViewPdb : str
        File name of the symbols file
    CodeViewUuid : uuid:UUID
        GUID for "RSDS" Debug Directory entry, or Mach-O UUID for "MTOC"

    Methods
    -----------
    pcToPeCoff(address, step, max_range, rom_range)
        Given an address(pc) find the PE/COFF image it is in
    sections_to_str()
        return a string giving info for all the PE/COFF sections
    '''

    def __init__(self, file, address=0):
        self._file = file

        # book keeping, but public
        self.PeHdr = None
        self.TeHdr = None
        self.Machine = None
        self.Subsystem = None
        self.CodeViewSig = None
        self.e_lfanew = 0
        self.NumberOfSections = 0
        self.Sections = None

        # Things debuggers may want to know
        self.LoadAddress = 0 if address is None else address
        self.EndLoadAddress = 0
        self.AddressOfEntryPoint = 0
        self.TextAddress = 0
        self.DataAddress = 0
        self.CodeViewPdb = None
        self.CodeViewUuid = None
        self.TeAdjust = 0

        self.dir_name = {
            0: 'Export Table',
            1: 'Import Table',
            2: 'Resource Table',
            3: 'Exception Table',
            4: 'Certificate Table',
            5: 'Relocation Table',
            6: 'Debug',
            7: 'Architecture',
            8: 'Global Ptr',
            9: 'TLS Table',
            10: 'Load Config Table',
            11: 'Bound Import',
            12: 'IAT',
            13: 'Delay Import Descriptor',
            14: 'CLR Runtime Header',
            15: 'Reserved',
        }

        if address is not None:
            if self.maybe():
                self.parse()

    def __str__(self):
        if self.PeHdr is None and self.TeHdr is None:
            # no PE/COFF header found
            return "<class: PeTeImage>"

        if self.CodeViewPdb:
            pdb = f'{self.Machine}`{self.CodeViewPdb}'
        else:
            pdb = 'No Debug Info:'

        if self.CodeViewUuid:
            guid = f'{self.CodeViewUuid}:'
        else:
            guid = ''

        slide = f'slide = {self.TeAdjust:d} ' if self.TeAdjust != 0 else ' '
        res = guid + f'{pdb} load = 0x{self.LoadAddress:08x} ' + slide
        return res

    def _seek(self, offset):
        """
        seek() relative to start of PE/COFF (TE) image
        """
        self._file.seek(self.LoadAddress + offset)

    def _read_offset(self, size, offset=None):
        """
        read() relative to start of PE/COFF (TE) image
        if offset is not None then seek() before the read
        """
        if offset is not None:
            self._seek(offset)

        return self._file.read(size)

    def _read_ctype(self, ctype_struct, offset=None):
        data = self._read_offset(sizeof(ctype_struct), offset)
        return ctype_struct.from_buffer(bytearray(data), 0)

    def _unsigned(self, i):
        """return a 32-bit unsigned int (UINT32) """
        return int.from_bytes(i, byteorder='little', signed=False)

    def pcToPeCoff(self,
                   address,
                   step=None,
                   max_range=None,
                   rom_range=[0xFE800000, 0xFFFFFFFF]):
        """
        Given an address search backwards for PE/COFF (TE) header
        For DXE 4K is probably OK
        For PEI you might have to search every 4 bytes.
        """
        if step is None:
            step = 0x1000

        if max_range is None:
            max_range = 0x200000

        if address in range(*rom_range):
            # The XIP code in the ROM ends up 4 byte aligned.
            step = 4
            max_range = min(max_range, 0x100000)

        # Align address to page boundary for memory image search.
        address = address & ~(step-1)
        # Search every step backward
        offset_range = list(range(0, min(max_range, address), step))
        for offset in offset_range:
            if self.maybe(address - offset):
                if self.parse():
                    return True

        return False

    def maybe(self, offset=None):
        """Probe to see if this offset is likely a PE/COFF or TE file """
        self.LoadAddress = 0
        e_magic = self._read_offset(2, offset)
        header_ok = e_magic == b'MZ' or e_magic == b'VZ'
        if offset is not None and header_ok:
            self.LoadAddress = offset
        return header_ok

    def parse(self):
        """Parse PE/COFF (TE) debug directory entry """
        DosHdr = self._read_ctype(EFI_IMAGE_DOS_HEADER, 0)
        if DosHdr.e_magic == self._unsigned(b'VZ'):
            # TE image
            self.TeHdr = self._read_ctype(EFI_TE_IMAGE_HEADER, 0)

            self.TeAdjust = sizeof(self.TeHdr) - self.TeHdr.StrippedSize
            self.Machine = image_machine_dict.get(self.TeHdr.Machine, None)
            self.Subsystem = self.TeHdr.Subsystem
            self.AddressOfEntryPoint = self.TeHdr.AddressOfEntryPoint

            debug_dir_size = self.TeHdr.DataDirectoryDebug.Size
            debug_dir_offset = (self.TeAdjust +
                                self.TeHdr.DataDirectoryDebug.VirtualAddress)
        else:
            if DosHdr.e_magic == self._unsigned(b'MZ'):
                self.e_lfanew = DosHdr.e_lfanew
            else:
                self.e_lfanew = 0

            self.PeHdr = self._read_ctype(
                EFI_IMAGE_NT_HEADERS64, self.e_lfanew)
            if self.PeHdr.Signature != self._unsigned(b'PE\0\0'):
                return False

            if self.PeHdr.OptionalHeader.Magic == \
                    EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
                self.PeHdr = self._read_ctype(
                    EFI_IMAGE_NT_HEADERS32, self.e_lfanew)

            if self.PeHdr.OptionalHeader.NumberOfRvaAndSizes <= \
                    DIRECTORY_DEBUG:
                return False

            self.Machine = image_machine_dict.get(
                self.PeHdr.FileHeader.Machine, None)
            self.Subsystem = self.PeHdr.OptionalHeader.Subsystem
            self.AddressOfEntryPoint = \
                self.PeHdr.OptionalHeader.AddressOfEntryPoint
            self.TeAdjust = 0

            debug_dir_size = self.PeHdr.OptionalHeader.DataDirectory[
                DIRECTORY_DEBUG].Size
            debug_dir_offset = self.PeHdr.OptionalHeader.DataDirectory[
                DIRECTORY_DEBUG].VirtualAddress

        if self.Machine is None or self.Subsystem not in [0, 10, 11, 12]:
            return False

        self.AddressOfEntryPoint += self.LoadAddress

        self.sections()
        return self.processDebugDirEntry(debug_dir_offset, debug_dir_size)

    def sections(self):
        '''Parse the PE/COFF (TE) section table'''
        if self.Sections is not None:
            return
        elif self.TeHdr is not None:
            self.NumberOfSections = self.TeHdr.NumberOfSections
            offset = sizeof(EFI_TE_IMAGE_HEADER)
        elif self.PeHdr is not None:
            self.NumberOfSections = self.PeHdr.FileHeader.NumberOfSections
            offset = sizeof(c_uint32) + \
                sizeof(EFI_IMAGE_FILE_HEADER)
            offset += self.PeHdr.FileHeader.SizeOfOptionalHeader
            offset += self.e_lfanew
        else:
            return

        self.Sections = EFI_IMAGE_SECTION_HEADER * self.NumberOfSections
        self.Sections = self._read_ctype(self.Sections, offset)

        for i in range(self.NumberOfSections):
            name = str(self.Sections[i].Name, 'ascii', 'ignore')
            addr = self.Sections[i].VirtualAddress
            addr += self.LoadAddress + self.TeAdjust
            if name == '.text':
                self.TextAddress = addr
            elif name == '.data':
                self.DataAddress = addr

            end_addr = addr + self.Sections[i].VirtualSize - 1
            if end_addr > self.EndLoadAddress:
                self.EndLoadAddress = end_addr

    def sections_to_str(self):
        # return text summary of sections
        # name virt addr (virt size) flags:Characteristics
        result = ''
        for i in range(self.NumberOfSections):
            name = str(self.Sections[i].Name, 'ascii', 'ignore')
            result += f'{name:8s} '
            result += f'0x{self.Sections[i].VirtualAddress:08X} '
            result += f'(0x{self.Sections[i].VirtualSize:05X}) '
            result += f'flags:0x{self.Sections[i].Characteristics:08X}\n'

        return result

    def directory_to_str(self):
        result = ''
        if self.TeHdr:
            debug_size = self.TeHdr.DataDirectoryDebug.Size
            if debug_size > 0:
                debug_offset = (self.TeAdjust
                                + self.TeHdr.DataDirectoryDebug.VirtualAddress)
                result += f"Debug 0x{debug_offset:08X} 0x{debug_size}\n"

            relocation_size = self.TeHdr.DataDirectoryBaseReloc.Size
            if relocation_size > 0:
                relocation_offset = (
                    self.TeAdjust
                    + self.TeHdr.DataDirectoryBaseReloc.VirtualAddress)
                result += f'Relocation 0x{relocation_offset:08X} '
                result += f' 0x{relocation_size}\n'

        elif self.PeHdr:
            for i in range(self.PeHdr.OptionalHeader.NumberOfRvaAndSizes):
                size = self.PeHdr.OptionalHeader.DataDirectory[i].Size
                if size == 0:
                    continue

                virt_addr = self.PeHdr.OptionalHeader.DataDirectory[
                    i].VirtualAddress
                name = self.dir_name.get(i, '?')
                result += f'{name:s} 0x{virt_addr:08X} 0x{size:X}\n'

        return result

    def processDebugDirEntry(self, virt_address, virt_size):
        """Process PE/COFF Debug Directory Entry"""
        if (virt_address == 0 or
                virt_size < sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)):
            return False

        data = bytearray(self._read_offset(virt_size, virt_address))
        for offset in range(0,
                            virt_size,
                            sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)):
            DirectoryEntry = EFI_IMAGE_DEBUG_DIRECTORY_ENTRY.from_buffer(
                data[offset:])
            if DirectoryEntry.Type != 2:
                continue

            entry = self._read_offset(
                DirectoryEntry.SizeOfData, DirectoryEntry.RVA + self.TeAdjust)
            self.CodeViewSig = entry[:4]
            if self.CodeViewSig == b'MTOC':
                self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16])
                PdbOffset = 20
            elif self.CodeViewSig == b'RSDS':
                self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16])
                PdbOffset = 24
            elif self.CodeViewSig == b'NB10':
                PdbOffset = 16
            else:
                continue

            # can't find documentation about Pdb string encoding?
            # guessing utf-8 since that will match file systems in macOS
            # and Linux Windows is UTF-16, or ANSI adjusted for local.
            # We might need a different value for Windows here?
            self.CodeViewPdb = entry[PdbOffset:].split(b'\x00')[
                0].decode('utf-8')
            return True
        return False


def main():
    '''Process arguments as PE/COFF files'''
    for fname in sys.argv[1:]:
        with open(fname, 'rb') as f:
            image = PeTeImage(f)
            print(image)
            res = f'EntryPoint = 0x{image.AddressOfEntryPoint:08x}  '
            res += f'TextAddress = 0x{image.TextAddress:08x} '
            res += f'DataAddress = 0x{image.DataAddress:08x}'
            print(res)
            print(image.sections_to_str())
            print('Data Directories:')
            print(image.directory_to_str())


if __name__ == "__main__":
    main()
