Skip to content

Commit a1a8bd6

Browse files
committed
Debug: Add support for UE image format
1 parent 88ada4d commit a1a8bd6

File tree

10 files changed

+88
-63
lines changed

10 files changed

+88
-63
lines changed

Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ OpenCore Changelog
88
- Updated `AppleEfiSignTool` to work with new PE COFF loader
99
- Fixed recovery failing to boot on some systems
1010
- Updated `ProvideCurrentCpuInfo` quirk to support CPUID leaf 0x2 cache size reporting on Mac OS X 10.5 and 10.6
11+
- Updated `efidebug.tool` to support new standard image format
1112

1213
#### v0.9.6
1314
- Updated builtin firmware versions for SMBIOS and the rest
63.1 KB
Binary file not shown.
-57.7 KB
Binary file not shown.
368 KB
Binary file not shown.
28.4 KB
Binary file not shown.
320 KB
Binary file not shown.
-193 KB
Binary file not shown.
-41 KB
Binary file not shown.

Debug/Scripts/gdb_uefi.py

+44-45
Original file line numberDiff line numberDiff line change
@@ -193,50 +193,28 @@ def pe_headers(self, imagebase):
193193
h_addr = h_addr + int(dosh['e_lfanew'])
194194
return gdb.Value(h_addr).cast(head_t)
195195

196-
#
197-
# Returns a dictionary with PE sections.
198-
#
199-
200-
def pe_sections(self, opt, file, _):
201-
sect_t = self.ptype('EFI_IMAGE_SECTION_HEADER')
202-
sections = (opt.address + 1).cast(sect_t)
203-
sects = {}
204-
for i in range(file['NumberOfSections']):
205-
name = UefiMisc.parse_utf8(sections[i]['Name'])
206-
addr = int(sections[i]['VirtualAddress'])
207-
if name != '':
208-
sects[name] = addr
209-
return sects
210-
211196
#
212197
# Returns True if pe_headers refer to a PE32+ image.
213198
#
214199

215200
def pe_is_64(self, pe_headers):
216-
return pe_headers['Pe32']['OptionalHeader']['Magic'] == self.PE32PLUS_MAGIC
217-
218-
#
219-
# Returns the PE fileheader.
220-
#
221-
222-
def pe_file(self, pe):
223-
return pe['Pe32Plus']['FileHeader'] if self.pe_is_64(pe) else pe['Pe32']['FileHeader']
201+
return pe_headers['Pe32']['Magic'] == self.PE32PLUS_MAGIC
224202

225203
#
226-
# Returns the PE(not so) optional header.
204+
# Returns the PE combined common and (not so) optional header.
227205
#
228206

229-
def pe_optional(self, pe):
230-
return pe['Pe32Plus']['OptionalHeader'] if self.pe_is_64(pe) else pe['Pe32']['OptionalHeader']
207+
def pe_combined(self, pe):
208+
return pe['Pe32Plus'] if self.pe_is_64(pe) else pe['Pe32']
231209

232210
#
233211
# Returns the symbol file name for a PE image.
234212
#
235213

236214
def pe_parse_debug(self, base):
237215
pe = self.pe_headers(base)
238-
opt = self.pe_optional(pe)
239-
debug_dir_entry = opt['DataDirectory'][6]
216+
combined = self.pe_combined(pe)
217+
debug_dir_entry = combined['DataDirectory'][6]
240218
dep = debug_dir_entry['VirtualAddress'] + int(base)
241219
dep = dep.cast(self.ptype('EFI_IMAGE_DEBUG_DIRECTORY_ENTRY'))
242220
cvp = dep.dereference()['RVA'] + int(base)
@@ -250,9 +228,9 @@ def pe_parse_debug(self, base):
250228
return gdb.Value(self.EINVAL)
251229

252230
#
253-
# Prepares gdb symbol load command with proper section information.
231+
# Prepares gdb symbol load command.
254232
#
255-
def get_sym_cmd(self, file, base, sections, macho):
233+
def get_sym_cmd(self, file, base):
256234
return f'add-symbol-file {file} -o 0x{base:x}'
257235

258236
#
@@ -265,25 +243,42 @@ def get_sym_cmd(self, file, base, sections, macho):
265243
def parse_image(self, image, syms):
266244
base = image['ImageBase']
267245
pe = self.pe_headers(base)
268-
opt = self.pe_optional(pe)
269-
file = self.pe_file(pe)
246+
combined = self.pe_combined(pe)
270247
sym_name = self.pe_parse_debug(base)
271-
sections = self.pe_sections(opt, file, base)
272248

