* [PATCH v2 0/3] BaseTools: Add support for gdb and lldb @ 2022-03-21 20:20 Rebecca Cran 2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran ` (3 more replies) 0 siblings, 4 replies; 10+ messages in thread From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw) To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng, Liming Gao, Yuwei Chen Cc: Rebecca Cran, Andrew Fish This patch set adds debugging support for gdb and lldb. It also adds generic debugging classes that use a file like object to make it easy to import into any debugger that supports Python. Changes from v1 to v2: - Moved scripts from the root of the repo into BaseTools/Scripts. - Fixed typo of "RISCV" as "RISKV". Testing: - Tested gdb on Ubuntu and lldb on macOS for IA32 and X64. - Tested gdb on openSUSE for AARCH64. Rebecca Cran (3): BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++ BaseTools/Scripts/efi_gdb.py | 918 ++++++++ BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++ 3 files changed, 4147 insertions(+) create mode 100755 BaseTools/Scripts/efi_debugging.py create mode 100755 BaseTools/Scripts/efi_gdb.py create mode 100755 BaseTools/Scripts/efi_lldb.py -- 2.34.1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes 2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran @ 2022-03-21 20:20 ` Rebecca Cran 2022-03-26 14:04 ` Bob Feng 2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran ` (2 subsequent siblings) 3 siblings, 1 reply; 10+ messages in thread From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw) To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng, Liming Gao, Yuwei Chen Cc: Rebecca Cran, Andrew Fish Add Scripts/efi_debugging.py to provide debugger agnostic debugging utility Python classes. Cc: Leif Lindholm <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> --- BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++ 1 file changed, 2185 insertions(+) diff --git a/BaseTools/Scripts/efi_debugging.py b/BaseTools/Scripts/efi_debugging.py new file mode 100755 index 000000000000..af975f4e6ceb --- /dev/null +++ b/BaseTools/Scripts/efi_debugging.py @@ -0,0 +1,2185 @@ +#!/usr/bin/python3 +''' +Copyright (c) Apple Inc. 2021 +SPDX-License-Identifier: BSD-2-Clause-Patent + +Class that abstracts PE/COFF debug info parsing via a Python file like +object. You can port this code into an arbitrary debugger by invoking +the classes and passing in a file like object that abstracts the debugger +reading memory. + +If you run this file directly it will parse the passed in PE/COFF files +for debug info: +python3 ./efi_pefcoff.py DxeCore.efi +IA32`<path...>/DxeCore.dll load = 0x00000000 +EntryPoint = 0x000030d2 TextAddress = 0x00000240 DataAddress = 0x000042c0 +.text 0x00000240 (0x04080) flags:0x60000020 +.data 0x000042C0 (0x001C0) flags:0xC0000040 +.reloc 0x00004480 (0x00240) flags:0x42000040 + +Note: PeCoffClass uses virtual addresses and not file offsets. + It needs to work when images are loaded into memory. + as long as virtual address map to file addresses this + code can process binary files. + +Note: This file can also contain generic worker functions (like GuidNames) + that abstract debugger agnostic services to the debugger. + +This file should never import debugger specific modules. +''' + +import sys +import os +import uuid +import struct +import re +from ctypes import c_char, c_uint8, c_uint16, c_uint32, c_uint64, c_void_p +from ctypes import ARRAY, sizeof +from ctypes import Structure, LittleEndianStructure + +# +# The empty LittleEndianStructure must have _fields_ assigned prior to use or +# sizeof(). Anything that is size UINTN may need to get adjusted. +# +# The issue is ctypes matches our local machine, not the machine we are +# trying to debug. Call patch_ctypes() passing in the byte width from the +# debugger python to make sure you are in sync. +# +# Splitting out the _field_ from the Structure (LittleEndianStructure) class +# allows it to be patched. +# + + +class EFI_LOADED_IMAGE_PROTOCOL(LittleEndianStructure): + pass + + +EFI_LOADED_IMAGE_PROTOCOL_fields_ = [ + ('Revision', c_uint32), + ('ParentHandle', c_void_p), + ('SystemTable', c_void_p), + ('DeviceHandle', c_void_p), + ('FilePath', c_void_p), + ('Reserved', c_void_p), + ('LoadOptionsSize', c_uint32), + ('LoadOptions', c_void_p), + ('ImageBase', c_void_p), + ('ImageSize', c_uint64), + ('ImageCodeType', c_uint32), + ('ImageDataType', c_uint32), + ('Unload', c_void_p), +] + + +class EFI_GUID(LittleEndianStructure): + _fields_ = [ + ('Data1', c_uint32), + ('Data2', c_uint16), + ('Data3', c_uint16), + ('Data4', ARRAY(c_uint8, 8)) + ] + + +class EFI_SYSTEM_TABLE_POINTER(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint64), + ('EfiSystemTableBase', c_uint64), + ('Crc32', c_uint32) + ] + + +class EFI_DEBUG_IMAGE_INFO_NORMAL(LittleEndianStructure): + pass + + +EFI_DEBUG_IMAGE_INFO_NORMAL_fields_ = [ + ('ImageInfoType', c_uint32), + ('LoadedImageProtocolInstance', c_void_p), + ('ImageHandle', c_void_p) +] + + +class EFI_DEBUG_IMAGE_INFO(LittleEndianStructure): + pass + + +EFI_DEBUG_IMAGE_INFO_fields_ = [ + ('NormalImage', c_void_p), +] + + +class EFI_DEBUG_IMAGE_INFO_TABLE_HEADER(LittleEndianStructure): + pass + + +EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_ = [ + ('UpdateStatus', c_uint32), + ('TableSize', c_uint32), + ('EfiDebugImageInfoTable', c_void_p), +] + + +class EFI_TABLE_HEADER(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint64), + ('Revision', c_uint32), + ('HeaderSize', c_uint32), + ('CRC32', c_uint32), + ('Reserved', c_uint32), + ] + + +class EFI_CONFIGURATION_TABLE(LittleEndianStructure): + pass + + +EFI_CONFIGURATION_TABLE_fields_ = [ + ('VendorGuid', EFI_GUID), + ('VendorTable', c_void_p) +] + + +class EFI_SYSTEM_TABLE(LittleEndianStructure): + pass + + +EFI_SYSTEM_TABLE_fields_ = [ + ('Hdr', EFI_TABLE_HEADER), + ('FirmwareVendor', c_void_p), + ('FirmwareRevision', c_uint32), + ('ConsoleInHandle', c_void_p), + ('ConIn', c_void_p), + ('ConsoleOutHandle', c_void_p), + ('ConOut', c_void_p), + ('StandardErrHandle', c_void_p), + ('StdErr', c_void_p), + ('RuntimeService', c_void_p), + ('BootService', c_void_p), + ('NumberOfTableEntries', c_void_p), + ('ConfigurationTable', c_void_p), +] + + +class EFI_IMAGE_DATA_DIRECTORY(LittleEndianStructure): + _fields_ = [ + ('VirtualAddress', c_uint32), + ('Size', c_uint32) + ] + + +class EFI_TE_IMAGE_HEADER(LittleEndianStructure): + _fields_ = [ + ('Signature', ARRAY(c_char, 2)), + ('Machine', c_uint16), + ('NumberOfSections', c_uint8), + ('Subsystem', c_uint8), + ('StrippedSize', c_uint16), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ('ImageBase', c_uint64), + ('DataDirectoryBaseReloc', EFI_IMAGE_DATA_DIRECTORY), + ('DataDirectoryDebug', EFI_IMAGE_DATA_DIRECTORY) + ] + + +class EFI_IMAGE_DOS_HEADER(LittleEndianStructure): + _fields_ = [ + ('e_magic', c_uint16), + ('e_cblp', c_uint16), + ('e_cp', c_uint16), + ('e_crlc', c_uint16), + ('e_cparhdr', c_uint16), + ('e_minalloc', c_uint16), + ('e_maxalloc', c_uint16), + ('e_ss', c_uint16), + ('e_sp', c_uint16), + ('e_csum', c_uint16), + ('e_ip', c_uint16), + ('e_cs', c_uint16), + ('e_lfarlc', c_uint16), + ('e_ovno', c_uint16), + ('e_res', ARRAY(c_uint16, 4)), + ('e_oemid', c_uint16), + ('e_oeminfo', c_uint16), + ('e_res2', ARRAY(c_uint16, 10)), + ('e_lfanew', c_uint16) + ] + + +class EFI_IMAGE_FILE_HEADER(LittleEndianStructure): + _fields_ = [ + ('Machine', c_uint16), + ('NumberOfSections', c_uint16), + ('TimeDateStamp', c_uint32), + ('PointerToSymbolTable', c_uint32), + ('NumberOfSymbols', c_uint32), + ('SizeOfOptionalHeader', c_uint16), + ('Characteristics', c_uint16) + ] + + +class EFI_IMAGE_OPTIONAL_HEADER32(LittleEndianStructure): + _fields_ = [ + ('Magic', c_uint16), + ('MajorLinkerVersion', c_uint8), + ('MinorLinkerVersion', c_uint8), + ('SizeOfCode', c_uint32), + ('SizeOfInitializedData', c_uint32), + ('SizeOfUninitializedData', c_uint32), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ('BaseOfData', c_uint32), + ('ImageBase', c_uint32), + ('SectionAlignment', c_uint32), + ('FileAlignment', c_uint32), + ('MajorOperatingSystemVersion', c_uint16), + ('MinorOperatingSystemVersion', c_uint16), + ('MajorImageVersion', c_uint16), + ('MinorImageVersion', c_uint16), + ('MajorSubsystemVersion', c_uint16), + ('MinorSubsystemVersion', c_uint16), + ('Win32VersionValue', c_uint32), + ('SizeOfImage', c_uint32), + ('SizeOfHeaders', c_uint32), + ('CheckSum', c_uint32), + ('Subsystem', c_uint16), + ('DllCharacteristics', c_uint16), + ('SizeOfStackReserve', c_uint32), + ('SizeOfStackCommit', c_uint32), + ('SizeOfHeapReserve', c_uint32), + ('SizeOfHeapCommit', c_uint32), + ('LoaderFlags', c_uint32), + ('NumberOfRvaAndSizes', c_uint32), + ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) + ] + + +class EFI_IMAGE_NT_HEADERS32(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint32), + ('FileHeader', EFI_IMAGE_FILE_HEADER), + ('OptionalHeader', EFI_IMAGE_OPTIONAL_HEADER32) + ] + + +class EFI_IMAGE_OPTIONAL_HEADER64(LittleEndianStructure): + _fields_ = [ + ('Magic', c_uint16), + ('MajorLinkerVersion', c_uint8), + ('MinorLinkerVersion', c_uint8), + ('SizeOfCode', c_uint32), + ('SizeOfInitializedData', c_uint32), + ('SizeOfUninitializedData', c_uint32), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ('BaseOfData', c_uint32), + ('ImageBase', c_uint32), + ('SectionAlignment', c_uint32), + ('FileAlignment', c_uint32), + ('MajorOperatingSystemVersion', c_uint16), + ('MinorOperatingSystemVersion', c_uint16), + ('MajorImageVersion', c_uint16), + ('MinorImageVersion', c_uint16), + ('MajorSubsystemVersion', c_uint16), + ('MinorSubsystemVersion', c_uint16), + ('Win32VersionValue', c_uint32), + ('SizeOfImage', c_uint32), + ('SizeOfHeaders', c_uint32), + ('CheckSum', c_uint32), + ('Subsystem', c_uint16), + ('DllCharacteristics', c_uint16), + ('SizeOfStackReserve', c_uint64), + ('SizeOfStackCommit', c_uint64), + ('SizeOfHeapReserve', c_uint64), + ('SizeOfHeapCommit', c_uint64), + ('LoaderFlags', c_uint32), + ('NumberOfRvaAndSizes', c_uint32), + ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) + ] + + +class EFI_IMAGE_NT_HEADERS64(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint32), + ('FileHeader', EFI_IMAGE_FILE_HEADER), + ('OptionalHeader', EFI_IMAGE_OPTIONAL_HEADER64) + ] + + +class EFI_IMAGE_DEBUG_DIRECTORY_ENTRY(LittleEndianStructure): + _fields_ = [ + ('Characteristics', c_uint32), + ('TimeDateStamp', c_uint32), + ('MajorVersion', c_uint16), + ('MinorVersion', c_uint16), + ('Type', c_uint32), + ('SizeOfData', c_uint32), + ('RVA', c_uint32), + ('FileOffset', c_uint32), + ] + + +class EFI_IMAGE_SECTION_HEADER(LittleEndianStructure): + _fields_ = [ + ('Name', ARRAY(c_char, 8)), + ('VirtualSize', c_uint32), + ('VirtualAddress', c_uint32), + ('SizeOfRawData', c_uint32), + ('PointerToRawData', c_uint32), + ('PointerToRelocations', c_uint32), + ('PointerToLinenumbers', c_uint32), + ('NumberOfRelocations', c_uint16), + ('NumberOfLinenumbers', c_uint16), + ('Characteristics', c_uint32), + ] + + +EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b +EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b + +DIRECTORY_DEBUG = 6 + + +image_machine_dict = { + 0x014c: "IA32", + 0x0200: "IPF", + 0x0EBC: "EBC", + 0x8664: "X64", + 0x01c2: "ARM", + 0xAA64: "AArch64", + 0x5032: "RISC32", + 0x5064: "RISC64", + 0x5128: "RISCV128", +} + + +def patch_void_p_to_ctype(patch_type, to_patch): + '''Optionally patch c_void_p in the Structure._fields_''' + if patch_type is None: + return to_patch + + result = [] + for name, c_type in to_patch: + if type(c_type) == type(c_void_p): + result.append((name, c_uint32)) + else: + result.append((name, c_type)) + return result + + +def patch_ctypes(pointer_width=8): + ''' + Pass in the pointer width of the system being debugged. If it is not + the same as c_void_p then patch the _fields_ with the correct type. + For any ctypes Structure that has a c_void_p this function needs to be + called prior to use or sizeof() to initialize _fields_. + ''' + + if sizeof(c_void_p) == pointer_width: + patch_type = None + elif pointer_width == 16: + assert False + elif pointer_width == 8: + patch_type = c_uint64 + elif pointer_width == 4: + patch_type = c_uint32 + else: + raise Exception(f'ERROR: Unkown pointer_width = {pointer_width}') + + # If you add a ctypes Structure class with a c_void_p you need to add + # it to this list. Note: you should use c_void_p for UINTN values. + EFI_LOADED_IMAGE_PROTOCOL._fields_ = patch_void_p_to_ctype( + patch_type, EFI_LOADED_IMAGE_PROTOCOL_fields_) + EFI_DEBUG_IMAGE_INFO_NORMAL._fields_ = patch_void_p_to_ctype( + patch_type, EFI_DEBUG_IMAGE_INFO_NORMAL_fields_) + EFI_DEBUG_IMAGE_INFO._fields_ = patch_void_p_to_ctype( + patch_type, EFI_DEBUG_IMAGE_INFO_fields_) + EFI_DEBUG_IMAGE_INFO_TABLE_HEADER._fields_ = patch_void_p_to_ctype( + patch_type, EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_) + EFI_CONFIGURATION_TABLE._fields_ = patch_void_p_to_ctype( + patch_type, EFI_CONFIGURATION_TABLE_fields_) + EFI_SYSTEM_TABLE._fields_ = patch_void_p_to_ctype( + patch_type, EFI_SYSTEM_TABLE_fields_) + + # patch up anything else that needs to know pointer_width + EfiStatusClass(pointer_width) + + +def ctype_to_str(ctype, indent='', hide_list=[]): + ''' + Given a ctype object print out as a string by walking the _fields_ + in the cstring Class + ''' + result = '' + for field in ctype._fields_: + attr = getattr(ctype, field[0]) + tname = type(attr).__name__ + if field[0] in hide_list: + continue + + result += indent + f'{field[0]} = ' + if tname == 'EFI_GUID': + result += GuidNames.to_name(GuidNames.to_uuid(attr)) + '\n' + elif issubclass(type(attr), Structure): + result += f'{tname}\n' + \ + ctype_to_str(attr, indent + ' ', hide_list) + elif isinstance(attr, int): + result += f'0x{attr:x}\n' + else: + result += f'{attr}\n' + + return result + + +def hexline(addr, data): + hexstr = '' + printable = '' + for i in range(0, len(data)): + hexstr += f'{data[i]:02x} ' + printable += chr(data[i]) if data[i] > 0x20 and data[i] < 0x7f else '.' + return f'{addr:04x} {hexstr:48s} |{printable:s}|' + + +def hexdump(data, indent=''): + if not isinstance(data, bytearray): + data = bytearray(data) + + result = '' + for i in range(0, len(data), 16): + result += indent + hexline(i, data[i:i+16]) + '\n' + return result + + +class EfiTpl: + ''' Return string for EFI_TPL''' + + def __init__(self, tpl): + self.tpl = tpl + + def __str__(self): + if self.tpl < 4: + result = f'{self.tpl:d}' + elif self.tpl < 8: + result = "TPL_APPLICATION" + if self.tpl - 4 > 0: + result += f' + {self.tpl - 4:d}' + elif self.tpl < 16: + result = "TPL_CALLBACK" + if self.tpl - 8 > 0: + result += f' + {self.tpl - 8:d}' + elif self.tpl < 31: + result = "TPL_NOTIFY" + if self.tpl - 16 > 0: + result += f' + {self.tpl - 16:d}' + elif self.tpl == 31: + result = "TPL_HIGH_LEVEL" + else: + result = f'Invalid TPL = {self.tpl:d}' + return result + + +class EfiBootMode: + ''' + Class to return human readable string for EFI_BOOT_MODE + + Methods + ----------- + to_str(boot_mode, default) + return string for boot_mode, and return default if there is not a + match. + ''' + + EFI_BOOT_MODE_dict = { + 0x00: "BOOT_WITH_FULL_CONFIGURATION", + 0x01: "BOOT_WITH_MINIMAL_CONFIGURATION", + 0x02: "BOOT_ASSUMING_NO_CONFIGURATION_CHANGES", + 0x03: "BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS", + 0x04: "BOOT_WITH_DEFAULT_SETTINGS", + 0x05: "BOOT_ON_S4_RESUME", + 0x06: "BOOT_ON_S5_RESUME", + 0x07: "BOOT_WITH_MFG_MODE_SETTINGS", + 0x10: "BOOT_ON_S2_RESUME", + 0x11: "BOOT_ON_S3_RESUME", + 0x12: "BOOT_ON_FLASH_UPDATE", + 0x20: "BOOT_IN_RECOVERY_MODE", + } + + def __init__(self, boot_mode): + self._boot_mode = boot_mode + + def __str__(self): + return self.to_str(self._boot_mode) + + @classmethod + def to_str(cls, boot_mode, default=''): + return cls.EFI_BOOT_MODE_dict.get(boot_mode, default) + + +class EfiStatusClass: + ''' + Class to decode EFI_STATUS to a human readable string. You need to + pass in pointer_width to get the corret value since the EFI_STATUS + code values are different based on the sizeof UINTN. The default is + sizeof(UINTN) == 8. + + Attributes + —————— + _dict_ : dictionary + dictionary of EFI_STATUS that has beed updated to match + pointer_width. + + Methods + ----------- + patch_dictionary(pointer_width) + + to_str(status, default) + ''' + + _dict_ = {} + _EFI_STATUS_UINT32_dict = { + 0: "Success", + 1: "Warning Unknown Glyph", + 2: "Warning Delete Failure", + 3: "Warning Write Failure", + 4: "Warning Buffer Too Small", + 5: "Warning Stale Data", + 6: "Warngin File System", + (0x20000000 | 0): "Warning interrupt source pending", + (0x20000000 | 1): "Warning interrupt source quiesced", + + (0x80000000 | 1): "Load Error", + (0x80000000 | 2): "Invalid Parameter", + (0x80000000 | 3): "Unsupported", + (0x80000000 | 4): "Bad Buffer Size", + (0x80000000 | 5): "Buffer Too Small", + (0x80000000 | 6): "Not Ready", + (0x80000000 | 7): "Device Error", + (0x80000000 | 8): "Write Protected", + (0x80000000 | 9): "Out of Resources", + (0x80000000 | 10): "Volume Corrupt", + (0x80000000 | 11): "Volume Full", + (0x80000000 | 12): "No Media", + (0x80000000 | 13): "Media changed", + (0x80000000 | 14): "Not Found", + (0x80000000 | 15): "Access Denied", + (0x80000000 | 16): "No Response", + (0x80000000 | 17): "No mapping", + (0x80000000 | 18): "Time out", + (0x80000000 | 19): "Not started", + (0x80000000 | 20): "Already started", + (0x80000000 | 21): "Aborted", + (0x80000000 | 22): "ICMP Error", + (0x80000000 | 23): "TFTP Error", + (0x80000000 | 24): "Protocol Error", + (0x80000000 | 25): "Incompatible Version", + (0x80000000 | 26): "Security Violation", + (0x80000000 | 27): "CRC Error", + (0x80000000 | 28): "End of Media", + (0x80000000 | 31): "End of File", + (0x80000000 | 32): "Invalid Language", + (0x80000000 | 33): "Compromised Data", + (0x80000000 | 35): "HTTP Error", + + (0xA0000000 | 0): "Interrupt Pending", + } + + def __init__(self, status=None, pointer_width=8): + self.status = status + # this will convert to 64-bit version if needed + self.patch_dictionary(pointer_width) + + def __str__(self): + return self.to_str(self.status) + + @classmethod + def to_str(cls, status, default=''): + return cls._dict_.get(status, default) + + @classmethod + def patch_dictionary(cls, pointer_width): + '''Patch UINTN upper bits like values ''' + + if cls._dict_: + # only patch the class variable once + return False + + if pointer_width == 4: + cls._dict = cls._EFI_STATUS_UINT32_dict + elif pointer_width == 8: + for key, value in cls._EFI_STATUS_UINT32_dict.items(): + mask = (key & 0xE0000000) << 32 + new_key = (key & 0x1FFFFFFF) | mask + cls._dict_[new_key] = value + return True + else: + return False + + +class GuidNames: + ''' + Class to expose the C names of EFI_GUID's. The _dict_ starts with + common EFI System Table entry EFI_GUID's. _dict_ can get updated with the + build generated Guid.xref file if a path to a module is passed + into add_build_guid_file(). If symbols are loaded for any module + in the build the path the build product should imply the + relative location of that builds Guid.xref file. + + Attributes + ——————---- + _dict_ : dictionary + dictionary of EFI_GUID (uuid) strings to C global names + + Methods + ------- + to_uuid(uuid) + convert a hex UUID string or bytearray to a uuid.UUID + to_name(uuid) + convert a UUID string to a C global constant name. + to_guid(guid_name) + convert a C global constant EFI_GUID name to uuid hex string. + is_guid_str(name) + name is a hex UUID string. + Example: 49152E77-1ADA-4764-B7A2-7AFEFED95E8B + + to_c_guid(value) + convert a uuid.UUID or UUID string to a c_guid string + (see is_c_guid()) + from_c_guid(value) + covert a C guid string to a hex UUID string. + is_c_guid(name) + name is the C initialization value for an EFI_GUID. Example: + { 0x414e6bdd, 0xe47b, 0x47cc, { 0xb2, 0x44, 0xbb, 0x61, + 0x02, 0x0c, 0xf5, 0x16 }} + + add_build_guid_file(module_path, custom_file): + assume module_path is an edk2 build product and load the Guid.xref + file from that build to fill in _dict_. If you know the path and + file name of a custom Guid.xref you can pass it in as custom_file. + + ''' + _dict_ = { # Common EFI System Table values + '05AD34BA-6F02-4214-952E-4DA0398E2BB9': + 'gEfiDxeServicesTableGuid', + '7739F24C-93D7-11D4-9A3A-0090273FC14D': + 'gEfiHobListGuid', + '4C19049F-4137-4DD3-9C10-8B97A83FFDFA': + 'gEfiMemoryTypeInformationGuid', + '49152E77-1ADA-4764-B7A2-7AFEFED95E8B': + 'gEfiDebugImageInfoTableGuid', + '060CC026-4C0D-4DDA-8F41-595FEF00A502': + 'gMemoryStatusCodeRecordGuid', + 'EB9D2D31-2D88-11D3-9A16-0090273FC14D': + 'gEfiSmbiosTableGuid', + 'EB9D2D30-2D88-11D3-9A16-0090273FC14D': + 'gEfiAcpi10TableGuid', + '8868E871-E4F1-11D3-BC22-0080C73C8881': + 'gEfiAcpi20TableGuid', + } + + guid_files = [] + + def __init__(self, uuid=None, pointer_width=8): + self.uuid = None if uuid is None else self.to_uuid(uuid) + + def __str__(self): + if self.uuid is None: + result = '' + for key, value in GuidNames._dict_.items(): + result += f'{key}: {value}\n' + else: + result = self.to_name(self.uuid) + + return result + + @classmethod + def to_uuid(cls, obj): + try: + return uuid.UUID(bytes_le=bytes(obj)) + except (ValueError, TypeError): + try: + return uuid.UUID(bytes_le=obj) + except (ValueError, TypeError): + return uuid.UUID(obj) + + @classmethod + def to_name(cls, uuid): + if not isinstance(uuid, str): + uuid = str(uuid) + if cls.is_c_guid(uuid): + uuid = cls.from_c_guid(uuid) + return cls._dict_.get(uuid.upper(), uuid.upper()) + + @classmethod + def to_guid(cls, guid_name): + for key, value in cls._dict_.items(): + if guid_name == value: + return key.upper() + else: + raise KeyError(key) + + @classmethod + def is_guid_str(cls, name): + if not isinstance(name, str): + return False + return name.count('-') >= 4 + + @classmethod + def to_c_guid(cls, value): + if isinstance(value, uuid.UUID): + guid = value + else: + guid = uuid.UUID(value) + + (data1, data2, data3, + data4_0, data4_1, data4_2, data4_3, + data4_4, data4_5, data4_6, data4_7) = struct.unpack( + '<IHH8B', guid.bytes_le) + return (f'{{ 0x{data1:08X}, 0x{data2:04X}, 0x{data3:04X}, ' + f'{{ 0x{data4_0:02X}, 0x{data4_1:02X}, 0x{data4_2:02X}, ' + f'0x{data4_3:02X}, 0x{data4_4:02X}, 0x{data4_5:02X}, ' + f'0x{data4_6:02X}, 0x{data4_7:02X} }} }}') + + @ classmethod + def from_c_guid(cls, value): + try: + hex = [int(x, 16) for x in re.findall(r"[\w']+", value)] + return (f'{hex[0]:08X}-{hex[1]:04X}-{hex[2]:04X}' + + f'-{hex[3]:02X}{hex[4]:02X}-{hex[5]:02X}{hex[6]:02X}' + + f'{hex[7]:02X}{hex[8]:02X}{hex[9]:02X}{hex[10]:02X}') + except ValueError: + return value + + @ classmethod + def is_c_guid(cls, name): + if not isinstance(name, str): + return False + return name.count('{') == 2 and name.count('}') == 2 + + @ classmethod + def add_build_guid_file(cls, module_path, custom_file=None): + if custom_file is not None: + xref = custom_file + else: + # module_path will look like: + # <repo>/Build/OvmfX64/DEBUG_XCODE5/X64/../DxeCore.dll + # Walk backwards looking for a toolchain like name. + # Then look for GUID database: + # Build/OvmfX64//DEBUG_XCODE5/FV/Guid.xref + for i in reversed(module_path.split(os.sep)): + if (i.startswith('DEBUG_') or + i.startswith('RELEASE_') or + i.startswith('NOOPT_')): + build_root = os.path.join( + module_path.rsplit(i, 1)[0], i) + break + + xref = os.path.join(build_root, 'FV', 'Guid.xref') + + if xref in cls.guid_files: + # only processes the file one time + return True + + with open(xref) as f: + content = f.readlines() + cls.guid_files.append(xref) + + for lines in content: + try: + if cls.is_guid_str(lines): + # a regex would be more pedantic + words = lines.split() + cls._dict_[words[0].upper()] = words[1].strip('\n') + except ValueError: + pass + + return True + + return False + + +class EFI_HOB_GENERIC_HEADER(LittleEndianStructure): + _fields_ = [ + ('HobType', c_uint16), + ('HobLength', c_uint16), + ('Reserved', c_uint32) + ] + + +class EFI_HOB_HANDOFF_INFO_TABLE(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Version', c_uint32), + ('BootMode', c_uint32), + ('EfiMemoryTop', c_uint64), + ('EfiMemoryBottom', c_uint64), + ('EfiFreeMemoryTop', c_uint64), + ('EfiFreeMemoryBottom', c_uint64), + ('EfiEndOfHobList', c_uint64), + ] + + +class EFI_HOB_MEMORY_ALLOCATION(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Name', EFI_GUID), + ('MemoryBaseAddress', c_uint64), + ('MemoryLength', c_uint64), + ('MemoryType', c_uint32), + ('Reserved', c_uint32), + ] + + +class EFI_HOB_RESOURCE_DESCRIPTOR(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Owner', EFI_GUID), + ('ResourceType', c_uint32), + ('ResourceAttribute', c_uint32), + ('PhysicalStart', c_uint64), + ('ResourceLength', c_uint64), + ] + + +class EFI_HOB_GUID_TYPE(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Name', EFI_GUID), + ] + + +class EFI_HOB_FIRMWARE_VOLUME(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ] + + +class EFI_HOB_CPU(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('SizeOfMemorySpace', c_uint8), + ('SizeOfIoSpace', c_uint8), + ('Reserved', ARRAY(c_uint8, 6)), + ] + + +class EFI_HOB_MEMORY_POOL(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ] + + +class EFI_HOB_FIRMWARE_VOLUME2(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ('FvName', EFI_GUID), + ('FileName', EFI_GUID) + ] + + +class EFI_HOB_FIRMWARE_VOLUME3(LittleEndianStructure): + _fields_ = [ + ('HobType', c_uint16), + ('HobLength', c_uint16), + ('Reserved', c_uint32), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ('AuthenticationStatus', c_uint32), + ('ExtractedFv', c_uint8), + ('FvName', EFI_GUID), + ('FileName', EFI_GUID), + ] + + +class EFI_HOB_UEFI_CAPSULE(LittleEndianStructure): + _fields_ = [ + ('HobType', c_uint16), + ('HobLength', c_uint16), + ('Reserved', c_uint32), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ] + + +class EfiHob: + ''' + Parse EFI Device Paths based on the edk2 C Structures defined above. + In the context of this class verbose means hexdump extra data. + + + Attributes + —————— + Hob : list + List of HOBs. Each entry contains the name, HOB type, HOB length, + the ctype struct for the HOB, and any extra data. + + Methods + ----------- + get_hob_by_type(hob_type) + return string that decodes the HOBs of hob_type. If hob_type is + None then return all HOBs. + ''' + + Hob = [] + verbose = False + + hob_dict = { + 1: EFI_HOB_HANDOFF_INFO_TABLE, + 2: EFI_HOB_MEMORY_ALLOCATION, + 3: EFI_HOB_RESOURCE_DESCRIPTOR, + 4: EFI_HOB_GUID_TYPE, + 5: EFI_HOB_FIRMWARE_VOLUME, + 6: EFI_HOB_CPU, + 7: EFI_HOB_MEMORY_POOL, + 9: EFI_HOB_FIRMWARE_VOLUME2, + 0xb: EFI_HOB_UEFI_CAPSULE, + 0xc: EFI_HOB_FIRMWARE_VOLUME3, + 0xffff: EFI_HOB_GENERIC_HEADER, + } + + def __init__(self, file, address=None, verbose=False, count=1000): + self._file = file + EfiHob.verbose = verbose + + if len(EfiHob.Hob) != 0 and address is None: + return + + if address is not None: + hob_ptr = address + else: + hob_ptr = EfiConfigurationTable(file).GetConfigTable( + '7739F24C-93D7-11D4-9A3A-0090273FC14D') + + self.read_hobs(hob_ptr) + + @ classmethod + def __str__(cls): + return cls.get_hob_by_type(None) + + @ classmethod + def get_hob_by_type(cls, hob_type): + result = "" + for (Name, HobType, HobLen, chob, extra) in cls.Hob: + if hob_type is not None: + if hob_type != HobType: + continue + + result += f'Type: {Name:s} (0x{HobType:01x}) Len: 0x{HobLen:03x}\n' + result += ctype_to_str(chob, ' ', ['Reserved']) + if cls.verbose: + if extra is not None: + result += hexdump(extra, ' ') + + return result + + def read_hobs(self, hob_ptr, count=1000): + if hob_ptr is None: + return + + try: + for _ in range(count): # while True + hdr, _ = self._ctype_read_ex(EFI_HOB_GENERIC_HEADER, hob_ptr) + if hdr.HobType == 0xffff: + break + + type_str = self.hob_dict.get( + hdr.HobType, EFI_HOB_GENERIC_HEADER) + hob, extra = self._ctype_read_ex( + type_str, hob_ptr, hdr.HobLength) + EfiHob.Hob.append( + (type(hob).__name__, + hdr.HobType, + hdr.HobLength, + hob, + extra)) + hob_ptr += hdr.HobLength + except ValueError: + pass + + def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None): + if offset != 0: + self._file.seek(offset) + + type_size = sizeof(ctype_struct) + size = rsize if rsize else type_size + data = self._file.read(size) + cdata = ctype_struct.from_buffer(bytearray(data)) + + if size > type_size: + return cdata, data[type_size:] + else: + return cdata, None + + +class EFI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Type', c_uint8), + ('SubType', c_uint8), + + # UINT8 Length[2] + # Cheat and use c_uint16 since we don't care about alignment + ('Length', c_uint16) + ] + + +class PCI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Function', c_uint8), + ('Device', c_uint8) + ] + + +class PCCARD_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('FunctionNumber', c_uint8), + ] + + +class MEMMAP_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('StartingAddress', c_uint64), + ('EndingAddress', c_uint64), + ] + + +class VENDOR_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Guid', EFI_GUID), + ] + + +class CONTROLLER_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('ControllerNumber', c_uint32), + ] + + +class BMC_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('InterfaceType', c_uint8), + ('BaseAddress', ARRAY(c_uint8, 8)), + ] + + +class BBS_BBS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('DeviceType', c_uint16), + ('StatusFlag', c_uint16) + ] + + +class ACPI_HID_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('HID', c_uint32), + ('UID', c_uint32) + ] + + +class ACPI_EXTENDED_HID_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('HID', c_uint32), + ('UID', c_uint32), + ('CID', c_uint32) + ] + + +class ACPI_ADR_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('ARD', c_uint32) + ] + + +class ACPI_NVDIMM_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('NFITDeviceHandle', c_uint32) + ] + + +class ATAPI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("PrimarySecondary", c_uint8), + ("SlaveMaster", c_uint8), + ("Lun", c_uint16) + ] + + +class SCSI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Pun", c_uint16), + ("Lun", c_uint16) + ] + + +class FIBRECHANNEL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Reserved", c_uint32), + ("WWN", c_uint64), + ("Lun", c_uint64) + ] + + +class F1394_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Reserved", c_uint32), + ("Guid", c_uint64) + ] + + +class USB_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("ParentPortNumber", c_uint8), + ("InterfaceNumber", c_uint8), + ] + + +class I2O_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Tid", c_uint32) + ] + + +class INFINIBAND_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("ResourceFlags", c_uint32), + ("PortGid", ARRAY(c_uint8, 16)), + ("ServiceId", c_uint64), + ("TargetPortId", c_uint64), + ("DeviceId", c_uint64) + ] + + +class UART_FLOW_CONTROL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Guid", EFI_GUID), + ("FlowControlMap", c_uint32) + ] + + +class SAS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Guid", EFI_GUID), + ("Reserved", c_uint32), + ("SasAddress", c_uint64), + ("Lun", c_uint64), + ("DeviceTopology", c_uint16), + ("RelativeTargetPort", c_uint16) + ] + + +class EFI_MAC_ADDRESS(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("Addr", ARRAY(c_uint8, 32)), + ] + + +class MAC_ADDR_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('MacAddress', EFI_MAC_ADDRESS), + ('IfType', c_uint8) + ] + + +class IPv4_ADDRESS(LittleEndianStructure): + _fields_ = [ + ("Addr", ARRAY(c_uint8, 4)), + ] + + +class IPv4_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('LocalIpAddress', IPv4_ADDRESS), + ('RemoteIpAddress', IPv4_ADDRESS), + ('LocalPort', c_uint16), + ('RemotePort', c_uint16), + ('Protocol', c_uint16), + ('StaticIpAddress', c_uint8), + ('GatewayIpAddress', IPv4_ADDRESS), + ('SubnetMask', IPv4_ADDRESS) + ] + + +class IPv6_ADDRESS(LittleEndianStructure): + _fields_ = [ + ("Addr", ARRAY(c_uint8, 16)), + ] + + +class IPv6_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('LocalIpAddress', IPv6_ADDRESS), + ('RemoteIpAddress', IPv6_ADDRESS), + ('LocalPort', c_uint16), + ('RemotePort', c_uint16), + ('Protocol', c_uint16), + ('IpAddressOrigin', c_uint8), + ('PrefixLength', c_uint8), + ('GatewayIpAddress', IPv6_ADDRESS) + ] + + +class UART_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Reserved', c_uint32), + ('BaudRate', c_uint64), + ('DataBits', c_uint8), + ('Parity', c_uint8), + ('StopBits', c_uint8) + ] + + +class USB_CLASS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('VendorId', c_uint16), + ('ProductId', c_uint16), + ('DeviceClass', c_uint8), + ('DeviceCSjblass', c_uint8), + ('DeviceProtocol', c_uint8), + ] + + +class USB_WWID_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('InterfaceNumber', c_uint16), + ('VendorId', c_uint16), + ('ProductId', c_uint16), + ] + + +class DEVICE_LOGICAL_UNIT_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Lun', c_uint8) + ] + + +class SATA_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('HBAPortNumber', c_uint16), + ('PortMultiplierPortNumber', c_uint16), + ('Lun', c_uint16), + ] + + +class ISCSI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('NetworkProtocol', c_uint16), + ('LoginOption', c_uint16), + ('Lun', c_uint64), + ('TargetPortalGroupTag', c_uint16), + ] + + +class VLAN_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("VlandId", c_uint16) + ] + + +class FIBRECHANNELEX_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Reserved", c_uint16), + ("WWN", ARRAY(c_uint8, 8)), + ("Lun", ARRAY(c_uint8, 8)), + ] + + +class SASEX_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SasAddress", ARRAY(c_uint8, 8)), + ("Lun", ARRAY(c_uint8, 8)), + ("DeviceTopology", c_uint16), + ("RelativeTargetPort", c_uint16) + ] + + +class NVME_NAMESPACE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("NamespaceId", c_uint32), + ("NamespaceUuid", c_uint64) + ] + + +class DNS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("IsIPv6", c_uint8), + ("DnsServerIp", IPv6_ADDRESS) + + ] + + +class UFS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Pun", c_uint8), + ("Lun", c_uint8), + ] + + +class SD_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SlotNumber", c_uint8) + ] + + +class BLUETOOTH_ADDRESS(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("Address", ARRAY(c_uint8, 6)) + ] + + +class BLUETOOTH_LE_ADDRESS(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("Format", c_uint8), + ("Class", c_uint16) + ] + + +class BLUETOOTH_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("BD_ADDR", BLUETOOTH_ADDRESS) + ] + + +class WIFI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SSId", ARRAY(c_uint8, 32)) + ] + + +class EMMC_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SlotNumber", c_uint8) + ] + + +class BLUETOOTH_LE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("BD_ADDR", BLUETOOTH_LE_ADDRESS) + ] + + +class NVDIMM_NAMESPACE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Uuid", EFI_GUID) + ] + + +class REST_SERVICE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("RESTService", c_uint8), + ("AccessMode", c_uint8) + ] + + +class REST_VENDOR_SERVICE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("RESTService", c_uint8), + ("AccessMode", c_uint8), + ("Guid", EFI_GUID), + ] + + +class HARDDRIVE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('PartitionNumber', c_uint32), + ('PartitionStart', c_uint64), + ('PartitionSize', c_uint64), + ('Signature', ARRAY(c_uint8, 16)), + ('MBRType', c_uint8), + ('SignatureType', c_uint8) + ] + + +class CDROM_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('BootEntry', c_uint32), + ('PartitionStart', c_uint64), + ('PartitionSize', c_uint64) + ] + + +class MEDIA_PROTOCOL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Protocol', EFI_GUID) + ] + + +class MEDIA_FW_VOL_FILEPATH_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('FvFileName', EFI_GUID) + ] + + +class MEDIA_FW_VOL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('FvName', EFI_GUID) + ] + + +class MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Reserved', c_uint32), + ('StartingOffset', c_uint64), + ('EndingOffset', c_uint64) + ] + + +class MEDIA_RAM_DISK_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('StartingAddr', c_uint64), + ('EndingAddr', c_uint64), + ('TypeGuid', EFI_GUID), + ('Instance', c_uint16) + ] + + +class EfiDevicePath: + ''' + Parse EFI Device Paths based on the edk2 C Structures defined above. + In the context of this class verbose means hexdump extra data. + + + Attributes + —————— + DevicePath : list + List of devixe path instances. Each instance is a list of nodes + for the given Device Path instance. + + Methods + ----------- + device_path_node(address) + return the Device Path ctype hdr, ctype, and any extra data in + the Device Path node. This is just a single Device Path node, + not the entire Device Path. + device_path_node_str(address) + return the device path node (not the entire Device Path) as a string + ''' + + DevicePath = [] + + device_path_dict = { + # ( Type, SubType ) : Device Path C typedef + # HARDWARE_DEVICE_PATH + (1, 1): PCI_DEVICE_PATH, + (1, 2): PCCARD_DEVICE_PATH, + (1, 3): MEMMAP_DEVICE_PATH, + (1, 4): VENDOR_DEVICE_PATH, + (1, 5): CONTROLLER_DEVICE_PATH, + (1, 6): BMC_DEVICE_PATH, + + # ACPI_DEVICE_PATH + (2, 1): ACPI_HID_DEVICE_PATH, + (2, 2): ACPI_EXTENDED_HID_DEVICE_PATH, + (2, 3): ACPI_ADR_DEVICE_PATH, + (2, 4): ACPI_NVDIMM_DEVICE_PATH, + + # MESSAGING_DEVICE_PATH + (3, 1): ATAPI_DEVICE_PATH, + (3, 2): SCSI_DEVICE_PATH, + (3, 3): FIBRECHANNEL_DEVICE_PATH, + (3, 4): F1394_DEVICE_PATH, + (3, 5): USB_DEVICE_PATH, + (3, 6): I2O_DEVICE_PATH, + + (3, 9): INFINIBAND_DEVICE_PATH, + (3, 10): VENDOR_DEVICE_PATH, + (3, 11): MAC_ADDR_DEVICE_PATH, + (3, 12): IPv4_DEVICE_PATH, + (3, 13): IPv6_DEVICE_PATH, + (3, 14): UART_DEVICE_PATH, + (3, 15): USB_CLASS_DEVICE_PATH, + (3, 16): USB_WWID_DEVICE_PATH, + (3, 17): DEVICE_LOGICAL_UNIT_DEVICE_PATH, + (3, 18): SATA_DEVICE_PATH, + (3, 19): ISCSI_DEVICE_PATH, + (3, 20): VLAN_DEVICE_PATH, + (3, 21): FIBRECHANNELEX_DEVICE_PATH, + (3, 22): SASEX_DEVICE_PATH, + (3, 23): NVME_NAMESPACE_DEVICE_PATH, + (3, 24): DNS_DEVICE_PATH, + (3, 25): UFS_DEVICE_PATH, + (3, 26): SD_DEVICE_PATH, + (3, 27): BLUETOOTH_DEVICE_PATH, + (3, 28): WIFI_DEVICE_PATH, + (3, 29): EMMC_DEVICE_PATH, + (3, 30): BLUETOOTH_LE_DEVICE_PATH, + (3, 31): DNS_DEVICE_PATH, + (3, 32): NVDIMM_NAMESPACE_DEVICE_PATH, + + (3, 33): REST_SERVICE_DEVICE_PATH, + (3, 34): REST_VENDOR_SERVICE_DEVICE_PATH, + + # MEDIA_DEVICE_PATH + (4, 1): HARDDRIVE_DEVICE_PATH, + (4, 2): CDROM_DEVICE_PATH, + (4, 3): VENDOR_DEVICE_PATH, + (4, 4): EFI_DEVICE_PATH, + (4, 5): MEDIA_PROTOCOL_DEVICE_PATH, + (4, 6): MEDIA_FW_VOL_FILEPATH_DEVICE_PATH, + (4, 7): MEDIA_FW_VOL_DEVICE_PATH, + (4, 8): MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH, + (4, 9): MEDIA_RAM_DISK_DEVICE_PATH, + + # BBS_DEVICE_PATH + (5, 1): BBS_BBS_DEVICE_PATH, + + } + + guid_override_dict = { + uuid.UUID('37499A9D-542F-4C89-A026-35DA142094E4'): + UART_FLOW_CONTROL_DEVICE_PATH, + uuid.UUID('D487DDB4-008B-11D9-AFDC-001083FFCA4D'): + SAS_DEVICE_PATH, + } + + def __init__(self, file, ptr=None, verbose=False, count=64): + ''' + Convert ptr into a list of Device Path nodes. If verbose also hexdump + extra data. + ''' + self._file = file + self._verbose = verbose + if ptr is None: + return + + try: + instance = [] + for _ in range(count): # while True + hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, ptr) + if hdr.Length < sizeof(EFI_DEVICE_PATH): + # Not a valid device path + break + + if hdr.Type == 0x7F: # END_DEVICE_PATH_TYPE + self.DevicePath.append(instance) + if hdr.SubType == 0xFF: # END_ENTIRE_DEVICE_PATH_SUBTYPE + break + if hdr.SubType == 0x01: # END_INSTANCE_DEVICE_PATH_SUBTYPE + # start new device path instance + instance = [] + + type_str = self.device_path_dict.get( + (hdr.Type, hdr.SubType), EFI_DEVICE_PATH) + node, extra = self._ctype_read_ex(type_str, ptr, hdr.Length) + if 'VENDOR_DEVICE_PATH' in type(node).__name__: + guid_type = self.guid_override_dict.get( + GuidNames.to_uuid(node.Guid), None) + if guid_type: + # use the ctype associated with the GUID + node, extra = self._ctype_read_ex( + guid_type, ptr, hdr.Length) + + instance.append((type(node).__name__, hdr.Type, + hdr.SubType, hdr.Length, node, extra)) + ptr += hdr.Length + except ValueError: + pass + + def __str__(self): + ''' ''' + if not self.valid(): + return '<class: EfiDevicePath>' + + result = "" + for instance in self.DevicePath: + for (Name, Type, SubType, Length, cnode, extra) in instance: + result += f'{Name:s} {Type:2d}:{SubType:2d} Len: {Length:3d}\n' + result += ctype_to_str(cnode, ' ', ['Reserved']) + if self._verbose: + if extra is not None: + result += hexdump(extra, ' ') + result += '\n' + + return result + + def valid(self): + return True if self.DevicePath else False + + def device_path_node(self, address): + try: + hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, address) + if hdr.Length < sizeof(EFI_DEVICE_PATH): + return None, None, None + + type_str = self.device_path_dict.get( + (hdr.Type, hdr.SubType), EFI_DEVICE_PATH) + cnode, extra = self._ctype_read_ex(type_str, address, hdr.Length) + return hdr, cnode, extra + except ValueError: + return None, None, None + + def device_path_node_str(self, address, verbose=False): + hdr, cnode, extra = self.device_path_node(address) + if hdr is None: + return '' + + cname = type(cnode).__name__ + result = f'{cname:s} {hdr.Type:2d}:{hdr.SubType:2d} ' + result += f'Len: 0x{hdr.Length:03x}\n' + result += ctype_to_str(cnode, ' ', ['Reserved']) + if verbose: + if extra is not None: + result += hexdump(extra, ' ') + + return result + + def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None): + if offset != 0: + self._file.seek(offset) + + type_size = sizeof(ctype_struct) + size = rsize if rsize else type_size + data = self._file.read(size) + if data is None: + return None, None + + cdata = ctype_struct.from_buffer(bytearray(data)) + + if size > type_size: + return cdata, data[type_size:] + else: + return cdata, None + + +class EfiConfigurationTable: + ''' + A class to abstract EFI Configuration Tables from gST->ConfigurationTable + and gST->NumberOfTableEntries. Pass in the gST pointer from EFI, + likely you need to look up this address after you have loaded symbols + + Attributes + —————— + ConfigurationTableDict : dictionary + dictionary of EFI Configuration Table entries + + Methods + ----------- + GetConfigTable(uuid) + pass in VendorGuid and return VendorTable from EFI System Table + DebugImageInfo(table) + return tuple of load address and size of PE/COFF images + ''' + + ConfigurationTableDict = {} + + def __init__(self, file, gST_addr=None): + self._file = file + if gST_addr is None: + # ToDo add code to search for gST via EFI_SYSTEM_TABLE_POINTER + return + + gST = self._ctype_read(EFI_SYSTEM_TABLE, gST_addr) + self.read_efi_config_table(gST.NumberOfTableEntries, + gST.ConfigurationTable, + self._ctype_read) + + @ classmethod + def __str__(cls): + '''return EFI_CONFIGURATION_TABLE entries as a string''' + result = "" + for key, value in cls.ConfigurationTableDict.items(): + result += f'{GuidNames().to_name(key):>37s}: ' + result += f'VendorTable = 0x{value:08x}\n' + + return result + + def _ctype_read(self, ctype_struct, offset=0): + '''ctype worker function to read data''' + if offset != 0: + self._file.seek(offset) + + data = self._file.read(sizeof(ctype_struct)) + return ctype_struct.from_buffer(bytearray(data)) + + @ classmethod + def read_efi_config_table(cls, table_cnt, table_ptr, ctype_read): + '''Create a dictionary of EFI Configuration table entries''' + EmptryTables = EFI_CONFIGURATION_TABLE * table_cnt + Tables = ctype_read(EmptryTables, table_ptr) + for i in range(table_cnt): + cls.ConfigurationTableDict[str(GuidNames.to_uuid( + Tables[i].VendorGuid)).upper()] = Tables[i].VendorTable + + return cls.ConfigurationTableDict + + def GetConfigTable(self, uuid): + ''' Return VendorTable for VendorGuid (uuid.UUID) or None''' + return self.ConfigurationTableDict.get(uuid.upper()) + + def DebugImageInfo(self, table=None): + ''' + Walk the debug image info table to find the LoadedImage protocols + for all the loaded PE/COFF images and return a list of load address + and image size. + ''' + ImageLoad = [] + + if table is None: + table = self.GetConfigTable('49152e77-1ada-4764-b7a2-7afefed95e8b') + + DbgInfoHdr = self._ctype_read(EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, table) + NormalImageArray = EFI_DEBUG_IMAGE_INFO * DbgInfoHdr.TableSize + NormalImageArray = self._ctype_read( + NormalImageArray, DbgInfoHdr.EfiDebugImageInfoTable) + for i in range(DbgInfoHdr.TableSize): + ImageInfo = self._ctype_read( + EFI_DEBUG_IMAGE_INFO_NORMAL, NormalImageArray[i].NormalImage) + LoadedImage = self._ctype_read( + EFI_LOADED_IMAGE_PROTOCOL, + ImageInfo.LoadedImageProtocolInstance) + ImageLoad.append((LoadedImage.ImageBase, LoadedImage.ImageSize)) + + return ImageLoad + + +class PeTeImage: + ''' + A class to abstract PE/COFF or TE image processing via passing in a + Python file like object. If you pass in an address the PE/COFF is parsed, + if you pass in NULL for an address then you get a class instance you can + use to search memory for a PE/COFF hader given a pc value. + + Attributes + —————— + LoadAddress : int + Load address of the PE/COFF image + AddressOfEntryPoint : int + Address of the Entry point of the PE/COFF image + TextAddress : int + Start of the PE/COFF text section + DataAddress : int + Start of the PE/COFF data section + CodeViewPdb : str + File name of the symbols file + CodeViewUuid : uuid:UUID + GUID for "RSDS" Debug Directory entry, or Mach-O UUID for "MTOC" + + Methods + ----------- + pcToPeCoff(address, step, max_range, rom_range) + Given an address(pc) find the PE/COFF image it is in + sections_to_str() + return a string giving info for all the PE/COFF sections + ''' + + def __init__(self, file, address=0): + self._file = file + + # book keeping, but public + self.PeHdr = None + self.TeHdr = None + self.Machine = None + self.Subsystem = None + self.CodeViewSig = None + self.e_lfanew = 0 + self.NumberOfSections = 0 + self.Sections = None + + # Things debuggers may want to know + self.LoadAddress = 0 if address is None else address + self.EndLoadAddress = 0 + self.AddressOfEntryPoint = 0 + self.TextAddress = 0 + self.DataAddress = 0 + self.CodeViewPdb = None + self.CodeViewUuid = None + self.TeAdjust = 0 + + self.dir_name = { + 0: 'Export Table', + 1: 'Import Table', + 2: 'Resource Table', + 3: 'Exception Table', + 4: 'Certificate Table', + 5: 'Relocation Table', + 6: 'Debug', + 7: 'Architecture', + 8: 'Global Ptr', + 9: 'TLS Table', + 10: 'Load Config Table', + 11: 'Bound Import', + 12: 'IAT', + 13: 'Delay Import Descriptor', + 14: 'CLR Runtime Header', + 15: 'Reserved', + } + + if address is not None: + if self.maybe(): + self.parse() + + def __str__(self): + if self.PeHdr is None and self.TeHdr is None: + # no PE/COFF header found + return "<class: PeTeImage>" + + if self.CodeViewPdb: + pdb = f'{self.Machine}`{self.CodeViewPdb}' + else: + pdb = 'No Debug Info:' + + if self.CodeViewUuid: + guid = f'{self.CodeViewUuid}:' + else: + guid = '' + + slide = f'slide = {self.TeAdjust:d} ' if self.TeAdjust != 0 else ' ' + res = guid + f'{pdb} load = 0x{self.LoadAddress:08x} ' + slide + return res + + def _seek(self, offset): + """ + seek() relative to start of PE/COFF (TE) image + """ + self._file.seek(self.LoadAddress + offset) + + def _read_offset(self, size, offset=None): + """ + read() relative to start of PE/COFF (TE) image + if offset is not None then seek() before the read + """ + if offset is not None: + self._seek(offset) + + return self._file.read(size) + + def _read_ctype(self, ctype_struct, offset=None): + data = self._read_offset(sizeof(ctype_struct), offset) + return ctype_struct.from_buffer(bytearray(data), 0) + + def _unsigned(self, i): + """return a 32-bit unsigned int (UINT32) """ + return int.from_bytes(i, byteorder='little', signed=False) + + def pcToPeCoff(self, + address, + step=None, + max_range=None, + rom_range=[0xFE800000, 0xFFFFFFFF]): + """ + Given an address search backwards for PE/COFF (TE) header + For DXE 4K is probably OK + For PEI you might have to search every 4 bytes. + """ + if step is None: + step = 0x1000 + + if max_range is None: + max_range = 0x200000 + + if address in range(*rom_range): + # The XIP code in the ROM ends up 4 byte aligned. + step = 4 + max_range = min(max_range, 0x100000) + + # Align address to page boundary for memory image search. + address = address & ~(step-1) + # Search every step backward + offset_range = list(range(0, min(max_range, address), step)) + for offset in offset_range: + if self.maybe(address - offset): + if self.parse(): + return True + + return False + + def maybe(self, offset=None): + """Probe to see if this offset is likely a PE/COFF or TE file """ + self.LoadAddress = 0 + e_magic = self._read_offset(2, offset) + header_ok = e_magic == b'MZ' or e_magic == b'VZ' + if offset is not None and header_ok: + self.LoadAddress = offset + return header_ok + + def parse(self): + """Parse PE/COFF (TE) debug directory entry """ + DosHdr = self._read_ctype(EFI_IMAGE_DOS_HEADER, 0) + if DosHdr.e_magic == self._unsigned(b'VZ'): + # TE image + self.TeHdr = self._read_ctype(EFI_TE_IMAGE_HEADER, 0) + + self.TeAdjust = sizeof(self.TeHdr) - self.TeHdr.StrippedSize + self.Machine = image_machine_dict.get(self.TeHdr.Machine, None) + self.Subsystem = self.TeHdr.Subsystem + self.AddressOfEntryPoint = self.TeHdr.AddressOfEntryPoint + + debug_dir_size = self.TeHdr.DataDirectoryDebug.Size + debug_dir_offset = (self.TeAdjust + + self.TeHdr.DataDirectoryDebug.VirtualAddress) + else: + if DosHdr.e_magic == self._unsigned(b'MZ'): + self.e_lfanew = DosHdr.e_lfanew + else: + self.e_lfanew = 0 + + self.PeHdr = self._read_ctype( + EFI_IMAGE_NT_HEADERS64, self.e_lfanew) + if self.PeHdr.Signature != self._unsigned(b'PE\0\0'): + return False + + if self.PeHdr.OptionalHeader.Magic == \ + EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: + self.PeHdr = self._read_ctype( + EFI_IMAGE_NT_HEADERS32, self.e_lfanew) + + if self.PeHdr.OptionalHeader.NumberOfRvaAndSizes <= \ + DIRECTORY_DEBUG: + return False + + self.Machine = image_machine_dict.get( + self.PeHdr.FileHeader.Machine, None) + self.Subsystem = self.PeHdr.OptionalHeader.Subsystem + self.AddressOfEntryPoint = \ + self.PeHdr.OptionalHeader.AddressOfEntryPoint + self.TeAdjust = 0 + + debug_dir_size = self.PeHdr.OptionalHeader.DataDirectory[ + DIRECTORY_DEBUG].Size + debug_dir_offset = self.PeHdr.OptionalHeader.DataDirectory[ + DIRECTORY_DEBUG].VirtualAddress + + if self.Machine is None or self.Subsystem not in [0, 10, 11, 12]: + return False + + self.AddressOfEntryPoint += self.LoadAddress + + self.sections() + return self.processDebugDirEntry(debug_dir_offset, debug_dir_size) + + def sections(self): + '''Parse the PE/COFF (TE) section table''' + if self.Sections is not None: + return + elif self.TeHdr is not None: + self.NumberOfSections = self.TeHdr.NumberOfSections + offset = sizeof(EFI_TE_IMAGE_HEADER) + elif self.PeHdr is not None: + self.NumberOfSections = self.PeHdr.FileHeader.NumberOfSections + offset = sizeof(c_uint32) + \ + sizeof(EFI_IMAGE_FILE_HEADER) + offset += self.PeHdr.FileHeader.SizeOfOptionalHeader + offset += self.e_lfanew + else: + return + + self.Sections = EFI_IMAGE_SECTION_HEADER * self.NumberOfSections + self.Sections = self._read_ctype(self.Sections, offset) + + for i in range(self.NumberOfSections): + name = str(self.Sections[i].Name, 'ascii', 'ignore') + addr = self.Sections[i].VirtualAddress + addr += self.LoadAddress + self.TeAdjust + if name == '.text': + self.TextAddress = addr + elif name == '.data': + self.DataAddress = addr + + end_addr = addr + self.Sections[i].VirtualSize - 1 + if end_addr > self.EndLoadAddress: + self.EndLoadAddress = end_addr + + def sections_to_str(self): + # return text summary of sections + # name virt addr (virt size) flags:Characteristics + result = '' + for i in range(self.NumberOfSections): + name = str(self.Sections[i].Name, 'ascii', 'ignore') + result += f'{name:8s} ' + result += f'0x{self.Sections[i].VirtualAddress:08X} ' + result += f'(0x{self.Sections[i].VirtualSize:05X}) ' + result += f'flags:0x{self.Sections[i].Characteristics:08X}\n' + + return result + + def directory_to_str(self): + result = '' + if self.TeHdr: + debug_size = self.TeHdr.DataDirectoryDebug.Size + if debug_size > 0: + debug_offset = (self.TeAdjust + + self.TeHdr.DataDirectoryDebug.VirtualAddress) + result += f"Debug 0x{debug_offset:08X} 0x{debug_size}\n" + + relocation_size = self.TeHdr.DataDirectoryBaseReloc.Size + if relocation_size > 0: + relocation_offset = ( + self.TeAdjust + + self.TeHdr.DataDirectoryBaseReloc.VirtualAddress) + result += f'Relocation 0x{relocation_offset:08X} ' + result += f' 0x{relocation_size}\n' + + elif self.PeHdr: + for i in range(self.PeHdr.OptionalHeader.NumberOfRvaAndSizes): + size = self.PeHdr.OptionalHeader.DataDirectory[i].Size + if size == 0: + continue + + virt_addr = self.PeHdr.OptionalHeader.DataDirectory[ + i].VirtualAddress + name = self.dir_name.get(i, '?') + result += f'{name:s} 0x{virt_addr:08X} 0x{size:X}\n' + + return result + + def processDebugDirEntry(self, virt_address, virt_size): + """Process PE/COFF Debug Directory Entry""" + if (virt_address == 0 or + virt_size < sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)): + return False + + data = bytearray(self._read_offset(virt_size, virt_address)) + for offset in range(0, + virt_size, + sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)): + DirectoryEntry = EFI_IMAGE_DEBUG_DIRECTORY_ENTRY.from_buffer( + data[offset:]) + if DirectoryEntry.Type != 2: + continue + + entry = self._read_offset( + DirectoryEntry.SizeOfData, DirectoryEntry.RVA + self.TeAdjust) + self.CodeViewSig = entry[:4] + if self.CodeViewSig == b'MTOC': + self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16]) + PdbOffset = 20 + elif self.CodeViewSig == b'RSDS': + self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16]) + PdbOffset = 24 + elif self.CodeViewSig == b'NB10': + PdbOffset = 16 + else: + continue + + # can't find documentation about Pdb string encoding? + # guessing utf-8 since that will match file systems in macOS + # and Linux Windows is UTF-16, or ANSI adjusted for local. + # We might need a different value for Windows here? + self.CodeViewPdb = entry[PdbOffset:].split(b'\x00')[ + 0].decode('utf-8') + return True + return False + + +def main(): + '''Process arguments as PE/COFF files''' + for fname in sys.argv[1:]: + with open(fname, 'rb') as f: + image = PeTeImage(f) + print(image) + res = f'EntryPoint = 0x{image.AddressOfEntryPoint:08x} ' + res += f'TextAddress = 0x{image.TextAddress:08x} ' + res += f'DataAddress = 0x{image.DataAddress:08x}' + print(res) + print(image.sections_to_str()) + print('Data Directories:') + print(image.directory_to_str()) + + +if __name__ == "__main__": + main() -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes 2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran @ 2022-03-26 14:04 ` Bob Feng 0 siblings, 0 replies; 10+ messages in thread From: Bob Feng @ 2022-03-26 14:04 UTC (permalink / raw) To: Rebecca Cran, devel@edk2.groups.io, Leif Lindholm, Kinney, Michael D, Wu, Hao A, Gao, Liming, Chen, Christine Cc: Andrew Fish Reviewed-by: Bob Feng<bob.c.feng@intel.com> -----Original Message----- From: Rebecca Cran <quic_rcran@quicinc.com> Sent: Tuesday, March 22, 2022 4:21 AM To: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>; Kinney, Michael D <michael.d.kinney@intel.com>; Wu, Hao A <hao.a.wu@intel.com>; Feng, Bob C <bob.c.feng@intel.com>; Gao, Liming <gaoliming@byosoft.com.cn>; Chen, Christine <yuwei.chen@intel.com> Cc: Rebecca Cran <quic_rcran@quicinc.com>; Andrew Fish <afish@apple.com> Subject: [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Add Scripts/efi_debugging.py to provide debugger agnostic debugging utility Python classes. Cc: Leif Lindholm <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> --- BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++ 1 file changed, 2185 insertions(+) diff --git a/BaseTools/Scripts/efi_debugging.py b/BaseTools/Scripts/efi_debugging.py new file mode 100755 index 000000000000..af975f4e6ceb --- /dev/null +++ b/BaseTools/Scripts/efi_debugging.py @@ -0,0 +1,2185 @@ +#!/usr/bin/python3 +''' +Copyright (c) Apple Inc. 2021 +SPDX-License-Identifier: BSD-2-Clause-Patent + +Class that abstracts PE/COFF debug info parsing via a Python file like +object. You can port this code into an arbitrary debugger by invoking +the classes and passing in a file like object that abstracts the debugger +reading memory. + +If you run this file directly it will parse the passed in PE/COFF files +for debug info: +python3 ./efi_pefcoff.py DxeCore.efi +IA32`<path...>/DxeCore.dll load = 0x00000000 +EntryPoint = 0x000030d2 TextAddress = 0x00000240 DataAddress = 0x000042c0 +.text 0x00000240 (0x04080) flags:0x60000020 +.data 0x000042C0 (0x001C0) flags:0xC0000040 +.reloc 0x00004480 (0x00240) flags:0x42000040 + +Note: PeCoffClass uses virtual addresses and not file offsets. + It needs to work when images are loaded into memory. + as long as virtual address map to file addresses this + code can process binary files. + +Note: This file can also contain generic worker functions (like GuidNames) + that abstract debugger agnostic services to the debugger. + +This file should never import debugger specific modules. +''' + +import sys +import os +import uuid +import struct +import re +from ctypes import c_char, c_uint8, c_uint16, c_uint32, c_uint64, c_void_p +from ctypes import ARRAY, sizeof +from ctypes import Structure, LittleEndianStructure + +# +# The empty LittleEndianStructure must have _fields_ assigned prior to use or +# sizeof(). Anything that is size UINTN may need to get adjusted. +# +# The issue is ctypes matches our local machine, not the machine we are +# trying to debug. Call patch_ctypes() passing in the byte width from the +# debugger python to make sure you are in sync. +# +# Splitting out the _field_ from the Structure (LittleEndianStructure) class +# allows it to be patched. +# + + +class EFI_LOADED_IMAGE_PROTOCOL(LittleEndianStructure): + pass + + +EFI_LOADED_IMAGE_PROTOCOL_fields_ = [ + ('Revision', c_uint32), + ('ParentHandle', c_void_p), + ('SystemTable', c_void_p), + ('DeviceHandle', c_void_p), + ('FilePath', c_void_p), + ('Reserved', c_void_p), + ('LoadOptionsSize', c_uint32), + ('LoadOptions', c_void_p), + ('ImageBase', c_void_p), + ('ImageSize', c_uint64), + ('ImageCodeType', c_uint32), + ('ImageDataType', c_uint32), + ('Unload', c_void_p), +] + + +class EFI_GUID(LittleEndianStructure): + _fields_ = [ + ('Data1', c_uint32), + ('Data2', c_uint16), + ('Data3', c_uint16), + ('Data4', ARRAY(c_uint8, 8)) + ] + + +class EFI_SYSTEM_TABLE_POINTER(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint64), + ('EfiSystemTableBase', c_uint64), + ('Crc32', c_uint32) + ] + + +class EFI_DEBUG_IMAGE_INFO_NORMAL(LittleEndianStructure): + pass + + +EFI_DEBUG_IMAGE_INFO_NORMAL_fields_ = [ + ('ImageInfoType', c_uint32), + ('LoadedImageProtocolInstance', c_void_p), + ('ImageHandle', c_void_p) +] + + +class EFI_DEBUG_IMAGE_INFO(LittleEndianStructure): + pass + + +EFI_DEBUG_IMAGE_INFO_fields_ = [ + ('NormalImage', c_void_p), +] + + +class EFI_DEBUG_IMAGE_INFO_TABLE_HEADER(LittleEndianStructure): + pass + + +EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_ = [ + ('UpdateStatus', c_uint32), + ('TableSize', c_uint32), + ('EfiDebugImageInfoTable', c_void_p), +] + + +class EFI_TABLE_HEADER(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint64), + ('Revision', c_uint32), + ('HeaderSize', c_uint32), + ('CRC32', c_uint32), + ('Reserved', c_uint32), + ] + + +class EFI_CONFIGURATION_TABLE(LittleEndianStructure): + pass + + +EFI_CONFIGURATION_TABLE_fields_ = [ + ('VendorGuid', EFI_GUID), + ('VendorTable', c_void_p) +] + + +class EFI_SYSTEM_TABLE(LittleEndianStructure): + pass + + +EFI_SYSTEM_TABLE_fields_ = [ + ('Hdr', EFI_TABLE_HEADER), + ('FirmwareVendor', c_void_p), + ('FirmwareRevision', c_uint32), + ('ConsoleInHandle', c_void_p), + ('ConIn', c_void_p), + ('ConsoleOutHandle', c_void_p), + ('ConOut', c_void_p), + ('StandardErrHandle', c_void_p), + ('StdErr', c_void_p), + ('RuntimeService', c_void_p), + ('BootService', c_void_p), + ('NumberOfTableEntries', c_void_p), + ('ConfigurationTable', c_void_p), +] + + +class EFI_IMAGE_DATA_DIRECTORY(LittleEndianStructure): + _fields_ = [ + ('VirtualAddress', c_uint32), + ('Size', c_uint32) + ] + + +class EFI_TE_IMAGE_HEADER(LittleEndianStructure): + _fields_ = [ + ('Signature', ARRAY(c_char, 2)), + ('Machine', c_uint16), + ('NumberOfSections', c_uint8), + ('Subsystem', c_uint8), + ('StrippedSize', c_uint16), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ('ImageBase', c_uint64), + ('DataDirectoryBaseReloc', EFI_IMAGE_DATA_DIRECTORY), + ('DataDirectoryDebug', EFI_IMAGE_DATA_DIRECTORY) + ] + + +class EFI_IMAGE_DOS_HEADER(LittleEndianStructure): + _fields_ = [ + ('e_magic', c_uint16), + ('e_cblp', c_uint16), + ('e_cp', c_uint16), + ('e_crlc', c_uint16), + ('e_cparhdr', c_uint16), + ('e_minalloc', c_uint16), + ('e_maxalloc', c_uint16), + ('e_ss', c_uint16), + ('e_sp', c_uint16), + ('e_csum', c_uint16), + ('e_ip', c_uint16), + ('e_cs', c_uint16), + ('e_lfarlc', c_uint16), + ('e_ovno', c_uint16), + ('e_res', ARRAY(c_uint16, 4)), + ('e_oemid', c_uint16), + ('e_oeminfo', c_uint16), + ('e_res2', ARRAY(c_uint16, 10)), + ('e_lfanew', c_uint16) + ] + + +class EFI_IMAGE_FILE_HEADER(LittleEndianStructure): + _fields_ = [ + ('Machine', c_uint16), + ('NumberOfSections', c_uint16), + ('TimeDateStamp', c_uint32), + ('PointerToSymbolTable', c_uint32), + ('NumberOfSymbols', c_uint32), + ('SizeOfOptionalHeader', c_uint16), + ('Characteristics', c_uint16) + ] + + +class EFI_IMAGE_OPTIONAL_HEADER32(LittleEndianStructure): + _fields_ = [ + ('Magic', c_uint16), + ('MajorLinkerVersion', c_uint8), + ('MinorLinkerVersion', c_uint8), + ('SizeOfCode', c_uint32), + ('SizeOfInitializedData', c_uint32), + ('SizeOfUninitializedData', c_uint32), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ('BaseOfData', c_uint32), + ('ImageBase', c_uint32), + ('SectionAlignment', c_uint32), + ('FileAlignment', c_uint32), + ('MajorOperatingSystemVersion', c_uint16), + ('MinorOperatingSystemVersion', c_uint16), + ('MajorImageVersion', c_uint16), + ('MinorImageVersion', c_uint16), + ('MajorSubsystemVersion', c_uint16), + ('MinorSubsystemVersion', c_uint16), + ('Win32VersionValue', c_uint32), + ('SizeOfImage', c_uint32), + ('SizeOfHeaders', c_uint32), + ('CheckSum', c_uint32), + ('Subsystem', c_uint16), + ('DllCharacteristics', c_uint16), + ('SizeOfStackReserve', c_uint32), + ('SizeOfStackCommit', c_uint32), + ('SizeOfHeapReserve', c_uint32), + ('SizeOfHeapCommit', c_uint32), + ('LoaderFlags', c_uint32), + ('NumberOfRvaAndSizes', c_uint32), + ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) + ] + + +class EFI_IMAGE_NT_HEADERS32(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint32), + ('FileHeader', EFI_IMAGE_FILE_HEADER), + ('OptionalHeader', EFI_IMAGE_OPTIONAL_HEADER32) + ] + + +class EFI_IMAGE_OPTIONAL_HEADER64(LittleEndianStructure): + _fields_ = [ + ('Magic', c_uint16), + ('MajorLinkerVersion', c_uint8), + ('MinorLinkerVersion', c_uint8), + ('SizeOfCode', c_uint32), + ('SizeOfInitializedData', c_uint32), + ('SizeOfUninitializedData', c_uint32), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ('BaseOfData', c_uint32), + ('ImageBase', c_uint32), + ('SectionAlignment', c_uint32), + ('FileAlignment', c_uint32), + ('MajorOperatingSystemVersion', c_uint16), + ('MinorOperatingSystemVersion', c_uint16), + ('MajorImageVersion', c_uint16), + ('MinorImageVersion', c_uint16), + ('MajorSubsystemVersion', c_uint16), + ('MinorSubsystemVersion', c_uint16), + ('Win32VersionValue', c_uint32), + ('SizeOfImage', c_uint32), + ('SizeOfHeaders', c_uint32), + ('CheckSum', c_uint32), + ('Subsystem', c_uint16), + ('DllCharacteristics', c_uint16), + ('SizeOfStackReserve', c_uint64), + ('SizeOfStackCommit', c_uint64), + ('SizeOfHeapReserve', c_uint64), + ('SizeOfHeapCommit', c_uint64), + ('LoaderFlags', c_uint32), + ('NumberOfRvaAndSizes', c_uint32), + ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) + ] + + +class EFI_IMAGE_NT_HEADERS64(LittleEndianStructure): + _fields_ = [ + ('Signature', c_uint32), + ('FileHeader', EFI_IMAGE_FILE_HEADER), + ('OptionalHeader', EFI_IMAGE_OPTIONAL_HEADER64) + ] + + +class EFI_IMAGE_DEBUG_DIRECTORY_ENTRY(LittleEndianStructure): + _fields_ = [ + ('Characteristics', c_uint32), + ('TimeDateStamp', c_uint32), + ('MajorVersion', c_uint16), + ('MinorVersion', c_uint16), + ('Type', c_uint32), + ('SizeOfData', c_uint32), + ('RVA', c_uint32), + ('FileOffset', c_uint32), + ] + + +class EFI_IMAGE_SECTION_HEADER(LittleEndianStructure): + _fields_ = [ + ('Name', ARRAY(c_char, 8)), + ('VirtualSize', c_uint32), + ('VirtualAddress', c_uint32), + ('SizeOfRawData', c_uint32), + ('PointerToRawData', c_uint32), + ('PointerToRelocations', c_uint32), + ('PointerToLinenumbers', c_uint32), + ('NumberOfRelocations', c_uint16), + ('NumberOfLinenumbers', c_uint16), + ('Characteristics', c_uint32), + ] + + +EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b +EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b + +DIRECTORY_DEBUG = 6 + + +image_machine_dict = { + 0x014c: "IA32", + 0x0200: "IPF", + 0x0EBC: "EBC", + 0x8664: "X64", + 0x01c2: "ARM", + 0xAA64: "AArch64", + 0x5032: "RISC32", + 0x5064: "RISC64", + 0x5128: "RISCV128", +} + + +def patch_void_p_to_ctype(patch_type, to_patch): + '''Optionally patch c_void_p in the Structure._fields_''' + if patch_type is None: + return to_patch + + result = [] + for name, c_type in to_patch: + if type(c_type) == type(c_void_p): + result.append((name, c_uint32)) + else: + result.append((name, c_type)) + return result + + +def patch_ctypes(pointer_width=8): + ''' + Pass in the pointer width of the system being debugged. If it is not + the same as c_void_p then patch the _fields_ with the correct type. + For any ctypes Structure that has a c_void_p this function needs to be + called prior to use or sizeof() to initialize _fields_. + ''' + + if sizeof(c_void_p) == pointer_width: + patch_type = None + elif pointer_width == 16: + assert False + elif pointer_width == 8: + patch_type = c_uint64 + elif pointer_width == 4: + patch_type = c_uint32 + else: + raise Exception(f'ERROR: Unkown pointer_width = {pointer_width}') + + # If you add a ctypes Structure class with a c_void_p you need to add + # it to this list. Note: you should use c_void_p for UINTN values. + EFI_LOADED_IMAGE_PROTOCOL._fields_ = patch_void_p_to_ctype( + patch_type, EFI_LOADED_IMAGE_PROTOCOL_fields_) + EFI_DEBUG_IMAGE_INFO_NORMAL._fields_ = patch_void_p_to_ctype( + patch_type, EFI_DEBUG_IMAGE_INFO_NORMAL_fields_) + EFI_DEBUG_IMAGE_INFO._fields_ = patch_void_p_to_ctype( + patch_type, EFI_DEBUG_IMAGE_INFO_fields_) + EFI_DEBUG_IMAGE_INFO_TABLE_HEADER._fields_ = patch_void_p_to_ctype( + patch_type, EFI_DEBUG_IMAGE_INFO_TABLE_HEADER_fields_) + EFI_CONFIGURATION_TABLE._fields_ = patch_void_p_to_ctype( + patch_type, EFI_CONFIGURATION_TABLE_fields_) + EFI_SYSTEM_TABLE._fields_ = patch_void_p_to_ctype( + patch_type, EFI_SYSTEM_TABLE_fields_) + + # patch up anything else that needs to know pointer_width + EfiStatusClass(pointer_width) + + +def ctype_to_str(ctype, indent='', hide_list=[]): + ''' + Given a ctype object print out as a string by walking the _fields_ + in the cstring Class + ''' + result = '' + for field in ctype._fields_: + attr = getattr(ctype, field[0]) + tname = type(attr).__name__ + if field[0] in hide_list: + continue + + result += indent + f'{field[0]} = ' + if tname == 'EFI_GUID': + result += GuidNames.to_name(GuidNames.to_uuid(attr)) + '\n' + elif issubclass(type(attr), Structure): + result += f'{tname}\n' + \ + ctype_to_str(attr, indent + ' ', hide_list) + elif isinstance(attr, int): + result += f'0x{attr:x}\n' + else: + result += f'{attr}\n' + + return result + + +def hexline(addr, data): + hexstr = '' + printable = '' + for i in range(0, len(data)): + hexstr += f'{data[i]:02x} ' + printable += chr(data[i]) if data[i] > 0x20 and data[i] < 0x7f else '.' + return f'{addr:04x} {hexstr:48s} |{printable:s}|' + + +def hexdump(data, indent=''): + if not isinstance(data, bytearray): + data = bytearray(data) + + result = '' + for i in range(0, len(data), 16): + result += indent + hexline(i, data[i:i+16]) + '\n' + return result + + +class EfiTpl: + ''' Return string for EFI_TPL''' + + def __init__(self, tpl): + self.tpl = tpl + + def __str__(self): + if self.tpl < 4: + result = f'{self.tpl:d}' + elif self.tpl < 8: + result = "TPL_APPLICATION" + if self.tpl - 4 > 0: + result += f' + {self.tpl - 4:d}' + elif self.tpl < 16: + result = "TPL_CALLBACK" + if self.tpl - 8 > 0: + result += f' + {self.tpl - 8:d}' + elif self.tpl < 31: + result = "TPL_NOTIFY" + if self.tpl - 16 > 0: + result += f' + {self.tpl - 16:d}' + elif self.tpl == 31: + result = "TPL_HIGH_LEVEL" + else: + result = f'Invalid TPL = {self.tpl:d}' + return result + + +class EfiBootMode: + ''' + Class to return human readable string for EFI_BOOT_MODE + + Methods + ----------- + to_str(boot_mode, default) + return string for boot_mode, and return default if there is not a + match. + ''' + + EFI_BOOT_MODE_dict = { + 0x00: "BOOT_WITH_FULL_CONFIGURATION", + 0x01: "BOOT_WITH_MINIMAL_CONFIGURATION", + 0x02: "BOOT_ASSUMING_NO_CONFIGURATION_CHANGES", + 0x03: "BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS", + 0x04: "BOOT_WITH_DEFAULT_SETTINGS", + 0x05: "BOOT_ON_S4_RESUME", + 0x06: "BOOT_ON_S5_RESUME", + 0x07: "BOOT_WITH_MFG_MODE_SETTINGS", + 0x10: "BOOT_ON_S2_RESUME", + 0x11: "BOOT_ON_S3_RESUME", + 0x12: "BOOT_ON_FLASH_UPDATE", + 0x20: "BOOT_IN_RECOVERY_MODE", + } + + def __init__(self, boot_mode): + self._boot_mode = boot_mode + + def __str__(self): + return self.to_str(self._boot_mode) + + @classmethod + def to_str(cls, boot_mode, default=''): + return cls.EFI_BOOT_MODE_dict.get(boot_mode, default) + + +class EfiStatusClass: + ''' + Class to decode EFI_STATUS to a human readable string. You need to + pass in pointer_width to get the corret value since the EFI_STATUS + code values are different based on the sizeof UINTN. The default is + sizeof(UINTN) == 8. + + Attributes + —————— + _dict_ : dictionary + dictionary of EFI_STATUS that has beed updated to match + pointer_width. + + Methods + ----------- + patch_dictionary(pointer_width) + + to_str(status, default) + ''' + + _dict_ = {} + _EFI_STATUS_UINT32_dict = { + 0: "Success", + 1: "Warning Unknown Glyph", + 2: "Warning Delete Failure", + 3: "Warning Write Failure", + 4: "Warning Buffer Too Small", + 5: "Warning Stale Data", + 6: "Warngin File System", + (0x20000000 | 0): "Warning interrupt source pending", + (0x20000000 | 1): "Warning interrupt source quiesced", + + (0x80000000 | 1): "Load Error", + (0x80000000 | 2): "Invalid Parameter", + (0x80000000 | 3): "Unsupported", + (0x80000000 | 4): "Bad Buffer Size", + (0x80000000 | 5): "Buffer Too Small", + (0x80000000 | 6): "Not Ready", + (0x80000000 | 7): "Device Error", + (0x80000000 | 8): "Write Protected", + (0x80000000 | 9): "Out of Resources", + (0x80000000 | 10): "Volume Corrupt", + (0x80000000 | 11): "Volume Full", + (0x80000000 | 12): "No Media", + (0x80000000 | 13): "Media changed", + (0x80000000 | 14): "Not Found", + (0x80000000 | 15): "Access Denied", + (0x80000000 | 16): "No Response", + (0x80000000 | 17): "No mapping", + (0x80000000 | 18): "Time out", + (0x80000000 | 19): "Not started", + (0x80000000 | 20): "Already started", + (0x80000000 | 21): "Aborted", + (0x80000000 | 22): "ICMP Error", + (0x80000000 | 23): "TFTP Error", + (0x80000000 | 24): "Protocol Error", + (0x80000000 | 25): "Incompatible Version", + (0x80000000 | 26): "Security Violation", + (0x80000000 | 27): "CRC Error", + (0x80000000 | 28): "End of Media", + (0x80000000 | 31): "End of File", + (0x80000000 | 32): "Invalid Language", + (0x80000000 | 33): "Compromised Data", + (0x80000000 | 35): "HTTP Error", + + (0xA0000000 | 0): "Interrupt Pending", + } + + def __init__(self, status=None, pointer_width=8): + self.status = status + # this will convert to 64-bit version if needed + self.patch_dictionary(pointer_width) + + def __str__(self): + return self.to_str(self.status) + + @classmethod + def to_str(cls, status, default=''): + return cls._dict_.get(status, default) + + @classmethod + def patch_dictionary(cls, pointer_width): + '''Patch UINTN upper bits like values ''' + + if cls._dict_: + # only patch the class variable once + return False + + if pointer_width == 4: + cls._dict = cls._EFI_STATUS_UINT32_dict + elif pointer_width == 8: + for key, value in cls._EFI_STATUS_UINT32_dict.items(): + mask = (key & 0xE0000000) << 32 + new_key = (key & 0x1FFFFFFF) | mask + cls._dict_[new_key] = value + return True + else: + return False + + +class GuidNames: + ''' + Class to expose the C names of EFI_GUID's. The _dict_ starts with + common EFI System Table entry EFI_GUID's. _dict_ can get updated with the + build generated Guid.xref file if a path to a module is passed + into add_build_guid_file(). If symbols are loaded for any module + in the build the path the build product should imply the + relative location of that builds Guid.xref file. + + Attributes + ——————---- + _dict_ : dictionary + dictionary of EFI_GUID (uuid) strings to C global names + + Methods + ------- + to_uuid(uuid) + convert a hex UUID string or bytearray to a uuid.UUID + to_name(uuid) + convert a UUID string to a C global constant name. + to_guid(guid_name) + convert a C global constant EFI_GUID name to uuid hex string. + is_guid_str(name) + name is a hex UUID string. + Example: 49152E77-1ADA-4764-B7A2-7AFEFED95E8B + + to_c_guid(value) + convert a uuid.UUID or UUID string to a c_guid string + (see is_c_guid()) + from_c_guid(value) + covert a C guid string to a hex UUID string. + is_c_guid(name) + name is the C initialization value for an EFI_GUID. Example: + { 0x414e6bdd, 0xe47b, 0x47cc, { 0xb2, 0x44, 0xbb, 0x61, + 0x02, 0x0c, 0xf5, 0x16 }} + + add_build_guid_file(module_path, custom_file): + assume module_path is an edk2 build product and load the Guid.xref + file from that build to fill in _dict_. If you know the path and + file name of a custom Guid.xref you can pass it in as custom_file. + + ''' + _dict_ = { # Common EFI System Table values + '05AD34BA-6F02-4214-952E-4DA0398E2BB9': + 'gEfiDxeServicesTableGuid', + '7739F24C-93D7-11D4-9A3A-0090273FC14D': + 'gEfiHobListGuid', + '4C19049F-4137-4DD3-9C10-8B97A83FFDFA': + 'gEfiMemoryTypeInformationGuid', + '49152E77-1ADA-4764-B7A2-7AFEFED95E8B': + 'gEfiDebugImageInfoTableGuid', + '060CC026-4C0D-4DDA-8F41-595FEF00A502': + 'gMemoryStatusCodeRecordGuid', + 'EB9D2D31-2D88-11D3-9A16-0090273FC14D': + 'gEfiSmbiosTableGuid', + 'EB9D2D30-2D88-11D3-9A16-0090273FC14D': + 'gEfiAcpi10TableGuid', + '8868E871-E4F1-11D3-BC22-0080C73C8881': + 'gEfiAcpi20TableGuid', + } + + guid_files = [] + + def __init__(self, uuid=None, pointer_width=8): + self.uuid = None if uuid is None else self.to_uuid(uuid) + + def __str__(self): + if self.uuid is None: + result = '' + for key, value in GuidNames._dict_.items(): + result += f'{key}: {value}\n' + else: + result = self.to_name(self.uuid) + + return result + + @classmethod + def to_uuid(cls, obj): + try: + return uuid.UUID(bytes_le=bytes(obj)) + except (ValueError, TypeError): + try: + return uuid.UUID(bytes_le=obj) + except (ValueError, TypeError): + return uuid.UUID(obj) + + @classmethod + def to_name(cls, uuid): + if not isinstance(uuid, str): + uuid = str(uuid) + if cls.is_c_guid(uuid): + uuid = cls.from_c_guid(uuid) + return cls._dict_.get(uuid.upper(), uuid.upper()) + + @classmethod + def to_guid(cls, guid_name): + for key, value in cls._dict_.items(): + if guid_name == value: + return key.upper() + else: + raise KeyError(key) + + @classmethod + def is_guid_str(cls, name): + if not isinstance(name, str): + return False + return name.count('-') >= 4 + + @classmethod + def to_c_guid(cls, value): + if isinstance(value, uuid.UUID): + guid = value + else: + guid = uuid.UUID(value) + + (data1, data2, data3, + data4_0, data4_1, data4_2, data4_3, + data4_4, data4_5, data4_6, data4_7) = struct.unpack( + '<IHH8B', guid.bytes_le) + return (f'{{ 0x{data1:08X}, 0x{data2:04X}, 0x{data3:04X}, ' + f'{{ 0x{data4_0:02X}, 0x{data4_1:02X}, 0x{data4_2:02X}, ' + f'0x{data4_3:02X}, 0x{data4_4:02X}, 0x{data4_5:02X}, ' + f'0x{data4_6:02X}, 0x{data4_7:02X} }} }}') + + @ classmethod + def from_c_guid(cls, value): + try: + hex = [int(x, 16) for x in re.findall(r"[\w']+", value)] + return (f'{hex[0]:08X}-{hex[1]:04X}-{hex[2]:04X}' + + f'-{hex[3]:02X}{hex[4]:02X}-{hex[5]:02X}{hex[6]:02X}' + + f'{hex[7]:02X}{hex[8]:02X}{hex[9]:02X}{hex[10]:02X}') + except ValueError: + return value + + @ classmethod + def is_c_guid(cls, name): + if not isinstance(name, str): + return False + return name.count('{') == 2 and name.count('}') == 2 + + @ classmethod + def add_build_guid_file(cls, module_path, custom_file=None): + if custom_file is not None: + xref = custom_file + else: + # module_path will look like: + # <repo>/Build/OvmfX64/DEBUG_XCODE5/X64/../DxeCore.dll + # Walk backwards looking for a toolchain like name. + # Then look for GUID database: + # Build/OvmfX64//DEBUG_XCODE5/FV/Guid.xref + for i in reversed(module_path.split(os.sep)): + if (i.startswith('DEBUG_') or + i.startswith('RELEASE_') or + i.startswith('NOOPT_')): + build_root = os.path.join( + module_path.rsplit(i, 1)[0], i) + break + + xref = os.path.join(build_root, 'FV', 'Guid.xref') + + if xref in cls.guid_files: + # only processes the file one time + return True + + with open(xref) as f: + content = f.readlines() + cls.guid_files.append(xref) + + for lines in content: + try: + if cls.is_guid_str(lines): + # a regex would be more pedantic + words = lines.split() + cls._dict_[words[0].upper()] = words[1].strip('\n') + except ValueError: + pass + + return True + + return False + + +class EFI_HOB_GENERIC_HEADER(LittleEndianStructure): + _fields_ = [ + ('HobType', c_uint16), + ('HobLength', c_uint16), + ('Reserved', c_uint32) + ] + + +class EFI_HOB_HANDOFF_INFO_TABLE(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Version', c_uint32), + ('BootMode', c_uint32), + ('EfiMemoryTop', c_uint64), + ('EfiMemoryBottom', c_uint64), + ('EfiFreeMemoryTop', c_uint64), + ('EfiFreeMemoryBottom', c_uint64), + ('EfiEndOfHobList', c_uint64), + ] + + +class EFI_HOB_MEMORY_ALLOCATION(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Name', EFI_GUID), + ('MemoryBaseAddress', c_uint64), + ('MemoryLength', c_uint64), + ('MemoryType', c_uint32), + ('Reserved', c_uint32), + ] + + +class EFI_HOB_RESOURCE_DESCRIPTOR(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Owner', EFI_GUID), + ('ResourceType', c_uint32), + ('ResourceAttribute', c_uint32), + ('PhysicalStart', c_uint64), + ('ResourceLength', c_uint64), + ] + + +class EFI_HOB_GUID_TYPE(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('Name', EFI_GUID), + ] + + +class EFI_HOB_FIRMWARE_VOLUME(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ] + + +class EFI_HOB_CPU(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('SizeOfMemorySpace', c_uint8), + ('SizeOfIoSpace', c_uint8), + ('Reserved', ARRAY(c_uint8, 6)), + ] + + +class EFI_HOB_MEMORY_POOL(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ] + + +class EFI_HOB_FIRMWARE_VOLUME2(LittleEndianStructure): + _fields_ = [ + ('Header', EFI_HOB_GENERIC_HEADER), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ('FvName', EFI_GUID), + ('FileName', EFI_GUID) + ] + + +class EFI_HOB_FIRMWARE_VOLUME3(LittleEndianStructure): + _fields_ = [ + ('HobType', c_uint16), + ('HobLength', c_uint16), + ('Reserved', c_uint32), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ('AuthenticationStatus', c_uint32), + ('ExtractedFv', c_uint8), + ('FvName', EFI_GUID), + ('FileName', EFI_GUID), + ] + + +class EFI_HOB_UEFI_CAPSULE(LittleEndianStructure): + _fields_ = [ + ('HobType', c_uint16), + ('HobLength', c_uint16), + ('Reserved', c_uint32), + ('BaseAddress', c_uint64), + ('Length', c_uint64), + ] + + +class EfiHob: + ''' + Parse EFI Device Paths based on the edk2 C Structures defined above. + In the context of this class verbose means hexdump extra data. + + + Attributes + —————— + Hob : list + List of HOBs. Each entry contains the name, HOB type, HOB length, + the ctype struct for the HOB, and any extra data. + + Methods + ----------- + get_hob_by_type(hob_type) + return string that decodes the HOBs of hob_type. If hob_type is + None then return all HOBs. + ''' + + Hob = [] + verbose = False + + hob_dict = { + 1: EFI_HOB_HANDOFF_INFO_TABLE, + 2: EFI_HOB_MEMORY_ALLOCATION, + 3: EFI_HOB_RESOURCE_DESCRIPTOR, + 4: EFI_HOB_GUID_TYPE, + 5: EFI_HOB_FIRMWARE_VOLUME, + 6: EFI_HOB_CPU, + 7: EFI_HOB_MEMORY_POOL, + 9: EFI_HOB_FIRMWARE_VOLUME2, + 0xb: EFI_HOB_UEFI_CAPSULE, + 0xc: EFI_HOB_FIRMWARE_VOLUME3, + 0xffff: EFI_HOB_GENERIC_HEADER, + } + + def __init__(self, file, address=None, verbose=False, count=1000): + self._file = file + EfiHob.verbose = verbose + + if len(EfiHob.Hob) != 0 and address is None: + return + + if address is not None: + hob_ptr = address + else: + hob_ptr = EfiConfigurationTable(file).GetConfigTable( + '7739F24C-93D7-11D4-9A3A-0090273FC14D') + + self.read_hobs(hob_ptr) + + @ classmethod + def __str__(cls): + return cls.get_hob_by_type(None) + + @ classmethod + def get_hob_by_type(cls, hob_type): + result = "" + for (Name, HobType, HobLen, chob, extra) in cls.Hob: + if hob_type is not None: + if hob_type != HobType: + continue + + result += f'Type: {Name:s} (0x{HobType:01x}) Len: 0x{HobLen:03x}\n' + result += ctype_to_str(chob, ' ', ['Reserved']) + if cls.verbose: + if extra is not None: + result += hexdump(extra, ' ') + + return result + + def read_hobs(self, hob_ptr, count=1000): + if hob_ptr is None: + return + + try: + for _ in range(count): # while True + hdr, _ = self._ctype_read_ex(EFI_HOB_GENERIC_HEADER, hob_ptr) + if hdr.HobType == 0xffff: + break + + type_str = self.hob_dict.get( + hdr.HobType, EFI_HOB_GENERIC_HEADER) + hob, extra = self._ctype_read_ex( + type_str, hob_ptr, hdr.HobLength) + EfiHob.Hob.append( + (type(hob).__name__, + hdr.HobType, + hdr.HobLength, + hob, + extra)) + hob_ptr += hdr.HobLength + except ValueError: + pass + + def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None): + if offset != 0: + self._file.seek(offset) + + type_size = sizeof(ctype_struct) + size = rsize if rsize else type_size + data = self._file.read(size) + cdata = ctype_struct.from_buffer(bytearray(data)) + + if size > type_size: + return cdata, data[type_size:] + else: + return cdata, None + + +class EFI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Type', c_uint8), + ('SubType', c_uint8), + + # UINT8 Length[2] + # Cheat and use c_uint16 since we don't care about alignment + ('Length', c_uint16) + ] + + +class PCI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Function', c_uint8), + ('Device', c_uint8) + ] + + +class PCCARD_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('FunctionNumber', c_uint8), + ] + + +class MEMMAP_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('StartingAddress', c_uint64), + ('EndingAddress', c_uint64), + ] + + +class VENDOR_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Guid', EFI_GUID), + ] + + +class CONTROLLER_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('ControllerNumber', c_uint32), + ] + + +class BMC_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('InterfaceType', c_uint8), + ('BaseAddress', ARRAY(c_uint8, 8)), + ] + + +class BBS_BBS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('DeviceType', c_uint16), + ('StatusFlag', c_uint16) + ] + + +class ACPI_HID_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('HID', c_uint32), + ('UID', c_uint32) + ] + + +class ACPI_EXTENDED_HID_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('HID', c_uint32), + ('UID', c_uint32), + ('CID', c_uint32) + ] + + +class ACPI_ADR_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('ARD', c_uint32) + ] + + +class ACPI_NVDIMM_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('NFITDeviceHandle', c_uint32) + ] + + +class ATAPI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("PrimarySecondary", c_uint8), + ("SlaveMaster", c_uint8), + ("Lun", c_uint16) + ] + + +class SCSI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Pun", c_uint16), + ("Lun", c_uint16) + ] + + +class FIBRECHANNEL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Reserved", c_uint32), + ("WWN", c_uint64), + ("Lun", c_uint64) + ] + + +class F1394_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Reserved", c_uint32), + ("Guid", c_uint64) + ] + + +class USB_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("ParentPortNumber", c_uint8), + ("InterfaceNumber", c_uint8), + ] + + +class I2O_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Tid", c_uint32) + ] + + +class INFINIBAND_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("ResourceFlags", c_uint32), + ("PortGid", ARRAY(c_uint8, 16)), + ("ServiceId", c_uint64), + ("TargetPortId", c_uint64), + ("DeviceId", c_uint64) + ] + + +class UART_FLOW_CONTROL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Guid", EFI_GUID), + ("FlowControlMap", c_uint32) + ] + + +class SAS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Guid", EFI_GUID), + ("Reserved", c_uint32), + ("SasAddress", c_uint64), + ("Lun", c_uint64), + ("DeviceTopology", c_uint16), + ("RelativeTargetPort", c_uint16) + ] + + +class EFI_MAC_ADDRESS(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("Addr", ARRAY(c_uint8, 32)), + ] + + +class MAC_ADDR_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('MacAddress', EFI_MAC_ADDRESS), + ('IfType', c_uint8) + ] + + +class IPv4_ADDRESS(LittleEndianStructure): + _fields_ = [ + ("Addr", ARRAY(c_uint8, 4)), + ] + + +class IPv4_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('LocalIpAddress', IPv4_ADDRESS), + ('RemoteIpAddress', IPv4_ADDRESS), + ('LocalPort', c_uint16), + ('RemotePort', c_uint16), + ('Protocol', c_uint16), + ('StaticIpAddress', c_uint8), + ('GatewayIpAddress', IPv4_ADDRESS), + ('SubnetMask', IPv4_ADDRESS) + ] + + +class IPv6_ADDRESS(LittleEndianStructure): + _fields_ = [ + ("Addr", ARRAY(c_uint8, 16)), + ] + + +class IPv6_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('LocalIpAddress', IPv6_ADDRESS), + ('RemoteIpAddress', IPv6_ADDRESS), + ('LocalPort', c_uint16), + ('RemotePort', c_uint16), + ('Protocol', c_uint16), + ('IpAddressOrigin', c_uint8), + ('PrefixLength', c_uint8), + ('GatewayIpAddress', IPv6_ADDRESS) + ] + + +class UART_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Reserved', c_uint32), + ('BaudRate', c_uint64), + ('DataBits', c_uint8), + ('Parity', c_uint8), + ('StopBits', c_uint8) + ] + + +class USB_CLASS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('VendorId', c_uint16), + ('ProductId', c_uint16), + ('DeviceClass', c_uint8), + ('DeviceCSjblass', c_uint8), + ('DeviceProtocol', c_uint8), + ] + + +class USB_WWID_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('InterfaceNumber', c_uint16), + ('VendorId', c_uint16), + ('ProductId', c_uint16), + ] + + +class DEVICE_LOGICAL_UNIT_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Lun', c_uint8) + ] + + +class SATA_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('HBAPortNumber', c_uint16), + ('PortMultiplierPortNumber', c_uint16), + ('Lun', c_uint16), + ] + + +class ISCSI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('NetworkProtocol', c_uint16), + ('LoginOption', c_uint16), + ('Lun', c_uint64), + ('TargetPortalGroupTag', c_uint16), + ] + + +class VLAN_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("VlandId", c_uint16) + ] + + +class FIBRECHANNELEX_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Reserved", c_uint16), + ("WWN", ARRAY(c_uint8, 8)), + ("Lun", ARRAY(c_uint8, 8)), + ] + + +class SASEX_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SasAddress", ARRAY(c_uint8, 8)), + ("Lun", ARRAY(c_uint8, 8)), + ("DeviceTopology", c_uint16), + ("RelativeTargetPort", c_uint16) + ] + + +class NVME_NAMESPACE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("NamespaceId", c_uint32), + ("NamespaceUuid", c_uint64) + ] + + +class DNS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("IsIPv6", c_uint8), + ("DnsServerIp", IPv6_ADDRESS) + + ] + + +class UFS_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Pun", c_uint8), + ("Lun", c_uint8), + ] + + +class SD_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SlotNumber", c_uint8) + ] + + +class BLUETOOTH_ADDRESS(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("Address", ARRAY(c_uint8, 6)) + ] + + +class BLUETOOTH_LE_ADDRESS(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("Format", c_uint8), + ("Class", c_uint16) + ] + + +class BLUETOOTH_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("BD_ADDR", BLUETOOTH_ADDRESS) + ] + + +class WIFI_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SSId", ARRAY(c_uint8, 32)) + ] + + +class EMMC_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("SlotNumber", c_uint8) + ] + + +class BLUETOOTH_LE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("BD_ADDR", BLUETOOTH_LE_ADDRESS) + ] + + +class NVDIMM_NAMESPACE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("Uuid", EFI_GUID) + ] + + +class REST_SERVICE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("RESTService", c_uint8), + ("AccessMode", c_uint8) + ] + + +class REST_VENDOR_SERVICE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ("RESTService", c_uint8), + ("AccessMode", c_uint8), + ("Guid", EFI_GUID), + ] + + +class HARDDRIVE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('PartitionNumber', c_uint32), + ('PartitionStart', c_uint64), + ('PartitionSize', c_uint64), + ('Signature', ARRAY(c_uint8, 16)), + ('MBRType', c_uint8), + ('SignatureType', c_uint8) + ] + + +class CDROM_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('BootEntry', c_uint32), + ('PartitionStart', c_uint64), + ('PartitionSize', c_uint64) + ] + + +class MEDIA_PROTOCOL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Protocol', EFI_GUID) + ] + + +class MEDIA_FW_VOL_FILEPATH_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('FvFileName', EFI_GUID) + ] + + +class MEDIA_FW_VOL_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('FvName', EFI_GUID) + ] + + +class MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('Reserved', c_uint32), + ('StartingOffset', c_uint64), + ('EndingOffset', c_uint64) + ] + + +class MEDIA_RAM_DISK_DEVICE_PATH(LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ('Header', EFI_DEVICE_PATH), + ('StartingAddr', c_uint64), + ('EndingAddr', c_uint64), + ('TypeGuid', EFI_GUID), + ('Instance', c_uint16) + ] + + +class EfiDevicePath: + ''' + Parse EFI Device Paths based on the edk2 C Structures defined above. + In the context of this class verbose means hexdump extra data. + + + Attributes + —————— + DevicePath : list + List of devixe path instances. Each instance is a list of nodes + for the given Device Path instance. + + Methods + ----------- + device_path_node(address) + return the Device Path ctype hdr, ctype, and any extra data in + the Device Path node. This is just a single Device Path node, + not the entire Device Path. + device_path_node_str(address) + return the device path node (not the entire Device Path) as a string + ''' + + DevicePath = [] + + device_path_dict = { + # ( Type, SubType ) : Device Path C typedef + # HARDWARE_DEVICE_PATH + (1, 1): PCI_DEVICE_PATH, + (1, 2): PCCARD_DEVICE_PATH, + (1, 3): MEMMAP_DEVICE_PATH, + (1, 4): VENDOR_DEVICE_PATH, + (1, 5): CONTROLLER_DEVICE_PATH, + (1, 6): BMC_DEVICE_PATH, + + # ACPI_DEVICE_PATH + (2, 1): ACPI_HID_DEVICE_PATH, + (2, 2): ACPI_EXTENDED_HID_DEVICE_PATH, + (2, 3): ACPI_ADR_DEVICE_PATH, + (2, 4): ACPI_NVDIMM_DEVICE_PATH, + + # MESSAGING_DEVICE_PATH + (3, 1): ATAPI_DEVICE_PATH, + (3, 2): SCSI_DEVICE_PATH, + (3, 3): FIBRECHANNEL_DEVICE_PATH, + (3, 4): F1394_DEVICE_PATH, + (3, 5): USB_DEVICE_PATH, + (3, 6): I2O_DEVICE_PATH, + + (3, 9): INFINIBAND_DEVICE_PATH, + (3, 10): VENDOR_DEVICE_PATH, + (3, 11): MAC_ADDR_DEVICE_PATH, + (3, 12): IPv4_DEVICE_PATH, + (3, 13): IPv6_DEVICE_PATH, + (3, 14): UART_DEVICE_PATH, + (3, 15): USB_CLASS_DEVICE_PATH, + (3, 16): USB_WWID_DEVICE_PATH, + (3, 17): DEVICE_LOGICAL_UNIT_DEVICE_PATH, + (3, 18): SATA_DEVICE_PATH, + (3, 19): ISCSI_DEVICE_PATH, + (3, 20): VLAN_DEVICE_PATH, + (3, 21): FIBRECHANNELEX_DEVICE_PATH, + (3, 22): SASEX_DEVICE_PATH, + (3, 23): NVME_NAMESPACE_DEVICE_PATH, + (3, 24): DNS_DEVICE_PATH, + (3, 25): UFS_DEVICE_PATH, + (3, 26): SD_DEVICE_PATH, + (3, 27): BLUETOOTH_DEVICE_PATH, + (3, 28): WIFI_DEVICE_PATH, + (3, 29): EMMC_DEVICE_PATH, + (3, 30): BLUETOOTH_LE_DEVICE_PATH, + (3, 31): DNS_DEVICE_PATH, + (3, 32): NVDIMM_NAMESPACE_DEVICE_PATH, + + (3, 33): REST_SERVICE_DEVICE_PATH, + (3, 34): REST_VENDOR_SERVICE_DEVICE_PATH, + + # MEDIA_DEVICE_PATH + (4, 1): HARDDRIVE_DEVICE_PATH, + (4, 2): CDROM_DEVICE_PATH, + (4, 3): VENDOR_DEVICE_PATH, + (4, 4): EFI_DEVICE_PATH, + (4, 5): MEDIA_PROTOCOL_DEVICE_PATH, + (4, 6): MEDIA_FW_VOL_FILEPATH_DEVICE_PATH, + (4, 7): MEDIA_FW_VOL_DEVICE_PATH, + (4, 8): MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH, + (4, 9): MEDIA_RAM_DISK_DEVICE_PATH, + + # BBS_DEVICE_PATH + (5, 1): BBS_BBS_DEVICE_PATH, + + } + + guid_override_dict = { + uuid.UUID('37499A9D-542F-4C89-A026-35DA142094E4'): + UART_FLOW_CONTROL_DEVICE_PATH, + uuid.UUID('D487DDB4-008B-11D9-AFDC-001083FFCA4D'): + SAS_DEVICE_PATH, + } + + def __init__(self, file, ptr=None, verbose=False, count=64): + ''' + Convert ptr into a list of Device Path nodes. If verbose also hexdump + extra data. + ''' + self._file = file + self._verbose = verbose + if ptr is None: + return + + try: + instance = [] + for _ in range(count): # while True + hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, ptr) + if hdr.Length < sizeof(EFI_DEVICE_PATH): + # Not a valid device path + break + + if hdr.Type == 0x7F: # END_DEVICE_PATH_TYPE + self.DevicePath.append(instance) + if hdr.SubType == 0xFF: # END_ENTIRE_DEVICE_PATH_SUBTYPE + break + if hdr.SubType == 0x01: # END_INSTANCE_DEVICE_PATH_SUBTYPE + # start new device path instance + instance = [] + + type_str = self.device_path_dict.get( + (hdr.Type, hdr.SubType), EFI_DEVICE_PATH) + node, extra = self._ctype_read_ex(type_str, ptr, hdr.Length) + if 'VENDOR_DEVICE_PATH' in type(node).__name__: + guid_type = self.guid_override_dict.get( + GuidNames.to_uuid(node.Guid), None) + if guid_type: + # use the ctype associated with the GUID + node, extra = self._ctype_read_ex( + guid_type, ptr, hdr.Length) + + instance.append((type(node).__name__, hdr.Type, + hdr.SubType, hdr.Length, node, extra)) + ptr += hdr.Length + except ValueError: + pass + + def __str__(self): + ''' ''' + if not self.valid(): + return '<class: EfiDevicePath>' + + result = "" + for instance in self.DevicePath: + for (Name, Type, SubType, Length, cnode, extra) in instance: + result += f'{Name:s} {Type:2d}:{SubType:2d} Len: {Length:3d}\n' + result += ctype_to_str(cnode, ' ', ['Reserved']) + if self._verbose: + if extra is not None: + result += hexdump(extra, ' ') + result += '\n' + + return result + + def valid(self): + return True if self.DevicePath else False + + def device_path_node(self, address): + try: + hdr, _ = self._ctype_read_ex(EFI_DEVICE_PATH, address) + if hdr.Length < sizeof(EFI_DEVICE_PATH): + return None, None, None + + type_str = self.device_path_dict.get( + (hdr.Type, hdr.SubType), EFI_DEVICE_PATH) + cnode, extra = self._ctype_read_ex(type_str, address, hdr.Length) + return hdr, cnode, extra + except ValueError: + return None, None, None + + def device_path_node_str(self, address, verbose=False): + hdr, cnode, extra = self.device_path_node(address) + if hdr is None: + return '' + + cname = type(cnode).__name__ + result = f'{cname:s} {hdr.Type:2d}:{hdr.SubType:2d} ' + result += f'Len: 0x{hdr.Length:03x}\n' + result += ctype_to_str(cnode, ' ', ['Reserved']) + if verbose: + if extra is not None: + result += hexdump(extra, ' ') + + return result + + def _ctype_read_ex(self, ctype_struct, offset=0, rsize=None): + if offset != 0: + self._file.seek(offset) + + type_size = sizeof(ctype_struct) + size = rsize if rsize else type_size + data = self._file.read(size) + if data is None: + return None, None + + cdata = ctype_struct.from_buffer(bytearray(data)) + + if size > type_size: + return cdata, data[type_size:] + else: + return cdata, None + + +class EfiConfigurationTable: + ''' + A class to abstract EFI Configuration Tables from gST->ConfigurationTable + and gST->NumberOfTableEntries. Pass in the gST pointer from EFI, + likely you need to look up this address after you have loaded symbols + + Attributes + —————— + ConfigurationTableDict : dictionary + dictionary of EFI Configuration Table entries + + Methods + ----------- + GetConfigTable(uuid) + pass in VendorGuid and return VendorTable from EFI System Table + DebugImageInfo(table) + return tuple of load address and size of PE/COFF images + ''' + + ConfigurationTableDict = {} + + def __init__(self, file, gST_addr=None): + self._file = file + if gST_addr is None: + # ToDo add code to search for gST via EFI_SYSTEM_TABLE_POINTER + return + + gST = self._ctype_read(EFI_SYSTEM_TABLE, gST_addr) + self.read_efi_config_table(gST.NumberOfTableEntries, + gST.ConfigurationTable, + self._ctype_read) + + @ classmethod + def __str__(cls): + '''return EFI_CONFIGURATION_TABLE entries as a string''' + result = "" + for key, value in cls.ConfigurationTableDict.items(): + result += f'{GuidNames().to_name(key):>37s}: ' + result += f'VendorTable = 0x{value:08x}\n' + + return result + + def _ctype_read(self, ctype_struct, offset=0): + '''ctype worker function to read data''' + if offset != 0: + self._file.seek(offset) + + data = self._file.read(sizeof(ctype_struct)) + return ctype_struct.from_buffer(bytearray(data)) + + @ classmethod + def read_efi_config_table(cls, table_cnt, table_ptr, ctype_read): + '''Create a dictionary of EFI Configuration table entries''' + EmptryTables = EFI_CONFIGURATION_TABLE * table_cnt + Tables = ctype_read(EmptryTables, table_ptr) + for i in range(table_cnt): + cls.ConfigurationTableDict[str(GuidNames.to_uuid( + Tables[i].VendorGuid)).upper()] = Tables[i].VendorTable + + return cls.ConfigurationTableDict + + def GetConfigTable(self, uuid): + ''' Return VendorTable for VendorGuid (uuid.UUID) or None''' + return self.ConfigurationTableDict.get(uuid.upper()) + + def DebugImageInfo(self, table=None): + ''' + Walk the debug image info table to find the LoadedImage protocols + for all the loaded PE/COFF images and return a list of load address + and image size. + ''' + ImageLoad = [] + + if table is None: + table = self.GetConfigTable('49152e77-1ada-4764-b7a2-7afefed95e8b') + + DbgInfoHdr = self._ctype_read(EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, table) + NormalImageArray = EFI_DEBUG_IMAGE_INFO * DbgInfoHdr.TableSize + NormalImageArray = self._ctype_read( + NormalImageArray, DbgInfoHdr.EfiDebugImageInfoTable) + for i in range(DbgInfoHdr.TableSize): + ImageInfo = self._ctype_read( + EFI_DEBUG_IMAGE_INFO_NORMAL, NormalImageArray[i].NormalImage) + LoadedImage = self._ctype_read( + EFI_LOADED_IMAGE_PROTOCOL, + ImageInfo.LoadedImageProtocolInstance) + ImageLoad.append((LoadedImage.ImageBase, LoadedImage.ImageSize)) + + return ImageLoad + + +class PeTeImage: + ''' + A class to abstract PE/COFF or TE image processing via passing in a + Python file like object. If you pass in an address the PE/COFF is parsed, + if you pass in NULL for an address then you get a class instance you can + use to search memory for a PE/COFF hader given a pc value. + + Attributes + —————— + LoadAddress : int + Load address of the PE/COFF image + AddressOfEntryPoint : int + Address of the Entry point of the PE/COFF image + TextAddress : int + Start of the PE/COFF text section + DataAddress : int + Start of the PE/COFF data section + CodeViewPdb : str + File name of the symbols file + CodeViewUuid : uuid:UUID + GUID for "RSDS" Debug Directory entry, or Mach-O UUID for "MTOC" + + Methods + ----------- + pcToPeCoff(address, step, max_range, rom_range) + Given an address(pc) find the PE/COFF image it is in + sections_to_str() + return a string giving info for all the PE/COFF sections + ''' + + def __init__(self, file, address=0): + self._file = file + + # book keeping, but public + self.PeHdr = None + self.TeHdr = None + self.Machine = None + self.Subsystem = None + self.CodeViewSig = None + self.e_lfanew = 0 + self.NumberOfSections = 0 + self.Sections = None + + # Things debuggers may want to know + self.LoadAddress = 0 if address is None else address + self.EndLoadAddress = 0 + self.AddressOfEntryPoint = 0 + self.TextAddress = 0 + self.DataAddress = 0 + self.CodeViewPdb = None + self.CodeViewUuid = None + self.TeAdjust = 0 + + self.dir_name = { + 0: 'Export Table', + 1: 'Import Table', + 2: 'Resource Table', + 3: 'Exception Table', + 4: 'Certificate Table', + 5: 'Relocation Table', + 6: 'Debug', + 7: 'Architecture', + 8: 'Global Ptr', + 9: 'TLS Table', + 10: 'Load Config Table', + 11: 'Bound Import', + 12: 'IAT', + 13: 'Delay Import Descriptor', + 14: 'CLR Runtime Header', + 15: 'Reserved', + } + + if address is not None: + if self.maybe(): + self.parse() + + def __str__(self): + if self.PeHdr is None and self.TeHdr is None: + # no PE/COFF header found + return "<class: PeTeImage>" + + if self.CodeViewPdb: + pdb = f'{self.Machine}`{self.CodeViewPdb}' + else: + pdb = 'No Debug Info:' + + if self.CodeViewUuid: + guid = f'{self.CodeViewUuid}:' + else: + guid = '' + + slide = f'slide = {self.TeAdjust:d} ' if self.TeAdjust != 0 else ' ' + res = guid + f'{pdb} load = 0x{self.LoadAddress:08x} ' + slide + return res + + def _seek(self, offset): + """ + seek() relative to start of PE/COFF (TE) image + """ + self._file.seek(self.LoadAddress + offset) + + def _read_offset(self, size, offset=None): + """ + read() relative to start of PE/COFF (TE) image + if offset is not None then seek() before the read + """ + if offset is not None: + self._seek(offset) + + return self._file.read(size) + + def _read_ctype(self, ctype_struct, offset=None): + data = self._read_offset(sizeof(ctype_struct), offset) + return ctype_struct.from_buffer(bytearray(data), 0) + + def _unsigned(self, i): + """return a 32-bit unsigned int (UINT32) """ + return int.from_bytes(i, byteorder='little', signed=False) + + def pcToPeCoff(self, + address, + step=None, + max_range=None, + rom_range=[0xFE800000, 0xFFFFFFFF]): + """ + Given an address search backwards for PE/COFF (TE) header + For DXE 4K is probably OK + For PEI you might have to search every 4 bytes. + """ + if step is None: + step = 0x1000 + + if max_range is None: + max_range = 0x200000 + + if address in range(*rom_range): + # The XIP code in the ROM ends up 4 byte aligned. + step = 4 + max_range = min(max_range, 0x100000) + + # Align address to page boundary for memory image search. + address = address & ~(step-1) + # Search every step backward + offset_range = list(range(0, min(max_range, address), step)) + for offset in offset_range: + if self.maybe(address - offset): + if self.parse(): + return True + + return False + + def maybe(self, offset=None): + """Probe to see if this offset is likely a PE/COFF or TE file """ + self.LoadAddress = 0 + e_magic = self._read_offset(2, offset) + header_ok = e_magic == b'MZ' or e_magic == b'VZ' + if offset is not None and header_ok: + self.LoadAddress = offset + return header_ok + + def parse(self): + """Parse PE/COFF (TE) debug directory entry """ + DosHdr = self._read_ctype(EFI_IMAGE_DOS_HEADER, 0) + if DosHdr.e_magic == self._unsigned(b'VZ'): + # TE image + self.TeHdr = self._read_ctype(EFI_TE_IMAGE_HEADER, 0) + + self.TeAdjust = sizeof(self.TeHdr) - self.TeHdr.StrippedSize + self.Machine = image_machine_dict.get(self.TeHdr.Machine, None) + self.Subsystem = self.TeHdr.Subsystem + self.AddressOfEntryPoint = self.TeHdr.AddressOfEntryPoint + + debug_dir_size = self.TeHdr.DataDirectoryDebug.Size + debug_dir_offset = (self.TeAdjust + + self.TeHdr.DataDirectoryDebug.VirtualAddress) + else: + if DosHdr.e_magic == self._unsigned(b'MZ'): + self.e_lfanew = DosHdr.e_lfanew + else: + self.e_lfanew = 0 + + self.PeHdr = self._read_ctype( + EFI_IMAGE_NT_HEADERS64, self.e_lfanew) + if self.PeHdr.Signature != self._unsigned(b'PE\0\0'): + return False + + if self.PeHdr.OptionalHeader.Magic == \ + EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: + self.PeHdr = self._read_ctype( + EFI_IMAGE_NT_HEADERS32, self.e_lfanew) + + if self.PeHdr.OptionalHeader.NumberOfRvaAndSizes <= \ + DIRECTORY_DEBUG: + return False + + self.Machine = image_machine_dict.get( + self.PeHdr.FileHeader.Machine, None) + self.Subsystem = self.PeHdr.OptionalHeader.Subsystem + self.AddressOfEntryPoint = \ + self.PeHdr.OptionalHeader.AddressOfEntryPoint + self.TeAdjust = 0 + + debug_dir_size = self.PeHdr.OptionalHeader.DataDirectory[ + DIRECTORY_DEBUG].Size + debug_dir_offset = self.PeHdr.OptionalHeader.DataDirectory[ + DIRECTORY_DEBUG].VirtualAddress + + if self.Machine is None or self.Subsystem not in [0, 10, 11, 12]: + return False + + self.AddressOfEntryPoint += self.LoadAddress + + self.sections() + return self.processDebugDirEntry(debug_dir_offset, debug_dir_size) + + def sections(self): + '''Parse the PE/COFF (TE) section table''' + if self.Sections is not None: + return + elif self.TeHdr is not None: + self.NumberOfSections = self.TeHdr.NumberOfSections + offset = sizeof(EFI_TE_IMAGE_HEADER) + elif self.PeHdr is not None: + self.NumberOfSections = self.PeHdr.FileHeader.NumberOfSections + offset = sizeof(c_uint32) + \ + sizeof(EFI_IMAGE_FILE_HEADER) + offset += self.PeHdr.FileHeader.SizeOfOptionalHeader + offset += self.e_lfanew + else: + return + + self.Sections = EFI_IMAGE_SECTION_HEADER * self.NumberOfSections + self.Sections = self._read_ctype(self.Sections, offset) + + for i in range(self.NumberOfSections): + name = str(self.Sections[i].Name, 'ascii', 'ignore') + addr = self.Sections[i].VirtualAddress + addr += self.LoadAddress + self.TeAdjust + if name == '.text': + self.TextAddress = addr + elif name == '.data': + self.DataAddress = addr + + end_addr = addr + self.Sections[i].VirtualSize - 1 + if end_addr > self.EndLoadAddress: + self.EndLoadAddress = end_addr + + def sections_to_str(self): + # return text summary of sections + # name virt addr (virt size) flags:Characteristics + result = '' + for i in range(self.NumberOfSections): + name = str(self.Sections[i].Name, 'ascii', 'ignore') + result += f'{name:8s} ' + result += f'0x{self.Sections[i].VirtualAddress:08X} ' + result += f'(0x{self.Sections[i].VirtualSize:05X}) ' + result += f'flags:0x{self.Sections[i].Characteristics:08X}\n' + + return result + + def directory_to_str(self): + result = '' + if self.TeHdr: + debug_size = self.TeHdr.DataDirectoryDebug.Size + if debug_size > 0: + debug_offset = (self.TeAdjust + + self.TeHdr.DataDirectoryDebug.VirtualAddress) + result += f"Debug 0x{debug_offset:08X} 0x{debug_size}\n" + + relocation_size = self.TeHdr.DataDirectoryBaseReloc.Size + if relocation_size > 0: + relocation_offset = ( + self.TeAdjust + + self.TeHdr.DataDirectoryBaseReloc.VirtualAddress) + result += f'Relocation 0x{relocation_offset:08X} ' + result += f' 0x{relocation_size}\n' + + elif self.PeHdr: + for i in range(self.PeHdr.OptionalHeader.NumberOfRvaAndSizes): + size = self.PeHdr.OptionalHeader.DataDirectory[i].Size + if size == 0: + continue + + virt_addr = self.PeHdr.OptionalHeader.DataDirectory[ + i].VirtualAddress + name = self.dir_name.get(i, '?') + result += f'{name:s} 0x{virt_addr:08X} 0x{size:X}\n' + + return result + + def processDebugDirEntry(self, virt_address, virt_size): + """Process PE/COFF Debug Directory Entry""" + if (virt_address == 0 or + virt_size < sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)): + return False + + data = bytearray(self._read_offset(virt_size, virt_address)) + for offset in range(0, + virt_size, + sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)): + DirectoryEntry = EFI_IMAGE_DEBUG_DIRECTORY_ENTRY.from_buffer( + data[offset:]) + if DirectoryEntry.Type != 2: + continue + + entry = self._read_offset( + DirectoryEntry.SizeOfData, DirectoryEntry.RVA + self.TeAdjust) + self.CodeViewSig = entry[:4] + if self.CodeViewSig == b'MTOC': + self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16]) + PdbOffset = 20 + elif self.CodeViewSig == b'RSDS': + self.CodeViewUuid = uuid.UUID(bytes_le=entry[4:4+16]) + PdbOffset = 24 + elif self.CodeViewSig == b'NB10': + PdbOffset = 16 + else: + continue + + # can't find documentation about Pdb string encoding? + # guessing utf-8 since that will match file systems in macOS + # and Linux Windows is UTF-16, or ANSI adjusted for local. + # We might need a different value for Windows here? + self.CodeViewPdb = entry[PdbOffset:].split(b'\x00')[ + 0].decode('utf-8') + return True + return False + + +def main(): + '''Process arguments as PE/COFF files''' + for fname in sys.argv[1:]: + with open(fname, 'rb') as f: + image = PeTeImage(f) + print(image) + res = f'EntryPoint = 0x{image.AddressOfEntryPoint:08x} ' + res += f'TextAddress = 0x{image.TextAddress:08x} ' + res += f'DataAddress = 0x{image.DataAddress:08x}' + print(res) + print(image.sections_to_str()) + print('Data Directories:') + print(image.directory_to_str()) + + +if __name__ == "__main__": + main() -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print 2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran 2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran @ 2022-03-21 20:20 ` Rebecca Cran 2022-03-26 14:05 ` Bob Feng 2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran 2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran 3 siblings, 1 reply; 10+ messages in thread From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw) To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng, Liming Gao, Yuwei Chen Cc: Rebecca Cran, Andrew Fish https://bugzilla.tianocore.org/show_bug.cgi?id=3500 Use efi_debugging.py Python Classes to implement EFI gdb commands: (gdb) help efi Commands for debugging EFI. efi <cmd> List of efi subcommands: efi devicepath -- Display an EFI device path. efi guid -- Display info about EFI GUID's. efi hob -- Dump EFI HOBs. Type 'hob -h' for more info. efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info. efi table -- Dump EFI System Tables. Type 'table -h' for more info. This module is coded against a generic gdb remote serial stub. It should work with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub. No modifications of EFI is required to load symbols. Example usage: OvmfPkg/build.sh qemu -gdb tcp::9000 gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py" Cc: Leif Lindholm <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> --- BaseTools/Scripts/efi_gdb.py | 918 ++++++++++++++++++++ 1 file changed, 918 insertions(+) diff --git a/BaseTools/Scripts/efi_gdb.py b/BaseTools/Scripts/efi_gdb.py new file mode 100755 index 000000000000..e9bae8e9b913 --- /dev/null +++ b/BaseTools/Scripts/efi_gdb.py @@ -0,0 +1,918 @@ +#!/usr/bin/python3 +''' +Copyright 2021 (c) Apple Inc. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +EFI gdb commands based on efi_debugging classes. + +Example usage: +OvmfPkg/build.sh qemu -gdb tcp::9000 +gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py" + +(gdb) help efi +Commands for debugging EFI. efi <cmd> + +List of efi subcommands: + +efi devicepath -- Display an EFI device path. +efi guid -- Display info about EFI GUID's. +efi hob -- Dump EFI HOBs. Type 'hob -h' for more info. +efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info. +efi table -- Dump EFI System Tables. Type 'table -h' for more info. + +This module is coded against a generic gdb remote serial stub. It should work +with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub. + +If you are debugging with QEMU or a JTAG hardware debugger you can insert +a CpuDeadLoop(); in your code, attach with gdb, and then `p Index=1` to +step past. If you have a debug stub in EFI you can use CpuBreakpoint();. +''' + +from gdb.printing import RegexpCollectionPrettyPrinter +from gdb.printing import register_pretty_printer +import gdb +import os +import sys +import uuid +import optparse +import shlex + +# gdb will not import from the same path as this script. +# so lets fix that for gdb... +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from efi_debugging import PeTeImage, patch_ctypes # noqa: E402 +from efi_debugging import EfiHob, GuidNames, EfiStatusClass # noqa: E402 +from efi_debugging import EfiBootMode, EfiDevicePath # noqa: E402 +from efi_debugging import EfiConfigurationTable, EfiTpl # noqa: E402 + + +class GdbFileObject(object): + '''Provide a file like object required by efi_debugging''' + + def __init__(self): + self.inferior = gdb.selected_inferior() + self.offset = 0 + + def tell(self): + return self.offset + + def read(self, size=-1): + if size == -1: + # arbitrary default size + size = 0x1000000 + + try: + data = self.inferior.read_memory(self.offset, size) + except MemoryError: + data = bytearray(size) + assert False + if len(data) != size: + raise MemoryError( + f'gdb could not read memory 0x{size:x}' + + f' bytes from 0x{self.offset:08x}') + else: + # convert memoryview object to a bytestring. + return data.tobytes() + + def readable(self): + return True + + def seek(self, offset, whence=0): + if whence == 0: + self.offset = offset + elif whence == 1: + self.offset += offset + else: + # whence == 2 is seek from end + raise NotImplementedError + + def seekable(self): + return True + + def write(self, data): + self.inferior.write_memory(self.offset, data) + return len(data) + + def writable(self): + return True + + def truncate(self, size=None): + raise NotImplementedError + + def flush(self): + raise NotImplementedError + + def fileno(self): + raise NotImplementedError + + +class EfiSymbols: + """Class to manage EFI Symbols""" + + loaded = {} + stride = None + range = None + verbose = False + + def __init__(self, file=None): + EfiSymbols.file = file if file else GdbFileObject() + + @ classmethod + def __str__(cls): + return ''.join(f'{value}\n' for value in cls.loaded.values()) + + @ classmethod + def configure_search(cls, stride, range=None, verbose=False): + cls.stride = stride + cls.range = range + cls.verbose = verbose + + @ classmethod + def clear(cls): + cls.loaded = {} + + @ classmethod + def add_symbols_for_pecoff(cls, pecoff): + '''Tell lldb the location of the .text and .data sections.''' + + if pecoff.TextAddress in cls.loaded: + return 'Already Loaded: ' + try: + res = 'Loading Symbols Failed:' + res = gdb.execute('add-symbol-file ' + pecoff.CodeViewPdb + + ' ' + hex(pecoff.TextAddress) + + ' -s .data ' + hex(pecoff.DataAddress), + False, True) + + cls.loaded[pecoff.TextAddress] = pecoff + if cls.verbose: + print(f'\n{res:s}\n') + return '' + except gdb.error: + return res + + @ classmethod + def address_to_symbols(cls, address, reprobe=False): + ''' + Given an address search backwards for a PE/COFF (or TE) header + and load symbols. Return a status string. + ''' + if not isinstance(address, int): + address = int(address) + + pecoff = cls.address_in_loaded_pecoff(address) + if not reprobe and pecoff is not None: + # skip the probe of the remote + return f'{pecoff} is already loaded' + + pecoff = PeTeImage(cls.file, None) + if pecoff.pcToPeCoff(address, cls.stride, cls.range): + res = cls.add_symbols_for_pecoff(pecoff) + return f'{res}{pecoff}' + else: + return f'0x{address:08x} not in a PE/COFF (or TE) image' + + @ classmethod + def address_in_loaded_pecoff(cls, address): + if not isinstance(address, int): + address = int(address) + + for value in cls.loaded.values(): + if (address >= value.LoadAddress and + address <= value.EndLoadAddress): + return value + + return None + + @ classmethod + def unload_symbols(cls, address): + if not isinstance(address, int): + address = int(address) + + pecoff = cls.address_in_loaded_pecoff(address) + try: + res = 'Unloading Symbols Failed:' + res = gdb.execute( + f'remove-symbol-file -a {hex(pecoff.TextAddress):s}', + False, True) + del cls.loaded[pecoff.LoadAddress] + return res + except gdb.error: + return res + + +class CHAR16_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + if int(self.val) < 0x20: + return f"L'\\x{int(self.val):02x}'" + else: + return f"L'{chr(self.val):s}'" + + +class EFI_TPL_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + return str(EfiTpl(int(self.val))) + + +class EFI_STATUS_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + status = int(self.val) + return f'{str(EfiStatusClass(status)):s} (0x{status:08x})' + + +class EFI_BOOT_MODE_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + return str(EfiBootMode(int(self.val))) + + +class EFI_GUID_PrettyPrinter(object): + """Print 'EFI_GUID' as 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'""" + + def __init__(self, val): + self.val = val + + def to_string(self): + # if we could get a byte like object of *(unsigned char (*)[16]) + # then we could just use uuid.UUID() to convert + Data1 = int(self.val['Data1']) + Data2 = int(self.val['Data2']) + Data3 = int(self.val['Data3']) + Data4 = self.val['Data4'] + guid = f'{Data1:08X}-{Data2:04X}-' + guid += f'{Data3:04X}-' + guid += f'{int(Data4[0]):02X}{int(Data4[1]):02X}-' + guid += f'{int(Data4[2]):02X}{int(Data4[3]):02X}' + guid += f'{int(Data4[4]):02X}{int(Data4[5]):02X}' + guid += f'{int(Data4[6]):02X}{int(Data4[7]):02X}' + return str(GuidNames(guid)) + + +def build_pretty_printer(): + # Turn off via: disable pretty-printer global EFI + pp = RegexpCollectionPrettyPrinter("EFI") + # you can also tell gdb `x/sh <address>` to print CHAR16 string + pp.add_printer('CHAR16', '^CHAR16$', CHAR16_PrettyPrinter) + pp.add_printer('EFI_BOOT_MODE', '^EFI_BOOT_MODE$', + EFI_BOOT_MODE_PrettyPrinter) + pp.add_printer('EFI_GUID', '^EFI_GUID$', EFI_GUID_PrettyPrinter) + pp.add_printer('EFI_STATUS', '^EFI_STATUS$', EFI_STATUS_PrettyPrinter) + pp.add_printer('EFI_TPL', '^EFI_TPL$', EFI_TPL_PrettyPrinter) + return pp + + +class EfiDevicePathCmd (gdb.Command): + """Display an EFI device path. Type 'efi devicepath -h' for more info""" + + def __init__(self): + super(EfiDevicePathCmd, self).__init__( + "efi devicepath", gdb.COMMAND_NONE) + + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = ( + "Command that can load EFI PE/COFF and TE image symbols. ") + + self.parser = optparse.OptionParser( + description=description, + prog='efi devicepath', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='hex dump extra data', + default=False) + + self.parser.add_option( + '-n', + '--node', + action='store_true', + dest='node', + help='dump a single device path node', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI device paths''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + + dev_addr = int(gdb.parse_and_eval(arg)) + except ValueError: + print("Invalid argument!") + return + + if options.node: + print(EfiDevicePath( + self.file).device_path_node_str(dev_addr, + options.verbose)) + else: + device_path = EfiDevicePath(self.file, dev_addr, options.verbose) + if device_path.valid(): + print(device_path) + + +class EfiGuidCmd (gdb.Command): + """Display info about EFI GUID's. Type 'efi guid -h' for more info""" + + def __init__(self): + super(EfiGuidCmd, self).__init__("efi guid", + gdb.COMMAND_NONE, + gdb.COMPLETE_EXPRESSION) + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = ( + "Show EFI_GUID values and the C name of the EFI_GUID variables" + "in the C code. If symbols are loaded the Guid.xref file" + "can be processed and the complete GUID database can be shown." + "This command also suports generating new GUID's, and showing" + "the value used to initialize the C variable.") + + self.parser = optparse.OptionParser( + description=description, + prog='efi guid', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-n', + '--new', + action='store_true', + dest='new', + help='Generate a new GUID', + default=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='Also display GUID C structure values', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI System Tables''' + + try: + (options, args) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + if len(args) >= 1: + # guid { 0x414e6bdd, 0xe47b, 0x47cc, + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} + # this generates multiple args + guid = ' '.join(args) + except ValueError: + print('bad arguments!') + return + + if options.new: + guid = uuid.uuid4() + print(str(guid).upper()) + print(GuidNames.to_c_guid(guid)) + return + + if len(args) > 0: + if GuidNames.is_guid_str(arg): + # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9 + key = guid.upper() + name = GuidNames.to_name(key) + elif GuidNames.is_c_guid(arg): + # guid { 0x414e6bdd, 0xe47b, 0x47cc, + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} + key = GuidNames.from_c_guid(arg) + name = GuidNames.to_name(key) + else: + # guid gEfiDxeServicesTableGuid + name = guid + try: + key = GuidNames.to_guid(name) + name = GuidNames.to_name(key) + except ValueError: + return + + extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else '' + print(f'{key}: {extra}{name}') + + else: + for key, value in GuidNames._dict_.items(): + if options.verbose: + extra = f'{GuidNames.to_c_guid(key)}: ' + else: + extra = '' + print(f'{key}: {extra}{value}') + + +class EfiHobCmd (gdb.Command): + """Dump EFI HOBs. Type 'hob -h' for more info.""" + + def __init__(self): + super(EfiHobCmd, self).__init__("efi hob", gdb.COMMAND_NONE) + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = ( + "Command that can load EFI PE/COFF and TE image symbols. ") + + self.parser = optparse.OptionParser( + description=description, + prog='efi hob', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-a', + '--address', + type="int", + dest='address', + help='Parse HOBs from address', + default=None) + + self.parser.add_option( + '-t', + '--type', + type="int", + dest='type', + help='Only dump HOBS of his type', + default=None) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='hex dump extra data', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI System Tables''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + except ValueError: + print('bad arguments!') + return + + if options.address: + try: + value = gdb.parse_and_eval(options.address) + address = int(value) + except ValueError: + address = None + else: + address = None + + hob = EfiHob(self.file, + address, + options.verbose).get_hob_by_type(options.type) + print(hob) + + +class EfiTablesCmd (gdb.Command): + """Dump EFI System Tables. Type 'table -h' for more info.""" + + def __init__(self): + super(EfiTablesCmd, self).__init__("efi table", gdb.COMMAND_NONE) + + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = "Dump EFI System Tables. Requires symbols to be loaded" + + self.parser = optparse.OptionParser( + description=description, + prog='efi table', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI System Tables''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + except ValueError: + print('bad arguments!') + return + + gST = gdb.lookup_global_symbol('gST') + if gST is None: + print('Error: This command requires symbols for gST to be loaded') + return + + table = EfiConfigurationTable( + self.file, int(gST.value(gdb.selected_frame()))) + if table: + print(table, '\n') + + +class EfiSymbolsCmd (gdb.Command): + """Load Symbols for EFI. Type 'efi symbols -h' for more info.""" + + def __init__(self): + super(EfiSymbolsCmd, self).__init__("efi symbols", + gdb.COMMAND_NONE, + gdb.COMPLETE_EXPRESSION) + self.file = GdbFileObject() + self.gST = None + self.efi_symbols = EfiSymbols(self.file) + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options]" + description = ( + "Command that can load EFI PE/COFF and TE image symbols. " + "If you are having trouble in PEI try adding --pei. " + "Given any address search backward for the PE/COFF (or TE header) " + "and then parse the PE/COFF image to get debug info. " + "The address can come from the current pc, pc values in the " + "frame, or an address provided to the command" + "") + + self.parser = optparse.OptionParser( + description=description, + prog='efi symbols', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-a', + '--address', + type="str", + dest='address', + help='Load symbols for image that contains address', + default=None) + + self.parser.add_option( + '-c', + '--clear', + action='store_true', + dest='clear', + help='Clear the cache of loaded images', + default=False) + + self.parser.add_option( + '-f', + '--frame', + action='store_true', + dest='frame', + help='Load symbols for current stack frame', + default=False) + + self.parser.add_option( + '-p', + '--pc', + action='store_true', + dest='pc', + help='Load symbols for pc', + default=False) + + self.parser.add_option( + '--pei', + action='store_true', + dest='pei', + help='Load symbols for PEI (searches every 4 bytes)', + default=False) + + self.parser.add_option( + '-e', + '--extended', + action='store_true', + dest='extended', + help='Try to load all symbols based on config tables', + default=False) + + self.parser.add_option( + '-r', + '--range', + type="long", + dest='range', + help='How far to search backward for start of PE/COFF Image', + default=None) + + self.parser.add_option( + '-s', + '--stride', + type="long", + dest='stride', + help='Boundary to search for PE/COFF header', + default=None) + + self.parser.add_option( + '-t', + '--thread', + action='store_true', + dest='thread', + help='Load symbols for the frames of all threads', + default=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='Show more info on symbols loading in gdb', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def save_user_state(self): + self.pagination = gdb.parameter("pagination") + if self.pagination: + gdb.execute("set pagination off") + + self.user_selected_thread = gdb.selected_thread() + self.user_selected_frame = gdb.selected_frame() + + def restore_user_state(self): + self.user_selected_thread.switch() + self.user_selected_frame.select() + + if self.pagination: + gdb.execute("set pagination on") + + def canonical_address(self, address): + ''' + Scrub out 48-bit non canonical addresses + Raw frames in gdb can have some funky values + ''' + + # Skip lowest 256 bytes to avoid interrupt frames + if address > 0xFF and address < 0x00007FFFFFFFFFFF: + return True + if address >= 0xFFFF800000000000: + return True + + return False + + def pc_set_for_frames(self): + '''Return a set for the PC's in the current frame''' + pc_list = [] + frame = gdb.newest_frame() + while frame: + pc = int(frame.read_register('pc')) + if self.canonical_address(pc): + pc_list.append(pc) + frame = frame.older() + + return set(pc_list) + + def invoke(self, arg, from_tty): + '''gdb command to symbolicate all the frames from all the threads''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + except ValueError: + print('bad arguments!') + return + + self.dont_repeat() + + self.save_user_state() + + if options.clear: + self.efi_symbols.clear() + return + + if options.pei: + # XIP code can be 4 byte aligned in the FV + options.stride = 4 + options.range = 0x100000 + self.efi_symbols.configure_search(options.stride, + options.range, + options.verbose) + + if options.thread: + thread_list = gdb.selected_inferior().threads() + else: + thread_list = (gdb.selected_thread(),) + + address = None + if options.address: + value = gdb.parse_and_eval(options.address) + address = int(value) + elif options.pc: + address = gdb.selected_frame().pc() + + if address: + res = self.efi_symbols.address_to_symbols(address) + print(res) + else: + + for thread in thread_list: + thread.switch() + + # You can not iterate over frames as you load symbols. Loading + # symbols changes the frames gdb can see due to inlining and + # boom. So we loop adding symbols for the current frame, and + # we test to see if new frames have shown up. If new frames + # show up we process those new frames. Thus 1st pass is the + # raw frame, and other passes are only new PC values. + NewPcSet = self.pc_set_for_frames() + while NewPcSet: + PcSet = self.pc_set_for_frames() + for pc in NewPcSet: + res = self.efi_symbols.address_to_symbols(pc) + print(res) + + NewPcSet = PcSet.symmetric_difference( + self.pc_set_for_frames()) + + # find the EFI System tables the 1st time + if self.gST is None: + gST = gdb.lookup_global_symbol('gST') + if gST is not None: + self.gST = int(gST.value(gdb.selected_frame())) + table = EfiConfigurationTable(self.file, self.gST) + else: + table = None + else: + table = EfiConfigurationTable(self.file, self.gST) + + if options.extended and table: + # load symbols from EFI System Table entry + for address, _ in table.DebugImageInfo(): + res = self.efi_symbols.address_to_symbols(address) + print(res) + + # sync up the GUID database from the build output + for m in gdb.objfiles(): + if GuidNames.add_build_guid_file(str(m.filename)): + break + + self.restore_user_state() + + +class EfiCmd (gdb.Command): + """Commands for debugging EFI. efi <cmd>""" + + def __init__(self): + super(EfiCmd, self).__init__("efi", + gdb.COMMAND_NONE, + gdb.COMPLETE_NONE, + True) + + def invoke(self, arg, from_tty): + '''default to loading symbols''' + if '-h' in arg or '--help' in arg: + gdb.execute('help efi') + else: + # default to loading all symbols + gdb.execute('efi symbols --extended') + + +class LoadEmulatorEfiSymbols(gdb.Breakpoint): + ''' + breakpoint for EmulatorPkg to load symbols + Note: make sure SecGdbScriptBreak is not optimized away! + Also turn off the dlopen() flow like on macOS. + ''' + def stop(self): + symbols = EfiSymbols() + # Emulator adds SizeOfHeaders so we need file alignment to search + symbols.configure_search(0x20) + + frame = gdb.newest_frame() + + try: + # gdb was looking at spill address, pre spill :( + LoadAddress = frame.read_register('rdx') + AddSymbolFlag = frame.read_register('rcx') + except gdb.error: + LoadAddress = frame.read_var('LoadAddress') + AddSymbolFlag = frame.read_var('AddSymbolFlag') + + if AddSymbolFlag == 1: + res = symbols.address_to_symbols(LoadAddress) + else: + res = symbols.unload_symbols(LoadAddress) + print(res) + + # keep running + return False + + +# Get python backtraces to debug errors in this script +gdb.execute("set python print-stack full") + +# tell efi_debugging how to walk data structures with pointers +try: + pointer_width = gdb.lookup_type('int').pointer().sizeof +except ValueError: + pointer_width = 8 +patch_ctypes(pointer_width) + +register_pretty_printer(None, build_pretty_printer(), replace=True) + +# gdb commands that we are adding +# add `efi` prefix gdb command +EfiCmd() + +# subcommands for `efi` +EfiSymbolsCmd() +EfiTablesCmd() +EfiHobCmd() +EfiDevicePathCmd() +EfiGuidCmd() + +# +bp = LoadEmulatorEfiSymbols('SecGdbScriptBreak', internal=True) +if bp.pending: + try: + gdb.selected_frame() + # Not the emulator so do this when you attach + gdb.execute('efi symbols --frame --extended', True) + gdb.execute('bt') + # If you want to skip the above commands comment them out + pass + except gdb.error: + # If you load the script and there is no target ignore the error. + pass +else: + # start the emulator + gdb.execute('run') -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print 2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran @ 2022-03-26 14:05 ` Bob Feng 0 siblings, 0 replies; 10+ messages in thread From: Bob Feng @ 2022-03-26 14:05 UTC (permalink / raw) To: Rebecca Cran, devel@edk2.groups.io, Leif Lindholm, Kinney, Michael D, Wu, Hao A, Gao, Liming, Chen, Christine Cc: Andrew Fish Reviewed-by: Bob Feng <bob.c.feng@intel.com> -----Original Message----- From: Rebecca Cran <quic_rcran@quicinc.com> Sent: Tuesday, March 22, 2022 4:21 AM To: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>; Kinney, Michael D <michael.d.kinney@intel.com>; Wu, Hao A <hao.a.wu@intel.com>; Feng, Bob C <bob.c.feng@intel.com>; Gao, Liming <gaoliming@byosoft.com.cn>; Chen, Christine <yuwei.chen@intel.com> Cc: Rebecca Cran <quic_rcran@quicinc.com>; Andrew Fish <afish@apple.com> Subject: [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print https://bugzilla.tianocore.org/show_bug.cgi?id=3500 Use efi_debugging.py Python Classes to implement EFI gdb commands: (gdb) help efi Commands for debugging EFI. efi <cmd> List of efi subcommands: efi devicepath -- Display an EFI device path. efi guid -- Display info about EFI GUID's. efi hob -- Dump EFI HOBs. Type 'hob -h' for more info. efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info. efi table -- Dump EFI System Tables. Type 'table -h' for more info. This module is coded against a generic gdb remote serial stub. It should work with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub. No modifications of EFI is required to load symbols. Example usage: OvmfPkg/build.sh qemu -gdb tcp::9000 gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py" Cc: Leif Lindholm <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> --- BaseTools/Scripts/efi_gdb.py | 918 ++++++++++++++++++++ 1 file changed, 918 insertions(+) diff --git a/BaseTools/Scripts/efi_gdb.py b/BaseTools/Scripts/efi_gdb.py new file mode 100755 index 000000000000..e9bae8e9b913 --- /dev/null +++ b/BaseTools/Scripts/efi_gdb.py @@ -0,0 +1,918 @@ +#!/usr/bin/python3 +''' +Copyright 2021 (c) Apple Inc. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +EFI gdb commands based on efi_debugging classes. + +Example usage: +OvmfPkg/build.sh qemu -gdb tcp::9000 +gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py" + +(gdb) help efi +Commands for debugging EFI. efi <cmd> + +List of efi subcommands: + +efi devicepath -- Display an EFI device path. +efi guid -- Display info about EFI GUID's. +efi hob -- Dump EFI HOBs. Type 'hob -h' for more info. +efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info. +efi table -- Dump EFI System Tables. Type 'table -h' for more info. + +This module is coded against a generic gdb remote serial stub. It +should work with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub. + +If you are debugging with QEMU or a JTAG hardware debugger you can +insert a CpuDeadLoop(); in your code, attach with gdb, and then `p +Index=1` to step past. If you have a debug stub in EFI you can use CpuBreakpoint();. +''' + +from gdb.printing import RegexpCollectionPrettyPrinter from +gdb.printing import register_pretty_printer import gdb import os import +sys import uuid import optparse import shlex + +# gdb will not import from the same path as this script. +# so lets fix that for gdb... +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from efi_debugging import PeTeImage, patch_ctypes # noqa: E402 +from efi_debugging import EfiHob, GuidNames, EfiStatusClass # noqa: E402 +from efi_debugging import EfiBootMode, EfiDevicePath # noqa: E402 +from efi_debugging import EfiConfigurationTable, EfiTpl # noqa: E402 + + +class GdbFileObject(object): + '''Provide a file like object required by efi_debugging''' + + def __init__(self): + self.inferior = gdb.selected_inferior() + self.offset = 0 + + def tell(self): + return self.offset + + def read(self, size=-1): + if size == -1: + # arbitrary default size + size = 0x1000000 + + try: + data = self.inferior.read_memory(self.offset, size) + except MemoryError: + data = bytearray(size) + assert False + if len(data) != size: + raise MemoryError( + f'gdb could not read memory 0x{size:x}' + + f' bytes from 0x{self.offset:08x}') + else: + # convert memoryview object to a bytestring. + return data.tobytes() + + def readable(self): + return True + + def seek(self, offset, whence=0): + if whence == 0: + self.offset = offset + elif whence == 1: + self.offset += offset + else: + # whence == 2 is seek from end + raise NotImplementedError + + def seekable(self): + return True + + def write(self, data): + self.inferior.write_memory(self.offset, data) + return len(data) + + def writable(self): + return True + + def truncate(self, size=None): + raise NotImplementedError + + def flush(self): + raise NotImplementedError + + def fileno(self): + raise NotImplementedError + + +class EfiSymbols: + """Class to manage EFI Symbols""" + + loaded = {} + stride = None + range = None + verbose = False + + def __init__(self, file=None): + EfiSymbols.file = file if file else GdbFileObject() + + @ classmethod + def __str__(cls): + return ''.join(f'{value}\n' for value in cls.loaded.values()) + + @ classmethod + def configure_search(cls, stride, range=None, verbose=False): + cls.stride = stride + cls.range = range + cls.verbose = verbose + + @ classmethod + def clear(cls): + cls.loaded = {} + + @ classmethod + def add_symbols_for_pecoff(cls, pecoff): + '''Tell lldb the location of the .text and .data sections.''' + + if pecoff.TextAddress in cls.loaded: + return 'Already Loaded: ' + try: + res = 'Loading Symbols Failed:' + res = gdb.execute('add-symbol-file ' + pecoff.CodeViewPdb + + ' ' + hex(pecoff.TextAddress) + + ' -s .data ' + hex(pecoff.DataAddress), + False, True) + + cls.loaded[pecoff.TextAddress] = pecoff + if cls.verbose: + print(f'\n{res:s}\n') + return '' + except gdb.error: + return res + + @ classmethod + def address_to_symbols(cls, address, reprobe=False): + ''' + Given an address search backwards for a PE/COFF (or TE) header + and load symbols. Return a status string. + ''' + if not isinstance(address, int): + address = int(address) + + pecoff = cls.address_in_loaded_pecoff(address) + if not reprobe and pecoff is not None: + # skip the probe of the remote + return f'{pecoff} is already loaded' + + pecoff = PeTeImage(cls.file, None) + if pecoff.pcToPeCoff(address, cls.stride, cls.range): + res = cls.add_symbols_for_pecoff(pecoff) + return f'{res}{pecoff}' + else: + return f'0x{address:08x} not in a PE/COFF (or TE) image' + + @ classmethod + def address_in_loaded_pecoff(cls, address): + if not isinstance(address, int): + address = int(address) + + for value in cls.loaded.values(): + if (address >= value.LoadAddress and + address <= value.EndLoadAddress): + return value + + return None + + @ classmethod + def unload_symbols(cls, address): + if not isinstance(address, int): + address = int(address) + + pecoff = cls.address_in_loaded_pecoff(address) + try: + res = 'Unloading Symbols Failed:' + res = gdb.execute( + f'remove-symbol-file -a {hex(pecoff.TextAddress):s}', + False, True) + del cls.loaded[pecoff.LoadAddress] + return res + except gdb.error: + return res + + +class CHAR16_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + if int(self.val) < 0x20: + return f"L'\\x{int(self.val):02x}'" + else: + return f"L'{chr(self.val):s}'" + + +class EFI_TPL_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + return str(EfiTpl(int(self.val))) + + +class EFI_STATUS_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + status = int(self.val) + return f'{str(EfiStatusClass(status)):s} (0x{status:08x})' + + +class EFI_BOOT_MODE_PrettyPrinter(object): + + def __init__(self, val): + self.val = val + + def to_string(self): + return str(EfiBootMode(int(self.val))) + + +class EFI_GUID_PrettyPrinter(object): + """Print 'EFI_GUID' as 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'""" + + def __init__(self, val): + self.val = val + + def to_string(self): + # if we could get a byte like object of *(unsigned char (*)[16]) + # then we could just use uuid.UUID() to convert + Data1 = int(self.val['Data1']) + Data2 = int(self.val['Data2']) + Data3 = int(self.val['Data3']) + Data4 = self.val['Data4'] + guid = f'{Data1:08X}-{Data2:04X}-' + guid += f'{Data3:04X}-' + guid += f'{int(Data4[0]):02X}{int(Data4[1]):02X}-' + guid += f'{int(Data4[2]):02X}{int(Data4[3]):02X}' + guid += f'{int(Data4[4]):02X}{int(Data4[5]):02X}' + guid += f'{int(Data4[6]):02X}{int(Data4[7]):02X}' + return str(GuidNames(guid)) + + +def build_pretty_printer(): + # Turn off via: disable pretty-printer global EFI + pp = RegexpCollectionPrettyPrinter("EFI") + # you can also tell gdb `x/sh <address>` to print CHAR16 string + pp.add_printer('CHAR16', '^CHAR16$', CHAR16_PrettyPrinter) + pp.add_printer('EFI_BOOT_MODE', '^EFI_BOOT_MODE$', + EFI_BOOT_MODE_PrettyPrinter) + pp.add_printer('EFI_GUID', '^EFI_GUID$', EFI_GUID_PrettyPrinter) + pp.add_printer('EFI_STATUS', '^EFI_STATUS$', EFI_STATUS_PrettyPrinter) + pp.add_printer('EFI_TPL', '^EFI_TPL$', EFI_TPL_PrettyPrinter) + return pp + + +class EfiDevicePathCmd (gdb.Command): + """Display an EFI device path. Type 'efi devicepath -h' for more info""" + + def __init__(self): + super(EfiDevicePathCmd, self).__init__( + "efi devicepath", gdb.COMMAND_NONE) + + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = ( + "Command that can load EFI PE/COFF and TE image symbols. ") + + self.parser = optparse.OptionParser( + description=description, + prog='efi devicepath', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='hex dump extra data', + default=False) + + self.parser.add_option( + '-n', + '--node', + action='store_true', + dest='node', + help='dump a single device path node', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI device paths''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + + dev_addr = int(gdb.parse_and_eval(arg)) + except ValueError: + print("Invalid argument!") + return + + if options.node: + print(EfiDevicePath( + self.file).device_path_node_str(dev_addr, + options.verbose)) + else: + device_path = EfiDevicePath(self.file, dev_addr, options.verbose) + if device_path.valid(): + print(device_path) + + +class EfiGuidCmd (gdb.Command): + """Display info about EFI GUID's. Type 'efi guid -h' for more info""" + + def __init__(self): + super(EfiGuidCmd, self).__init__("efi guid", + gdb.COMMAND_NONE, + gdb.COMPLETE_EXPRESSION) + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = ( + "Show EFI_GUID values and the C name of the EFI_GUID variables" + "in the C code. If symbols are loaded the Guid.xref file" + "can be processed and the complete GUID database can be shown." + "This command also suports generating new GUID's, and showing" + "the value used to initialize the C variable.") + + self.parser = optparse.OptionParser( + description=description, + prog='efi guid', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-n', + '--new', + action='store_true', + dest='new', + help='Generate a new GUID', + default=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='Also display GUID C structure values', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI System Tables''' + + try: + (options, args) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + if len(args) >= 1: + # guid { 0x414e6bdd, 0xe47b, 0x47cc, + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} + # this generates multiple args + guid = ' '.join(args) + except ValueError: + print('bad arguments!') + return + + if options.new: + guid = uuid.uuid4() + print(str(guid).upper()) + print(GuidNames.to_c_guid(guid)) + return + + if len(args) > 0: + if GuidNames.is_guid_str(arg): + # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9 + key = guid.upper() + name = GuidNames.to_name(key) + elif GuidNames.is_c_guid(arg): + # guid { 0x414e6bdd, 0xe47b, 0x47cc, + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} + key = GuidNames.from_c_guid(arg) + name = GuidNames.to_name(key) + else: + # guid gEfiDxeServicesTableGuid + name = guid + try: + key = GuidNames.to_guid(name) + name = GuidNames.to_name(key) + except ValueError: + return + + extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else '' + print(f'{key}: {extra}{name}') + + else: + for key, value in GuidNames._dict_.items(): + if options.verbose: + extra = f'{GuidNames.to_c_guid(key)}: ' + else: + extra = '' + print(f'{key}: {extra}{value}') + + +class EfiHobCmd (gdb.Command): + """Dump EFI HOBs. Type 'hob -h' for more info.""" + + def __init__(self): + super(EfiHobCmd, self).__init__("efi hob", gdb.COMMAND_NONE) + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = ( + "Command that can load EFI PE/COFF and TE image symbols. ") + + self.parser = optparse.OptionParser( + description=description, + prog='efi hob', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-a', + '--address', + type="int", + dest='address', + help='Parse HOBs from address', + default=None) + + self.parser.add_option( + '-t', + '--type', + type="int", + dest='type', + help='Only dump HOBS of his type', + default=None) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='hex dump extra data', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI System Tables''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + except ValueError: + print('bad arguments!') + return + + if options.address: + try: + value = gdb.parse_and_eval(options.address) + address = int(value) + except ValueError: + address = None + else: + address = None + + hob = EfiHob(self.file, + address, + options.verbose).get_hob_by_type(options.type) + print(hob) + + +class EfiTablesCmd (gdb.Command): + """Dump EFI System Tables. Type 'table -h' for more info.""" + + def __init__(self): + super(EfiTablesCmd, self).__init__("efi table", + gdb.COMMAND_NONE) + + self.file = GdbFileObject() + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options] [arg]" + description = "Dump EFI System Tables. Requires symbols to be loaded" + + self.parser = optparse.OptionParser( + description=description, + prog='efi table', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def invoke(self, arg, from_tty): + '''gdb command to dump EFI System Tables''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + except ValueError: + print('bad arguments!') + return + + gST = gdb.lookup_global_symbol('gST') + if gST is None: + print('Error: This command requires symbols for gST to be loaded') + return + + table = EfiConfigurationTable( + self.file, int(gST.value(gdb.selected_frame()))) + if table: + print(table, '\n') + + +class EfiSymbolsCmd (gdb.Command): + """Load Symbols for EFI. Type 'efi symbols -h' for more info.""" + + def __init__(self): + super(EfiSymbolsCmd, self).__init__("efi symbols", + gdb.COMMAND_NONE, + gdb.COMPLETE_EXPRESSION) + self.file = GdbFileObject() + self.gST = None + self.efi_symbols = EfiSymbols(self.file) + + def create_options(self, arg, from_tty): + usage = "usage: %prog [options]" + description = ( + "Command that can load EFI PE/COFF and TE image symbols. " + "If you are having trouble in PEI try adding --pei. " + "Given any address search backward for the PE/COFF (or TE header) " + "and then parse the PE/COFF image to get debug info. " + "The address can come from the current pc, pc values in the " + "frame, or an address provided to the command" + "") + + self.parser = optparse.OptionParser( + description=description, + prog='efi symbols', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-a', + '--address', + type="str", + dest='address', + help='Load symbols for image that contains address', + default=None) + + self.parser.add_option( + '-c', + '--clear', + action='store_true', + dest='clear', + help='Clear the cache of loaded images', + default=False) + + self.parser.add_option( + '-f', + '--frame', + action='store_true', + dest='frame', + help='Load symbols for current stack frame', + default=False) + + self.parser.add_option( + '-p', + '--pc', + action='store_true', + dest='pc', + help='Load symbols for pc', + default=False) + + self.parser.add_option( + '--pei', + action='store_true', + dest='pei', + help='Load symbols for PEI (searches every 4 bytes)', + default=False) + + self.parser.add_option( + '-e', + '--extended', + action='store_true', + dest='extended', + help='Try to load all symbols based on config tables', + default=False) + + self.parser.add_option( + '-r', + '--range', + type="long", + dest='range', + help='How far to search backward for start of PE/COFF Image', + default=None) + + self.parser.add_option( + '-s', + '--stride', + type="long", + dest='stride', + help='Boundary to search for PE/COFF header', + default=None) + + self.parser.add_option( + '-t', + '--thread', + action='store_true', + dest='thread', + help='Load symbols for the frames of all threads', + default=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='Show more info on symbols loading in gdb', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + return self.parser.parse_args(shlex.split(arg)) + + def save_user_state(self): + self.pagination = gdb.parameter("pagination") + if self.pagination: + gdb.execute("set pagination off") + + self.user_selected_thread = gdb.selected_thread() + self.user_selected_frame = gdb.selected_frame() + + def restore_user_state(self): + self.user_selected_thread.switch() + self.user_selected_frame.select() + + if self.pagination: + gdb.execute("set pagination on") + + def canonical_address(self, address): + ''' + Scrub out 48-bit non canonical addresses + Raw frames in gdb can have some funky values + ''' + + # Skip lowest 256 bytes to avoid interrupt frames + if address > 0xFF and address < 0x00007FFFFFFFFFFF: + return True + if address >= 0xFFFF800000000000: + return True + + return False + + def pc_set_for_frames(self): + '''Return a set for the PC's in the current frame''' + pc_list = [] + frame = gdb.newest_frame() + while frame: + pc = int(frame.read_register('pc')) + if self.canonical_address(pc): + pc_list.append(pc) + frame = frame.older() + + return set(pc_list) + + def invoke(self, arg, from_tty): + '''gdb command to symbolicate all the frames from all the threads''' + + try: + (options, _) = self.create_options(arg, from_tty) + if options.help: + self.parser.print_help() + return + except ValueError: + print('bad arguments!') + return + + self.dont_repeat() + + self.save_user_state() + + if options.clear: + self.efi_symbols.clear() + return + + if options.pei: + # XIP code can be 4 byte aligned in the FV + options.stride = 4 + options.range = 0x100000 + self.efi_symbols.configure_search(options.stride, + options.range, + options.verbose) + + if options.thread: + thread_list = gdb.selected_inferior().threads() + else: + thread_list = (gdb.selected_thread(),) + + address = None + if options.address: + value = gdb.parse_and_eval(options.address) + address = int(value) + elif options.pc: + address = gdb.selected_frame().pc() + + if address: + res = self.efi_symbols.address_to_symbols(address) + print(res) + else: + + for thread in thread_list: + thread.switch() + + # You can not iterate over frames as you load symbols. Loading + # symbols changes the frames gdb can see due to inlining and + # boom. So we loop adding symbols for the current frame, and + # we test to see if new frames have shown up. If new frames + # show up we process those new frames. Thus 1st pass is the + # raw frame, and other passes are only new PC values. + NewPcSet = self.pc_set_for_frames() + while NewPcSet: + PcSet = self.pc_set_for_frames() + for pc in NewPcSet: + res = self.efi_symbols.address_to_symbols(pc) + print(res) + + NewPcSet = PcSet.symmetric_difference( + self.pc_set_for_frames()) + + # find the EFI System tables the 1st time + if self.gST is None: + gST = gdb.lookup_global_symbol('gST') + if gST is not None: + self.gST = int(gST.value(gdb.selected_frame())) + table = EfiConfigurationTable(self.file, self.gST) + else: + table = None + else: + table = EfiConfigurationTable(self.file, self.gST) + + if options.extended and table: + # load symbols from EFI System Table entry + for address, _ in table.DebugImageInfo(): + res = self.efi_symbols.address_to_symbols(address) + print(res) + + # sync up the GUID database from the build output + for m in gdb.objfiles(): + if GuidNames.add_build_guid_file(str(m.filename)): + break + + self.restore_user_state() + + +class EfiCmd (gdb.Command): + """Commands for debugging EFI. efi <cmd>""" + + def __init__(self): + super(EfiCmd, self).__init__("efi", + gdb.COMMAND_NONE, + gdb.COMPLETE_NONE, + True) + + def invoke(self, arg, from_tty): + '''default to loading symbols''' + if '-h' in arg or '--help' in arg: + gdb.execute('help efi') + else: + # default to loading all symbols + gdb.execute('efi symbols --extended') + + +class LoadEmulatorEfiSymbols(gdb.Breakpoint): + ''' + breakpoint for EmulatorPkg to load symbols + Note: make sure SecGdbScriptBreak is not optimized away! + Also turn off the dlopen() flow like on macOS. + ''' + def stop(self): + symbols = EfiSymbols() + # Emulator adds SizeOfHeaders so we need file alignment to search + symbols.configure_search(0x20) + + frame = gdb.newest_frame() + + try: + # gdb was looking at spill address, pre spill :( + LoadAddress = frame.read_register('rdx') + AddSymbolFlag = frame.read_register('rcx') + except gdb.error: + LoadAddress = frame.read_var('LoadAddress') + AddSymbolFlag = frame.read_var('AddSymbolFlag') + + if AddSymbolFlag == 1: + res = symbols.address_to_symbols(LoadAddress) + else: + res = symbols.unload_symbols(LoadAddress) + print(res) + + # keep running + return False + + +# Get python backtraces to debug errors in this script gdb.execute("set +python print-stack full") + +# tell efi_debugging how to walk data structures with pointers +try: + pointer_width = gdb.lookup_type('int').pointer().sizeof +except ValueError: + pointer_width = 8 +patch_ctypes(pointer_width) + +register_pretty_printer(None, build_pretty_printer(), replace=True) + +# gdb commands that we are adding +# add `efi` prefix gdb command +EfiCmd() + +# subcommands for `efi` +EfiSymbolsCmd() +EfiTablesCmd() +EfiHobCmd() +EfiDevicePathCmd() +EfiGuidCmd() + +# +bp = LoadEmulatorEfiSymbols('SecGdbScriptBreak', internal=True) if +bp.pending: + try: + gdb.selected_frame() + # Not the emulator so do this when you attach + gdb.execute('efi symbols --frame --extended', True) + gdb.execute('bt') + # If you want to skip the above commands comment them out + pass + except gdb.error: + # If you load the script and there is no target ignore the error. + pass +else: + # start the emulator + gdb.execute('run') -- 2.34.1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print 2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran 2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran 2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran @ 2022-03-21 20:20 ` Rebecca Cran 2022-04-09 4:16 ` [edk2-devel] " Bob Feng 2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran 3 siblings, 1 reply; 10+ messages in thread From: Rebecca Cran @ 2022-03-21 20:20 UTC (permalink / raw) To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng, Liming Gao, Yuwei Chen Cc: Rebecca Cran, Andrew Fish https://bugzilla.tianocore.org/show_bug.cgi?id=3500 Use efi_debugging.py Python Classes to implement EFI gdb commands: efi_symbols, guid, table, hob, and devicepath You can attach to any standard gdb or kdp remote server and get EFI symbols. No modifications of EFI are required. Example usage: OvmfPkg/build.sh qemu -gdb tcp::9000 lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py" Note you may also have to teach lldb about QEMU: -o "settings set plugin.process.gdb-remote.target-definition-file x86_64_target_definition.py" Cc: Leif Lindholm <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> --- BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++ 1 file changed, 1044 insertions(+) diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py new file mode 100755 index 000000000000..089b6ba58ab8 --- /dev/null +++ b/BaseTools/Scripts/efi_lldb.py @@ -0,0 +1,1044 @@ +#!/usr/bin/python3 +''' +Copyright (c) Apple Inc. 2021 +SPDX-License-Identifier: BSD-2-Clause-Patent + +Example usage: +OvmfPkg/build.sh qemu -gdb tcp::9000 +lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py" +''' + +import optparse +import shlex +import subprocess +import uuid +import sys +import os +from pathlib import Path +from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl +from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode +from efi_debugging import PeTeImage, patch_ctypes + +try: + # Just try for LLDB in case PYTHONPATH is already correctly setup + import lldb +except ImportError: + try: + env = os.environ.copy() + env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major) + lldb_python_path = subprocess.check_output( + ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip() + sys.path.append(lldb_python_path) + import lldb + except ValueError: + print("Couldn't find LLDB.framework from lldb -P") + print("PYTHONPATH should match the currently selected lldb") + sys.exit(-1) + + +class LldbFileObject(object): + ''' + Class that fakes out file object to abstract lldb from the generic code. + For lldb this is memory so we don't have a concept of the end of the file. + ''' + + def __init__(self, process): + # _exe_ctx is lldb.SBExecutionContext + self._process = process + self._offset = 0 + self._SBError = lldb.SBError() + + def tell(self): + return self._offset + + def read(self, size=-1): + if size == -1: + # arbitrary default size + size = 0x1000000 + + data = self._process.ReadMemory(self._offset, size, self._SBError) + if self._SBError.fail: + raise MemoryError( + f'lldb could not read memory 0x{size:x} ' + f' bytes from 0x{self._offset:08x}') + else: + return data + + def readable(self): + return True + + def seek(self, offset, whence=0): + if whence == 0: + self._offset = offset + elif whence == 1: + self._offset += offset + else: + # whence == 2 is seek from end + raise NotImplementedError + + def seekable(self): + return True + + def write(self, data): + result = self._process.WriteMemory(self._offset, data, self._SBError) + if self._SBError.fail: + raise MemoryError( + f'lldb could not write memory to 0x{self._offset:08x}') + return result + + def writable(self): + return True + + def truncate(self, size=None): + raise NotImplementedError + + def flush(self): + raise NotImplementedError + + def fileno(self): + raise NotImplementedError + + +class EfiSymbols: + """ + Class to manage EFI Symbols + You need to pass file, and exe_ctx to load symbols. + You can print(EfiSymbols()) to see the currently loaded symbols + """ + + loaded = {} + stride = None + range = None + verbose = False + + def __init__(self, target=None): + if target: + EfiSymbols.target = target + EfiSymbols._file = LldbFileObject(target.process) + + @ classmethod + def __str__(cls): + return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values()) + + @ classmethod + def configure_search(cls, stride, range, verbose=False): + cls.stride = stride + cls.range = range + cls.verbose = verbose + + @ classmethod + def clear(cls): + cls.loaded = {} + + @ classmethod + def add_symbols_for_pecoff(cls, pecoff): + '''Tell lldb the location of the .text and .data sections.''' + + if pecoff.LoadAddress in cls.loaded: + return 'Already Loaded: ' + + module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid)) + if not module: + module = cls.target.AddModule(pecoff.CodeViewPdb, + None, + str(pecoff.CodeViewUuid)) + if module.IsValid(): + SBError = cls.target.SetModuleLoadAddress( + module, pecoff.LoadAddress + pecoff.TeAdjust) + if SBError.success: + cls.loaded[pecoff.LoadAddress] = (pecoff, module) + return '' + + return 'Symbols NOT FOUND: ' + + @ classmethod + def address_to_symbols(cls, address, reprobe=False): + ''' + Given an address search backwards for a PE/COFF (or TE) header + and load symbols. Return a status string. + ''' + if not isinstance(address, int): + address = int(address) + + pecoff, _ = cls.address_in_loaded_pecoff(address) + if not reprobe and pecoff is not None: + # skip the probe of the remote + return f'{pecoff} is already loaded' + + pecoff = PeTeImage(cls._file, None) + if pecoff.pcToPeCoff(address, cls.stride, cls.range): + res = cls.add_symbols_for_pecoff(pecoff) + return f'{res}{pecoff}' + else: + return f'0x{address:08x} not in a PE/COFF (or TE) image' + + @ classmethod + def address_in_loaded_pecoff(cls, address): + if not isinstance(address, int): + address = int(address) + + for (pecoff, module) in cls.loaded.values(): + if (address >= pecoff.LoadAddress and + address <= pecoff.EndLoadAddress): + + return pecoff, module + + return None, None + + @ classmethod + def unload_symbols(cls, address): + pecoff, module = cls.address_in_loaded_pecoff(address) + if module: + name = str(module) + cls.target.ClearModuleLoadAddress(module) + cls.target.RemoveModule(module) + del cls.loaded[pecoff.LoadAddress] + return f'{name:s} was unloaded' + return f'0x{address:x} was not in a loaded image' + + +def arg_to_address(frame, arg): + ''' convert an lldb command arg into a memory address (addr_t)''' + + if arg is None: + return None + + arg_str = arg if isinstance(arg, str) else str(arg) + SBValue = frame.EvaluateExpression(arg_str) + if SBValue.error.fail: + return arg + + if (SBValue.TypeIsPointerType() or + SBValue.value_type == lldb.eValueTypeRegister or + SBValue.value_type == lldb.eValueTypeRegisterSet or + SBValue.value_type == lldb.eValueTypeConstResult): + try: + addr = SBValue.GetValueAsAddress() + except ValueError: + addr = SBValue.unsigned + else: + try: + addr = SBValue.address_of.GetValueAsAddress() + except ValueError: + addr = SBValue.address_of.unsigned + + return addr + + +def arg_to_data(frame, arg): + ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)''' + if not isinstance(arg, str): + arg_str = str(str) + + SBValue = frame.EvaluateExpression(arg_str) + return SBValue.unsigned + + +class EfiDevicePathCommand: + + def create_options(self): + ''' standard lldb command help/options parser''' + usage = "usage: %prog [options]" + description = '''Command that can EFI Config Tables +''' + + # Pass add_help_option = False, since this keeps the command in line + # with lldb commands, and we wire up "help command" to work by + # providing the long & short help methods below. + self.parser = optparse.OptionParser( + description=description, + prog='devicepath', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='hex dump extra data', + default=False) + + self.parser.add_option( + '-n', + '--node', + action='store_true', + dest='node', + help='dump a single device path node', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + def get_short_help(self): + '''standard lldb function method''' + return "Display EFI Tables" + + def get_long_help(self): + '''standard lldb function method''' + return self.help_string + + def __init__(self, debugger, internal_dict): + '''standard lldb function method''' + self.create_options() + self.help_string = self.parser.format_help() + + def __call__(self, debugger, command, exe_ctx, result): + '''standard lldb function method''' + # Use the Shell Lexer to properly parse up command options just like a + # shell would + command_args = shlex.split(command) + + try: + (options, args) = self.parser.parse_args(command_args) + dev_list = [] + for arg in args: + dev_list.append(arg_to_address(exe_ctx.frame, arg)) + except ValueError: + # if you don't handle exceptions, passing an incorrect argument + # to the OptionParser will cause LLDB to exit (courtesy of + # OptParse dealing with argument errors by throwing SystemExit) + result.SetError("option parsing failed") + return + + if options.help: + self.parser.print_help() + return + + file = LldbFileObject(exe_ctx.process) + + for dev_addr in dev_list: + if options.node: + print(EfiDevicePath(file).device_path_node_str( + dev_addr, options.verbose)) + else: + device_path = EfiDevicePath(file, dev_addr, options.verbose) + if device_path.valid(): + print(device_path) + + +class EfiHobCommand: + def create_options(self): + ''' standard lldb command help/options parser''' + usage = "usage: %prog [options]" + description = '''Command that can EFI dump EFI HOBs''' + + # Pass add_help_option = False, since this keeps the command in line + # with lldb commands, and we wire up "help command" to work by + # providing the long & short help methods below. + self.parser = optparse.OptionParser( + description=description, + prog='table', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-a', + '--address', + type="int", + dest='address', + help='Parse HOBs from address', + default=None) + + self.parser.add_option( + '-t', + '--type', + type="int", + dest='type', + help='Only dump HOBS of his type', + default=None) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='hex dump extra data', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + def get_short_help(self): + '''standard lldb function method''' + return "Display EFI Hobs" + + def get_long_help(self): + '''standard lldb function method''' + return self.help_string + + def __init__(self, debugger, internal_dict): + '''standard lldb function method''' + self.create_options() + self.help_string = self.parser.format_help() + + def __call__(self, debugger, command, exe_ctx, result): + '''standard lldb function method''' + # Use the Shell Lexer to properly parse up command options just like a + # shell would + command_args = shlex.split(command) + + try: + (options, _) = self.parser.parse_args(command_args) + except ValueError: + # if you don't handle exceptions, passing an incorrect argument + # to the OptionParser will cause LLDB to exit (courtesy of + # OptParse dealing with argument errors by throwing SystemExit) + result.SetError("option parsing failed") + return + + if options.help: + self.parser.print_help() + return + + address = arg_to_address(exe_ctx.frame, options.address) + + file = LldbFileObject(exe_ctx.process) + hob = EfiHob(file, address, options.verbose).get_hob_by_type( + options.type) + print(hob) + + +class EfiTableCommand: + + def create_options(self): + ''' standard lldb command help/options parser''' + usage = "usage: %prog [options]" + description = '''Command that can display EFI Config Tables +''' + + # Pass add_help_option = False, since this keeps the command in line + # with lldb commands, and we wire up "help command" to work by + # providing the long & short help methods below. + self.parser = optparse.OptionParser( + description=description, + prog='table', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + def get_short_help(self): + '''standard lldb function method''' + return "Display EFI Tables" + + def get_long_help(self): + '''standard lldb function method''' + return self.help_string + + def __init__(self, debugger, internal_dict): + '''standard lldb function method''' + self.create_options() + self.help_string = self.parser.format_help() + + def __call__(self, debugger, command, exe_ctx, result): + '''standard lldb function method''' + # Use the Shell Lexer to properly parse up command options just like a + # shell would + command_args = shlex.split(command) + + try: + (options, _) = self.parser.parse_args(command_args) + except ValueError: + # if you don't handle exceptions, passing an incorrect argument + # to the OptionParser will cause LLDB to exit (courtesy of + # OptParse dealing with argument errors by throwing SystemExit) + result.SetError("option parsing failed") + return + + if options.help: + self.parser.print_help() + return + + gST = exe_ctx.target.FindFirstGlobalVariable('gST') + if gST.error.fail: + print('Error: This command requires symbols for gST to be loaded') + return + + file = LldbFileObject(exe_ctx.process) + table = EfiConfigurationTable(file, gST.unsigned) + if table: + print(table, '\n') + + +class EfiGuidCommand: + + def create_options(self): + ''' standard lldb command help/options parser''' + usage = "usage: %prog [options]" + description = ''' + Command that can display all EFI GUID's or give info on a + specific GUID's + ''' + self.parser = optparse.OptionParser( + description=description, + prog='guid', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-n', + '--new', + action='store_true', + dest='new', + help='Generate a new GUID', + default=False) + + self.parser.add_option( + '-v', + '--verbose', + action='store_true', + dest='verbose', + help='Also display GUID C structure values', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + def get_short_help(self): + '''standard lldb function method''' + return "Display EFI GUID's" + + def get_long_help(self): + '''standard lldb function method''' + return self.help_string + + def __init__(self, debugger, internal_dict): + '''standard lldb function method''' + self.create_options() + self.help_string = self.parser.format_help() + + def __call__(self, debugger, command, exe_ctx, result): + '''standard lldb function method''' + # Use the Shell Lexer to properly parse up command options just like a + # shell would + command_args = shlex.split(command) + + try: + (options, args) = self.parser.parse_args(command_args) + if len(args) >= 1: + # guid { 0x414e6bdd, 0xe47b, 0x47cc, + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} + # this generates multiple args + arg = ' '.join(args) + except ValueError: + # if you don't handle exceptions, passing an incorrect argument + # to the OptionParser will cause LLDB to exit (courtesy of + # OptParse dealing with argument errors by throwing SystemExit) + result.SetError("option parsing failed") + return + + if options.help: + self.parser.print_help() + return + + if options.new: + guid = uuid.uuid4() + print(str(guid).upper()) + print(GuidNames.to_c_guid(guid)) + return + + if len(args) > 0: + if GuidNames.is_guid_str(arg): + # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9 + key = arg.lower() + name = GuidNames.to_name(key) + elif GuidNames.is_c_guid(arg): + # guid { 0x414e6bdd, 0xe47b, 0x47cc, + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} + key = GuidNames.from_c_guid(arg) + name = GuidNames.to_name(key) + else: + # guid gEfiDxeServicesTableGuid + name = arg + try: + key = GuidNames.to_guid(name) + name = GuidNames.to_name(key) + except ValueError: + return + + extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else '' + print(f'{key}: {extra}{name}') + + else: + for key, value in GuidNames._dict_.items(): + if options.verbose: + extra = f'{GuidNames.to_c_guid(key)}: ' + else: + extra = '' + print(f'{key}: {extra}{value}') + + +class EfiSymbolicateCommand(object): + '''Class to abstract an lldb command''' + + def create_options(self): + ''' standard lldb command help/options parser''' + usage = "usage: %prog [options]" + description = '''Command that can load EFI PE/COFF and TE image + symbols. If you are having trouble in PEI try adding --pei. + ''' + + # Pass add_help_option = False, since this keeps the command in line + # with lldb commands, and we wire up "help command" to work by + # providing the long & short help methods below. + self.parser = optparse.OptionParser( + description=description, + prog='efi_symbols', + usage=usage, + add_help_option=False) + + self.parser.add_option( + '-a', + '--address', + type="int", + dest='address', + help='Load symbols for image at address', + default=None) + + self.parser.add_option( + '-f', + '--frame', + action='store_true', + dest='frame', + help='Load symbols for current stack frame', + default=False) + + self.parser.add_option( + '-p', + '--pc', + action='store_true', + dest='pc', + help='Load symbols for pc', + default=False) + + self.parser.add_option( + '--pei', + action='store_true', + dest='pei', + help='Load symbols for PEI (searches every 4 bytes)', + default=False) + + self.parser.add_option( + '-e', + '--extended', + action='store_true', + dest='extended', + help='Try to load all symbols based on config tables.', + default=False) + + self.parser.add_option( + '-r', + '--range', + type="long", + dest='range', + help='How far to search backward for start of PE/COFF Image', + default=None) + + self.parser.add_option( + '-s', + '--stride', + type="long", + dest='stride', + help='Boundary to search for PE/COFF header', + default=None) + + self.parser.add_option( + '-t', + '--thread', + action='store_true', + dest='thread', + help='Load symbols for the frames of all threads', + default=False) + + self.parser.add_option( + '-h', + '--help', + action='store_true', + dest='help', + help='Show help for the command', + default=False) + + def get_short_help(self): + '''standard lldb function method''' + return ( + "Load symbols based on an address that is part of" + " a PE/COFF EFI image.") + + def get_long_help(self): + '''standard lldb function method''' + return self.help_string + + def __init__(self, debugger, unused): + '''standard lldb function method''' + self.create_options() + self.help_string = self.parser.format_help() + + def lldb_print(self, lldb_str): + # capture command out like an lldb command + self.result.PutCString(lldb_str) + # flush the output right away + self.result.SetImmediateOutputFile( + self.exe_ctx.target.debugger.GetOutputFile()) + + def __call__(self, debugger, command, exe_ctx, result): + '''standard lldb function method''' + # Use the Shell Lexer to properly parse up command options just like a + # shell would + command_args = shlex.split(command) + + try: + (options, _) = self.parser.parse_args(command_args) + except ValueError: + # if you don't handle exceptions, passing an incorrect argument + # to the OptionParser will cause LLDB to exit (courtesy of + # OptParse dealing with argument errors by throwing SystemExit) + result.SetError("option parsing failed") + return + + if options.help: + self.parser.print_help() + return + + file = LldbFileObject(exe_ctx.process) + efi_symbols = EfiSymbols(exe_ctx.target) + self.result = result + self.exe_ctx = exe_ctx + + if options.pei: + # XIP code ends up on a 4 byte boundary. + options.stride = 4 + options.range = 0x100000 + efi_symbols.configure_search(options.stride, options.range) + + if not options.pc and options.address is None: + # default to + options.frame = True + + if options.frame: + if not exe_ctx.frame.IsValid(): + result.SetError("invalid frame") + return + + threads = exe_ctx.process.threads if options.thread else [ + exe_ctx.thread] + + for thread in threads: + for frame in thread: + res = efi_symbols.address_to_symbols(frame.pc) + self.lldb_print(res) + + else: + if options.address is not None: + address = options.address + elif options.pc: + try: + address = exe_ctx.thread.GetSelectedFrame().pc + except ValueError: + result.SetError("invalid pc") + return + else: + address = 0 + + res = efi_symbols.address_to_symbols(address.pc) + print(res) + + if options.extended: + + gST = exe_ctx.target.FindFirstGlobalVariable('gST') + if gST.error.fail: + print('Error: This command requires symbols to be loaded') + else: + table = EfiConfigurationTable(file, gST.unsigned) + for address, _ in table.DebugImageInfo(): + res = efi_symbols.address_to_symbols(address) + self.lldb_print(res) + + # keep trying module file names until we find a GUID xref file + for m in exe_ctx.target.modules: + if GuidNames.add_build_guid_file(str(m.file)): + break + + +def CHAR16_TypeSummary(valobj, internal_dict): + ''' + Display CHAR16 as a String in the debugger. + Note: utf-8 is returned as that is the value for the debugger. + ''' + SBError = lldb.SBError() + Str = '' + if valobj.TypeIsPointerType(): + if valobj.GetValueAsUnsigned() == 0: + return "NULL" + + # CHAR16 * max string size 1024 + for i in range(1024): + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0) + if SBError.fail or Char == 0: + break + Str += chr(Char) + return 'L"' + Str + '"' + + if valobj.num_children == 0: + # CHAR16 + return "L'" + chr(valobj.unsigned) + "'" + + else: + # CHAR16 [] + for i in range(valobj.num_children): + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0) + if Char == 0: + break + Str += chr(Char) + return 'L"' + Str + '"' + + return Str + + +def CHAR8_TypeSummary(valobj, internal_dict): + ''' + Display CHAR8 as a String in the debugger. + Note: utf-8 is returned as that is the value for the debugger. + ''' + SBError = lldb.SBError() + Str = '' + if valobj.TypeIsPointerType(): + if valobj.GetValueAsUnsigned() == 0: + return "NULL" + + # CHAR8 * max string size 1024 + for i in range(1024): + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0) + if SBError.fail or Char == 0: + break + Str += chr(Char) + Str = '"' + Str + '"' + return Str + + if valobj.num_children == 0: + # CHAR8 + return "'" + chr(valobj.unsigned) + "'" + else: + # CHAR8 [] + for i in range(valobj.num_children): + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0) + if SBError.fail or Char == 0: + break + Str += chr(Char) + return '"' + Str + '"' + + return Str + + +def EFI_STATUS_TypeSummary(valobj, internal_dict): + if valobj.TypeIsPointerType(): + return '' + return str(EfiStatusClass(valobj.unsigned)) + + +def EFI_TPL_TypeSummary(valobj, internal_dict): + if valobj.TypeIsPointerType(): + return '' + return str(EfiTpl(valobj.unsigned)) + + +def EFI_GUID_TypeSummary(valobj, internal_dict): + if valobj.TypeIsPointerType(): + return '' + return str(GuidNames(bytes(valobj.data.uint8))) + + +def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict): + if valobj.TypeIsPointerType(): + return '' + '''Return #define name for EFI_BOOT_MODE''' + return str(EfiBootMode(valobj.unsigned)) + + +def lldb_type_formaters(debugger, mod_name): + '''Teach lldb about EFI types''' + + category = debugger.GetDefaultCategory() + FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool) + + FormatHex = lldb.SBTypeFormat(lldb.eFormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier( + "EFI_PHYSICAL_ADDRESS"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier( + "PHYSICAL_ADDRESS"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex) + category.AddTypeFormat( + lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex) + category.AddTypeFormat(lldb.SBTypeNameSpecifier( + "EFI_FV_FILETYPE"), FormatHex) + + # + # Smart type printing for EFI + # + + debugger.HandleCommand( + f'type summary add GUID - -python-function ' + f'{mod_name}.EFI_GUID_TypeSummary') + debugger.HandleCommand( + f'type summary add EFI_GUID --python-function ' + f'{mod_name}.EFI_GUID_TypeSummary') + debugger.HandleCommand( + f'type summary add EFI_STATUS --python-function ' + f'{mod_name}.EFI_STATUS_TypeSummary') + debugger.HandleCommand( + f'type summary add EFI_TPL - -python-function ' + f'{mod_name}.EFI_TPL_TypeSummary') + debugger.HandleCommand( + f'type summary add EFI_BOOT_MODE --python-function ' + f'{mod_name}.EFI_BOOT_MODE_TypeSummary') + + debugger.HandleCommand( + f'type summary add CHAR16 --python-function ' + f'{mod_name}.CHAR16_TypeSummary') + + # W605 this is the correct escape sequence for the lldb command + debugger.HandleCommand( + f'type summary add --regex "CHAR16 \[[0-9]+\]" ' # noqa: W605 + f'--python-function {mod_name}.CHAR16_TypeSummary') + + debugger.HandleCommand( + f'type summary add CHAR8 --python-function ' + f'{mod_name}.CHAR8_TypeSummary') + + # W605 this is the correct escape sequence for the lldb command + debugger.HandleCommand( + f'type summary add --regex "CHAR8 \[[0-9]+\]" ' # noqa: W605 + f'--python-function {mod_name}.CHAR8_TypeSummary') + + +class LldbWorkaround: + needed = True + + @classmethod + def activate(cls): + if cls.needed: + lldb.debugger.HandleCommand("process handle SIGALRM -n false") + cls.needed = False + + +def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict): + # + # This is an lldb breakpoint script, and assumes the breakpoint is on a + # function with the same prototype as SecGdbScriptBreak(). The + # argument names are important as lldb looks them up. + # + # VOID + # SecGdbScriptBreak ( + # char *FileName, + # int FileNameLength, + # long unsigned int LoadAddress, + # int AddSymbolFlag + # ) + # { + # return; + # } + # + # When the emulator loads a PE/COFF image, it calls the stub function with + # the filename of the symbol file, the length of the FileName, the + # load address and a flag to indicate if this is a load or unload operation + # + LldbWorkaround().activate() + + symbols = EfiSymbols(frame.thread.process.target) + LoadAddress = frame.FindVariable("LoadAddress").unsigned + if frame.FindVariable("AddSymbolFlag").unsigned == 1: + res = symbols.address_to_symbols(LoadAddress) + else: + res = symbols.unload_symbols(LoadAddress) + print(res) + + # make breakpoint command continue + return False + + +def __lldb_init_module(debugger, internal_dict): + ''' + This initializer is being run from LLDB in the embedded command interpreter + ''' + + mod_name = Path(__file__).stem + lldb_type_formaters(debugger, mod_name) + + # Add any commands contained in this module to LLDB + debugger.HandleCommand( + f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols') + debugger.HandleCommand( + f'command script add -c {mod_name}.EfiGuidCommand guid') + debugger.HandleCommand( + f'command script add -c {mod_name}.EfiTableCommand table') + debugger.HandleCommand( + f'command script add -c {mod_name}.EfiHobCommand hob') + debugger.HandleCommand( + f'command script add -c {mod_name}.EfiDevicePathCommand devicepath') + + print('EFI specific commands have been installed.') + + # patch the ctypes c_void_p values if the debuggers OS and EFI have + # different ideas on the size of the debug. + try: + patch_ctypes(debugger.GetSelectedTarget().addr_size) + except ValueError: + # incase the script is imported and the debugger has not target + # defaults to sizeof(UINTN) == sizeof(UINT64) + patch_ctypes() + + try: + target = debugger.GetSelectedTarget() + if target.FindFunctions('SecGdbScriptBreak').symbols: + breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak') + # Set the emulator breakpoints, if we are in the emulator + cmd = 'breakpoint command add -s python -F ' + cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}' + debugger.HandleCommand(cmd) + print('Type r to run emulator.') + else: + raise ValueError("No Emulator Symbols") + + except ValueError: + # default action when the script is imported + debugger.HandleCommand("efi_symbols --frame --extended") + debugger.HandleCommand("register read") + debugger.HandleCommand("bt all") + + +if __name__ == '__main__': + pass -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [edk2-devel] [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print 2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran @ 2022-04-09 4:16 ` Bob Feng 0 siblings, 0 replies; 10+ messages in thread From: Bob Feng @ 2022-04-09 4:16 UTC (permalink / raw) To: Rebecca Cran, devel [-- Attachment #1: Type: text/plain, Size: 32626 bytes --] Reviewed-by: Bob Feng <bob.c.feng@intel.com> On Tue, Mar 22, 2022 at 04:21 AM, Rebecca Cran wrote: > > https://bugzilla.tianocore.org/show_bug.cgi?id=3500 > > Use efi_debugging.py Python Classes to implement EFI gdb commands: > efi_symbols, guid, table, hob, and devicepath > > You can attach to any standard gdb or kdp remote server and get EFI > symbols. No modifications of EFI are required. > > Example usage: > OvmfPkg/build.sh qemu -gdb tcp::9000 > lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py" > > Note you may also have to teach lldb about QEMU: > -o "settings set plugin.process.gdb-remote.target-definition-file > x86_64_target_definition.py" > > Cc: Leif Lindholm <quic_llindhol@quicinc.com> > Cc: Michael D Kinney <michael.d.kinney@intel.com> > Cc: Hao A Wu <hao.a.wu@intel.com> > Cc: Bob Feng <bob.c.feng@intel.com> > Cc: Liming Gao <gaoliming@byosoft.com.cn> > Cc: Yuwei Chen <yuwei.chen@intel.com> > Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> > --- > BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++ > 1 file changed, 1044 insertions(+) > > diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py > > new file mode 100755 > index 000000000000..089b6ba58ab8 > --- /dev/null > +++ b/BaseTools/Scripts/efi_lldb.py > @@ -0,0 +1,1044 @@ > +#!/usr/bin/python3 > +''' > +Copyright (c) Apple Inc. 2021 > +SPDX-License-Identifier: BSD-2-Clause-Patent > + > +Example usage: > +OvmfPkg/build.sh qemu -gdb tcp::9000 > +lldb -o "gdb-remote localhost:9000" -o "command script import > efi_lldb.py" > +''' > + > +import optparse > +import shlex > +import subprocess > +import uuid > +import sys > +import os > +from pathlib import Path > +from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl > +from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode > +from efi_debugging import PeTeImage, patch_ctypes > + > +try: > + # Just try for LLDB in case PYTHONPATH is already correctly setup > + import lldb > +except ImportError: > + try: > + env = os.environ.copy() > + env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major) > + lldb_python_path = subprocess.check_output( > + ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip() > + sys.path.append(lldb_python_path) > + import lldb > + except ValueError: > + print("Couldn't find LLDB.framework from lldb -P") > + print("PYTHONPATH should match the currently selected lldb") > + sys.exit(-1) > + > + > +class LldbFileObject(object): > + ''' > + Class that fakes out file object to abstract lldb from the generic code. > > + For lldb this is memory so we don't have a concept of the end of the > file. > + ''' > + > + def __init__(self, process): > + # _exe_ctx is lldb.SBExecutionContext > + self._process = process > + self._offset = 0 > + self._SBError = lldb.SBError() > + > + def tell(self): > + return self._offset > + > + def read(self, size=-1): > + if size == -1: > + # arbitrary default size > + size = 0x1000000 > + > + data = self._process.ReadMemory(self._offset, size, self._SBError) > + if self._SBError.fail: > + raise MemoryError( > + f'lldb could not read memory 0x{size:x} ' > + f' bytes from 0x{self._offset:08x}') > + else: > + return data > + > + def readable(self): > + return True > + > + def seek(self, offset, whence=0): > + if whence == 0: > + self._offset = offset > + elif whence == 1: > + self._offset += offset > + else: > + # whence == 2 is seek from end > + raise NotImplementedError > + > + def seekable(self): > + return True > + > + def write(self, data): > + result = self._process.WriteMemory(self._offset, data, self._SBError) > + if self._SBError.fail: > + raise MemoryError( > + f'lldb could not write memory to 0x{self._offset:08x}') > + return result > + > + def writable(self): > + return True > + > + def truncate(self, size=None): > + raise NotImplementedError > + > + def flush(self): > + raise NotImplementedError > + > + def fileno(self): > + raise NotImplementedError > + > + > +class EfiSymbols: > + """ > + Class to manage EFI Symbols > + You need to pass file, and exe_ctx to load symbols. > + You can print(EfiSymbols()) to see the currently loaded symbols > + """ > + > + loaded = {} > + stride = None > + range = None > + verbose = False > + > + def __init__(self, target=None): > + if target: > + EfiSymbols.target = target > + EfiSymbols._file = LldbFileObject(target.process) > + > + @ classmethod > + def __str__(cls): > + return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values()) > + > + @ classmethod > + def configure_search(cls, stride, range, verbose=False): > + cls.stride = stride > + cls.range = range > + cls.verbose = verbose > + > + @ classmethod > + def clear(cls): > + cls.loaded = {} > + > + @ classmethod > + def add_symbols_for_pecoff(cls, pecoff): > + '''Tell lldb the location of the .text and .data sections.''' > + > + if pecoff.LoadAddress in cls.loaded: > + return 'Already Loaded: ' > + > + module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid)) > + if not module: > + module = cls.target.AddModule(pecoff.CodeViewPdb, > + None, > + str(pecoff.CodeViewUuid)) > + if module.IsValid(): > + SBError = cls.target.SetModuleLoadAddress( > + module, pecoff.LoadAddress + pecoff.TeAdjust) > + if SBError.success: > + cls.loaded[pecoff.LoadAddress] = (pecoff, module) > + return '' > + > + return 'Symbols NOT FOUND: ' > + > + @ classmethod > + def address_to_symbols(cls, address, reprobe=False): > + ''' > + Given an address search backwards for a PE/COFF (or TE) header > + and load symbols. Return a status string. > + ''' > + if not isinstance(address, int): > + address = int(address) > + > + pecoff, _ = cls.address_in_loaded_pecoff(address) > + if not reprobe and pecoff is not None: > + # skip the probe of the remote > + return f'{pecoff} is already loaded' > + > + pecoff = PeTeImage(cls._file, None) > + if pecoff.pcToPeCoff(address, cls.stride, cls.range): > + res = cls.add_symbols_for_pecoff(pecoff) > + return f'{res}{pecoff}' > + else: > + return f'0x{address:08x} not in a PE/COFF (or TE) image' > + > + @ classmethod > + def address_in_loaded_pecoff(cls, address): > + if not isinstance(address, int): > + address = int(address) > + > + for (pecoff, module) in cls.loaded.values(): > + if (address >= pecoff.LoadAddress and > + address <= pecoff.EndLoadAddress): > + > + return pecoff, module > + > + return None, None > + > + @ classmethod > + def unload_symbols(cls, address): > + pecoff, module = cls.address_in_loaded_pecoff(address) > + if module: > + name = str(module) > + cls.target.ClearModuleLoadAddress(module) > + cls.target.RemoveModule(module) > + del cls.loaded[pecoff.LoadAddress] > + return f'{name:s} was unloaded' > + return f'0x{address:x} was not in a loaded image' > + > + > +def arg_to_address(frame, arg): > + ''' convert an lldb command arg into a memory address (addr_t)''' > + > + if arg is None: > + return None > + > + arg_str = arg if isinstance(arg, str) else str(arg) > + SBValue = frame.EvaluateExpression(arg_str) > + if SBValue.error.fail: > + return arg > + > + if (SBValue.TypeIsPointerType() or > + SBValue.value_type == lldb.eValueTypeRegister or > + SBValue.value_type == lldb.eValueTypeRegisterSet or > + SBValue.value_type == lldb.eValueTypeConstResult): > + try: > + addr = SBValue.GetValueAsAddress() > + except ValueError: > + addr = SBValue.unsigned > + else: > + try: > + addr = SBValue.address_of.GetValueAsAddress() > + except ValueError: > + addr = SBValue.address_of.unsigned > + > + return addr > + > + > +def arg_to_data(frame, arg): > + ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)''' > + if not isinstance(arg, str): > + arg_str = str(str) > + > + SBValue = frame.EvaluateExpression(arg_str) > + return SBValue.unsigned > + > + > +class EfiDevicePathCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can EFI Config Tables > +''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='devicepath', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action='store_true', > + dest='verbose', > + help='hex dump extra data', > + default=False) > + > + self.parser.add_option( > + '-n', > + '--node', > + action='store_true', > + dest='node', > + help='dump a single device path node', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI Tables" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, args) = self.parser.parse_args(command_args) > + dev_list = [] > + for arg in args: > + dev_list.append(arg_to_address(exe_ctx.frame, arg)) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + file = LldbFileObject(exe_ctx.process) > + > + for dev_addr in dev_list: > + if options.node: > + print(EfiDevicePath(file).device_path_node_str( > + dev_addr, options.verbose)) > + else: > + device_path = EfiDevicePath(file, dev_addr, options.verbose) > + if device_path.valid(): > + print(device_path) > + > + > +class EfiHobCommand: > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can EFI dump EFI HOBs''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='table', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-a', > + '--address', > + type="int", > + dest='address', > + help='Parse HOBs from address', > + default=None) > + > + self.parser.add_option( > + '-t', > + '--type', > + type="int", > + dest='type', > + help='Only dump HOBS of his type', > + default=None) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action='store_true', > + dest='verbose', > + help='hex dump extra data', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI Hobs" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, _) = self.parser.parse_args(command_args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + address = arg_to_address(exe_ctx.frame, options.address) > + > + file = LldbFileObject(exe_ctx.process) > + hob = EfiHob(file, address, options.verbose).get_hob_by_type( > + options.type) > + print(hob) > + > + > +class EfiTableCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can display EFI Config Tables > +''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='table', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI Tables" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, _) = self.parser.parse_args(command_args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + gST = exe_ctx.target.FindFirstGlobalVariable('gST') > + if gST.error.fail: > + print('Error: This command requires symbols for gST to be loaded') > + return > + > + file = LldbFileObject(exe_ctx.process) > + table = EfiConfigurationTable(file, gST.unsigned) > + if table: > + print(table, '\n') > + > + > +class EfiGuidCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = ''' > + Command that can display all EFI GUID's or give info on a > + specific GUID's > + ''' > + self.parser = optparse.OptionParser( > + description=description, > + prog='guid', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-n', > + '--new', > + action='store_true', > + dest='new', > + help='Generate a new GUID', > + default=False) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action='store_true', > + dest='verbose', > + help='Also display GUID C structure values', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return "Display EFI GUID's" > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, internal_dict): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, args) = self.parser.parse_args(command_args) > + if len(args) >= 1: > + # guid { 0x414e6bdd, 0xe47b, 0x47cc, > + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} > + # this generates multiple args > + arg = ' '.join(args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + if options.new: > + guid = uuid.uuid4() > + print(str(guid).upper()) > + print(GuidNames.to_c_guid(guid)) > + return > + > + if len(args) > 0: > + if GuidNames.is_guid_str(arg): > + # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9 > + key = arg.lower() > + name = GuidNames.to_name(key) > + elif GuidNames.is_c_guid(arg): > + # guid { 0x414e6bdd, 0xe47b, 0x47cc, > + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} > + key = GuidNames.from_c_guid(arg) > + name = GuidNames.to_name(key) > + else: > + # guid gEfiDxeServicesTableGuid > + name = arg > + try: > + key = GuidNames.to_guid(name) > + name = GuidNames.to_name(key) > + except ValueError: > + return > + > + extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else '' > + print(f'{key}: {extra}{name}') > + > + else: > + for key, value in GuidNames._dict_.items(): > + if options.verbose: > + extra = f'{GuidNames.to_c_guid(key)}: ' > + else: > + extra = '' > + print(f'{key}: {extra}{value}') > + > + > +class EfiSymbolicateCommand(object): > + '''Class to abstract an lldb command''' > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage = "usage: %prog [options]" > + description = '''Command that can load EFI PE/COFF and TE image > + symbols. If you are having trouble in PEI try adding --pei. > + ''' > + > + # Pass add_help_option = False, since this keeps the command in line > + # with lldb commands, and we wire up "help command" to work by > + # providing the long & short help methods below. > + self.parser = optparse.OptionParser( > + description=description, > + prog='efi_symbols', > + usage=usage, > + add_help_option=False) > + > + self.parser.add_option( > + '-a', > + '--address', > + type="int", > + dest='address', > + help='Load symbols for image at address', > + default=None) > + > + self.parser.add_option( > + '-f', > + '--frame', > + action='store_true', > + dest='frame', > + help='Load symbols for current stack frame', > + default=False) > + > + self.parser.add_option( > + '-p', > + '--pc', > + action='store_true', > + dest='pc', > + help='Load symbols for pc', > + default=False) > + > + self.parser.add_option( > + '--pei', > + action='store_true', > + dest='pei', > + help='Load symbols for PEI (searches every 4 bytes)', > + default=False) > + > + self.parser.add_option( > + '-e', > + '--extended', > + action='store_true', > + dest='extended', > + help='Try to load all symbols based on config tables.', > + default=False) > + > + self.parser.add_option( > + '-r', > + '--range', > + type="long", > + dest='range', > + help='How far to search backward for start of PE/COFF Image', > + default=None) > + > + self.parser.add_option( > + '-s', > + '--stride', > + type="long", > + dest='stride', > + help='Boundary to search for PE/COFF header', > + default=None) > + > + self.parser.add_option( > + '-t', > + '--thread', > + action='store_true', > + dest='thread', > + help='Load symbols for the frames of all threads', > + default=False) > + > + self.parser.add_option( > + '-h', > + '--help', > + action='store_true', > + dest='help', > + help='Show help for the command', > + default=False) > + > + def get_short_help(self): > + '''standard lldb function method''' > + return ( > + "Load symbols based on an address that is part of" > + " a PE/COFF EFI image.") > + > + def get_long_help(self): > + '''standard lldb function method''' > + return self.help_string > + > + def __init__(self, debugger, unused): > + '''standard lldb function method''' > + self.create_options() > + self.help_string = self.parser.format_help() > + > + def lldb_print(self, lldb_str): > + # capture command out like an lldb command > + self.result.PutCString(lldb_str) > + # flush the output right away > + self.result.SetImmediateOutputFile( > + self.exe_ctx.target.debugger.GetOutputFile()) > + > + def __call__(self, debugger, command, exe_ctx, result): > + '''standard lldb function method''' > + # Use the Shell Lexer to properly parse up command options just like a > + # shell would > + command_args = shlex.split(command) > + > + try: > + (options, _) = self.parser.parse_args(command_args) > + except ValueError: > + # if you don't handle exceptions, passing an incorrect argument > + # to the OptionParser will cause LLDB to exit (courtesy of > + # OptParse dealing with argument errors by throwing SystemExit) > + result.SetError("option parsing failed") > + return > + > + if options.help: > + self.parser.print_help() > + return > + > + file = LldbFileObject(exe_ctx.process) > + efi_symbols = EfiSymbols(exe_ctx.target) > + self.result = result > + self.exe_ctx = exe_ctx > + > + if options.pei: > + # XIP code ends up on a 4 byte boundary. > + options.stride = 4 > + options.range = 0x100000 > + efi_symbols.configure_search(options.stride, options.range) > + > + if not options.pc and options.address is None: > + # default to > + options.frame = True > + > + if options.frame: > + if not exe_ctx.frame.IsValid(): > + result.SetError("invalid frame") > + return > + > + threads = exe_ctx.process.threads if options.thread else [ > + exe_ctx.thread] > + > + for thread in threads: > + for frame in thread: > + res = efi_symbols.address_to_symbols(frame.pc) > + self.lldb_print(res) > + > + else: > + if options.address is not None: > + address = options.address > + elif options.pc: > + try: > + address = exe_ctx.thread.GetSelectedFrame().pc > + except ValueError: > + result.SetError("invalid pc") > + return > + else: > + address = 0 > + > + res = efi_symbols.address_to_symbols(address.pc) > + print(res) > + > + if options.extended: > + > + gST = exe_ctx.target.FindFirstGlobalVariable('gST') > + if gST.error.fail: > + print('Error: This command requires symbols to be loaded') > + else: > + table = EfiConfigurationTable(file, gST.unsigned) > + for address, _ in table.DebugImageInfo(): > + res = efi_symbols.address_to_symbols(address) > + self.lldb_print(res) > + > + # keep trying module file names until we find a GUID xref file > + for m in exe_ctx.target.modules: > + if GuidNames.add_build_guid_file(str(m.file)): > + break > + > + > +def CHAR16_TypeSummary(valobj, internal_dict): > + ''' > + Display CHAR16 as a String in the debugger. > + Note: utf-8 is returned as that is the value for the debugger. > + ''' > + SBError = lldb.SBError() > + Str = '' > + if valobj.TypeIsPointerType(): > + if valobj.GetValueAsUnsigned() == 0: > + return "NULL" > + > + # CHAR16 * max string size 1024 > + for i in range(1024): > + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0) > + if SBError.fail or Char == 0: > + break > + Str += chr(Char) > + return 'L"' + Str + '"' > + > + if valobj.num_children == 0: > + # CHAR16 > + return "L'" + chr(valobj.unsigned) + "'" > + > + else: > + # CHAR16 [] > + for i in range(valobj.num_children): > + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0) > + if Char == 0: > + break > + Str += chr(Char) > + return 'L"' + Str + '"' > + > + return Str > + > + > +def CHAR8_TypeSummary(valobj, internal_dict): > + ''' > + Display CHAR8 as a String in the debugger. > + Note: utf-8 is returned as that is the value for the debugger. > + ''' > + SBError = lldb.SBError() > + Str = '' > + if valobj.TypeIsPointerType(): > + if valobj.GetValueAsUnsigned() == 0: > + return "NULL" > + > + # CHAR8 * max string size 1024 > + for i in range(1024): > + Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0) > + if SBError.fail or Char == 0: > + break > + Str += chr(Char) > + Str = '"' + Str + '"' > + return Str > + > + if valobj.num_children == 0: > + # CHAR8 > + return "'" + chr(valobj.unsigned) + "'" > + else: > + # CHAR8 [] > + for i in range(valobj.num_children): > + Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0) > + if SBError.fail or Char == 0: > + break > + Str += chr(Char) > + return '"' + Str + '"' > + > + return Str > + > + > +def EFI_STATUS_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + return str(EfiStatusClass(valobj.unsigned)) > + > + > +def EFI_TPL_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + return str(EfiTpl(valobj.unsigned)) > + > + > +def EFI_GUID_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + return str(GuidNames(bytes(valobj.data.uint8))) > + > + > +def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict): > + if valobj.TypeIsPointerType(): > + return '' > + '''Return #define name for EFI_BOOT_MODE''' > + return str(EfiBootMode(valobj.unsigned)) > + > + > +def lldb_type_formaters(debugger, mod_name): > + '''Teach lldb about EFI types''' > + > + category = debugger.GetDefaultCategory() > + FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool) > + > + FormatHex = lldb.SBTypeFormat(lldb.eFormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier( > + "EFI_PHYSICAL_ADDRESS"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier( > + "PHYSICAL_ADDRESS"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex) > + category.AddTypeFormat( > + lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier( > + "EFI_FV_FILETYPE"), FormatHex) > + > + # > + # Smart type printing for EFI > + # > + > + debugger.HandleCommand( > + f'type summary add GUID - -python-function ' > + f'{mod_name}.EFI_GUID_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_GUID --python-function ' > + f'{mod_name}.EFI_GUID_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_STATUS --python-function ' > + f'{mod_name}.EFI_STATUS_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_TPL - -python-function ' > + f'{mod_name}.EFI_TPL_TypeSummary') > + debugger.HandleCommand( > + f'type summary add EFI_BOOT_MODE --python-function ' > + f'{mod_name}.EFI_BOOT_MODE_TypeSummary') > + > + debugger.HandleCommand( > + f'type summary add CHAR16 --python-function ' > + f'{mod_name}.CHAR16_TypeSummary') > + > + # W605 this is the correct escape sequence for the lldb command > + debugger.HandleCommand( > + f'type summary add --regex "CHAR16 \[[0-9]+\]" ' # noqa: W605 > + f'--python-function {mod_name}.CHAR16_TypeSummary') > + > + debugger.HandleCommand( > + f'type summary add CHAR8 --python-function ' > + f'{mod_name}.CHAR8_TypeSummary') > + > + # W605 this is the correct escape sequence for the lldb command > + debugger.HandleCommand( > + f'type summary add --regex "CHAR8 \[[0-9]+\]" ' # noqa: W605 > + f'--python-function {mod_name}.CHAR8_TypeSummary') > + > + > +class LldbWorkaround: > + needed = True > + > + @classmethod > + def activate(cls): > + if cls.needed: > + lldb.debugger.HandleCommand("process handle SIGALRM -n false") > + cls.needed = False > + > + > +def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict): > + # > + # This is an lldb breakpoint script, and assumes the breakpoint is on a > + # function with the same prototype as SecGdbScriptBreak(). The > + # argument names are important as lldb looks them up. > + # > + # VOID > + # SecGdbScriptBreak ( > + # char *FileName, > + # int FileNameLength, > + # long unsigned int LoadAddress, > + # int AddSymbolFlag > + # ) > + # { > + # return; > + # } > + # > + # When the emulator loads a PE/COFF image, it calls the stub function > with > + # the filename of the symbol file, the length of the FileName, the > + # load address and a flag to indicate if this is a load or unload > operation > + # > + LldbWorkaround().activate() > + > + symbols = EfiSymbols(frame.thread.process.target) > + LoadAddress = frame.FindVariable("LoadAddress").unsigned > + if frame.FindVariable("AddSymbolFlag").unsigned == 1: > + res = symbols.address_to_symbols(LoadAddress) > + else: > + res = symbols.unload_symbols(LoadAddress) > + print(res) > + > + # make breakpoint command continue > + return False > + > + > +def __lldb_init_module(debugger, internal_dict): > + ''' > + This initializer is being run from LLDB in the embedded command > interpreter > + ''' > + > + mod_name = Path(__file__).stem > + lldb_type_formaters(debugger, mod_name) > + > + # Add any commands contained in this module to LLDB > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiGuidCommand guid') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiTableCommand table') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiHobCommand hob') > + debugger.HandleCommand( > + f'command script add -c {mod_name}.EfiDevicePathCommand devicepath') > + > + print('EFI specific commands have been installed.') > + > + # patch the ctypes c_void_p values if the debuggers OS and EFI have > + # different ideas on the size of the debug. > + try: > + patch_ctypes(debugger.GetSelectedTarget().addr_size) > + except ValueError: > + # incase the script is imported and the debugger has not target > + # defaults to sizeof(UINTN) == sizeof(UINT64) > + patch_ctypes() > + > + try: > + target = debugger.GetSelectedTarget() > + if target.FindFunctions('SecGdbScriptBreak').symbols: > + breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak') > + # Set the emulator breakpoints, if we are in the emulator > + cmd = 'breakpoint command add -s python -F ' > + cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}' > + debugger.HandleCommand(cmd) > + print('Type r to run emulator.') > + else: > + raise ValueError("No Emulator Symbols") > + > + except ValueError: > + # default action when the script is imported > + debugger.HandleCommand("efi_symbols --frame --extended") > + debugger.HandleCommand("register read") > + debugger.HandleCommand("bt all") > + > + > +if __name__ == '__main__': > + pass > -- > 2.34.1 [-- Attachment #2: Type: text/html, Size: 34958 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb 2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran ` (2 preceding siblings ...) 2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran @ 2022-04-06 22:34 ` Rebecca Cran 2022-04-07 5:02 ` 回复: " gaoliming 2022-04-09 4:47 ` Bob Feng 3 siblings, 2 replies; 10+ messages in thread From: Rebecca Cran @ 2022-04-06 22:34 UTC (permalink / raw) To: devel, Leif Lindholm, Michael D Kinney, Hao A Wu, Bob Feng, Liming Gao, Yuwei Chen Cc: Andrew Fish Could I have some more reviews on this please? I'd like to get this into the tree soon. -- Rebecca Cran On 3/21/22 14:20, Rebecca Cran wrote: > This patch set adds debugging support for gdb and lldb. > It also adds generic debugging classes that use a file like object to > make it easy to import into any debugger that supports Python. > > Changes from v1 to v2: > - Moved scripts from the root of the repo into BaseTools/Scripts. > - Fixed typo of "RISCV" as "RISKV". > > Testing: > - Tested gdb on Ubuntu and lldb on macOS for IA32 and X64. > - Tested gdb on openSUSE for AARCH64. > > Rebecca Cran (3): > BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes > BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print > BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print > > BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++ > BaseTools/Scripts/efi_gdb.py | 918 ++++++++ > BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++ > 3 files changed, 4147 insertions(+) > create mode 100755 BaseTools/Scripts/efi_debugging.py > create mode 100755 BaseTools/Scripts/efi_gdb.py > create mode 100755 BaseTools/Scripts/efi_lldb.py > ^ permalink raw reply [flat|nested] 10+ messages in thread
* 回复: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb 2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran @ 2022-04-07 5:02 ` gaoliming 2022-04-09 4:47 ` Bob Feng 1 sibling, 0 replies; 10+ messages in thread From: gaoliming @ 2022-04-07 5:02 UTC (permalink / raw) To: 'Rebecca Cran', devel, 'Leif Lindholm', 'Michael D Kinney', 'Hao A Wu', 'Bob Feng', 'Yuwei Chen' Cc: 'Andrew Fish' Acked-by: Liming Gao <gaoliming@byosoft.com.cn> > -----邮件原件----- > 发件人: Rebecca Cran <quic_rcran@quicinc.com> > 发送时间: 2022年4月7日 6:34 > 收件人: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>; > Michael D Kinney <michael.d.kinney@intel.com>; Hao A Wu > <hao.a.wu@intel.com>; Bob Feng <bob.c.feng@intel.com>; Liming Gao > <gaoliming@byosoft.com.cn>; Yuwei Chen <yuwei.chen@intel.com> > 抄送: Andrew Fish <afish@apple.com> > 主题: Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb > > Could I have some more reviews on this please? I'd like to get this into > the tree soon. > > -- > Rebecca Cran > > On 3/21/22 14:20, Rebecca Cran wrote: > > This patch set adds debugging support for gdb and lldb. > > It also adds generic debugging classes that use a file like object to > > make it easy to import into any debugger that supports Python. > > > > Changes from v1 to v2: > > - Moved scripts from the root of the repo into BaseTools/Scripts. > > - Fixed typo of "RISCV" as "RISKV". > > > > Testing: > > - Tested gdb on Ubuntu and lldb on macOS for IA32 and X64. > > - Tested gdb on openSUSE for AARCH64. > > > > Rebecca Cran (3): > > BaseTools: efi_debugging.py: Add debugger agnostic dbg Python > Classes > > BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print > > BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print > > > > BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++ > > BaseTools/Scripts/efi_gdb.py | 918 ++++++++ > > BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++ > > 3 files changed, 4147 insertions(+) > > create mode 100755 BaseTools/Scripts/efi_debugging.py > > create mode 100755 BaseTools/Scripts/efi_gdb.py > > create mode 100755 BaseTools/Scripts/efi_lldb.py > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb 2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran 2022-04-07 5:02 ` 回复: " gaoliming @ 2022-04-09 4:47 ` Bob Feng 1 sibling, 0 replies; 10+ messages in thread From: Bob Feng @ 2022-04-09 4:47 UTC (permalink / raw) To: Rebecca Cran, devel@edk2.groups.io, Leif Lindholm, Kinney, Michael D, Wu, Hao A, Gao, Liming, Chen, Christine Cc: Andrew Fish Created the PR. https://github.com/tianocore/edk2/pull/2758 -----Original Message----- From: Rebecca Cran <quic_rcran@quicinc.com> Sent: Thursday, April 7, 2022 6:34 AM To: devel@edk2.groups.io; Leif Lindholm <quic_llindhol@quicinc.com>; Kinney, Michael D <michael.d.kinney@intel.com>; Wu, Hao A <hao.a.wu@intel.com>; Feng, Bob C <bob.c.feng@intel.com>; Gao, Liming <gaoliming@byosoft.com.cn>; Chen, Christine <yuwei.chen@intel.com> Cc: Andrew Fish <afish@apple.com> Subject: Re: [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Could I have some more reviews on this please? I'd like to get this into the tree soon. -- Rebecca Cran On 3/21/22 14:20, Rebecca Cran wrote: > This patch set adds debugging support for gdb and lldb. > It also adds generic debugging classes that use a file like object to > make it easy to import into any debugger that supports Python. > > Changes from v1 to v2: > - Moved scripts from the root of the repo into BaseTools/Scripts. > - Fixed typo of "RISCV" as "RISKV". > > Testing: > - Tested gdb on Ubuntu and lldb on macOS for IA32 and X64. > - Tested gdb on openSUSE for AARCH64. > > Rebecca Cran (3): > BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes > BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print > BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty > Print > > BaseTools/Scripts/efi_debugging.py | 2185 ++++++++++++++++++++ > BaseTools/Scripts/efi_gdb.py | 918 ++++++++ > BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++ > 3 files changed, 4147 insertions(+) > create mode 100755 BaseTools/Scripts/efi_debugging.py > create mode 100755 BaseTools/Scripts/efi_gdb.py > create mode 100755 BaseTools/Scripts/efi_lldb.py > ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2022-04-09 4:47 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran 2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran 2022-03-26 14:04 ` Bob Feng 2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran 2022-03-26 14:05 ` Bob Feng 2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran 2022-04-09 4:16 ` [edk2-devel] " Bob Feng 2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran 2022-04-07 5:02 ` 回复: " gaoliming 2022-04-09 4:47 ` Bob Feng
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox