Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug: Add support for UE image format #510

Merged
merged 1 commit into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ OpenCore Changelog
- Updated `AppleEfiSignTool` to work with new PE COFF loader
- Fixed recovery failing to boot on some systems
- Updated `ProvideCurrentCpuInfo` quirk to support CPUID leaf 0x2 cache size reporting on Mac OS X 10.5 and 10.6
- Updated `efidebug.tool` to support new standard image format

#### v0.9.6
- Updated builtin firmware versions for SMBIOS and the rest
Expand Down
Binary file modified Debug/GdbSyms/Bin/Ia32_GCC5/GdbSyms.debug
Binary file not shown.
Binary file modified Debug/GdbSyms/Bin/Ia32_XCODE5/GdbSyms.dll
Binary file not shown.
Binary file modified Debug/GdbSyms/Bin/X64_CLANGDWARF/GdbSyms.debug
Binary file not shown.
Binary file modified Debug/GdbSyms/Bin/X64_CLANGPDB/GdbSyms.dll
Binary file not shown.
Binary file modified Debug/GdbSyms/Bin/X64_CLANGPDB/GdbSyms.pdb
Binary file not shown.
Binary file modified Debug/GdbSyms/Bin/X64_GCC5/GdbSyms.debug
Binary file not shown.
Binary file modified Debug/GdbSyms/Bin/X64_XCODE5/GdbSyms.dll
Binary file not shown.
89 changes: 44 additions & 45 deletions Debug/Scripts/gdb_uefi.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,50 +193,28 @@ def pe_headers(self, imagebase):
h_addr = h_addr + int(dosh['e_lfanew'])
return gdb.Value(h_addr).cast(head_t)

#
# Returns a dictionary with PE sections.
#

def pe_sections(self, opt, file, _):
sect_t = self.ptype('EFI_IMAGE_SECTION_HEADER')
sections = (opt.address + 1).cast(sect_t)
sects = {}
for i in range(file['NumberOfSections']):
name = UefiMisc.parse_utf8(sections[i]['Name'])
addr = int(sections[i]['VirtualAddress'])
if name != '':
sects[name] = addr
return sects

#
# Returns True if pe_headers refer to a PE32+ image.
#

def pe_is_64(self, pe_headers):
return pe_headers['Pe32']['OptionalHeader']['Magic'] == self.PE32PLUS_MAGIC

#
# Returns the PE fileheader.
#

def pe_file(self, pe):
return pe['Pe32Plus']['FileHeader'] if self.pe_is_64(pe) else pe['Pe32']['FileHeader']
return pe_headers['Pe32']['Magic'] == self.PE32PLUS_MAGIC

#
# Returns the PE(not so) optional header.
# Returns the PE combined common and (not so) optional header.
#

def pe_optional(self, pe):
return pe['Pe32Plus']['OptionalHeader'] if self.pe_is_64(pe) else pe['Pe32']['OptionalHeader']
def pe_combined(self, pe):
return pe['Pe32Plus'] if self.pe_is_64(pe) else pe['Pe32']

#
# Returns the symbol file name for a PE image.
#

def pe_parse_debug(self, base):
pe = self.pe_headers(base)
opt = self.pe_optional(pe)
debug_dir_entry = opt['DataDirectory'][6]
combined = self.pe_combined(pe)
debug_dir_entry = combined['DataDirectory'][6]
dep = debug_dir_entry['VirtualAddress'] + int(base)
dep = dep.cast(self.ptype('EFI_IMAGE_DEBUG_DIRECTORY_ENTRY'))
cvp = dep.dereference()['RVA'] + int(base)
Expand All @@ -250,9 +228,9 @@ def pe_parse_debug(self, base):
return gdb.Value(self.EINVAL)

#
# Prepares gdb symbol load command with proper section information.
# Prepares gdb symbol load command.
#
def get_sym_cmd(self, file, base, sections, macho):
def get_sym_cmd(self, file, base):
return f'add-symbol-file {file} -o 0x{base:x}'

#
Expand All @@ -265,25 +243,42 @@ def get_sym_cmd(self, file, base, sections, macho):
def parse_image(self, image, syms):
base = image['ImageBase']
pe = self.pe_headers(base)
opt = self.pe_optional(pe)
file = self.pe_file(pe)
combined = self.pe_combined(pe)
sym_name = self.pe_parse_debug(base)
sections = self.pe_sections(opt, file, base)

