blob: 6487e41bf50caea6e53159a796d71eac52225987 [file] [log] [blame]
#!/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