From 6027c63ab50a70e34042d4b769cbcb2650b9c09a Mon Sep 17 00:00:00 2001 From: Mike Beaton Date: Sat, 28 Oct 2023 11:24:23 +0100 Subject: [PATCH] Debug: Add support for 32-bit on 32-bit under LLDB Also includes not-quite-working 32-bit on 64-bit LLDB support --- Changelog.md | 2 +- Debug/Scripts/lldb_uefi.py | 63 ++++++++++++++++++++++++-------------- Debug/efidebug.tool | 21 ++++++++++++- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/Changelog.md b/Changelog.md index 3079f3ac2ec7..2835bcf4da06 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,7 +3,7 @@ OpenCore Changelog #### v0.9.6 - Updated builtin firmware versions for SMBIOS and the rest - Fixed hang while generating boot entries on some systems -- Add `efidebug.tool` support for 32-bit on 32-bit using GDB (in addition to existing 32-bit on 64-bit support) +- Add `efidebug.tool` support for 32-bit on 32-bit using GDB or LLDB (in addition to existing 32-bit on 64-bit support) #### v0.9.5 - Fixed GUID formatting for legacy NVRAM saving diff --git a/Debug/Scripts/lldb_uefi.py b/Debug/Scripts/lldb_uefi.py index b1b29149413b..fbb63b908554 100644 --- a/Debug/Scripts/lldb_uefi.py +++ b/Debug/Scripts/lldb_uefi.py @@ -67,28 +67,44 @@ def ptype(self, typename): # Returns typed SBValue for an address. # def typed_ptr(self, typename, address): - target = self.debugger.GetSelectedTarget() - sbdata = lldb.SBData.CreateDataFromInt(address, size=self.typetarget.GetAddressByteSize()) + target = self.activetarget + sbdata = lldb.SBData.CreateDataFromInt(address, size=8) ##< size=8 required on 32-bit as well as 64-bit, or resultant pointer cannot be dereferenced. return target.CreateValueFromData('ptr', sbdata, typename) + # + # Cast pointer in way which works round fragilities of 32-bit handling (simple ptr.Cast(type) works in 64-bit). + # + def cast_ptr(self, type, ptr): + return self.typed_ptr(type, ptr.GetValueAsUnsigned()) + # # Computes CRC32 on an array of data. # def crc32(self, data): return binascii.crc32(data) & 0xFFFFFFFF + + # + # GetChildMemberWithName working round fragilities of 32-bit handling. + # + + def get_child_member_with_name(self, value, field_name): + member = value.GetChildMemberWithName(field_name) + if member.TypeIsPointerType(): + member = self.cast_ptr(member.GetType(), member) + return member # # Gets a field from struct as an unsigned value. # - def get_field(self, value, field_name=None, force_bytes=False, single_entry=False): + def get_field(self, value, field_name=None, force_bytes=False, force_int=False, single_entry=False): if field_name is not None: - member = value.GetChildMemberWithName(field_name) + member = self.get_child_member_with_name(value, field_name) else: member = value - if member.GetByteSize() > self.typetarget.GetAddressByteSize() or force_bytes: + if (not force_int and member.GetByteSize() > self.typetarget.GetAddressByteSize()) or force_bytes: sbdata = member.GetData() byte_size = sbdata.GetByteSize() data = array.array('B') @@ -132,7 +148,7 @@ def get_field(self, value, field_name=None, force_bytes=False, single_entry=Fals # def set_field(self, value, field_name, data): - member = value.GetChildMemberWithName(field_name) + member = self.get_child_member_with_name(value, field_name) data = lldb.SBData.CreateDataFromInt(data, size=member.GetByteSize()) error = lldb.SBError() member.SetData(data, error) @@ -147,14 +163,14 @@ def search_est(self): estp_t = self.ptype('EFI_SYSTEM_TABLE_POINTER') while True: estp = self.typed_ptr(estp_t, address) - if self.get_field(estp, 'Signature') == self.EST_SIGNATURE: + if self.get_field(estp, 'Signature', force_int=True) == self.EST_SIGNATURE: oldcrc = self.get_field(estp, 'Crc32') self.set_field(estp, 'Crc32', 0) newcrc = self.crc32(self.get_field(estp.Dereference())) self.set_field(estp, 'Crc32', oldcrc) if newcrc == oldcrc: print(f'EFI_SYSTEM_TABLE_POINTER @ 0x{address:x}') - return estp.GetChildMemberWithName('EfiSystemTableBase') + return self.get_child_member_with_name(estp, 'EfiSystemTableBase') address += 4 * 2**20 if address >= 2**32: @@ -171,12 +187,12 @@ def search_config(self, cfg_table, count, guid): while index != count: # GetChildAtIndex accesses inner structure fields, so we have to use the fugly way. cfg_entry = cfg_table.GetValueForExpressionPath(f'[{index}]') - cfg_guid = cfg_entry.GetChildMemberWithName('VendorGuid') + cfg_guid = self.get_child_member_with_name(cfg_entry, 'VendorGuid') if self.get_field(cfg_guid, 'Data1') == guid[0] and \ self.get_field(cfg_guid, 'Data2') == guid[1] and \ self.get_field(cfg_guid, 'Data3') == guid[2] and \ self.get_field(cfg_guid, 'Data4', True).tolist() == guid[3]: - return cfg_entry.GetChildMemberWithName('VendorTable') + return self.get_child_member_with_name(cfg_entry, 'VendorTable') index += 1 return self.EINVAL @@ -213,6 +229,7 @@ def pe_headers(self, imagebase): if self.get_field(dosh, 'e_magic') == self.DOS_MAGIC: h_addr = h_addr + self.get_field(dosh, 'e_lfanew') return self.typed_ptr(head_t, h_addr) + # # Returns a dictionary with PE sections. # @@ -245,10 +262,10 @@ def pe_is_64(self, pe_headers): def pe_file(self, pe): if self.pe_is_64(pe): - obj = pe.GetChildMemberWithName('Pe32Plus') + obj = self.get_child_member_with_name(pe, 'Pe32Plus') else: - obj = pe.GetChildMemberWithName('Pe32') - return obj.GetChildMemberWithName('FileHeader') + obj = self.get_child_member_with_name(pe, 'Pe32') + return self.get_child_member_with_name(obj, 'FileHeader') # # Returns the PE (not so) optional header. @@ -256,10 +273,10 @@ def pe_file(self, pe): def pe_optional(self, pe): if self.pe_is_64(pe): - obj = pe.GetChildMemberWithName('Pe32Plus') + obj = self.get_child_member_with_name(pe, 'Pe32Plus') else: - obj = pe.GetChildMemberWithName('Pe32') - return obj.GetChildMemberWithName('OptionalHeader') + obj = self.get_child_member_with_name(pe, 'Pe32') + return self.get_child_member_with_name(obj, 'OptionalHeader') # # Returns the symbol file name for a PE image. @@ -371,10 +388,10 @@ def parse_edii(self, edii, count): entry = edii.GetValueForExpressionPath(f'[{index}]') image_type = self.get_field(entry, 'ImageInfoType', single_entry=True) if image_type == 1: - entry = entry.GetChildMemberWithName('NormalImage') - self.parse_image(entry.GetChildMemberWithName('LoadedImageProtocolInstance'), syms) + entry = self.get_child_member_with_name(entry, 'NormalImage') + self.parse_image(self.get_child_member_with_name(entry, 'LoadedImageProtocolInstance'), syms) else: - print(f'Skipping unknown EFI_DEBUG_IMAGE_INFO (Type {str(image_type)})') + print(f'Skipping unknown EFI_DEBUG_IMAGE_INFO (ImageInfoType {image_type})') index = index + 1 print('Loading new symbols...') for sym in syms: @@ -389,12 +406,12 @@ def parse_edii(self, edii, count): def parse_dh(self, dh): dh_t = self.ptype('EFI_DEBUG_IMAGE_INFO_TABLE_HEADER') - dh = dh.Cast(dh_t) + dh = self.cast_ptr(dh_t, dh) print(f"DebugImageInfoTable @ 0x{self.get_field(dh, 'EfiDebugImageInfoTable'):x}, 0x{self.get_field(dh, 'TableSize'):x} entries") if self.get_field(dh, 'UpdateStatus') & self.DEBUG_IS_UPDATING: print('EfiDebugImageInfoTable update in progress, retry later') return - self.parse_edii(dh.GetChildMemberWithName('EfiDebugImageInfoTable'), self.get_field(dh, 'TableSize')) + self.parse_edii(self.get_child_member_with_name(dh, 'EfiDebugImageInfoTable'), self.get_field(dh, 'TableSize')) # # Parses EFI_SYSTEM_TABLE, in order to load image symbols. @@ -402,10 +419,10 @@ def parse_dh(self, dh): def parse_est(self, est): est_t = self.ptype('EFI_SYSTEM_TABLE') - est = est.Cast(est_t) + est = self.cast_ptr(est_t, est) print(f"Connected to {UefiMisc.parse_utf16(self.get_field(est, 'FirmwareVendor'))}(Rev. 0x{self.get_field(est, 'FirmwareRevision'):x}") print(f"ConfigurationTable @ 0x{self.get_field(est, 'ConfigurationTable'):x}, 0x{self.get_field(est, 'NumberOfTableEntries'):x} entries") - dh = self.search_config(est.GetChildMemberWithName('ConfigurationTable'), self.get_field(est, 'NumberOfTableEntries'), self.DEBUG_GUID) + dh = self.search_config(self.get_child_member_with_name(est, 'ConfigurationTable'), self.get_field(est, 'NumberOfTableEntries'), self.DEBUG_GUID) if dh == self.EINVAL: print('No EFI_DEBUG_IMAGE_INFO_TABLE_HEADER') return diff --git a/Debug/efidebug.tool b/Debug/efidebug.tool index aa73cab663e4..ae8d438eaf7e 100755 --- a/Debug/efidebug.tool +++ b/Debug/efidebug.tool @@ -12,6 +12,8 @@ # defaults to X64; use CPU_ARCH=Ia32 to debug 32-bit firmware on 32-bit CPU # GDB_ARCH - GDB `set arch` value # defaults to correct value for CPU_ARCH +# LLDB_ARCH - LLDB `--arch` value +# defaults to correct value for CPU_ARCH # EFI_PORT - debugger TCP connection port # defaults to 8864 for X64 and 8832 for Ia32 # EFI_HOST - debugger TCP connection host @@ -80,6 +82,14 @@ choose_debugger() { fi fi + if [ "${LLDB_ARCH}" = "" ]; then + if [ "${CPU_ARCH}" = "X64" ]; then + LLDB_ARCH="x86_64" + else + LLDB_ARCH="i386" + fi + fi + if [ "${EFI_ARCH}" = "" ]; then EFI_ARCH="${CPU_ARCH}" elif [ "${EFI_ARCH}" = "IA32" ]; then @@ -155,6 +165,14 @@ choose_debugger() { export EFI_TRIPLE="${triple_arch}-linux-gnu" fi fi + + if [ "${CPU_ARCH}" = "X64" ]; then + LLDB_TARGET_DEFINITION1="-o" + LLDB_TARGET_DEFINITION2="settings set plugin.process.gdb-remote.target-definition-file Scripts/x86_64_target_definition.py" + else + LLDB_TARGET_DEFINITION1="" + LLDB_TARGET_DEFINITION2="" + fi } choose_debugger @@ -168,7 +186,8 @@ if [ "${EFI_DEBUGGER}" = "GDB" ] || [ "${EFI_DEBUGGER}" = "gdb" ]; then -ex "b DebugBreak" \ "${EFI_SYMS}" elif [ "${EFI_DEBUGGER}" = "LLDB" ] || [ "${EFI_DEBUGGER}" = "lldb" ]; then - "$LLDB" -o "settings set plugin.process.gdb-remote.target-definition-file Scripts/x86_64_target_definition.py" \ + "$LLDB" --arch "${LLDB_ARCH}" \ + "${LLDB_TARGET_DEFINITION1}" "${LLDB_TARGET_DEFINITION2}" \ -o "gdb-remote ${EFI_HOST}:${EFI_PORT}" \ -o "target create ${EFI_SYMS_PDB} ${EFI_SYMS}" \ -o "command script import Scripts/lldb_uefi.py" \