# For ELF and Mach-O-derived images...
if self.offset_by_headers:
base = base + opt['SizeOfHeaders']
base = base + combined['SizeOfHeaders']
if sym_name != self.EINVAL:
sym_name = sym_name.cast(self.ptype('CHAR8')).string()
sym_name_dbg = re.sub(r'\.dll$', '.debug', sym_name)
macho = False
if os.path.isdir(sym_name + '.dSYM'):
sym_name += '.dSYM/Contents/Resources/DWARF/' + os.path.basename(sym_name)
macho = True
elif sym_name_dbg != sym_name and os.path.exists(sym_name_dbg):
# TODO: implement .elf handling.
sym_name = sym_name_dbg
syms.append(self.get_sym_cmd(sym_name, int(base), sections, macho))
self.add_sym(sym_name, base, syms)

#
# Add symbol load command with additional processing for correct file location.
#

def add_sym(self, sym_name, base, syms):
sym_name = sym_name.cast(self.ptype('CHAR8')).string()
sym_name_dbg = re.sub(r'\.dll$', '.debug', sym_name)
# macho = False
if os.path.isdir(sym_name + '.dSYM'):
sym_name += '.dSYM/Contents/Resources/DWARF/' + os.path.basename(sym_name)
# macho = True
elif sym_name_dbg != sym_name and os.path.exists(sym_name_dbg):
# TODO: implement .elf handling.
sym_name = sym_name_dbg
syms.append(self.get_sym_cmd(sym_name, int(base)))

#
# Use debug info from new image loader.
#

def use_new_debug_info(self, entry, syms):
pdb_path = entry['PdbPath']
if pdb_path:
debug_base = entry['DebugBase']
self.add_sym(pdb_path, debug_base, syms)
else:
print('No symbol file')

#
# Parses table EFI_DEBUG_IMAGE_INFO structures, builds
Expand All @@ -297,9 +292,13 @@ def parse_edii(self, edii, count):
print(f'Found {count} images...')
while index != count:
entry = edii[index]
if entry['ImageInfoType'].dereference() == 1:
image_type = entry['ImageInfoType'].dereference()
if image_type == 1:
entry = entry['NormalImage']
self.parse_image(entry['LoadedImageProtocolInstance'], syms)
elif image_type == 2:
entry = entry['NormalImage2']
self.use_new_debug_info(entry, syms)
else:
print(f"Skipping unknown EFI_DEBUG_IMAGE_INFO(Type {str(entry['ImageInfoType'].dereference())})")
index += 1
Expand Down
61 changes: 43 additions & 18 deletions Debug/Scripts/lldb_uefi.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,10 @@ def pe_headers(self, imagebase):
# Returns a dictionary with PE sections.
#

def pe_sections(self, opt, file, _):
def pe_sections(self, combined, file, _):
sect_t = self.ptype('EFI_IMAGE_SECTION_HEADER')
sections_addr = opt.GetLoadAddress() + opt.GetByteSize()
common = self.get_child_member_with_name(combined, 'CommonHeader')
sections_addr = common.GetLoadAddress() + common.GetByteSize() + self.get_field(file, 'SizeOfOptionalHeader')
sections = self.typed_ptr(sect_t, sections_addr)
sects = OrderedDict()
for i in range(self.get_field(file, 'NumberOfSections')):
Expand All @@ -253,7 +254,7 @@ def pe_sections(self, opt, file, _):
#

def pe_is_64(self, pe_headers):
magic = pe_headers.GetValueForExpressionPath('.Pe32.OptionalHeader.Magic').GetValueAsUnsigned()
magic = pe_headers.GetValueForExpressionPath('.Pe32.Magic').GetValueAsUnsigned()
return magic == self.PE32PLUS_MAGIC

#
Expand All @@ -271,21 +272,20 @@ def pe_file(self, pe):
# Returns the PE (not so) optional header.
#

def pe_optional(self, pe):
def pe_combined(self, pe):
if self.pe_is_64(pe):
obj = self.get_child_member_with_name(pe, 'Pe32Plus')
return self.get_child_member_with_name(pe, 'Pe32Plus')
else:
obj = self.get_child_member_with_name(pe, 'Pe32')
return self.get_child_member_with_name(obj, 'OptionalHeader')
return self.get_child_member_with_name(pe, 'Pe32')

#
# Returns the symbol file name for a PE image.
#

