mirror of https://github.com/acidanthera/audk.git
919 lines
28 KiB
Python
Executable File
919 lines
28 KiB
Python
Executable File
#!/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')
|