Skip to content

Commit

Permalink
Debug: Add support for 32-bit on 32-bit under LLDB
Browse files Browse the repository at this point in the history
Also includes not-quite-working 32-bit on 64-bit LLDB support
  • Loading branch information
mikebeaton committed Oct 28, 2023
1 parent 763b0ea commit 6027c63
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 40 additions & 23 deletions Debug/Scripts/lldb_uefi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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.
#
Expand Down Expand Up @@ -245,21 +262,21 @@ 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.
#

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.
Expand Down Expand Up @@ -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:
Expand All @@ -389,23 +406,23 @@ 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.
#

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
Expand Down
21 changes: 20 additions & 1 deletion Debug/efidebug.tool
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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" \
Expand Down

0 comments on commit 6027c63

Please sign in to comment.