def pe_parse_debug(self, base):
pe = self.pe_headers(base)
opt = self.pe_optional(pe)
debug_dir_entry = opt.GetValueForExpressionPath('.DataDirectory[6]')
combined = self.pe_combined(pe)
debug_dir_entry = combined.GetValueForExpressionPath('.DataDirectory[6]')
dep = self.get_field(debug_dir_entry, 'VirtualAddress') + base
dep = self.typed_ptr(self.ptype('EFI_IMAGE_DEBUG_DIRECTORY_ENTRY'), dep)
cvp = self.get_field(dep, 'RVA') + base
Expand All @@ -303,10 +303,10 @@ def pe_parse_debug(self, base):
return self.EINVAL

#
# Prepares symbol load command with proper section information.
# Prepares lldb symbol load command.
# Currently supports Mach-O and single-section files.
#
def get_sym_cmd(self, filename, orgbase, *_):
def get_sym_cmd(self, filename, orgbase):
if filename.endswith('.pdb'):
dll_file = filename.replace('.pdb', '.dll')
module_cmd = f'target modules add -s {filename} {dll_file}'
Expand All @@ -324,19 +324,19 @@ def get_sym_cmd(self, filename, orgbase, *_):
#

def parse_image(self, image, syms):
orgbase = base = self.get_field(image, 'ImageBase')
base = self.get_field(image, 'ImageBase')
pe = self.pe_headers(base)
opt = self.pe_optional(pe)
file = self.pe_file(pe)
combined = self.pe_combined(pe)
sym_address = self.pe_parse_debug(base)
sections = self.pe_sections(opt, file, base)

if sym_address == 0:
# llvm-objcopy --add-gnu-debuglink=a/x.debug a/x.dll does not update
# DataDirectory with any data, instead it creates a new section
# with a /\d+ name containing:
# - ASCII debug file name (x.debug) padded by 4 bytes
# - CRC32
file = self.pe_file(pe)
sections = self.pe_sections(combined, file, base)
last_section = next(reversed(sections))
if re.match(r'^/\d+$', last_section):
sym_address = sections[last_section]
Expand All @@ -352,8 +352,15 @@ def parse_image(self, image, syms):

# For ELF and Mach-O-derived images...
if self.offset_by_headers:
base = base + self.get_field(opt, 'SizeOfHeaders')
base = base + self.get_field(combined, 'SizeOfHeaders')
if sym_name != self.EINVAL:
self.add_sym(sym_name, base, syms)

#
# Add symbol load command with additional processing for correct file location.
#

def add_sym(self, sym_name, base, syms):
macho = os.path.isdir(sym_name + '.dSYM')
if macho:
real_sym = sym_name
Expand All @@ -370,10 +377,24 @@ def parse_image(self, image, syms):
break

if real_sym:
syms.append(self.get_sym_cmd(real_sym, orgbase, sections, macho, base))
syms.append(self.get_sym_cmd(real_sym, base))
else:
print(f'No symbol file {sym_name}')

#
# Use debug info from new image loader.
#

def use_new_debug_info(self, entry, syms):
sym_address = self.get_child_member_with_name(entry, 'PdbPath')
if sym_address:
sym_ptr = self.cast_ptr(self.ptype('char'), sym_address)
sym_name = UefiMisc.parse_utf8(self.get_field(sym_ptr))
debug_base = self.get_field(entry, 'DebugBase')
self.add_sym(sym_name, debug_base, syms)
else:
print('No symbol file')

#
# Parses table EFI_DEBUG_IMAGE_INFO structures, builds
# a list of add-symbol-file commands, and reloads debugger
Expand All @@ -390,6 +411,9 @@ def parse_edii(self, edii, count):
if image_type == 1:
entry = self.get_child_member_with_name(entry, 'NormalImage')
self.parse_image(self.get_child_member_with_name(entry, 'LoadedImageProtocolInstance'), syms)
elif image_type == 2:
entry = self.get_child_member_with_name(entry, 'NormalImage2')
self.use_new_debug_info(entry, syms)
else:
print(f'Skipping unknown EFI_DEBUG_IMAGE_INFO (ImageInfoType {image_type})')
index = index + 1
Expand All @@ -399,6 +423,7 @@ def parse_edii(self, edii, count):
self.debugger.HandleCommand(sym[0])
print(sym[1])
self.debugger.HandleCommand(sym[1])

#
# Parses EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, in order to load
# image symbols.
Expand All @@ -420,7 +445,7 @@ def parse_dh(self, dh):
def parse_est(self, est):
est_t = self.ptype('EFI_SYSTEM_TABLE')
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"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(self.get_child_member_with_name(est, 'ConfigurationTable'), self.get_field(est, 'NumberOfTableEntries'), self.DEBUG_GUID)
if dh == self.EINVAL:
Expand Down