1 | #!/usr/bin/python3
|
---|
2 | '''
|
---|
3 | Copyright 2021 (c) Apple Inc. All rights reserved.
|
---|
4 | SPDX-License-Identifier: BSD-2-Clause-Patent
|
---|
5 |
|
---|
6 | EFI gdb commands based on efi_debugging classes.
|
---|
7 |
|
---|
8 | Example usage:
|
---|
9 | OvmfPkg/build.sh qemu -gdb tcp::9000
|
---|
10 | gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py"
|
---|
11 |
|
---|
12 | (gdb) help efi
|
---|
13 | Commands for debugging EFI. efi <cmd>
|
---|
14 |
|
---|
15 | List of efi subcommands:
|
---|
16 |
|
---|
17 | efi devicepath -- Display an EFI device path.
|
---|
18 | efi guid -- Display info about EFI GUID's.
|
---|
19 | efi hob -- Dump EFI HOBs. Type 'hob -h' for more info.
|
---|
20 | efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info.
|
---|
21 | efi table -- Dump EFI System Tables. Type 'table -h' for more info.
|
---|
22 |
|
---|
23 | This module is coded against a generic gdb remote serial stub. It should work
|
---|
24 | with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub.
|
---|
25 |
|
---|
26 | If you are debugging with QEMU or a JTAG hardware debugger you can insert
|
---|
27 | a CpuDeadLoop(); in your code, attach with gdb, and then `p Index=1` to
|
---|
28 | step past. If you have a debug stub in EFI you can use CpuBreakpoint();.
|
---|
29 | '''
|
---|
30 |
|
---|
31 | from gdb.printing import RegexpCollectionPrettyPrinter
|
---|
32 | from gdb.printing import register_pretty_printer
|
---|
33 | import gdb
|
---|
34 | import os
|
---|
35 | import sys
|
---|
36 | import uuid
|
---|
37 | import optparse
|
---|
38 | import shlex
|
---|
39 |
|
---|
40 | # gdb will not import from the same path as this script.
|
---|
41 | # so lets fix that for gdb...
|
---|
42 | sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
---|
43 |
|
---|
44 | from efi_debugging import PeTeImage, patch_ctypes # noqa: E402
|
---|
45 | from efi_debugging import EfiHob, GuidNames, EfiStatusClass # noqa: E402
|
---|
46 | from efi_debugging import EfiBootMode, EfiDevicePath # noqa: E402
|
---|
47 | from efi_debugging import EfiConfigurationTable, EfiTpl # noqa: E402
|
---|
48 |
|
---|
49 |
|
---|
50 | class GdbFileObject(object):
|
---|
51 | '''Provide a file like object required by efi_debugging'''
|
---|
52 |
|
---|
53 | def __init__(self):
|
---|
54 | self.inferior = gdb.selected_inferior()
|
---|
55 | self.offset = 0
|
---|
56 |
|
---|
57 | def tell(self):
|
---|
58 | return self.offset
|
---|
59 |
|
---|
60 | def read(self, size=-1):
|
---|
61 | if size == -1:
|
---|
62 | # arbitrary default size
|
---|
63 | size = 0x1000000
|
---|
64 |
|
---|
65 | try:
|
---|
66 | data = self.inferior.read_memory(self.offset, size)
|
---|
67 | except MemoryError:
|
---|
68 | data = bytearray(size)
|
---|
69 | assert False
|
---|
70 | if len(data) != size:
|
---|
71 | raise MemoryError(
|
---|
72 | f'gdb could not read memory 0x{size:x}'
|
---|
73 | + f' bytes from 0x{self.offset:08x}')
|
---|
74 | else:
|
---|
75 | # convert memoryview object to a bytestring.
|
---|
76 | return data.tobytes()
|
---|
77 |
|
---|
78 | def readable(self):
|
---|
79 | return True
|
---|
80 |
|
---|
81 | def seek(self, offset, whence=0):
|
---|
82 | if whence == 0:
|
---|
83 | self.offset = offset
|
---|
84 | elif whence == 1:
|
---|
85 | self.offset += offset
|
---|
86 | else:
|
---|
87 | # whence == 2 is seek from end
|
---|
88 | raise NotImplementedError
|
---|
89 |
|
---|
90 | def seekable(self):
|
---|
91 | return True
|
---|
92 |
|
---|
93 | def write(self, data):
|
---|
94 | self.inferior.write_memory(self.offset, data)
|
---|
95 | return len(data)
|
---|
96 |
|
---|
97 | def writable(self):
|
---|
98 | return True
|
---|
99 |
|
---|
100 | def truncate(self, size=None):
|
---|
101 | raise NotImplementedError
|
---|
102 |
|
---|
103 | def flush(self):
|
---|
104 | raise NotImplementedError
|
---|
105 |
|
---|
106 | def fileno(self):
|
---|
107 | raise NotImplementedError
|
---|
108 |
|
---|
109 |
|
---|
110 | class EfiSymbols:
|
---|
111 | """Class to manage EFI Symbols"""
|
---|
112 |
|
---|
113 | loaded = {}
|
---|
114 | stride = None
|
---|
115 | range = None
|
---|
116 | verbose = False
|
---|
117 |
|
---|
118 | def __init__(self, file=None):
|
---|
119 | EfiSymbols.file = file if file else GdbFileObject()
|
---|
120 |
|
---|
121 | @ classmethod
|
---|
122 | def __str__(cls):
|
---|
123 | return ''.join(f'{value}\n' for value in cls.loaded.values())
|
---|
124 |
|
---|
125 | @ classmethod
|
---|
126 | def configure_search(cls, stride, range=None, verbose=False):
|
---|
127 | cls.stride = stride
|
---|
128 | cls.range = range
|
---|
129 | cls.verbose = verbose
|
---|
130 |
|
---|
131 | @ classmethod
|
---|
132 | def clear(cls):
|
---|
133 | cls.loaded = {}
|
---|
134 |
|
---|
135 | @ classmethod
|
---|
136 | def add_symbols_for_pecoff(cls, pecoff):
|
---|
137 | '''Tell lldb the location of the .text and .data sections.'''
|
---|
138 |
|
---|
139 | if pecoff.TextAddress in cls.loaded:
|
---|
140 | return 'Already Loaded: '
|
---|
141 | try:
|
---|
142 | res = 'Loading Symbols Failed:'
|
---|
143 | res = gdb.execute('add-symbol-file ' + pecoff.CodeViewPdb +
|
---|
144 | ' ' + hex(pecoff.TextAddress) +
|
---|
145 | ' -s .data ' + hex(pecoff.DataAddress),
|
---|
146 | False, True)
|
---|
147 |
|
---|
148 | cls.loaded[pecoff.TextAddress] = pecoff
|
---|
149 | if cls.verbose:
|
---|
150 | print(f'\n{res:s}\n')
|
---|
151 | return ''
|
---|
152 | except gdb.error:
|
---|
153 | return res
|
---|
154 |
|
---|
155 | @ classmethod
|
---|
156 | def address_to_symbols(cls, address, reprobe=False):
|
---|
157 | '''
|
---|
158 | Given an address search backwards for a PE/COFF (or TE) header
|
---|
159 | and load symbols. Return a status string.
|
---|
160 | '''
|
---|
161 | if not isinstance(address, int):
|
---|
162 | address = int(address)
|
---|
163 |
|
---|
164 | pecoff = cls.address_in_loaded_pecoff(address)
|
---|
165 | if not reprobe and pecoff is not None:
|
---|
166 | # skip the probe of the remote
|
---|
167 | return f'{pecoff} is already loaded'
|
---|
168 |
|
---|
169 | pecoff = PeTeImage(cls.file, None)
|
---|
170 | if pecoff.pcToPeCoff(address, cls.stride, cls.range):
|
---|
171 | res = cls.add_symbols_for_pecoff(pecoff)
|
---|
172 | return f'{res}{pecoff}'
|
---|
173 | else:
|
---|
174 | return f'0x{address:08x} not in a PE/COFF (or TE) image'
|
---|
175 |
|
---|
176 | @ classmethod
|
---|
177 | def address_in_loaded_pecoff(cls, address):
|
---|
178 | if not isinstance(address, int):
|
---|
179 | address = int(address)
|
---|
180 |
|
---|
181 | for value in cls.loaded.values():
|
---|
182 | if (address >= value.LoadAddress and
|
---|
183 | address <= value.EndLoadAddress):
|
---|
184 | return value
|
---|
185 |
|
---|
186 | return None
|
---|
187 |
|
---|
188 | @ classmethod
|
---|
189 | def unload_symbols(cls, address):
|
---|
190 | if not isinstance(address, int):
|
---|
191 | address = int(address)
|
---|
192 |
|
---|
193 | pecoff = cls.address_in_loaded_pecoff(address)
|
---|
194 | try:
|
---|
195 | res = 'Unloading Symbols Failed:'
|
---|
196 | res = gdb.execute(
|
---|
197 | f'remove-symbol-file -a {hex(pecoff.TextAddress):s}',
|
---|
198 | False, True)
|
---|
199 | del cls.loaded[pecoff.LoadAddress]
|
---|
200 | return res
|
---|
201 | except gdb.error:
|
---|
202 | return res
|
---|
203 |
|
---|
204 |
|
---|
205 | class CHAR16_PrettyPrinter(object):
|
---|
206 |
|
---|
207 | def __init__(self, val):
|
---|
208 | self.val = val
|
---|
209 |
|
---|
210 | def to_string(self):
|
---|
211 | if int(self.val) < 0x20:
|
---|
212 | return f"L'\\x{int(self.val):02x}'"
|
---|
213 | else:
|
---|
214 | return f"L'{chr(self.val):s}'"
|
---|
215 |
|
---|
216 |
|
---|
217 | class EFI_TPL_PrettyPrinter(object):
|
---|
218 |
|
---|
219 | def __init__(self, val):
|
---|
220 | self.val = val
|
---|
221 |
|
---|
222 | def to_string(self):
|
---|
223 | return str(EfiTpl(int(self.val)))
|
---|
224 |
|
---|
225 |
|
---|
226 | class EFI_STATUS_PrettyPrinter(object):
|
---|
227 |
|
---|
228 | def __init__(self, val):
|
---|
229 | self.val = val
|
---|
230 |
|
---|
231 | def to_string(self):
|
---|
232 | status = int(self.val)
|
---|
233 | return f'{str(EfiStatusClass(status)):s} (0x{status:08x})'
|
---|
234 |
|
---|
235 |
|
---|
236 | class EFI_BOOT_MODE_PrettyPrinter(object):
|
---|
237 |
|
---|
238 | def __init__(self, val):
|
---|
239 | self.val = val
|
---|
240 |
|
---|
241 | def to_string(self):
|
---|
242 | return str(EfiBootMode(int(self.val)))
|
---|
243 |
|
---|
244 |
|
---|
245 | class EFI_GUID_PrettyPrinter(object):
|
---|
246 | """Print 'EFI_GUID' as 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'"""
|
---|
247 |
|
---|
248 | def __init__(self, val):
|
---|
249 | self.val = val
|
---|
250 |
|
---|
251 | def to_string(self):
|
---|
252 | # if we could get a byte like object of *(unsigned char (*)[16])
|
---|
253 | # then we could just use uuid.UUID() to convert
|
---|
254 | Data1 = int(self.val['Data1'])
|
---|
255 | Data2 = int(self.val['Data2'])
|
---|
256 | Data3 = int(self.val['Data3'])
|
---|
257 | Data4 = self.val['Data4']
|
---|
258 | guid = f'{Data1:08X}-{Data2:04X}-'
|
---|
259 | guid += f'{Data3:04X}-'
|
---|
260 | guid += f'{int(Data4[0]):02X}{int(Data4[1]):02X}-'
|
---|
261 | guid += f'{int(Data4[2]):02X}{int(Data4[3]):02X}'
|
---|
262 | guid += f'{int(Data4[4]):02X}{int(Data4[5]):02X}'
|
---|
263 | guid += f'{int(Data4[6]):02X}{int(Data4[7]):02X}'
|
---|
264 | return str(GuidNames(guid))
|
---|
265 |
|
---|
266 |
|
---|
267 | def build_pretty_printer():
|
---|
268 | # Turn off via: disable pretty-printer global EFI
|
---|
269 | pp = RegexpCollectionPrettyPrinter("EFI")
|
---|
270 | # you can also tell gdb `x/sh <address>` to print CHAR16 string
|
---|
271 | pp.add_printer('CHAR16', '^CHAR16$', CHAR16_PrettyPrinter)
|
---|
272 | pp.add_printer('EFI_BOOT_MODE', '^EFI_BOOT_MODE$',
|
---|
273 | EFI_BOOT_MODE_PrettyPrinter)
|
---|
274 | pp.add_printer('EFI_GUID', '^EFI_GUID$', EFI_GUID_PrettyPrinter)
|
---|
275 | pp.add_printer('EFI_STATUS', '^EFI_STATUS$', EFI_STATUS_PrettyPrinter)
|
---|
276 | pp.add_printer('EFI_TPL', '^EFI_TPL$', EFI_TPL_PrettyPrinter)
|
---|
277 | return pp
|
---|
278 |
|
---|
279 |
|
---|
280 | class EfiDevicePathCmd (gdb.Command):
|
---|
281 | """Display an EFI device path. Type 'efi devicepath -h' for more info"""
|
---|
282 |
|
---|
283 | def __init__(self):
|
---|
284 | super(EfiDevicePathCmd, self).__init__(
|
---|
285 | "efi devicepath", gdb.COMMAND_NONE)
|
---|
286 |
|
---|
287 | self.file = GdbFileObject()
|
---|
288 |
|
---|
289 | def create_options(self, arg, from_tty):
|
---|
290 | usage = "usage: %prog [options] [arg]"
|
---|
291 | description = (
|
---|
292 | "Command that can load EFI PE/COFF and TE image symbols. ")
|
---|
293 |
|
---|
294 | self.parser = optparse.OptionParser(
|
---|
295 | description=description,
|
---|
296 | prog='efi devicepath',
|
---|
297 | usage=usage,
|
---|
298 | add_help_option=False)
|
---|
299 |
|
---|
300 | self.parser.add_option(
|
---|
301 | '-v',
|
---|
302 | '--verbose',
|
---|
303 | action='store_true',
|
---|
304 | dest='verbose',
|
---|
305 | help='hex dump extra data',
|
---|
306 | default=False)
|
---|
307 |
|
---|
308 | self.parser.add_option(
|
---|
309 | '-n',
|
---|
310 | '--node',
|
---|
311 | action='store_true',
|
---|
312 | dest='node',
|
---|
313 | help='dump a single device path node',
|
---|
314 | default=False)
|
---|
315 |
|
---|
316 | self.parser.add_option(
|
---|
317 | '-h',
|
---|
318 | '--help',
|
---|
319 | action='store_true',
|
---|
320 | dest='help',
|
---|
321 | help='Show help for the command',
|
---|
322 | default=False)
|
---|
323 |
|
---|
324 | return self.parser.parse_args(shlex.split(arg))
|
---|
325 |
|
---|
326 | def invoke(self, arg, from_tty):
|
---|
327 | '''gdb command to dump EFI device paths'''
|
---|
328 |
|
---|
329 | try:
|
---|
330 | (options, _) = self.create_options(arg, from_tty)
|
---|
331 | if options.help:
|
---|
332 | self.parser.print_help()
|
---|
333 | return
|
---|
334 |
|
---|
335 | dev_addr = int(gdb.parse_and_eval(arg))
|
---|
336 | except ValueError:
|
---|
337 | print("Invalid argument!")
|
---|
338 | return
|
---|
339 |
|
---|
340 | if options.node:
|
---|
341 | print(EfiDevicePath(
|
---|
342 | self.file).device_path_node_str(dev_addr,
|
---|
343 | options.verbose))
|
---|
344 | else:
|
---|
345 | device_path = EfiDevicePath(self.file, dev_addr, options.verbose)
|
---|
346 | if device_path.valid():
|
---|
347 | print(device_path)
|
---|
348 |
|
---|
349 |
|
---|
350 | class EfiGuidCmd (gdb.Command):
|
---|
351 | """Display info about EFI GUID's. Type 'efi guid -h' for more info"""
|
---|
352 |
|
---|
353 | def __init__(self):
|
---|
354 | super(EfiGuidCmd, self).__init__("efi guid",
|
---|
355 | gdb.COMMAND_NONE,
|
---|
356 | gdb.COMPLETE_EXPRESSION)
|
---|
357 | self.file = GdbFileObject()
|
---|
358 |
|
---|
359 | def create_options(self, arg, from_tty):
|
---|
360 | usage = "usage: %prog [options] [arg]"
|
---|
361 | description = (
|
---|
362 | "Show EFI_GUID values and the C name of the EFI_GUID variables"
|
---|
363 | "in the C code. If symbols are loaded the Guid.xref file"
|
---|
364 | "can be processed and the complete GUID database can be shown."
|
---|
365 | "This command also suports generating new GUID's, and showing"
|
---|
366 | "the value used to initialize the C variable.")
|
---|
367 |
|
---|
368 | self.parser = optparse.OptionParser(
|
---|
369 | description=description,
|
---|
370 | prog='efi guid',
|
---|
371 | usage=usage,
|
---|
372 | add_help_option=False)
|
---|
373 |
|
---|
374 | self.parser.add_option(
|
---|
375 | '-n',
|
---|
376 | '--new',
|
---|
377 | action='store_true',
|
---|
378 | dest='new',
|
---|
379 | help='Generate a new GUID',
|
---|
380 | default=False)
|
---|
381 |
|
---|
382 | self.parser.add_option(
|
---|
383 | '-v',
|
---|
384 | '--verbose',
|
---|
385 | action='store_true',
|
---|
386 | dest='verbose',
|
---|
387 | help='Also display GUID C structure values',
|
---|
388 | default=False)
|
---|
389 |
|
---|
390 | self.parser.add_option(
|
---|
391 | '-h',
|
---|
392 | '--help',
|
---|
393 | action='store_true',
|
---|
394 | dest='help',
|
---|
395 | help='Show help for the command',
|
---|
396 | default=False)
|
---|
397 |
|
---|
398 | return self.parser.parse_args(shlex.split(arg))
|
---|
399 |
|
---|
400 | def invoke(self, arg, from_tty):
|
---|
401 | '''gdb command to dump EFI System Tables'''
|
---|
402 |
|
---|
403 | try:
|
---|
404 | (options, args) = self.create_options(arg, from_tty)
|
---|
405 | if options.help:
|
---|
406 | self.parser.print_help()
|
---|
407 | return
|
---|
408 | if len(args) >= 1:
|
---|
409 | # guid { 0x414e6bdd, 0xe47b, 0x47cc,
|
---|
410 | # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
|
---|
411 | # this generates multiple args
|
---|
412 | guid = ' '.join(args)
|
---|
413 | except ValueError:
|
---|
414 | print('bad arguments!')
|
---|
415 | return
|
---|
416 |
|
---|
417 | if options.new:
|
---|
418 | guid = uuid.uuid4()
|
---|
419 | print(str(guid).upper())
|
---|
420 | print(GuidNames.to_c_guid(guid))
|
---|
421 | return
|
---|
422 |
|
---|
423 | if len(args) > 0:
|
---|
424 | if GuidNames.is_guid_str(arg):
|
---|
425 | # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9
|
---|
426 | key = guid.upper()
|
---|
427 | name = GuidNames.to_name(key)
|
---|
428 | elif GuidNames.is_c_guid(arg):
|
---|
429 | # guid { 0x414e6bdd, 0xe47b, 0x47cc,
|
---|
430 | # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
|
---|
431 | key = GuidNames.from_c_guid(arg)
|
---|
432 | name = GuidNames.to_name(key)
|
---|
433 | else:
|
---|
434 | # guid gEfiDxeServicesTableGuid
|
---|
435 | name = guid
|
---|
436 | try:
|
---|
437 | key = GuidNames.to_guid(name)
|
---|
438 | name = GuidNames.to_name(key)
|
---|
439 | except ValueError:
|
---|
440 | return
|
---|
441 |
|
---|
442 | extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else ''
|
---|
443 | print(f'{key}: {extra}{name}')
|
---|
444 |
|
---|
445 | else:
|
---|
446 | for key, value in GuidNames._dict_.items():
|
---|
447 | if options.verbose:
|
---|
448 | extra = f'{GuidNames.to_c_guid(key)}: '
|
---|
449 | else:
|
---|
450 | extra = ''
|
---|
451 | print(f'{key}: {extra}{value}')
|
---|
452 |
|
---|
453 |
|
---|
454 | class EfiHobCmd (gdb.Command):
|
---|
455 | """Dump EFI HOBs. Type 'hob -h' for more info."""
|
---|
456 |
|
---|
457 | def __init__(self):
|
---|
458 | super(EfiHobCmd, self).__init__("efi hob", gdb.COMMAND_NONE)
|
---|
459 | self.file = GdbFileObject()
|
---|
460 |
|
---|
461 | def create_options(self, arg, from_tty):
|
---|
462 | usage = "usage: %prog [options] [arg]"
|
---|
463 | description = (
|
---|
464 | "Command that can load EFI PE/COFF and TE image symbols. ")
|
---|
465 |
|
---|
466 | self.parser = optparse.OptionParser(
|
---|
467 | description=description,
|
---|
468 | prog='efi hob',
|
---|
469 | usage=usage,
|
---|
470 | add_help_option=False)
|
---|
471 |
|
---|
472 | self.parser.add_option(
|
---|
473 | '-a',
|
---|
474 | '--address',
|
---|
475 | type="int",
|
---|
476 | dest='address',
|
---|
477 | help='Parse HOBs from address',
|
---|
478 | default=None)
|
---|
479 |
|
---|
480 | self.parser.add_option(
|
---|
481 | '-t',
|
---|
482 | '--type',
|
---|
483 | type="int",
|
---|
484 | dest='type',
|
---|
485 | help='Only dump HOBS of his type',
|
---|
486 | default=None)
|
---|
487 |
|
---|
488 | self.parser.add_option(
|
---|
489 | '-v',
|
---|
490 | '--verbose',
|
---|
491 | action='store_true',
|
---|
492 | dest='verbose',
|
---|
493 | help='hex dump extra data',
|
---|
494 | default=False)
|
---|
495 |
|
---|
496 | self.parser.add_option(
|
---|
497 | '-h',
|
---|
498 | '--help',
|
---|
499 | action='store_true',
|
---|
500 | dest='help',
|
---|
501 | help='Show help for the command',
|
---|
502 | default=False)
|
---|
503 |
|
---|
504 | return self.parser.parse_args(shlex.split(arg))
|
---|
505 |
|
---|
506 | def invoke(self, arg, from_tty):
|
---|
507 | '''gdb command to dump EFI System Tables'''
|
---|
508 |
|
---|
509 | try:
|
---|
510 | (options, _) = self.create_options(arg, from_tty)
|
---|
511 | if options.help:
|
---|
512 | self.parser.print_help()
|
---|
513 | return
|
---|
514 | except ValueError:
|
---|
515 | print('bad arguments!')
|
---|
516 | return
|
---|
517 |
|
---|
518 | if options.address:
|
---|
519 | try:
|
---|
520 | value = gdb.parse_and_eval(options.address)
|
---|
521 | address = int(value)
|
---|
522 | except ValueError:
|
---|
523 | address = None
|
---|
524 | else:
|
---|
525 | address = None
|
---|
526 |
|
---|
527 | hob = EfiHob(self.file,
|
---|
528 | address,
|
---|
529 | options.verbose).get_hob_by_type(options.type)
|
---|
530 | print(hob)
|
---|
531 |
|
---|
532 |
|
---|
533 | class EfiTablesCmd (gdb.Command):
|
---|
534 | """Dump EFI System Tables. Type 'table -h' for more info."""
|
---|
535 |
|
---|
536 | def __init__(self):
|
---|
537 | super(EfiTablesCmd, self).__init__("efi table", gdb.COMMAND_NONE)
|
---|
538 |
|
---|
539 | self.file = GdbFileObject()
|
---|
540 |
|
---|
541 | def create_options(self, arg, from_tty):
|
---|
542 | usage = "usage: %prog [options] [arg]"
|
---|
543 | description = "Dump EFI System Tables. Requires symbols to be loaded"
|
---|
544 |
|
---|
545 | self.parser = optparse.OptionParser(
|
---|
546 | description=description,
|
---|
547 | prog='efi table',
|
---|
548 | usage=usage,
|
---|
549 | add_help_option=False)
|
---|
550 |
|
---|
551 | self.parser.add_option(
|
---|
552 | '-h',
|
---|
553 | '--help',
|
---|
554 | action='store_true',
|
---|
555 | dest='help',
|
---|
556 | help='Show help for the command',
|
---|
557 | default=False)
|
---|
558 |
|
---|
559 | return self.parser.parse_args(shlex.split(arg))
|
---|
560 |
|
---|
561 | def invoke(self, arg, from_tty):
|
---|
562 | '''gdb command to dump EFI System Tables'''
|
---|
563 |
|
---|
564 | try:
|
---|
565 | (options, _) = self.create_options(arg, from_tty)
|
---|
566 | if options.help:
|
---|
567 | self.parser.print_help()
|
---|
568 | return
|
---|
569 | except ValueError:
|
---|
570 | print('bad arguments!')
|
---|
571 | return
|
---|
572 |
|
---|
573 | gST = gdb.lookup_global_symbol('gST')
|
---|
574 | if gST is None:
|
---|
575 | print('Error: This command requires symbols for gST to be loaded')
|
---|
576 | return
|
---|
577 |
|
---|
578 | table = EfiConfigurationTable(
|
---|
579 | self.file, int(gST.value(gdb.selected_frame())))
|
---|
580 | if table:
|
---|
581 | print(table, '\n')
|
---|
582 |
|
---|
583 |
|
---|
584 | class EfiSymbolsCmd (gdb.Command):
|
---|
585 | """Load Symbols for EFI. Type 'efi symbols -h' for more info."""
|
---|
586 |
|
---|
587 | def __init__(self):
|
---|
588 | super(EfiSymbolsCmd, self).__init__("efi symbols",
|
---|
589 | gdb.COMMAND_NONE,
|
---|
590 | gdb.COMPLETE_EXPRESSION)
|
---|
591 | self.file = GdbFileObject()
|
---|
592 | self.gST = None
|
---|
593 | self.efi_symbols = EfiSymbols(self.file)
|
---|
594 |
|
---|
595 | def create_options(self, arg, from_tty):
|
---|
596 | usage = "usage: %prog [options]"
|
---|
597 | description = (
|
---|
598 | "Command that can load EFI PE/COFF and TE image symbols. "
|
---|
599 | "If you are having trouble in PEI try adding --pei. "
|
---|
600 | "Given any address search backward for the PE/COFF (or TE header) "
|
---|
601 | "and then parse the PE/COFF image to get debug info. "
|
---|
602 | "The address can come from the current pc, pc values in the "
|
---|
603 | "frame, or an address provided to the command"
|
---|
604 | "")
|
---|
605 |
|
---|
606 | self.parser = optparse.OptionParser(
|
---|
607 | description=description,
|
---|
608 | prog='efi symbols',
|
---|
609 | usage=usage,
|
---|
610 | add_help_option=False)
|
---|
611 |
|
---|
612 | self.parser.add_option(
|
---|
613 | '-a',
|
---|
614 | '--address',
|
---|
615 | type="str",
|
---|
616 | dest='address',
|
---|
617 | help='Load symbols for image that contains address',
|
---|
618 | default=None)
|
---|
619 |
|
---|
620 | self.parser.add_option(
|
---|
621 | '-c',
|
---|
622 | '--clear',
|
---|
623 | action='store_true',
|
---|
624 | dest='clear',
|
---|
625 | help='Clear the cache of loaded images',
|
---|
626 | default=False)
|
---|
627 |
|
---|
628 | self.parser.add_option(
|
---|
629 | '-f',
|
---|
630 | '--frame',
|
---|
631 | action='store_true',
|
---|
632 | dest='frame',
|
---|
633 | help='Load symbols for current stack frame',
|
---|
634 | default=False)
|
---|
635 |
|
---|
636 | self.parser.add_option(
|
---|
637 | '-p',
|
---|
638 | '--pc',
|
---|
639 | action='store_true',
|
---|
640 | dest='pc',
|
---|
641 | help='Load symbols for pc',
|
---|
642 | default=False)
|
---|
643 |
|
---|
644 | self.parser.add_option(
|
---|
645 | '--pei',
|
---|
646 | action='store_true',
|
---|
647 | dest='pei',
|
---|
648 | help='Load symbols for PEI (searches every 4 bytes)',
|
---|
649 | default=False)
|
---|
650 |
|
---|
651 | self.parser.add_option(
|
---|
652 | '-e',
|
---|
653 | '--extended',
|
---|
654 | action='store_true',
|
---|
655 | dest='extended',
|
---|
656 | help='Try to load all symbols based on config tables',
|
---|
657 | default=False)
|
---|
658 |
|
---|
659 | self.parser.add_option(
|
---|
660 | '-r',
|
---|
661 | '--range',
|
---|
662 | type="long",
|
---|
663 | dest='range',
|
---|
664 | help='How far to search backward for start of PE/COFF Image',
|
---|
665 | default=None)
|
---|
666 |
|
---|
667 | self.parser.add_option(
|
---|
668 | '-s',
|
---|
669 | '--stride',
|
---|
670 | type="long",
|
---|
671 | dest='stride',
|
---|
672 | help='Boundary to search for PE/COFF header',
|
---|
673 | default=None)
|
---|
674 |
|
---|
675 | self.parser.add_option(
|
---|
676 | '-t',
|
---|
677 | '--thread',
|
---|
678 | action='store_true',
|
---|
679 | dest='thread',
|
---|
680 | help='Load symbols for the frames of all threads',
|
---|
681 | default=False)
|
---|
682 |
|
---|
683 | self.parser.add_option(
|
---|
684 | '-v',
|
---|
685 | '--verbose',
|
---|
686 | action='store_true',
|
---|
687 | dest='verbose',
|
---|
688 | help='Show more info on symbols loading in gdb',
|
---|
689 | default=False)
|
---|
690 |
|
---|
691 | self.parser.add_option(
|
---|
692 | '-h',
|
---|
693 | '--help',
|
---|
694 | action='store_true',
|
---|
695 | dest='help',
|
---|
696 | help='Show help for the command',
|
---|
697 | default=False)
|
---|
698 |
|
---|
699 | return self.parser.parse_args(shlex.split(arg))
|
---|
700 |
|
---|
701 | def save_user_state(self):
|
---|
702 | self.pagination = gdb.parameter("pagination")
|
---|
703 | if self.pagination:
|
---|
704 | gdb.execute("set pagination off")
|
---|
705 |
|
---|
706 | self.user_selected_thread = gdb.selected_thread()
|
---|
707 | self.user_selected_frame = gdb.selected_frame()
|
---|
708 |
|
---|
709 | def restore_user_state(self):
|
---|
710 | self.user_selected_thread.switch()
|
---|
711 | self.user_selected_frame.select()
|
---|
712 |
|
---|
713 | if self.pagination:
|
---|
714 | gdb.execute("set pagination on")
|
---|
715 |
|
---|
716 | def canonical_address(self, address):
|
---|
717 | '''
|
---|
718 | Scrub out 48-bit non canonical addresses
|
---|
719 | Raw frames in gdb can have some funky values
|
---|
720 | '''
|
---|
721 |
|
---|
722 | # Skip lowest 256 bytes to avoid interrupt frames
|
---|
723 | if address > 0xFF and address < 0x00007FFFFFFFFFFF:
|
---|
724 | return True
|
---|
725 | if address >= 0xFFFF800000000000:
|
---|
726 | return True
|
---|
727 |
|
---|
728 | return False
|
---|
729 |
|
---|
730 | def pc_set_for_frames(self):
|
---|
731 | '''Return a set for the PC's in the current frame'''
|
---|
732 | pc_list = []
|
---|
733 | frame = gdb.newest_frame()
|
---|
734 | while frame:
|
---|
735 | pc = int(frame.read_register('pc'))
|
---|
736 | if self.canonical_address(pc):
|
---|
737 | pc_list.append(pc)
|
---|
738 | frame = frame.older()
|
---|
739 |
|
---|
740 | return set(pc_list)
|
---|
741 |
|
---|
742 | def invoke(self, arg, from_tty):
|
---|
743 | '''gdb command to symbolicate all the frames from all the threads'''
|
---|
744 |
|
---|
745 | try:
|
---|
746 | (options, _) = self.create_options(arg, from_tty)
|
---|
747 | if options.help:
|
---|
748 | self.parser.print_help()
|
---|
749 | return
|
---|
750 | except ValueError:
|
---|
751 | print('bad arguments!')
|
---|
752 | return
|
---|
753 |
|
---|
754 | self.dont_repeat()
|
---|
755 |
|
---|
756 | self.save_user_state()
|
---|
757 |
|
---|
758 | if options.clear:
|
---|
759 | self.efi_symbols.clear()
|
---|
760 | return
|
---|
761 |
|
---|
762 | if options.pei:
|
---|
763 | # XIP code can be 4 byte aligned in the FV
|
---|
764 | options.stride = 4
|
---|
765 | options.range = 0x100000
|
---|
766 | self.efi_symbols.configure_search(options.stride,
|
---|
767 | options.range,
|
---|
768 | options.verbose)
|
---|
769 |
|
---|
770 | if options.thread:
|
---|
771 | thread_list = gdb.selected_inferior().threads()
|
---|
772 | else:
|
---|
773 | thread_list = (gdb.selected_thread(),)
|
---|
774 |
|
---|
775 | address = None
|
---|
776 | if options.address:
|
---|
777 | value = gdb.parse_and_eval(options.address)
|
---|
778 | address = int(value)
|
---|
779 | elif options.pc:
|
---|
780 | address = gdb.selected_frame().pc()
|
---|
781 |
|
---|
782 | if address:
|
---|
783 | res = self.efi_symbols.address_to_symbols(address)
|
---|
784 | print(res)
|
---|
785 | else:
|
---|
786 |
|
---|
787 | for thread in thread_list:
|
---|
788 | thread.switch()
|
---|
789 |
|
---|
790 | # You can not iterate over frames as you load symbols. Loading
|
---|
791 | # symbols changes the frames gdb can see due to inlining and
|
---|
792 | # boom. So we loop adding symbols for the current frame, and
|
---|
793 | # we test to see if new frames have shown up. If new frames
|
---|
794 | # show up we process those new frames. Thus 1st pass is the
|
---|
795 | # raw frame, and other passes are only new PC values.
|
---|
796 | NewPcSet = self.pc_set_for_frames()
|
---|
797 | while NewPcSet:
|
---|
798 | PcSet = self.pc_set_for_frames()
|
---|
799 | for pc in NewPcSet:
|
---|
800 | res = self.efi_symbols.address_to_symbols(pc)
|
---|
801 | print(res)
|
---|
802 |
|
---|
803 | NewPcSet = PcSet.symmetric_difference(
|
---|
804 | self.pc_set_for_frames())
|
---|
805 |
|
---|
806 | # find the EFI System tables the 1st time
|
---|
807 | if self.gST is None:
|
---|
808 | gST = gdb.lookup_global_symbol('gST')
|
---|
809 | if gST is not None:
|
---|
810 | self.gST = int(gST.value(gdb.selected_frame()))
|
---|
811 | table = EfiConfigurationTable(self.file, self.gST)
|
---|
812 | else:
|
---|
813 | table = None
|
---|
814 | else:
|
---|
815 | table = EfiConfigurationTable(self.file, self.gST)
|
---|
816 |
|
---|
817 | if options.extended and table:
|
---|
818 | # load symbols from EFI System Table entry
|
---|
819 | for address, _ in table.DebugImageInfo():
|
---|
820 | res = self.efi_symbols.address_to_symbols(address)
|
---|
821 | print(res)
|
---|
822 |
|
---|
823 | # sync up the GUID database from the build output
|
---|
824 | for m in gdb.objfiles():
|
---|
825 | if GuidNames.add_build_guid_file(str(m.filename)):
|
---|
826 | break
|
---|
827 |
|
---|
828 | self.restore_user_state()
|
---|
829 |
|
---|
830 |
|
---|
831 | class EfiCmd (gdb.Command):
|
---|
832 | """Commands for debugging EFI. efi <cmd>"""
|
---|
833 |
|
---|
834 | def __init__(self):
|
---|
835 | super(EfiCmd, self).__init__("efi",
|
---|
836 | gdb.COMMAND_NONE,
|
---|
837 | gdb.COMPLETE_NONE,
|
---|
838 | True)
|
---|
839 |
|
---|
840 | def invoke(self, arg, from_tty):
|
---|
841 | '''default to loading symbols'''
|
---|
842 | if '-h' in arg or '--help' in arg:
|
---|
843 | gdb.execute('help efi')
|
---|
844 | else:
|
---|
845 | # default to loading all symbols
|
---|
846 | gdb.execute('efi symbols --extended')
|
---|
847 |
|
---|
848 |
|
---|
849 | class LoadEmulatorEfiSymbols(gdb.Breakpoint):
|
---|
850 | '''
|
---|
851 | breakpoint for EmulatorPkg to load symbols
|
---|
852 | Note: make sure SecGdbScriptBreak is not optimized away!
|
---|
853 | Also turn off the dlopen() flow like on macOS.
|
---|
854 | '''
|
---|
855 | def stop(self):
|
---|
856 | symbols = EfiSymbols()
|
---|
857 | # Emulator adds SizeOfHeaders so we need file alignment to search
|
---|
858 | symbols.configure_search(0x20)
|
---|
859 |
|
---|
860 | frame = gdb.newest_frame()
|
---|
861 |
|
---|
862 | try:
|
---|
863 | # gdb was looking at spill address, pre spill :(
|
---|
864 | LoadAddress = frame.read_register('rdx')
|
---|
865 | AddSymbolFlag = frame.read_register('rcx')
|
---|
866 | except gdb.error:
|
---|
867 | LoadAddress = frame.read_var('LoadAddress')
|
---|
868 | AddSymbolFlag = frame.read_var('AddSymbolFlag')
|
---|
869 |
|
---|
870 | if AddSymbolFlag == 1:
|
---|
871 | res = symbols.address_to_symbols(LoadAddress)
|
---|
872 | else:
|
---|
873 | res = symbols.unload_symbols(LoadAddress)
|
---|
874 | print(res)
|
---|
875 |
|
---|
876 | # keep running
|
---|
877 | return False
|
---|
878 |
|
---|
879 |
|
---|
880 | # Get python backtraces to debug errors in this script
|
---|
881 | gdb.execute("set python print-stack full")
|
---|
882 |
|
---|
883 | # tell efi_debugging how to walk data structures with pointers
|
---|
884 | try:
|
---|
885 | pointer_width = gdb.lookup_type('int').pointer().sizeof
|
---|
886 | except ValueError:
|
---|
887 | pointer_width = 8
|
---|
888 | patch_ctypes(pointer_width)
|
---|
889 |
|
---|
890 | register_pretty_printer(None, build_pretty_printer(), replace=True)
|
---|
891 |
|
---|
892 | # gdb commands that we are adding
|
---|
893 | # add `efi` prefix gdb command
|
---|
894 | EfiCmd()
|
---|
895 |
|
---|
896 | # subcommands for `efi`
|
---|
897 | EfiSymbolsCmd()
|
---|
898 | EfiTablesCmd()
|
---|
899 | EfiHobCmd()
|
---|
900 | EfiDevicePathCmd()
|
---|
901 | EfiGuidCmd()
|
---|
902 |
|
---|
903 | #
|
---|
904 | bp = LoadEmulatorEfiSymbols('SecGdbScriptBreak', internal=True)
|
---|
905 | if bp.pending:
|
---|
906 | try:
|
---|
907 | gdb.selected_frame()
|
---|
908 | # Not the emulator so do this when you attach
|
---|
909 | gdb.execute('efi symbols --frame --extended', True)
|
---|
910 | gdb.execute('bt')
|
---|
911 | # If you want to skip the above commands comment them out
|
---|
912 | pass
|
---|
913 | except gdb.error:
|
---|
914 | # If you load the script and there is no target ignore the error.
|
---|
915 | pass
|
---|
916 | else:
|
---|
917 | # start the emulator
|
---|
918 | gdb.execute('run')
|
---|