mirror of https://github.com/acidanthera/audk.git
1045 lines
34 KiB
Python
Executable File
1045 lines
34 KiB
Python
Executable File
#!/usr/bin/python3
|
|
'''
|
|
Copyright (c) Apple Inc. 2021
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
Example usage:
|
|
OvmfPkg/build.sh qemu -gdb tcp::9000
|
|
lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py"
|
|
'''
|
|
|
|
import optparse
|
|
import shlex
|
|
import subprocess
|
|
import uuid
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl
|
|
from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode
|
|
from efi_debugging import PeTeImage, patch_ctypes
|
|
|
|
try:
|
|
# Just try for LLDB in case PYTHONPATH is already correctly setup
|
|
import lldb
|
|
except ImportError:
|
|
try:
|
|
env = os.environ.copy()
|
|
env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
|
|
lldb_python_path = subprocess.check_output(
|
|
["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip()
|
|
sys.path.append(lldb_python_path)
|
|
import lldb
|
|
except ValueError:
|
|
print("Couldn't find LLDB.framework from lldb -P")
|
|
print("PYTHONPATH should match the currently selected lldb")
|
|
sys.exit(-1)
|
|
|
|
|
|
class LldbFileObject(object):
|
|
'''
|
|
Class that fakes out file object to abstract lldb from the generic code.
|
|
For lldb this is memory so we don't have a concept of the end of the file.
|
|
'''
|
|
|
|
def __init__(self, process):
|
|
# _exe_ctx is lldb.SBExecutionContext
|
|
self._process = process
|
|
self._offset = 0
|
|
self._SBError = lldb.SBError()
|
|
|
|
def tell(self):
|
|
return self._offset
|
|
|
|
def read(self, size=-1):
|
|
if size == -1:
|
|
# arbitrary default size
|
|
size = 0x1000000
|
|
|
|
data = self._process.ReadMemory(self._offset, size, self._SBError)
|
|
if self._SBError.fail:
|
|
raise MemoryError(
|
|
f'lldb could not read memory 0x{size:x} '
|
|
f' bytes from 0x{self._offset:08x}')
|
|
else:
|
|
return data
|
|
|
|
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):
|
|
result = self._process.WriteMemory(self._offset, data, self._SBError)
|
|
if self._SBError.fail:
|
|
raise MemoryError(
|
|
f'lldb could not write memory to 0x{self._offset:08x}')
|
|
return result
|
|
|
|
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
|
|
You need to pass file, and exe_ctx to load symbols.
|
|
You can print(EfiSymbols()) to see the currently loaded symbols
|
|
"""
|
|
|
|
loaded = {}
|
|
stride = None
|
|
range = None
|
|
verbose = False
|
|
|
|
def __init__(self, target=None):
|
|
if target:
|
|
EfiSymbols.target = target
|
|
EfiSymbols._file = LldbFileObject(target.process)
|
|
|
|
@ classmethod
|
|
def __str__(cls):
|
|
return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values())
|
|
|
|
@ classmethod
|
|
def configure_search(cls, stride, range, 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.LoadAddress in cls.loaded:
|
|
return 'Already Loaded: '
|
|
|
|
module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid))
|
|
if not module:
|
|
module = cls.target.AddModule(pecoff.CodeViewPdb,
|
|
None,
|
|
str(pecoff.CodeViewUuid))
|
|
if module.IsValid():
|
|
SBError = cls.target.SetModuleLoadAddress(
|
|
module, pecoff.LoadAddress + pecoff.TeAdjust)
|
|
if SBError.success:
|
|
cls.loaded[pecoff.LoadAddress] = (pecoff, module)
|
|
return ''
|
|
|
|
return 'Symbols NOT FOUND: '
|
|
|
|
@ 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 (pecoff, module) in cls.loaded.values():
|
|
if (address >= pecoff.LoadAddress and
|
|
address <= pecoff.EndLoadAddress):
|
|
|
|
return pecoff, module
|
|
|
|
return None, None
|
|
|
|
@ classmethod
|
|
def unload_symbols(cls, address):
|
|
pecoff, module = cls.address_in_loaded_pecoff(address)
|
|
if module:
|
|
name = str(module)
|
|
cls.target.ClearModuleLoadAddress(module)
|
|
cls.target.RemoveModule(module)
|
|
del cls.loaded[pecoff.LoadAddress]
|
|
return f'{name:s} was unloaded'
|
|
return f'0x{address:x} was not in a loaded image'
|
|
|
|
|
|
def arg_to_address(frame, arg):
|
|
''' convert an lldb command arg into a memory address (addr_t)'''
|
|
|
|
if arg is None:
|
|
return None
|
|
|
|
arg_str = arg if isinstance(arg, str) else str(arg)
|
|
SBValue = frame.EvaluateExpression(arg_str)
|
|
if SBValue.error.fail:
|
|
return arg
|
|
|
|
if (SBValue.TypeIsPointerType() or
|
|
SBValue.value_type == lldb.eValueTypeRegister or
|
|
SBValue.value_type == lldb.eValueTypeRegisterSet or
|
|
SBValue.value_type == lldb.eValueTypeConstResult):
|
|
try:
|
|
addr = SBValue.GetValueAsAddress()
|
|
except ValueError:
|
|
addr = SBValue.unsigned
|
|
else:
|
|
try:
|
|
addr = SBValue.address_of.GetValueAsAddress()
|
|
except ValueError:
|
|
addr = SBValue.address_of.unsigned
|
|
|
|
return addr
|
|
|
|
|
|
def arg_to_data(frame, arg):
|
|
''' convert an lldb command arg into a data vale (uint32_t/uint64_t)'''
|
|
if not isinstance(arg, str):
|
|
arg_str = str(str)
|
|
|
|
SBValue = frame.EvaluateExpression(arg_str)
|
|
return SBValue.unsigned
|
|
|
|
|
|
class EfiDevicePathCommand:
|
|
|
|
def create_options(self):
|
|
''' standard lldb command help/options parser'''
|
|
usage = "usage: %prog [options]"
|
|
description = '''Command that can EFI Config Tables
|
|
'''
|
|
|
|
# Pass add_help_option = False, since this keeps the command in line
|
|
# with lldb commands, and we wire up "help command" to work by
|
|
# providing the long & short help methods below.
|
|
self.parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='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)
|
|
|
|
def get_short_help(self):
|
|
'''standard lldb function method'''
|
|
return "Display EFI Tables"
|
|
|
|
def get_long_help(self):
|
|
'''standard lldb function method'''
|
|
return self.help_string
|
|
|
|
def __init__(self, debugger, internal_dict):
|
|
'''standard lldb function method'''
|
|
self.create_options()
|
|
self.help_string = self.parser.format_help()
|
|
|
|
def __call__(self, debugger, command, exe_ctx, result):
|
|
'''standard lldb function method'''
|
|
# Use the Shell Lexer to properly parse up command options just like a
|
|
# shell would
|
|
command_args = shlex.split(command)
|
|
|
|
try:
|
|
(options, args) = self.parser.parse_args(command_args)
|
|
dev_list = []
|
|
for arg in args:
|
|
dev_list.append(arg_to_address(exe_ctx.frame, arg))
|
|
except ValueError:
|
|
# if you don't handle exceptions, passing an incorrect argument
|
|
# to the OptionParser will cause LLDB to exit (courtesy of
|
|
# OptParse dealing with argument errors by throwing SystemExit)
|
|
result.SetError("option parsing failed")
|
|
return
|
|
|
|
if options.help:
|
|
self.parser.print_help()
|
|
return
|
|
|
|
file = LldbFileObject(exe_ctx.process)
|
|
|
|
for dev_addr in dev_list:
|
|
if options.node:
|
|
print(EfiDevicePath(file).device_path_node_str(
|
|
dev_addr, options.verbose))
|
|
else:
|
|
device_path = EfiDevicePath(file, dev_addr, options.verbose)
|
|
if device_path.valid():
|
|
print(device_path)
|
|
|
|
|
|
class EfiHobCommand:
|
|
def create_options(self):
|
|
''' standard lldb command help/options parser'''
|
|
usage = "usage: %prog [options]"
|
|
description = '''Command that can EFI dump EFI HOBs'''
|
|
|
|
# Pass add_help_option = False, since this keeps the command in line
|
|
# with lldb commands, and we wire up "help command" to work by
|
|
# providing the long & short help methods below.
|
|
self.parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='table',
|
|
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)
|
|
|
|
def get_short_help(self):
|
|
'''standard lldb function method'''
|
|
return "Display EFI Hobs"
|
|
|
|
def get_long_help(self):
|
|
'''standard lldb function method'''
|
|
return self.help_string
|
|
|
|
def __init__(self, debugger, internal_dict):
|
|
'''standard lldb function method'''
|
|
self.create_options()
|
|
self.help_string = self.parser.format_help()
|
|
|
|
def __call__(self, debugger, command, exe_ctx, result):
|
|
'''standard lldb function method'''
|
|
# Use the Shell Lexer to properly parse up command options just like a
|
|
# shell would
|
|
command_args = shlex.split(command)
|
|
|
|
try:
|
|
(options, _) = self.parser.parse_args(command_args)
|
|
except ValueError:
|
|
# if you don't handle exceptions, passing an incorrect argument
|
|
# to the OptionParser will cause LLDB to exit (courtesy of
|
|
# OptParse dealing with argument errors by throwing SystemExit)
|
|
result.SetError("option parsing failed")
|
|
return
|
|
|
|
if options.help:
|
|
self.parser.print_help()
|
|
return
|
|
|
|
address = arg_to_address(exe_ctx.frame, options.address)
|
|
|
|
file = LldbFileObject(exe_ctx.process)
|
|
hob = EfiHob(file, address, options.verbose).get_hob_by_type(
|
|
options.type)
|
|
print(hob)
|
|
|
|
|
|
class EfiTableCommand:
|
|
|
|
def create_options(self):
|
|
''' standard lldb command help/options parser'''
|
|
usage = "usage: %prog [options]"
|
|
description = '''Command that can display EFI Config Tables
|
|
'''
|
|
|
|
# Pass add_help_option = False, since this keeps the command in line
|
|
# with lldb commands, and we wire up "help command" to work by
|
|
# providing the long & short help methods below.
|
|
self.parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='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)
|
|
|
|
def get_short_help(self):
|
|
'''standard lldb function method'''
|
|
return "Display EFI Tables"
|
|
|
|
def get_long_help(self):
|
|
'''standard lldb function method'''
|
|
return self.help_string
|
|
|
|
def __init__(self, debugger, internal_dict):
|
|
'''standard lldb function method'''
|
|
self.create_options()
|
|
self.help_string = self.parser.format_help()
|
|
|
|
def __call__(self, debugger, command, exe_ctx, result):
|
|
'''standard lldb function method'''
|
|
# Use the Shell Lexer to properly parse up command options just like a
|
|
# shell would
|
|
command_args = shlex.split(command)
|
|
|
|
try:
|
|
(options, _) = self.parser.parse_args(command_args)
|
|
except ValueError:
|
|
# if you don't handle exceptions, passing an incorrect argument
|
|
# to the OptionParser will cause LLDB to exit (courtesy of
|
|
# OptParse dealing with argument errors by throwing SystemExit)
|
|
result.SetError("option parsing failed")
|
|
return
|
|
|
|
if options.help:
|
|
self.parser.print_help()
|
|
return
|
|
|
|
gST = exe_ctx.target.FindFirstGlobalVariable('gST')
|
|
if gST.error.fail:
|
|
print('Error: This command requires symbols for gST to be loaded')
|
|
return
|
|
|
|
file = LldbFileObject(exe_ctx.process)
|
|
table = EfiConfigurationTable(file, gST.unsigned)
|
|
if table:
|
|
print(table, '\n')
|
|
|
|
|
|
class EfiGuidCommand:
|
|
|
|
def create_options(self):
|
|
''' standard lldb command help/options parser'''
|
|
usage = "usage: %prog [options]"
|
|
description = '''
|
|
Command that can display all EFI GUID's or give info on a
|
|
specific GUID's
|
|
'''
|
|
self.parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='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)
|
|
|
|
def get_short_help(self):
|
|
'''standard lldb function method'''
|
|
return "Display EFI GUID's"
|
|
|
|
def get_long_help(self):
|
|
'''standard lldb function method'''
|
|
return self.help_string
|
|
|
|
def __init__(self, debugger, internal_dict):
|
|
'''standard lldb function method'''
|
|
self.create_options()
|
|
self.help_string = self.parser.format_help()
|
|
|
|
def __call__(self, debugger, command, exe_ctx, result):
|
|
'''standard lldb function method'''
|
|
# Use the Shell Lexer to properly parse up command options just like a
|
|
# shell would
|
|
command_args = shlex.split(command)
|
|
|
|
try:
|
|
(options, args) = self.parser.parse_args(command_args)
|
|
if len(args) >= 1:
|
|
# guid { 0x414e6bdd, 0xe47b, 0x47cc,
|
|
# { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
|
|
# this generates multiple args
|
|
arg = ' '.join(args)
|
|
except ValueError:
|
|
# if you don't handle exceptions, passing an incorrect argument
|
|
# to the OptionParser will cause LLDB to exit (courtesy of
|
|
# OptParse dealing with argument errors by throwing SystemExit)
|
|
result.SetError("option parsing failed")
|
|
return
|
|
|
|
if options.help:
|
|
self.parser.print_help()
|
|
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 = arg.lower()
|
|
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 = arg
|
|
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 EfiSymbolicateCommand(object):
|
|
'''Class to abstract an lldb command'''
|
|
|
|
def create_options(self):
|
|
''' standard lldb command help/options parser'''
|
|
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.
|
|
'''
|
|
|
|
# Pass add_help_option = False, since this keeps the command in line
|
|
# with lldb commands, and we wire up "help command" to work by
|
|
# providing the long & short help methods below.
|
|
self.parser = optparse.OptionParser(
|
|
description=description,
|
|
prog='efi_symbols',
|
|
usage=usage,
|
|
add_help_option=False)
|
|
|
|
self.parser.add_option(
|
|
'-a',
|
|
'--address',
|
|
type="int",
|
|
dest='address',
|
|
help='Load symbols for image at address',
|
|
default=None)
|
|
|
|
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(
|
|
'-h',
|
|
'--help',
|
|
action='store_true',
|
|
dest='help',
|
|
help='Show help for the command',
|
|
default=False)
|
|
|
|
def get_short_help(self):
|
|
'''standard lldb function method'''
|
|
return (
|
|
"Load symbols based on an address that is part of"
|
|
" a PE/COFF EFI image.")
|
|
|
|
def get_long_help(self):
|
|
'''standard lldb function method'''
|
|
return self.help_string
|
|
|
|
def __init__(self, debugger, unused):
|
|
'''standard lldb function method'''
|
|
self.create_options()
|
|
self.help_string = self.parser.format_help()
|
|
|
|
def lldb_print(self, lldb_str):
|
|
# capture command out like an lldb command
|
|
self.result.PutCString(lldb_str)
|
|
# flush the output right away
|
|
self.result.SetImmediateOutputFile(
|
|
self.exe_ctx.target.debugger.GetOutputFile())
|
|
|
|
def __call__(self, debugger, command, exe_ctx, result):
|
|
'''standard lldb function method'''
|
|
# Use the Shell Lexer to properly parse up command options just like a
|
|
# shell would
|
|
command_args = shlex.split(command)
|
|
|
|
try:
|
|
(options, _) = self.parser.parse_args(command_args)
|
|
except ValueError:
|
|
# if you don't handle exceptions, passing an incorrect argument
|
|
# to the OptionParser will cause LLDB to exit (courtesy of
|
|
# OptParse dealing with argument errors by throwing SystemExit)
|
|
result.SetError("option parsing failed")
|
|
return
|
|
|
|
if options.help:
|
|
self.parser.print_help()
|
|
return
|
|
|
|
file = LldbFileObject(exe_ctx.process)
|
|
efi_symbols = EfiSymbols(exe_ctx.target)
|
|
self.result = result
|
|
self.exe_ctx = exe_ctx
|
|
|
|
if options.pei:
|
|
# XIP code ends up on a 4 byte boundary.
|
|
options.stride = 4
|
|
options.range = 0x100000
|
|
efi_symbols.configure_search(options.stride, options.range)
|
|
|
|
if not options.pc and options.address is None:
|
|
# default to
|
|
options.frame = True
|
|
|
|
if options.frame:
|
|
if not exe_ctx.frame.IsValid():
|
|
result.SetError("invalid frame")
|
|
return
|
|
|
|
threads = exe_ctx.process.threads if options.thread else [
|
|
exe_ctx.thread]
|
|
|
|
for thread in threads:
|
|
for frame in thread:
|
|
res = efi_symbols.address_to_symbols(frame.pc)
|
|
self.lldb_print(res)
|
|
|
|
else:
|
|
if options.address is not None:
|
|
address = options.address
|
|
elif options.pc:
|
|
try:
|
|
address = exe_ctx.thread.GetSelectedFrame().pc
|
|
except ValueError:
|
|
result.SetError("invalid pc")
|
|
return
|
|
else:
|
|
address = 0
|
|
|
|
res = efi_symbols.address_to_symbols(address.pc)
|
|
print(res)
|
|
|
|
if options.extended:
|
|
|
|
gST = exe_ctx.target.FindFirstGlobalVariable('gST')
|
|
if gST.error.fail:
|
|
print('Error: This command requires symbols to be loaded')
|
|
else:
|
|
table = EfiConfigurationTable(file, gST.unsigned)
|
|
for address, _ in table.DebugImageInfo():
|
|
res = efi_symbols.address_to_symbols(address)
|
|
self.lldb_print(res)
|
|
|
|
# keep trying module file names until we find a GUID xref file
|
|
for m in exe_ctx.target.modules:
|
|
if GuidNames.add_build_guid_file(str(m.file)):
|
|
break
|
|
|
|
|
|
def CHAR16_TypeSummary(valobj, internal_dict):
|
|
'''
|
|
Display CHAR16 as a String in the debugger.
|
|
Note: utf-8 is returned as that is the value for the debugger.
|
|
'''
|
|
SBError = lldb.SBError()
|
|
Str = ''
|
|
if valobj.TypeIsPointerType():
|
|
if valobj.GetValueAsUnsigned() == 0:
|
|
return "NULL"
|
|
|
|
# CHAR16 * max string size 1024
|
|
for i in range(1024):
|
|
Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0)
|
|
if SBError.fail or Char == 0:
|
|
break
|
|
Str += chr(Char)
|
|
return 'L"' + Str + '"'
|
|
|
|
if valobj.num_children == 0:
|
|
# CHAR16
|
|
return "L'" + chr(valobj.unsigned) + "'"
|
|
|
|
else:
|
|
# CHAR16 []
|
|
for i in range(valobj.num_children):
|
|
Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0)
|
|
if Char == 0:
|
|
break
|
|
Str += chr(Char)
|
|
return 'L"' + Str + '"'
|
|
|
|
return Str
|
|
|
|
|
|
def CHAR8_TypeSummary(valobj, internal_dict):
|
|
'''
|
|
Display CHAR8 as a String in the debugger.
|
|
Note: utf-8 is returned as that is the value for the debugger.
|
|
'''
|
|
SBError = lldb.SBError()
|
|
Str = ''
|
|
if valobj.TypeIsPointerType():
|
|
if valobj.GetValueAsUnsigned() == 0:
|
|
return "NULL"
|
|
|
|
# CHAR8 * max string size 1024
|
|
for i in range(1024):
|
|
Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0)
|
|
if SBError.fail or Char == 0:
|
|
break
|
|
Str += chr(Char)
|
|
Str = '"' + Str + '"'
|
|
return Str
|
|
|
|
if valobj.num_children == 0:
|
|
# CHAR8
|
|
return "'" + chr(valobj.unsigned) + "'"
|
|
else:
|
|
# CHAR8 []
|
|
for i in range(valobj.num_children):
|
|
Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0)
|
|
if SBError.fail or Char == 0:
|
|
break
|
|
Str += chr(Char)
|
|
return '"' + Str + '"'
|
|
|
|
return Str
|
|
|
|
|
|
def EFI_STATUS_TypeSummary(valobj, internal_dict):
|
|
if valobj.TypeIsPointerType():
|
|
return ''
|
|
return str(EfiStatusClass(valobj.unsigned))
|
|
|
|
|
|
def EFI_TPL_TypeSummary(valobj, internal_dict):
|
|
if valobj.TypeIsPointerType():
|
|
return ''
|
|
return str(EfiTpl(valobj.unsigned))
|
|
|
|
|
|
def EFI_GUID_TypeSummary(valobj, internal_dict):
|
|
if valobj.TypeIsPointerType():
|
|
return ''
|
|
return str(GuidNames(bytes(valobj.data.uint8)))
|
|
|
|
|
|
def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict):
|
|
if valobj.TypeIsPointerType():
|
|
return ''
|
|
'''Return #define name for EFI_BOOT_MODE'''
|
|
return str(EfiBootMode(valobj.unsigned))
|
|
|
|
|
|
def lldb_type_formaters(debugger, mod_name):
|
|
'''Teach lldb about EFI types'''
|
|
|
|
category = debugger.GetDefaultCategory()
|
|
FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool)
|
|
|
|
FormatHex = lldb.SBTypeFormat(lldb.eFormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier(
|
|
"EFI_PHYSICAL_ADDRESS"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier(
|
|
"PHYSICAL_ADDRESS"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex)
|
|
category.AddTypeFormat(
|
|
lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex)
|
|
category.AddTypeFormat(lldb.SBTypeNameSpecifier(
|
|
"EFI_FV_FILETYPE"), FormatHex)
|
|
|
|
#
|
|
# Smart type printing for EFI
|
|
#
|
|
|
|
debugger.HandleCommand(
|
|
f'type summary add GUID - -python-function '
|
|
f'{mod_name}.EFI_GUID_TypeSummary')
|
|
debugger.HandleCommand(
|
|
f'type summary add EFI_GUID --python-function '
|
|
f'{mod_name}.EFI_GUID_TypeSummary')
|
|
debugger.HandleCommand(
|
|
f'type summary add EFI_STATUS --python-function '
|
|
f'{mod_name}.EFI_STATUS_TypeSummary')
|
|
debugger.HandleCommand(
|
|
f'type summary add EFI_TPL - -python-function '
|
|
f'{mod_name}.EFI_TPL_TypeSummary')
|
|
debugger.HandleCommand(
|
|
f'type summary add EFI_BOOT_MODE --python-function '
|
|
f'{mod_name}.EFI_BOOT_MODE_TypeSummary')
|
|
|
|
debugger.HandleCommand(
|
|
f'type summary add CHAR16 --python-function '
|
|
f'{mod_name}.CHAR16_TypeSummary')
|
|
|
|
# W605 this is the correct escape sequence for the lldb command
|
|
debugger.HandleCommand(
|
|
f'type summary add --regex "CHAR16 \[[0-9]+\]" ' # noqa: W605
|
|
f'--python-function {mod_name}.CHAR16_TypeSummary')
|
|
|
|
debugger.HandleCommand(
|
|
f'type summary add CHAR8 --python-function '
|
|
f'{mod_name}.CHAR8_TypeSummary')
|
|
|
|
# W605 this is the correct escape sequence for the lldb command
|
|
debugger.HandleCommand(
|
|
f'type summary add --regex "CHAR8 \[[0-9]+\]" ' # noqa: W605
|
|
f'--python-function {mod_name}.CHAR8_TypeSummary')
|
|
|
|
|
|
class LldbWorkaround:
|
|
needed = True
|
|
|
|
@classmethod
|
|
def activate(cls):
|
|
if cls.needed:
|
|
lldb.debugger.HandleCommand("process handle SIGALRM -n false")
|
|
cls.needed = False
|
|
|
|
|
|
def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict):
|
|
#
|
|
# This is an lldb breakpoint script, and assumes the breakpoint is on a
|
|
# function with the same prototype as SecGdbScriptBreak(). The
|
|
# argument names are important as lldb looks them up.
|
|
#
|
|
# VOID
|
|
# SecGdbScriptBreak (
|
|
# char *FileName,
|
|
# int FileNameLength,
|
|
# long unsigned int LoadAddress,
|
|
# int AddSymbolFlag
|
|
# )
|
|
# {
|
|
# return;
|
|
# }
|
|
#
|
|
# When the emulator loads a PE/COFF image, it calls the stub function with
|
|
# the filename of the symbol file, the length of the FileName, the
|
|
# load address and a flag to indicate if this is a load or unload operation
|
|
#
|
|
LldbWorkaround().activate()
|
|
|
|
symbols = EfiSymbols(frame.thread.process.target)
|
|
LoadAddress = frame.FindVariable("LoadAddress").unsigned
|
|
if frame.FindVariable("AddSymbolFlag").unsigned == 1:
|
|
res = symbols.address_to_symbols(LoadAddress)
|
|
else:
|
|
res = symbols.unload_symbols(LoadAddress)
|
|
print(res)
|
|
|
|
# make breakpoint command continue
|
|
return False
|
|
|
|
|
|
def __lldb_init_module(debugger, internal_dict):
|
|
'''
|
|
This initializer is being run from LLDB in the embedded command interpreter
|
|
'''
|
|
|
|
mod_name = Path(__file__).stem
|
|
lldb_type_formaters(debugger, mod_name)
|
|
|
|
# Add any commands contained in this module to LLDB
|
|
debugger.HandleCommand(
|
|
f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols')
|
|
debugger.HandleCommand(
|
|
f'command script add -c {mod_name}.EfiGuidCommand guid')
|
|
debugger.HandleCommand(
|
|
f'command script add -c {mod_name}.EfiTableCommand table')
|
|
debugger.HandleCommand(
|
|
f'command script add -c {mod_name}.EfiHobCommand hob')
|
|
debugger.HandleCommand(
|
|
f'command script add -c {mod_name}.EfiDevicePathCommand devicepath')
|
|
|
|
print('EFI specific commands have been installed.')
|
|
|
|
# patch the ctypes c_void_p values if the debuggers OS and EFI have
|
|
# different ideas on the size of the debug.
|
|
try:
|
|
patch_ctypes(debugger.GetSelectedTarget().addr_size)
|
|
except ValueError:
|
|
# incase the script is imported and the debugger has not target
|
|
# defaults to sizeof(UINTN) == sizeof(UINT64)
|
|
patch_ctypes()
|
|
|
|
try:
|
|
target = debugger.GetSelectedTarget()
|
|
if target.FindFunctions('SecGdbScriptBreak').symbols:
|
|
breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak')
|
|
# Set the emulator breakpoints, if we are in the emulator
|
|
cmd = 'breakpoint command add -s python -F '
|
|
cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}'
|
|
debugger.HandleCommand(cmd)
|
|
print('Type r to run emulator.')
|
|
else:
|
|
raise ValueError("No Emulator Symbols")
|
|
|
|
except ValueError:
|
|
# default action when the script is imported
|
|
debugger.HandleCommand("efi_symbols --frame --extended")
|
|
debugger.HandleCommand("register read")
|
|
debugger.HandleCommand("bt all")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pass
|