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