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