3 Copyright 2021 (c) Apple Inc. All rights reserved.
4 SPDX-License-Identifier: BSD-2-Clause-Patent
6 EFI gdb commands based on efi_debugging classes.
9 OvmfPkg/build.sh qemu -gdb tcp::9000
10 gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py"
13 Commands for debugging EFI. efi <cmd>
15 List of efi subcommands:
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.
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.
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();.
31 from gdb
.printing
import RegexpCollectionPrettyPrinter
32 from gdb
.printing
import register_pretty_printer
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__
)))
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
50 class GdbFileObject(object):
51 '''Provide a file like object required by efi_debugging'''
54 self
.inferior
= gdb
.selected_inferior()
60 def read(self
, size
=-1):
62 # arbitrary default size
66 data
= self
.inferior
.read_memory(self
.offset
, size
)
68 data
= bytearray(size
)
72 f
'gdb could not read memory 0x{size:x}'
73 + f
' bytes from 0x{self.offset:08x}')
75 # convert memoryview object to a bytestring.
81 def seek(self
, offset
, whence
=0):
87 # whence == 2 is seek from end
88 raise NotImplementedError
93 def write(self
, data
):
94 self
.inferior
.write_memory(self
.offset
, data
)
100 def truncate(self
, size
=None):
101 raise NotImplementedError
104 raise NotImplementedError
107 raise NotImplementedError
111 """Class to manage EFI Symbols"""
118 def __init__(self
, file=None):
119 EfiSymbols
.file = file if file else GdbFileObject()
123 return ''.join(f
'{value}\n' for value
in cls
.loaded
.values())
126 def configure_search(cls
, stride
, range=None, verbose
=False):
129 cls
.verbose
= verbose
136 def add_symbols_for_pecoff(cls
, pecoff
):
137 '''Tell lldb the location of the .text and .data sections.'''
139 if pecoff
.TextAddress
in cls
.loaded
:
140 return 'Already Loaded: '
142 res
= 'Loading Symbols Failed:'
143 res
= gdb
.execute('add-symbol-file ' + pecoff
.CodeViewPdb
+
144 ' ' + hex(pecoff
.TextAddress
) +
145 ' -s .data ' + hex(pecoff
.DataAddress
),
148 cls
.loaded
[pecoff
.TextAddress
] = pecoff
150 print(f
'\n{res:s}\n')
156 def address_to_symbols(cls
, address
, reprobe
=False):
158 Given an address search backwards for a PE/COFF (or TE) header
159 and load symbols. Return a status string.
161 if not isinstance(address
, int):
162 address
= int(address
)
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'
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}'
174 return f
'0x{address:08x} not in a PE/COFF (or TE) image'
177 def address_in_loaded_pecoff(cls
, address
):
178 if not isinstance(address
, int):
179 address
= int(address
)
181 for value
in cls
.loaded
.values():
182 if (address
>= value
.LoadAddress
and
183 address
<= value
.EndLoadAddress
):
189 def unload_symbols(cls
, address
):
190 if not isinstance(address
, int):
191 address
= int(address
)
193 pecoff
= cls
.address_in_loaded_pecoff(address
)
195 res
= 'Unloading Symbols Failed:'
197 f
'remove-symbol-file -a {hex(pecoff.TextAddress):s}',
199 del cls
.loaded
[pecoff
.LoadAddress
]
205 class CHAR16_PrettyPrinter(object):
207 def __init__(self
, val
):
211 if int(self
.val
) < 0x20:
212 return f
"L'\\x{int(self.val):02x}'"
214 return f
"L'{chr(self.val):s}'"
217 class EFI_TPL_PrettyPrinter(object):
219 def __init__(self
, val
):
223 return str(EfiTpl(int(self
.val
)))
226 class EFI_STATUS_PrettyPrinter(object):
228 def __init__(self
, val
):
232 status
= int(self
.val
)
233 return f
'{str(EfiStatusClass(status)):s} (0x{status:08x})'
236 class EFI_BOOT_MODE_PrettyPrinter(object):
238 def __init__(self
, val
):
242 return str(EfiBootMode(int(self
.val
)))
245 class EFI_GUID_PrettyPrinter(object):
246 """Print 'EFI_GUID' as 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'"""
248 def __init__(self
, val
):
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
))
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
)
280 class EfiDevicePathCmd (gdb
.Command
):
281 """Display an EFI device path. Type 'efi devicepath -h' for more info"""
284 super(EfiDevicePathCmd
, self
).__init
__(
285 "efi devicepath", gdb
.COMMAND_NONE
)
287 self
.file = GdbFileObject()
289 def create_options(self
, arg
, from_tty
):
290 usage
= "usage: %prog [options] [arg]"
292 "Command that can load EFI PE/COFF and TE image symbols. ")
294 self
.parser
= optparse
.OptionParser(
295 description
=description
,
296 prog
='efi devicepath',
298 add_help_option
=False)
300 self
.parser
.add_option(
305 help='hex dump extra data',
308 self
.parser
.add_option(
313 help='dump a single device path node',
316 self
.parser
.add_option(
321 help='Show help for the command',
324 return self
.parser
.parse_args(shlex
.split(arg
))
326 def invoke(self
, arg
, from_tty
):
327 '''gdb command to dump EFI device paths'''
330 (options
, _
) = self
.create_options(arg
, from_tty
)
332 self
.parser
.print_help()
335 dev_addr
= int(gdb
.parse_and_eval(arg
))
337 print("Invalid argument!")
342 self
.file).device_path_node_str(dev_addr
,
345 device_path
= EfiDevicePath(self
.file, dev_addr
, options
.verbose
)
346 if device_path
.valid():
350 class EfiGuidCmd (gdb
.Command
):
351 """Display info about EFI GUID's. Type 'efi guid -h' for more info"""
354 super(EfiGuidCmd
, self
).__init
__("efi guid",
356 gdb
.COMPLETE_EXPRESSION
)
357 self
.file = GdbFileObject()
359 def create_options(self
, arg
, from_tty
):
360 usage
= "usage: %prog [options] [arg]"
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.")
368 self
.parser
= optparse
.OptionParser(
369 description
=description
,
372 add_help_option
=False)
374 self
.parser
.add_option(
379 help='Generate a new GUID',
382 self
.parser
.add_option(
387 help='Also display GUID C structure values',
390 self
.parser
.add_option(
395 help='Show help for the command',
398 return self
.parser
.parse_args(shlex
.split(arg
))
400 def invoke(self
, arg
, from_tty
):
401 '''gdb command to dump EFI System Tables'''
404 (options
, args
) = self
.create_options(arg
, from_tty
)
406 self
.parser
.print_help()
409 # guid { 0x414e6bdd, 0xe47b, 0x47cc,
410 # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
411 # this generates multiple args
412 guid
= ' '.join(args
)
414 print('bad arguments!')
419 print(str(guid
).upper())
420 print(GuidNames
.to_c_guid(guid
))
424 if GuidNames
.is_guid_str(arg
):
425 # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9
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
)
434 # guid gEfiDxeServicesTableGuid
437 key
= GuidNames
.to_guid(name
)
438 name
= GuidNames
.to_name(key
)
442 extra
= f
'{GuidNames.to_c_guid(key)}: ' if options
.verbose
else ''
443 print(f
'{key}: {extra}{name}')
446 for key
, value
in GuidNames
._dict
_.items():
448 extra
= f
'{GuidNames.to_c_guid(key)}: '
451 print(f
'{key}: {extra}{value}')
454 class EfiHobCmd (gdb
.Command
):
455 """Dump EFI HOBs. Type 'hob -h' for more info."""
458 super(EfiHobCmd
, self
).__init
__("efi hob", gdb
.COMMAND_NONE
)
459 self
.file = GdbFileObject()
461 def create_options(self
, arg
, from_tty
):
462 usage
= "usage: %prog [options] [arg]"
464 "Command that can load EFI PE/COFF and TE image symbols. ")
466 self
.parser
= optparse
.OptionParser(
467 description
=description
,
470 add_help_option
=False)
472 self
.parser
.add_option(
477 help='Parse HOBs from address',
480 self
.parser
.add_option(
485 help='Only dump HOBS of his type',
488 self
.parser
.add_option(
493 help='hex dump extra data',
496 self
.parser
.add_option(
501 help='Show help for the command',
504 return self
.parser
.parse_args(shlex
.split(arg
))
506 def invoke(self
, arg
, from_tty
):
507 '''gdb command to dump EFI System Tables'''
510 (options
, _
) = self
.create_options(arg
, from_tty
)
512 self
.parser
.print_help()
515 print('bad arguments!')
520 value
= gdb
.parse_and_eval(options
.address
)
527 hob
= EfiHob(self
.file,
529 options
.verbose
).get_hob_by_type(options
.type)
533 class EfiTablesCmd (gdb
.Command
):
534 """Dump EFI System Tables. Type 'table -h' for more info."""
537 super(EfiTablesCmd
, self
).__init
__("efi table", gdb
.COMMAND_NONE
)
539 self
.file = GdbFileObject()
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"
545 self
.parser
= optparse
.OptionParser(
546 description
=description
,
549 add_help_option
=False)
551 self
.parser
.add_option(
556 help='Show help for the command',
559 return self
.parser
.parse_args(shlex
.split(arg
))
561 def invoke(self
, arg
, from_tty
):
562 '''gdb command to dump EFI System Tables'''
565 (options
, _
) = self
.create_options(arg
, from_tty
)
567 self
.parser
.print_help()
570 print('bad arguments!')
573 gST
= gdb
.lookup_global_symbol('gST')
575 print('Error: This command requires symbols for gST to be loaded')
578 table
= EfiConfigurationTable(
579 self
.file, int(gST
.value(gdb
.selected_frame())))
584 class EfiSymbolsCmd (gdb
.Command
):
585 """Load Symbols for EFI. Type 'efi symbols -h' for more info."""
588 super(EfiSymbolsCmd
, self
).__init
__("efi symbols",
590 gdb
.COMPLETE_EXPRESSION
)
591 self
.file = GdbFileObject()
593 self
.efi_symbols
= EfiSymbols(self
.file)
595 def create_options(self
, arg
, from_tty
):
596 usage
= "usage: %prog [options]"
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"
606 self
.parser
= optparse
.OptionParser(
607 description
=description
,
610 add_help_option
=False)
612 self
.parser
.add_option(
617 help='Load symbols for image that contains address',
620 self
.parser
.add_option(
625 help='Clear the cache of loaded images',
628 self
.parser
.add_option(
633 help='Load symbols for current stack frame',
636 self
.parser
.add_option(
641 help='Load symbols for pc',
644 self
.parser
.add_option(
648 help='Load symbols for PEI (searches every 4 bytes)',
651 self
.parser
.add_option(
656 help='Try to load all symbols based on config tables',
659 self
.parser
.add_option(
664 help='How far to search backward for start of PE/COFF Image',
667 self
.parser
.add_option(
672 help='Boundary to search for PE/COFF header',
675 self
.parser
.add_option(
680 help='Load symbols for the frames of all threads',
683 self
.parser
.add_option(
688 help='Show more info on symbols loading in gdb',
691 self
.parser
.add_option(
696 help='Show help for the command',
699 return self
.parser
.parse_args(shlex
.split(arg
))
701 def save_user_state(self
):
702 self
.pagination
= gdb
.parameter("pagination")
704 gdb
.execute("set pagination off")
706 self
.user_selected_thread
= gdb
.selected_thread()
707 self
.user_selected_frame
= gdb
.selected_frame()
709 def restore_user_state(self
):
710 self
.user_selected_thread
.switch()
711 self
.user_selected_frame
.select()
714 gdb
.execute("set pagination on")
716 def canonical_address(self
, address
):
718 Scrub out 48-bit non canonical addresses
719 Raw frames in gdb can have some funky values
722 # Skip lowest 256 bytes to avoid interrupt frames
723 if address
> 0xFF and address
< 0x00007FFFFFFFFFFF:
725 if address
>= 0xFFFF800000000000:
730 def pc_set_for_frames(self
):
731 '''Return a set for the PC's in the current frame'''
733 frame
= gdb
.newest_frame()
735 pc
= int(frame
.read_register('pc'))
736 if self
.canonical_address(pc
):
738 frame
= frame
.older()
742 def invoke(self
, arg
, from_tty
):
743 '''gdb command to symbolicate all the frames from all the threads'''
746 (options
, _
) = self
.create_options(arg
, from_tty
)
748 self
.parser
.print_help()
751 print('bad arguments!')
756 self
.save_user_state()
759 self
.efi_symbols
.clear()
763 # XIP code can be 4 byte aligned in the FV
765 options
.range = 0x100000
766 self
.efi_symbols
.configure_search(options
.stride
,
771 thread_list
= gdb
.selected_inferior().threads()
773 thread_list
= (gdb
.selected_thread(),)
777 value
= gdb
.parse_and_eval(options
.address
)
780 address
= gdb
.selected_frame().pc()
783 res
= self
.efi_symbols
.address_to_symbols(address
)
787 for thread
in thread_list
:
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()
798 PcSet
= self
.pc_set_for_frames()
800 res
= self
.efi_symbols
.address_to_symbols(pc
)
803 NewPcSet
= PcSet
.symmetric_difference(
804 self
.pc_set_for_frames())
806 # find the EFI System tables the 1st time
808 gST
= gdb
.lookup_global_symbol('gST')
810 self
.gST
= int(gST
.value(gdb
.selected_frame()))
811 table
= EfiConfigurationTable(self
.file, self
.gST
)
815 table
= EfiConfigurationTable(self
.file, self
.gST
)
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
)
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
)):
828 self
.restore_user_state()
831 class EfiCmd (gdb
.Command
):
832 """Commands for debugging EFI. efi <cmd>"""
835 super(EfiCmd
, self
).__init
__("efi",
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')
845 # default to loading all symbols
846 gdb
.execute('efi symbols --extended')
849 class LoadEmulatorEfiSymbols(gdb
.Breakpoint
):
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.
856 symbols
= EfiSymbols()
857 # Emulator adds SizeOfHeaders so we need file alignment to search
858 symbols
.configure_search(0x20)
860 frame
= gdb
.newest_frame()
863 # gdb was looking at spill address, pre spill :(
864 LoadAddress
= frame
.read_register('rdx')
865 AddSymbolFlag
= frame
.read_register('rcx')
867 LoadAddress
= frame
.read_var('LoadAddress')
868 AddSymbolFlag
= frame
.read_var('AddSymbolFlag')
870 if AddSymbolFlag
== 1:
871 res
= symbols
.address_to_symbols(LoadAddress
)
873 res
= symbols
.unload_symbols(LoadAddress
)
880 # Get python backtraces to debug errors in this script
881 gdb
.execute("set python print-stack full")
883 # tell efi_debugging how to walk data structures with pointers
885 pointer_width
= gdb
.lookup_type('int').pointer().sizeof
888 patch_ctypes(pointer_width
)
890 register_pretty_printer(None, build_pretty_printer(), replace
=True)
892 # gdb commands that we are adding
893 # add `efi` prefix gdb command
896 # subcommands for `efi`
904 bp
= LoadEmulatorEfiSymbols('SecGdbScriptBreak', internal
=True)
908 # Not the emulator so do this when you attach
909 gdb
.execute('efi symbols --frame --extended', True)
911 # If you want to skip the above commands comment them out
914 # If you load the script and there is no target ignore the error.