mirror of https://github.com/acidanthera/audk.git
BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print
https://bugzilla.tianocore.org/show_bug.cgi?id=3500 Use efi_debugging.py Python Classes to implement EFI gdb commands: (gdb) help efi Commands for debugging EFI. efi <cmd> List of efi subcommands: efi devicepath -- Display an EFI device path. efi guid -- Display info about EFI GUID's. efi hob -- Dump EFI HOBs. Type 'hob -h' for more info. efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info. efi table -- Dump EFI System Tables. Type 'table -h' for more info. This module is coded against a generic gdb remote serial stub. It should work with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub. No modifications of EFI is required to load symbols. Example usage: OvmfPkg/build.sh qemu -gdb tcp::9000 gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py" Cc: Leif Lindholm <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> Reviewed-by: Bob Feng <bob.c.feng@intel.com> Acked-by: Liming Gao <gaoliming@byosoft.com.cn>
This commit is contained in:
parent
b8c5ba2337
commit
0d7fec9f79
|
@ -0,0 +1,918 @@
|
|||
#!/usr/bin/python3
|
||||
'''
|
||||
Copyright 2021 (c) Apple Inc. All rights reserved.
|
||||
SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
|
||||
EFI gdb commands based on efi_debugging classes.
|
||||
|
||||
Example usage:
|
||||
OvmfPkg/build.sh qemu -gdb tcp::9000
|
||||
gdb -ex "target remote localhost:9000" -ex "source efi_gdb.py"
|
||||
|
||||
(gdb) help efi
|
||||
Commands for debugging EFI. efi <cmd>
|
||||
|
||||
List of efi subcommands:
|
||||
|
||||
efi devicepath -- Display an EFI device path.
|
||||
efi guid -- Display info about EFI GUID's.
|
||||
efi hob -- Dump EFI HOBs. Type 'hob -h' for more info.
|
||||
efi symbols -- Load Symbols for EFI. Type 'efi_symbols -h' for more info.
|
||||
efi table -- Dump EFI System Tables. Type 'table -h' for more info.
|
||||
|
||||
This module is coded against a generic gdb remote serial stub. It should work
|
||||
with QEMU, JTAG debugger, or a generic EFI gdb remote serial stub.
|
||||
|
||||
If you are debugging with QEMU or a JTAG hardware debugger you can insert
|
||||
a CpuDeadLoop(); in your code, attach with gdb, and then `p Index=1` to
|
||||
step past. If you have a debug stub in EFI you can use CpuBreakpoint();.
|
||||
'''
|
||||
|
||||
from gdb.printing import RegexpCollectionPrettyPrinter
|
||||
from gdb.printing import register_pretty_printer
|
||||
import gdb
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import optparse
|
||||
import shlex
|
||||
|
||||
# gdb will not import from the same path as this script.
|
||||
# so lets fix that for gdb...
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from efi_debugging import PeTeImage, patch_ctypes # noqa: E402
|
||||
from efi_debugging import EfiHob, GuidNames, EfiStatusClass # noqa: E402
|
||||
from efi_debugging import EfiBootMode, EfiDevicePath # noqa: E402
|
||||
from efi_debugging import EfiConfigurationTable, EfiTpl # noqa: E402
|
||||
|
||||
|
||||
class GdbFileObject(object):
|
||||
'''Provide a file like object required by efi_debugging'''
|
||||
|
||||
def __init__(self):
|
||||
self.inferior = gdb.selected_inferior()
|
||||
self.offset = 0
|
||||
|
||||
def tell(self):
|
||||
return self.offset
|
||||
|
||||
def read(self, size=-1):
|
||||
if size == -1:
|
||||
# arbitrary default size
|
||||
size = 0x1000000
|
||||
|
||||
try:
|
||||
data = self.inferior.read_memory(self.offset, size)
|
||||
except MemoryError:
|
||||
data = bytearray(size)
|
||||
assert False
|
||||
if len(data) != size:
|
||||
raise MemoryError(
|
||||
f'gdb could not read memory 0x{size:x}'
|
||||
+ f' bytes from 0x{self.offset:08x}')
|
||||
else:
|
||||
# convert memoryview object to a bytestring.
|
||||
return data.tobytes()
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def seek(self, offset, whence=0):
|
||||
if whence == 0:
|
||||
self.offset = offset
|
||||
elif whence == 1:
|
||||
self.offset += offset
|
||||
else:
|
||||
# whence == 2 is seek from end
|
||||
raise NotImplementedError
|
||||
|
||||
def seekable(self):
|
||||
return True
|
||||
|
||||
def write(self, data):
|
||||
self.inferior.write_memory(self.offset, data)
|
||||
return len(data)
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def truncate(self, size=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def flush(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def fileno(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class EfiSymbols:
|
||||
"""Class to manage EFI Symbols"""
|
||||
|
||||
loaded = {}
|
||||
stride = None
|
||||
range = None
|
||||
verbose = False
|
||||
|
||||
def __init__(self, file=None):
|
||||
EfiSymbols.file = file if file else GdbFileObject()
|
||||
|
||||
@ classmethod
|
||||
def __str__(cls):
|
||||
return ''.join(f'{value}\n' for value in cls.loaded.values())
|
||||
|
||||
@ classmethod
|
||||
def configure_search(cls, stride, range=None, verbose=False):
|
||||
cls.stride = stride
|
||||
cls.range = range
|
||||
cls.verbose = verbose
|
||||
|
||||
@ classmethod
|
||||
def clear(cls):
|
||||
cls.loaded = {}
|
||||
|
||||
@ classmethod
|
||||
def add_symbols_for_pecoff(cls, pecoff):
|
||||
'''Tell lldb the location of the .text and .data sections.'''
|
||||
|
||||
if pecoff.TextAddress in cls.loaded:
|
||||
return 'Already Loaded: '
|
||||
try:
|
||||
res = 'Loading Symbols Failed:'
|
||||
res = gdb.execute('add-symbol-file ' + pecoff.CodeViewPdb +
|
||||
' ' + hex(pecoff.TextAddress) +
|
||||
' -s .data ' + hex(pecoff.DataAddress),
|
||||
False, True)
|
||||
|
||||
cls.loaded[pecoff.TextAddress] = pecoff
|
||||
if cls.verbose:
|
||||
print(f'\n{res:s}\n')
|
||||
return ''
|
||||
except gdb.error:
|
||||
return res
|
||||
|
||||
@ classmethod
|
||||
def address_to_symbols(cls, address, reprobe=False):
|
||||
'''
|
||||
Given an address search backwards for a PE/COFF (or TE) header
|
||||
and load symbols. Return a status string.
|
||||
'''
|
||||
if not isinstance(address, int):
|
||||
address = int(address)
|
||||
|
||||
pecoff = cls.address_in_loaded_pecoff(address)
|
||||
if not reprobe and pecoff is not None:
|
||||
# skip the probe of the remote
|
||||
return f'{pecoff} is already loaded'
|
||||
|
||||
pecoff = PeTeImage(cls.file, None)
|
||||
if pecoff.pcToPeCoff(address, cls.stride, cls.range):
|
||||
res = cls.add_symbols_for_pecoff(pecoff)
|
||||
return f'{res}{pecoff}'
|
||||
else:
|
||||
return f'0x{address:08x} not in a PE/COFF (or TE) image'
|
||||
|
||||
@ classmethod
|
||||
def address_in_loaded_pecoff(cls, address):
|
||||
if not isinstance(address, int):
|
||||
address = int(address)
|
||||
|
||||
for value in cls.loaded.values():
|
||||
if (address >= value.LoadAddress and
|
||||
address <= value.EndLoadAddress):
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
@ classmethod
|
||||
def unload_symbols(cls, address):
|
||||
if not isinstance(address, int):
|
||||
address = int(address)
|
||||
|
||||
pecoff = cls.address_in_loaded_pecoff(address)
|
||||
try:
|
||||
res = 'Unloading Symbols Failed:'
|
||||
res = gdb.execute(
|
||||
f'remove-symbol-file -a {hex(pecoff.TextAddress):s}',
|
||||
False, True)
|
||||
del cls.loaded[pecoff.LoadAddress]
|
||||
return res
|
||||
except gdb.error:
|
||||
return res
|
||||
|
||||
|
||||
class CHAR16_PrettyPrinter(object):
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
if int(self.val) < 0x20:
|
||||
return f"L'\\x{int(self.val):02x}'"
|
||||
else:
|
||||
return f"L'{chr(self.val):s}'"
|
||||
|
||||
|
||||
class EFI_TPL_PrettyPrinter(object):
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
return str(EfiTpl(int(self.val)))
|
||||
|
||||
|
||||
class EFI_STATUS_PrettyPrinter(object):
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
status = int(self.val)
|
||||
return f'{str(EfiStatusClass(status)):s} (0x{status:08x})'
|
||||
|
||||
|
||||
class EFI_BOOT_MODE_PrettyPrinter(object):
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
return str(EfiBootMode(int(self.val)))
|
||||
|
||||
|
||||
class EFI_GUID_PrettyPrinter(object):
|
||||
"""Print 'EFI_GUID' as 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'"""
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
# if we could get a byte like object of *(unsigned char (*)[16])
|
||||
# then we could just use uuid.UUID() to convert
|
||||
Data1 = int(self.val['Data1'])
|
||||
Data2 = int(self.val['Data2'])
|
||||
Data3 = int(self.val['Data3'])
|
||||
Data4 = self.val['Data4']
|
||||
guid = f'{Data1:08X}-{Data2:04X}-'
|
||||
guid += f'{Data3:04X}-'
|
||||
guid += f'{int(Data4[0]):02X}{int(Data4[1]):02X}-'
|
||||
guid += f'{int(Data4[2]):02X}{int(Data4[3]):02X}'
|
||||
guid += f'{int(Data4[4]):02X}{int(Data4[5]):02X}'
|
||||
guid += f'{int(Data4[6]):02X}{int(Data4[7]):02X}'
|
||||
return str(GuidNames(guid))
|
||||
|
||||
|
||||
def build_pretty_printer():
|
||||
# Turn off via: disable pretty-printer global EFI
|
||||
pp = RegexpCollectionPrettyPrinter("EFI")
|
||||
# you can also tell gdb `x/sh <address>` to print CHAR16 string
|
||||
pp.add_printer('CHAR16', '^CHAR16$', CHAR16_PrettyPrinter)
|
||||
pp.add_printer('EFI_BOOT_MODE', '^EFI_BOOT_MODE$',
|
||||
EFI_BOOT_MODE_PrettyPrinter)
|
||||
pp.add_printer('EFI_GUID', '^EFI_GUID$', EFI_GUID_PrettyPrinter)
|
||||
pp.add_printer('EFI_STATUS', '^EFI_STATUS$', EFI_STATUS_PrettyPrinter)
|
||||
pp.add_printer('EFI_TPL', '^EFI_TPL$', EFI_TPL_PrettyPrinter)
|
||||
return pp
|
||||
|
||||
|
||||
class EfiDevicePathCmd (gdb.Command):
|
||||
"""Display an EFI device path. Type 'efi devicepath -h' for more info"""
|
||||
|
||||
def __init__(self):
|
||||
super(EfiDevicePathCmd, self).__init__(
|
||||
"efi devicepath", gdb.COMMAND_NONE)
|
||||
|
||||
self.file = GdbFileObject()
|
||||
|
||||
def create_options(self, arg, from_tty):
|
||||
usage = "usage: %prog [options] [arg]"
|
||||
description = (
|
||||
"Command that can load EFI PE/COFF and TE image symbols. ")
|
||||
|
||||
self.parser = optparse.OptionParser(
|
||||
description=description,
|
||||
prog='efi devicepath',
|
||||
usage=usage,
|
||||
add_help_option=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
dest='verbose',
|
||||
help='hex dump extra data',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-n',
|
||||
'--node',
|
||||
action='store_true',
|
||||
dest='node',
|
||||
help='dump a single device path node',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-h',
|
||||
'--help',
|
||||
action='store_true',
|
||||
dest='help',
|
||||
help='Show help for the command',
|
||||
default=False)
|
||||
|
||||
return self.parser.parse_args(shlex.split(arg))
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
'''gdb command to dump EFI device paths'''
|
||||
|
||||
try:
|
||||
(options, _) = self.create_options(arg, from_tty)
|
||||
if options.help:
|
||||
self.parser.print_help()
|
||||
return
|
||||
|
||||
dev_addr = int(gdb.parse_and_eval(arg))
|
||||
except ValueError:
|
||||
print("Invalid argument!")
|
||||
return
|
||||
|
||||
if options.node:
|
||||
print(EfiDevicePath(
|
||||
self.file).device_path_node_str(dev_addr,
|
||||
options.verbose))
|
||||
else:
|
||||
device_path = EfiDevicePath(self.file, dev_addr, options.verbose)
|
||||
if device_path.valid():
|
||||
print(device_path)
|
||||
|
||||
|
||||
class EfiGuidCmd (gdb.Command):
|
||||
"""Display info about EFI GUID's. Type 'efi guid -h' for more info"""
|
||||
|
||||
def __init__(self):
|
||||
super(EfiGuidCmd, self).__init__("efi guid",
|
||||
gdb.COMMAND_NONE,
|
||||
gdb.COMPLETE_EXPRESSION)
|
||||
self.file = GdbFileObject()
|
||||
|
||||
def create_options(self, arg, from_tty):
|
||||
usage = "usage: %prog [options] [arg]"
|
||||
description = (
|
||||
"Show EFI_GUID values and the C name of the EFI_GUID variables"
|
||||
"in the C code. If symbols are loaded the Guid.xref file"
|
||||
"can be processed and the complete GUID database can be shown."
|
||||
"This command also suports generating new GUID's, and showing"
|
||||
"the value used to initialize the C variable.")
|
||||
|
||||
self.parser = optparse.OptionParser(
|
||||
description=description,
|
||||
prog='efi guid',
|
||||
usage=usage,
|
||||
add_help_option=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-n',
|
||||
'--new',
|
||||
action='store_true',
|
||||
dest='new',
|
||||
help='Generate a new GUID',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
dest='verbose',
|
||||
help='Also display GUID C structure values',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-h',
|
||||
'--help',
|
||||
action='store_true',
|
||||
dest='help',
|
||||
help='Show help for the command',
|
||||
default=False)
|
||||
|
||||
return self.parser.parse_args(shlex.split(arg))
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
'''gdb command to dump EFI System Tables'''
|
||||
|
||||
try:
|
||||
(options, args) = self.create_options(arg, from_tty)
|
||||
if options.help:
|
||||
self.parser.print_help()
|
||||
return
|
||||
if len(args) >= 1:
|
||||
# guid { 0x414e6bdd, 0xe47b, 0x47cc,
|
||||
# { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
|
||||
# this generates multiple args
|
||||
guid = ' '.join(args)
|
||||
except ValueError:
|
||||
print('bad arguments!')
|
||||
return
|
||||
|
||||
if options.new:
|
||||
guid = uuid.uuid4()
|
||||
print(str(guid).upper())
|
||||
print(GuidNames.to_c_guid(guid))
|
||||
return
|
||||
|
||||
if len(args) > 0:
|
||||
if GuidNames.is_guid_str(arg):
|
||||
# guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9
|
||||
key = guid.upper()
|
||||
name = GuidNames.to_name(key)
|
||||
elif GuidNames.is_c_guid(arg):
|
||||
# guid { 0x414e6bdd, 0xe47b, 0x47cc,
|
||||
# { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
|
||||
key = GuidNames.from_c_guid(arg)
|
||||
name = GuidNames.to_name(key)
|
||||
else:
|
||||
# guid gEfiDxeServicesTableGuid
|
||||
name = guid
|
||||
try:
|
||||
key = GuidNames.to_guid(name)
|
||||
name = GuidNames.to_name(key)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else ''
|
||||
print(f'{key}: {extra}{name}')
|
||||
|
||||
else:
|
||||
for key, value in GuidNames._dict_.items():
|
||||
if options.verbose:
|
||||
extra = f'{GuidNames.to_c_guid(key)}: '
|
||||
else:
|
||||
extra = ''
|
||||
print(f'{key}: {extra}{value}')
|
||||
|
||||
|
||||
class EfiHobCmd (gdb.Command):
|
||||
"""Dump EFI HOBs. Type 'hob -h' for more info."""
|
||||
|
||||
def __init__(self):
|
||||
super(EfiHobCmd, self).__init__("efi hob", gdb.COMMAND_NONE)
|
||||
self.file = GdbFileObject()
|
||||
|
||||
def create_options(self, arg, from_tty):
|
||||
usage = "usage: %prog [options] [arg]"
|
||||
description = (
|
||||
"Command that can load EFI PE/COFF and TE image symbols. ")
|
||||
|
||||
self.parser = optparse.OptionParser(
|
||||
description=description,
|
||||
prog='efi hob',
|
||||
usage=usage,
|
||||
add_help_option=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-a',
|
||||
'--address',
|
||||
type="int",
|
||||
dest='address',
|
||||
help='Parse HOBs from address',
|
||||
default=None)
|
||||
|
||||
self.parser.add_option(
|
||||
'-t',
|
||||
'--type',
|
||||
type="int",
|
||||
dest='type',
|
||||
help='Only dump HOBS of his type',
|
||||
default=None)
|
||||
|
||||
self.parser.add_option(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
dest='verbose',
|
||||
help='hex dump extra data',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-h',
|
||||
'--help',
|
||||
action='store_true',
|
||||
dest='help',
|
||||
help='Show help for the command',
|
||||
default=False)
|
||||
|
||||
return self.parser.parse_args(shlex.split(arg))
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
'''gdb command to dump EFI System Tables'''
|
||||
|
||||
try:
|
||||
(options, _) = self.create_options(arg, from_tty)
|
||||
if options.help:
|
||||
self.parser.print_help()
|
||||
return
|
||||
except ValueError:
|
||||
print('bad arguments!')
|
||||
return
|
||||
|
||||
if options.address:
|
||||
try:
|
||||
value = gdb.parse_and_eval(options.address)
|
||||
address = int(value)
|
||||
except ValueError:
|
||||
address = None
|
||||
else:
|
||||
address = None
|
||||
|
||||
hob = EfiHob(self.file,
|
||||
address,
|
||||
options.verbose).get_hob_by_type(options.type)
|
||||
print(hob)
|
||||
|
||||
|
||||
class EfiTablesCmd (gdb.Command):
|
||||
"""Dump EFI System Tables. Type 'table -h' for more info."""
|
||||
|
||||
def __init__(self):
|
||||
super(EfiTablesCmd, self).__init__("efi table", gdb.COMMAND_NONE)
|
||||
|
||||
self.file = GdbFileObject()
|
||||
|
||||
def create_options(self, arg, from_tty):
|
||||
usage = "usage: %prog [options] [arg]"
|
||||
description = "Dump EFI System Tables. Requires symbols to be loaded"
|
||||
|
||||
self.parser = optparse.OptionParser(
|
||||
description=description,
|
||||
prog='efi table',
|
||||
usage=usage,
|
||||
add_help_option=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-h',
|
||||
'--help',
|
||||
action='store_true',
|
||||
dest='help',
|
||||
help='Show help for the command',
|
||||
default=False)
|
||||
|
||||
return self.parser.parse_args(shlex.split(arg))
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
'''gdb command to dump EFI System Tables'''
|
||||
|
||||
try:
|
||||
(options, _) = self.create_options(arg, from_tty)
|
||||
if options.help:
|
||||
self.parser.print_help()
|
||||
return
|
||||
except ValueError:
|
||||
print('bad arguments!')
|
||||
return
|
||||
|
||||
gST = gdb.lookup_global_symbol('gST')
|
||||
if gST is None:
|
||||
print('Error: This command requires symbols for gST to be loaded')
|
||||
return
|
||||
|
||||
table = EfiConfigurationTable(
|
||||
self.file, int(gST.value(gdb.selected_frame())))
|
||||
if table:
|
||||
print(table, '\n')
|
||||
|
||||
|
||||
class EfiSymbolsCmd (gdb.Command):
|
||||
"""Load Symbols for EFI. Type 'efi symbols -h' for more info."""
|
||||
|
||||
def __init__(self):
|
||||
super(EfiSymbolsCmd, self).__init__("efi symbols",
|
||||
gdb.COMMAND_NONE,
|
||||
gdb.COMPLETE_EXPRESSION)
|
||||
self.file = GdbFileObject()
|
||||
self.gST = None
|
||||
self.efi_symbols = EfiSymbols(self.file)
|
||||
|
||||
def create_options(self, arg, from_tty):
|
||||
usage = "usage: %prog [options]"
|
||||
description = (
|
||||
"Command that can load EFI PE/COFF and TE image symbols. "
|
||||
"If you are having trouble in PEI try adding --pei. "
|
||||
"Given any address search backward for the PE/COFF (or TE header) "
|
||||
"and then parse the PE/COFF image to get debug info. "
|
||||
"The address can come from the current pc, pc values in the "
|
||||
"frame, or an address provided to the command"
|
||||
"")
|
||||
|
||||
self.parser = optparse.OptionParser(
|
||||
description=description,
|
||||
prog='efi symbols',
|
||||
usage=usage,
|
||||
add_help_option=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-a',
|
||||
'--address',
|
||||
type="str",
|
||||
dest='address',
|
||||
help='Load symbols for image that contains address',
|
||||
default=None)
|
||||
|
||||
self.parser.add_option(
|
||||
'-c',
|
||||
'--clear',
|
||||
action='store_true',
|
||||
dest='clear',
|
||||
help='Clear the cache of loaded images',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-f',
|
||||
'--frame',
|
||||
action='store_true',
|
||||
dest='frame',
|
||||
help='Load symbols for current stack frame',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-p',
|
||||
'--pc',
|
||||
action='store_true',
|
||||
dest='pc',
|
||||
help='Load symbols for pc',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'--pei',
|
||||
action='store_true',
|
||||
dest='pei',
|
||||
help='Load symbols for PEI (searches every 4 bytes)',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-e',
|
||||
'--extended',
|
||||
action='store_true',
|
||||
dest='extended',
|
||||
help='Try to load all symbols based on config tables',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-r',
|
||||
'--range',
|
||||
type="long",
|
||||
dest='range',
|
||||
help='How far to search backward for start of PE/COFF Image',
|
||||
default=None)
|
||||
|
||||
self.parser.add_option(
|
||||
'-s',
|
||||
'--stride',
|
||||
type="long",
|
||||
dest='stride',
|
||||
help='Boundary to search for PE/COFF header',
|
||||
default=None)
|
||||
|
||||
self.parser.add_option(
|
||||
'-t',
|
||||
'--thread',
|
||||
action='store_true',
|
||||
dest='thread',
|
||||
help='Load symbols for the frames of all threads',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
dest='verbose',
|
||||
help='Show more info on symbols loading in gdb',
|
||||
default=False)
|
||||
|
||||
self.parser.add_option(
|
||||
'-h',
|
||||
'--help',
|
||||
action='store_true',
|
||||
dest='help',
|
||||
help='Show help for the command',
|
||||
default=False)
|
||||
|
||||
return self.parser.parse_args(shlex.split(arg))
|
||||
|
||||
def save_user_state(self):
|
||||
self.pagination = gdb.parameter("pagination")
|
||||
if self.pagination:
|
||||
gdb.execute("set pagination off")
|
||||
|
||||
self.user_selected_thread = gdb.selected_thread()
|
||||
self.user_selected_frame = gdb.selected_frame()
|
||||
|
||||
def restore_user_state(self):
|
||||
self.user_selected_thread.switch()
|
||||
self.user_selected_frame.select()
|
||||
|
||||
if self.pagination:
|
||||
gdb.execute("set pagination on")
|
||||
|
||||
def canonical_address(self, address):
|
||||
'''
|
||||
Scrub out 48-bit non canonical addresses
|
||||
Raw frames in gdb can have some funky values
|
||||
'''
|
||||
|
||||
# Skip lowest 256 bytes to avoid interrupt frames
|
||||
if address > 0xFF and address < 0x00007FFFFFFFFFFF:
|
||||
return True
|
||||
if address >= 0xFFFF800000000000:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def pc_set_for_frames(self):
|
||||
'''Return a set for the PC's in the current frame'''
|
||||
pc_list = []
|
||||
frame = gdb.newest_frame()
|
||||
while frame:
|
||||
pc = int(frame.read_register('pc'))
|
||||
if self.canonical_address(pc):
|
||||
pc_list.append(pc)
|
||||
frame = frame.older()
|
||||
|
||||
return set(pc_list)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
'''gdb command to symbolicate all the frames from all the threads'''
|
||||
|
||||
try:
|
||||
(options, _) = self.create_options(arg, from_tty)
|
||||
if options.help:
|
||||
self.parser.print_help()
|
||||
return
|
||||
except ValueError:
|
||||
print('bad arguments!')
|
||||
return
|
||||
|
||||
self.dont_repeat()
|
||||
|
||||
self.save_user_state()
|
||||
|
||||
if options.clear:
|
||||
self.efi_symbols.clear()
|
||||
return
|
||||
|
||||
if options.pei:
|
||||
# XIP code can be 4 byte aligned in the FV
|
||||
options.stride = 4
|
||||
options.range = 0x100000
|
||||
self.efi_symbols.configure_search(options.stride,
|
||||
options.range,
|
||||
options.verbose)
|
||||
|
||||
if options.thread:
|
||||
thread_list = gdb.selected_inferior().threads()
|
||||
else:
|
||||
thread_list = (gdb.selected_thread(),)
|
||||
|
||||
address = None
|
||||
if options.address:
|
||||
value = gdb.parse_and_eval(options.address)
|
||||
address = int(value)
|
||||
elif options.pc:
|
||||
address = gdb.selected_frame().pc()
|
||||
|
||||
if address:
|
||||
res = self.efi_symbols.address_to_symbols(address)
|
||||
print(res)
|
||||
else:
|
||||
|
||||
for thread in thread_list:
|
||||
thread.switch()
|
||||
|
||||
# You can not iterate over frames as you load symbols. Loading
|
||||
# symbols changes the frames gdb can see due to inlining and
|
||||
# boom. So we loop adding symbols for the current frame, and
|
||||
# we test to see if new frames have shown up. If new frames
|
||||
# show up we process those new frames. Thus 1st pass is the
|
||||
# raw frame, and other passes are only new PC values.
|
||||
NewPcSet = self.pc_set_for_frames()
|
||||
while NewPcSet:
|
||||
PcSet = self.pc_set_for_frames()
|
||||
for pc in NewPcSet:
|
||||
res = self.efi_symbols.address_to_symbols(pc)
|
||||
print(res)
|
||||
|
||||
NewPcSet = PcSet.symmetric_difference(
|
||||
self.pc_set_for_frames())
|
||||
|
||||
# find the EFI System tables the 1st time
|
||||
if self.gST is None:
|
||||
gST = gdb.lookup_global_symbol('gST')
|
||||
if gST is not None:
|
||||
self.gST = int(gST.value(gdb.selected_frame()))
|
||||
table = EfiConfigurationTable(self.file, self.gST)
|
||||
else:
|
||||
table = None
|
||||
else:
|
||||
table = EfiConfigurationTable(self.file, self.gST)
|
||||
|
||||
if options.extended and table:
|
||||
# load symbols from EFI System Table entry
|
||||
for address, _ in table.DebugImageInfo():
|
||||
res = self.efi_symbols.address_to_symbols(address)
|
||||
print(res)
|
||||
|
||||
# sync up the GUID database from the build output
|
||||
for m in gdb.objfiles():
|
||||
if GuidNames.add_build_guid_file(str(m.filename)):
|
||||
break
|
||||
|
||||
self.restore_user_state()
|
||||
|
||||
|
||||
class EfiCmd (gdb.Command):
|
||||
"""Commands for debugging EFI. efi <cmd>"""
|
||||
|
||||
def __init__(self):
|
||||
super(EfiCmd, self).__init__("efi",
|
||||
gdb.COMMAND_NONE,
|
||||
gdb.COMPLETE_NONE,
|
||||
True)
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
'''default to loading symbols'''
|
||||
if '-h' in arg or '--help' in arg:
|
||||
gdb.execute('help efi')
|
||||
else:
|
||||
# default to loading all symbols
|
||||
gdb.execute('efi symbols --extended')
|
||||
|
||||
|
||||
class LoadEmulatorEfiSymbols(gdb.Breakpoint):
|
||||
'''
|
||||
breakpoint for EmulatorPkg to load symbols
|
||||
Note: make sure SecGdbScriptBreak is not optimized away!
|
||||
Also turn off the dlopen() flow like on macOS.
|
||||
'''
|
||||
def stop(self):
|
||||
symbols = EfiSymbols()
|
||||
# Emulator adds SizeOfHeaders so we need file alignment to search
|
||||
symbols.configure_search(0x20)
|
||||
|
||||
frame = gdb.newest_frame()
|
||||
|
||||
try:
|
||||
# gdb was looking at spill address, pre spill :(
|
||||
LoadAddress = frame.read_register('rdx')
|
||||
AddSymbolFlag = frame.read_register('rcx')
|
||||
except gdb.error:
|
||||
LoadAddress = frame.read_var('LoadAddress')
|
||||
AddSymbolFlag = frame.read_var('AddSymbolFlag')
|
||||
|
||||
if AddSymbolFlag == 1:
|
||||
res = symbols.address_to_symbols(LoadAddress)
|
||||
else:
|
||||
res = symbols.unload_symbols(LoadAddress)
|
||||
print(res)
|
||||
|
||||
# keep running
|
||||
return False
|
||||
|
||||
|
||||
# Get python backtraces to debug errors in this script
|
||||
gdb.execute("set python print-stack full")
|
||||
|
||||
# tell efi_debugging how to walk data structures with pointers
|
||||
try:
|
||||
pointer_width = gdb.lookup_type('int').pointer().sizeof
|
||||
except ValueError:
|
||||
pointer_width = 8
|
||||
patch_ctypes(pointer_width)
|
||||
|
||||
register_pretty_printer(None, build_pretty_printer(), replace=True)
|
||||
|
||||
# gdb commands that we are adding
|
||||
# add `efi` prefix gdb command
|
||||
EfiCmd()
|
||||
|
||||
# subcommands for `efi`
|
||||
EfiSymbolsCmd()
|
||||
EfiTablesCmd()
|
||||
EfiHobCmd()
|
||||
EfiDevicePathCmd()
|
||||
EfiGuidCmd()
|
||||
|
||||
#
|
||||
bp = LoadEmulatorEfiSymbols('SecGdbScriptBreak', internal=True)
|
||||
if bp.pending:
|
||||
try:
|
||||
gdb.selected_frame()
|
||||
# Not the emulator so do this when you attach
|
||||
gdb.execute('efi symbols --frame --extended', True)
|
||||
gdb.execute('bt')
|
||||
# If you want to skip the above commands comment them out
|
||||
pass
|
||||
except gdb.error:
|
||||
# If you load the script and there is no target ignore the error.
|
||||
pass
|
||||
else:
|
||||
# start the emulator
|
||||
gdb.execute('run')
|
Loading…
Reference in New Issue