273249
# For ELF and Mach-O-derived images...
274250
if self.offset_by_headers:
275-
base = base + opt['SizeOfHeaders']
251+
base = base + combined['SizeOfHeaders']
276252
if sym_name != self.EINVAL:
277-
sym_name = sym_name.cast(self.ptype('CHAR8')).string()
278-
sym_name_dbg = re.sub(r'\.dll$', '.debug', sym_name)
279-
macho = False
280-
if os.path.isdir(sym_name + '.dSYM'):
281-
sym_name += '.dSYM/Contents/Resources/DWARF/' + os.path.basename(sym_name)
282-
macho = True
283-
elif sym_name_dbg != sym_name and os.path.exists(sym_name_dbg):
284-
# TODO: implement .elf handling.
285-
sym_name = sym_name_dbg
286-
syms.append(self.get_sym_cmd(sym_name, int(base), sections, macho))
253+
self.add_sym(sym_name, base, syms)
254+
255+
#
256+
# Add symbol load command with additional processing for correct file location.
257+
#
258+
259+
def add_sym(self, sym_name, base, syms):
260+
sym_name = sym_name.cast(self.ptype('CHAR8')).string()
261+
sym_name_dbg = re.sub(r'\.dll$', '.debug', sym_name)
262+
# macho = False
263+
if os.path.isdir(sym_name + '.dSYM'):
264+
sym_name += '.dSYM/Contents/Resources/DWARF/' + os.path.basename(sym_name)
265+
# macho = True
266+
elif sym_name_dbg != sym_name and os.path.exists(sym_name_dbg):
267+
# TODO: implement .elf handling.
268+
sym_name = sym_name_dbg
269+
syms.append(self.get_sym_cmd(sym_name, int(base)))
270+
271+
#
272+
# Use debug info from new image loader.
273+
#
274+
275+
def use_new_debug_info(self, entry, syms):
276+
pdb_path = entry['PdbPath']
277+
if pdb_path:
278+
debug_base = entry['DebugBase']
279+
self.add_sym(pdb_path, debug_base, syms)
280+
else:
281+
print('No symbol file')
287282

288283
#
289284
# Parses table EFI_DEBUG_IMAGE_INFO structures, builds
@@ -297,9 +292,13 @@ def parse_edii(self, edii, count):
297292
print(f'Found {count} images...')
298293
while index != count:
299294
entry = edii[index]
300-
if entry['ImageInfoType'].dereference() == 1:
295+
image_type = entry['ImageInfoType'].dereference()
296+
if image_type == 1:
301297
entry = entry['NormalImage']
302298
self.parse_image(entry['LoadedImageProtocolInstance'], syms)
299+
elif image_type == 2:
300+
entry = entry['NormalImage2']
301+
self.use_new_debug_info(entry, syms)
303302
else:
304303
print(f"Skipping unknown EFI_DEBUG_IMAGE_INFO(Type {str(entry['ImageInfoType'].dereference())})")
305304
index += 1

Debug/Scripts/lldb_uefi.py

+43-18
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,10 @@ def pe_headers(self, imagebase):
234234
# Returns a dictionary with PE sections.
235235
#
236236

237-
def pe_sections(self, opt, file, _):
237+
def pe_sections(self, combined, file, _):
238238
sect_t = self.ptype('EFI_IMAGE_SECTION_HEADER')
239-
sections_addr = opt.GetLoadAddress() + opt.GetByteSize()
239+
common = self.get_child_member_with_name(combined, 'CommonHeader')
240+
sections_addr = common.GetLoadAddress() + common.GetByteSize() + self.get_field(file, 'SizeOfOptionalHeader')
240241
sections = self.typed_ptr(sect_t, sections_addr)
241242
sects = OrderedDict()
242243
for i in range(self.get_field(file, 'NumberOfSections')):
@@ -253,7 +254,7 @@ def pe_sections(self, opt, file, _):
253254
#
254255

255256
def pe_is_64(self, pe_headers):
256-
magic = pe_headers.GetValueForExpressionPath('.Pe32.OptionalHeader.Magic').GetValueAsUnsigned()
257+
magic = pe_headers.GetValueForExpressionPath('.Pe32.Magic').GetValueAsUnsigned()
257258
return magic == self.PE32PLUS_MAGIC
258259

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

274-
def pe_optional(self, pe):
275+
def pe_combined(self, pe):
275276
if self.pe_is_64(pe):
276-
obj = self.get_child_member_with_name(pe, 'Pe32Plus')
277+
return self.get_child_member_with_name(pe, 'Pe32Plus')
277278
else:
278-
obj = self.get_child_member_with_name(pe, 'Pe32')
279-
return self.get_child_member_with_name(obj, 'OptionalHeader')
279+
return self.get_child_member_with_name(pe, 'Pe32')
280280

281281
#
282282
# Returns the symbol file name for a PE image.
283283
#
284284

