diff options
| author | kx <kx@radix.pro> | 2024-06-04 06:43:08 +0300 |
|---|---|---|
| committer | kx <kx@radix.pro> | 2024-06-04 06:43:08 +0300 |
| commit | 7e2ccccace24636f29ddc210b94606abd4c7e42b (patch) | |
| tree | 545d43ea12671048956cafeb12303e84d601e571 /BaseTools/Scripts/efi_lldb.py | |
| parent | 27b044605cd5f6b33a3d231576003850b3fe305b (diff) | |
| download | edk2-trunk.tar.xz | |
Renormalized end-of-lines from master@27b044605cd5f6b33a3d231576003850b3fe305btrunk
Diffstat (limited to 'BaseTools/Scripts/efi_lldb.py')
| -rwxr-xr-x | BaseTools/Scripts/efi_lldb.py | 2088 |
1 files changed, 1044 insertions, 1044 deletions
diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py index 6487e41bf5..089b6ba58a 100755 --- a/BaseTools/Scripts/efi_lldb.py +++ b/BaseTools/Scripts/efi_lldb.py @@ -1,1044 +1,1044 @@ -#!/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
+#!/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 |
