#!/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 |