285285
def pe_parse_debug(self, base):
286286
pe = self.pe_headers(base)
287-
opt = self.pe_optional(pe)
288-
debug_dir_entry = opt.GetValueForExpressionPath('.DataDirectory[6]')
287+
combined = self.pe_combined(pe)
288+
debug_dir_entry = combined.GetValueForExpressionPath('.DataDirectory[6]')
289289
dep = self.get_field(debug_dir_entry, 'VirtualAddress') + base
290290
dep = self.typed_ptr(self.ptype('EFI_IMAGE_DEBUG_DIRECTORY_ENTRY'), dep)
291291
cvp = self.get_field(dep, 'RVA') + base
@@ -303,10 +303,10 @@ def pe_parse_debug(self, base):
303303
return self.EINVAL
304304

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

326326
def parse_image(self, image, syms):
327-
orgbase = base = self.get_field(image, 'ImageBase')
327+
base = self.get_field(image, 'ImageBase')
328328
pe = self.pe_headers(base)
329-
opt = self.pe_optional(pe)
330-
file = self.pe_file(pe)
329+
combined = self.pe_combined(pe)
331330
sym_address = self.pe_parse_debug(base)
332-
sections = self.pe_sections(opt, file, base)
333331

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

353353
# For ELF and Mach-O-derived images...
354354
if self.offset_by_headers:
355-
base = base + self.get_field(opt, 'SizeOfHeaders')
355+
base = base + self.get_field(combined, 'SizeOfHeaders')
356356
if sym_name != self.EINVAL:
357+
self.add_sym(sym_name, base, syms)
358+
359+
#
360+
# Add symbol load command with additional processing for correct file location.
361+
#
362+
363+
def add_sym(self, sym_name, base, syms):
357364
macho = os.path.isdir(sym_name + '.dSYM')
358365
if macho:
359366
real_sym = sym_name
@@ -370,10 +377,24 @@ def parse_image(self, image, syms):
370377
break
371378

372379
if real_sym:
373-
syms.append(self.get_sym_cmd(real_sym, orgbase, sections, macho, base))
380+
syms.append(self.get_sym_cmd(real_sym, base))
374381
else:
375382
print(f'No symbol file {sym_name}')
376383

384+
#
385+
# Use debug info from new image loader.
386+
#
387+
388+
def use_new_debug_info(self, entry, syms):
389+
sym_address = self.get_child_member_with_name(entry, 'PdbPath')
390+
if sym_address:
391+
sym_ptr = self.cast_ptr(self.ptype('char'), sym_address)
392+
sym_name = UefiMisc.parse_utf8(self.get_field(sym_ptr))
393+
debug_base = self.get_field(entry, 'DebugBase')
394+
self.add_sym(sym_name, debug_base, syms)
395+
else:
396+
print('No symbol file')
397+
377398
#
378399
# Parses table EFI_DEBUG_IMAGE_INFO structures, builds
379400
# a list of add-symbol-file commands, and reloads debugger
@@ -390,6 +411,9 @@ def parse_edii(self, edii, count):
390411
if image_type == 1:
391412
entry = self.get_child_member_with_name(entry, 'NormalImage')
392413
self.parse_image(self.get_child_member_with_name(entry, 'LoadedImageProtocolInstance'), syms)
414+
elif image_type == 2:
415+
entry = self.get_child_member_with_name(entry, 'NormalImage2')
416+
self.use_new_debug_info(entry, syms)
393417
else:
394418
print(f'Skipping unknown EFI_DEBUG_IMAGE_INFO (ImageInfoType {image_type})')
395419
index = index + 1
@@ -399,6 +423,7 @@ def parse_edii(self, edii, count):
399423
self.debugger.HandleCommand(sym[0])
400424
print(sym[1])
401425
self.debugger.HandleCommand(sym[1])
426+
402427
#
403428
# Parses EFI_DEBUG_IMAGE_INFO_TABLE_HEADER, in order to load
404429
# image symbols.
@@ -420,7 +445,7 @@ def parse_dh(self, dh):
420445
def parse_est(self, est):
421446
est_t = self.ptype('EFI_SYSTEM_TABLE')
422447
est = self.cast_ptr(est_t, est)
423-
print(f"Connected to {UefiMisc.parse_utf16(self.get_field(est, 'FirmwareVendor'))}(Rev. 0x{self.get_field(est, 'FirmwareRevision'):x}")
448+
print(f"Connected to {UefiMisc.parse_utf16(self.get_field(est, 'FirmwareVendor'))}(Rev. 0x{self.get_field(est, 'FirmwareRevision'):x})")
424449
print(f"ConfigurationTable @ 0x{self.get_field(est, 'ConfigurationTable'):x}, 0x{self.get_field(est, 'NumberOfTableEntries'):x} entries")
425450
dh = self.search_config(self.get_child_member_with_name(est, 'ConfigurationTable'), self.get_field(est, 'NumberOfTableEntries'), self.DEBUG_GUID)
426451
if dh == self.EINVAL:

0 commit comments

Comments
 (0)