https://bugzilla.tianocore.org/show_bug.cgi?id=3500 Use efi_debugging.py Python Classes to implement EFI gdb commands: efi_symbols, guid, table, hob, and devicepath You can attach to any standard gdb or kdp remote server and get EFI symbols. No modifications of EFI are required. Example usage: OvmfPkg/build.sh qemu -gdb tcp::9000 lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py" Note you may also have to teach lldb about QEMU: -o "settings set plugin.process.gdb-remote.target-definition-file x86_64_target_definition.py" Cc: Leif Lindholm <quic_llindhol@quicinc.com> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com> Reviewed-by: Bob Feng <bob.c.feng@intel.com> Acked-by: Liming Gao <gaoliming@byosoft.com.cn>
		
			
				
	
	
		
			1045 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1045 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/python3
 | 
						|
'''
 | 
						|
Copyright (c) Apple Inc. 2021
 | 
						|
SPDX-License-Identifier: BSD-2-Clause-Patent
 | 
						|
 | 
						|
Example usage:
 | 
						|
OvmfPkg/build.sh qemu -gdb tcp::9000
 | 
						|
lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py"
 | 
						|
'''
 | 
						|
 | 
						|
import optparse
 | 
						|
import shlex
 | 
						|
import subprocess
 | 
						|
import uuid
 | 
						|
import sys
 | 
						|
import os
 | 
						|
from pathlib import Path
 | 
						|
from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl
 | 
						|
from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode
 | 
						|
from efi_debugging import PeTeImage, patch_ctypes
 | 
						|
 | 
						|
try:
 | 
						|
    # Just try for LLDB in case PYTHONPATH is already correctly setup
 | 
						|
    import lldb
 | 
						|
except ImportError:
 | 
						|
    try:
 | 
						|
        env = os.environ.copy()
 | 
						|
        env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
 | 
						|
        lldb_python_path = subprocess.check_output(
 | 
						|
            ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip()
 | 
						|
        sys.path.append(lldb_python_path)
 | 
						|
        import lldb
 | 
						|
    except ValueError:
 | 
						|
        print("Couldn't find LLDB.framework from lldb -P")
 | 
						|
        print("PYTHONPATH should match the currently selected lldb")
 | 
						|
        sys.exit(-1)
 | 
						|
 | 
						|
 | 
						|
class LldbFileObject(object):
 | 
						|
    '''
 | 
						|
    Class that fakes out file object to abstract lldb from the generic code.
 | 
						|
    For lldb this is memory so we don't have a concept of the end of the file.
 | 
						|
    '''
 | 
						|
 | 
						|
    def __init__(self, process):
 | 
						|
        # _exe_ctx is lldb.SBExecutionContext
 | 
						|
        self._process = process
 | 
						|
        self._offset = 0
 | 
						|
        self._SBError = lldb.SBError()
 | 
						|
 | 
						|
    def tell(self):
 | 
						|
        return self._offset
 | 
						|
 | 
						|
    def read(self, size=-1):
 | 
						|
        if size == -1:
 | 
						|
            # arbitrary default size
 | 
						|
            size = 0x1000000
 | 
						|
 | 
						|
        data = self._process.ReadMemory(self._offset, size, self._SBError)
 | 
						|
        if self._SBError.fail:
 | 
						|
            raise MemoryError(
 | 
						|
                f'lldb could not read memory 0x{size:x} '
 | 
						|
                f' bytes from 0x{self._offset:08x}')
 | 
						|
        else:
 | 
						|
            return data
 | 
						|
 | 
						|
    def readable(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def seek(self, offset, whence=0):
 | 
						|
        if whence == 0:
 | 
						|
            self._offset = offset
 | 
						|
        elif whence == 1:
 | 
						|
            self._offset += offset
 | 
						|
        else:
 | 
						|
            # whence == 2 is seek from end
 | 
						|
            raise NotImplementedError
 | 
						|
 | 
						|
    def seekable(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def write(self, data):
 | 
						|
        result = self._process.WriteMemory(self._offset, data, self._SBError)
 | 
						|
        if self._SBError.fail:
 | 
						|
            raise MemoryError(
 | 
						|
                f'lldb could not write memory to 0x{self._offset:08x}')
 | 
						|
        return result
 | 
						|
 | 
						|
    def writable(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def truncate(self, size=None):
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
    def flush(self):
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
    def fileno(self):
 | 
						|
        raise NotImplementedError
 | 
						|
 | 
						|
 | 
						|
class EfiSymbols:
 | 
						|
    """
 | 
						|
    Class to manage EFI Symbols
 | 
						|
    You need to pass file, and exe_ctx to load symbols.
 | 
						|
    You can print(EfiSymbols()) to see the currently loaded symbols
 | 
						|
    """
 | 
						|
 | 
						|
    loaded = {}
 | 
						|
    stride = None
 | 
						|
    range = None
 | 
						|
    verbose = False
 | 
						|
 | 
						|
    def __init__(self, target=None):
 | 
						|
        if target:
 | 
						|
            EfiSymbols.target = target
 | 
						|
            EfiSymbols._file = LldbFileObject(target.process)
 | 
						|
 | 
						|
    @ classmethod
 | 
						|
    def __str__(cls):
 | 
						|
        return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values())
 | 
						|
 | 
						|
    @ classmethod
 | 
						|
    def configure_search(cls, stride, range, verbose=False):
 | 
						|
        cls.stride = stride
 | 
						|
        cls.range = range
 | 
						|
        cls.verbose = verbose
 | 
						|
 | 
						|
    @ classmethod
 | 
						|
    def clear(cls):
 | 
						|
        cls.loaded = {}
 | 
						|
 | 
						|
    @ classmethod
 | 
						|
    def add_symbols_for_pecoff(cls, pecoff):
 | 
						|
        '''Tell lldb the location of the .text and .data sections.'''
 | 
						|
 | 
						|
        if pecoff.LoadAddress in cls.loaded:
 | 
						|
            return 'Already Loaded: '
 | 
						|
 | 
						|
        module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid))
 | 
						|
        if not module:
 | 
						|
            module = cls.target.AddModule(pecoff.CodeViewPdb,
 | 
						|
                                          None,
 | 
						|
                                          str(pecoff.CodeViewUuid))
 | 
						|
        if module.IsValid():
 | 
						|
            SBError = cls.target.SetModuleLoadAddress(
 | 
						|
                module, pecoff.LoadAddress + pecoff.TeAdjust)
 | 
						|
            if SBError.success:
 | 
						|
                cls.loaded[pecoff.LoadAddress] = (pecoff, module)
 | 
						|
                return ''
 | 
						|
 | 
						|
        return 'Symbols NOT FOUND: '
 | 
						|
 | 
						|
    @ classmethod
 | 
						|
    def address_to_symbols(cls, address, reprobe=False):
 | 
						|
        '''
 | 
						|
        Given an address search backwards for a PE/COFF (or TE) header
 | 
						|
        and load symbols. Return a status string.
 | 
						|
        '''
 | 
						|
        if not isinstance(address, int):
 | 
						|
            address = int(address)
 | 
						|
 | 
						|
        pecoff, _ = cls.address_in_loaded_pecoff(address)
 | 
						|
        if not reprobe and pecoff is not None:
 | 
						|
            # skip the probe of the remote
 | 
						|
            return f'{pecoff} is already loaded'
 | 
						|
 | 
						|
        pecoff = PeTeImage(cls._file, None)
 | 
						|
        if pecoff.pcToPeCoff(address, cls.stride, cls.range):
 | 
						|
            res = cls.add_symbols_for_pecoff(pecoff)
 | 
						|
            return f'{res}{pecoff}'
 | 
						|
        else:
 | 
						|
            return f'0x{address:08x} not in a PE/COFF (or TE) image'
 | 
						|
 | 
						|
    @ classmethod
 | 
						|
    def address_in_loaded_pecoff(cls, address):
 | 
						|
        if not isinstance(address, int):
 | 
						|
            address = int(address)
 | 
						|
 | 
						|
        for (pecoff, module) in cls.loaded.values():
 | 
						|
            if (address >= pecoff.LoadAddress and
 | 
						|
                    address <= pecoff.EndLoadAddress):
 | 
						|
 | 
						|
                return pecoff, module
 | 
						|
 | 
						|
        return None, None
 | 
						|
 | 
						|
    @ classmethod
 | 
						|
    def unload_symbols(cls, address):
 | 
						|
        pecoff, module = cls.address_in_loaded_pecoff(address)
 | 
						|
        if module:
 | 
						|
            name = str(module)
 | 
						|
            cls.target.ClearModuleLoadAddress(module)
 | 
						|
            cls.target.RemoveModule(module)
 | 
						|
            del cls.loaded[pecoff.LoadAddress]
 | 
						|
            return f'{name:s} was unloaded'
 | 
						|
        return f'0x{address:x} was not in a loaded image'
 | 
						|
 | 
						|
 | 
						|
def arg_to_address(frame, arg):
 | 
						|
    ''' convert an lldb command arg into a memory address (addr_t)'''
 | 
						|
 | 
						|
    if arg is None:
 | 
						|
        return None
 | 
						|
 | 
						|
    arg_str = arg if isinstance(arg, str) else str(arg)
 | 
						|
    SBValue = frame.EvaluateExpression(arg_str)
 | 
						|
    if SBValue.error.fail:
 | 
						|
        return arg
 | 
						|
 | 
						|
    if (SBValue.TypeIsPointerType() or
 | 
						|
            SBValue.value_type == lldb.eValueTypeRegister or
 | 
						|
            SBValue.value_type == lldb.eValueTypeRegisterSet or
 | 
						|
            SBValue.value_type == lldb.eValueTypeConstResult):
 | 
						|
        try:
 | 
						|
            addr = SBValue.GetValueAsAddress()
 | 
						|
        except ValueError:
 | 
						|
            addr = SBValue.unsigned
 | 
						|
    else:
 | 
						|
        try:
 | 
						|
            addr = SBValue.address_of.GetValueAsAddress()
 | 
						|
        except ValueError:
 | 
						|
            addr = SBValue.address_of.unsigned
 | 
						|
 | 
						|
    return addr
 | 
						|
 | 
						|
 | 
						|
def arg_to_data(frame, arg):
 | 
						|
    ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)'''
 | 
						|
    if not isinstance(arg, str):
 | 
						|
        arg_str = str(str)
 | 
						|
 | 
						|
    SBValue = frame.EvaluateExpression(arg_str)
 | 
						|
    return SBValue.unsigned
 | 
						|
 | 
						|
 | 
						|
class EfiDevicePathCommand:
 | 
						|
 | 
						|
    def create_options(self):
 | 
						|
        ''' standard lldb command help/options parser'''
 | 
						|
        usage = "usage: %prog [options]"
 | 
						|
        description = '''Command that can EFI Config Tables
 | 
						|
'''
 | 
						|
 | 
						|
        # Pass add_help_option = False, since this keeps the command in line
 | 
						|
        # with lldb commands, and we wire up "help command" to work by
 | 
						|
        # providing the long & short help methods below.
 | 
						|
        self.parser = optparse.OptionParser(
 | 
						|
            description=description,
 | 
						|
            prog='devicepath',
 | 
						|
            usage=usage,
 | 
						|
            add_help_option=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-v',
 | 
						|
            '--verbose',
 | 
						|
            action='store_true',
 | 
						|
            dest='verbose',
 | 
						|
            help='hex dump extra data',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-n',
 | 
						|
            '--node',
 | 
						|
            action='store_true',
 | 
						|
            dest='node',
 | 
						|
            help='dump a single device path node',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-h',
 | 
						|
            '--help',
 | 
						|
            action='store_true',
 | 
						|
            dest='help',
 | 
						|
            help='Show help for the command',
 | 
						|
            default=False)
 | 
						|
 | 
						|
    def get_short_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return "Display EFI Tables"
 | 
						|
 | 
						|
    def get_long_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return self.help_string
 | 
						|
 | 
						|
    def __init__(self, debugger, internal_dict):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        self.create_options()
 | 
						|
        self.help_string = self.parser.format_help()
 | 
						|
 | 
						|
    def __call__(self, debugger, command, exe_ctx, result):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        # Use the Shell Lexer to properly parse up command options just like a
 | 
						|
        # shell would
 | 
						|
        command_args = shlex.split(command)
 | 
						|
 | 
						|
        try:
 | 
						|
            (options, args) = self.parser.parse_args(command_args)
 | 
						|
            dev_list = []
 | 
						|
            for arg in args:
 | 
						|
                dev_list.append(arg_to_address(exe_ctx.frame, arg))
 | 
						|
        except ValueError:
 | 
						|
            # if you don't handle exceptions, passing an incorrect argument
 | 
						|
            # to the OptionParser will cause LLDB to exit (courtesy of
 | 
						|
            # OptParse dealing with argument errors by throwing SystemExit)
 | 
						|
            result.SetError("option parsing failed")
 | 
						|
            return
 | 
						|
 | 
						|
        if options.help:
 | 
						|
            self.parser.print_help()
 | 
						|
            return
 | 
						|
 | 
						|
        file = LldbFileObject(exe_ctx.process)
 | 
						|
 | 
						|
        for dev_addr in dev_list:
 | 
						|
            if options.node:
 | 
						|
                print(EfiDevicePath(file).device_path_node_str(
 | 
						|
                    dev_addr, options.verbose))
 | 
						|
            else:
 | 
						|
                device_path = EfiDevicePath(file, dev_addr, options.verbose)
 | 
						|
                if device_path.valid():
 | 
						|
                    print(device_path)
 | 
						|
 | 
						|
 | 
						|
class EfiHobCommand:
 | 
						|
    def create_options(self):
 | 
						|
        ''' standard lldb command help/options parser'''
 | 
						|
        usage = "usage: %prog [options]"
 | 
						|
        description = '''Command that can EFI dump EFI HOBs'''
 | 
						|
 | 
						|
        # Pass add_help_option = False, since this keeps the command in line
 | 
						|
        # with lldb commands, and we wire up "help command" to work by
 | 
						|
        # providing the long & short help methods below.
 | 
						|
        self.parser = optparse.OptionParser(
 | 
						|
            description=description,
 | 
						|
            prog='table',
 | 
						|
            usage=usage,
 | 
						|
            add_help_option=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-a',
 | 
						|
            '--address',
 | 
						|
            type="int",
 | 
						|
            dest='address',
 | 
						|
            help='Parse HOBs from address',
 | 
						|
            default=None)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-t',
 | 
						|
            '--type',
 | 
						|
            type="int",
 | 
						|
            dest='type',
 | 
						|
            help='Only dump HOBS of his type',
 | 
						|
            default=None)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-v',
 | 
						|
            '--verbose',
 | 
						|
            action='store_true',
 | 
						|
            dest='verbose',
 | 
						|
            help='hex dump extra data',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-h',
 | 
						|
            '--help',
 | 
						|
            action='store_true',
 | 
						|
            dest='help',
 | 
						|
            help='Show help for the command',
 | 
						|
            default=False)
 | 
						|
 | 
						|
    def get_short_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return "Display EFI Hobs"
 | 
						|
 | 
						|
    def get_long_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return self.help_string
 | 
						|
 | 
						|
    def __init__(self, debugger, internal_dict):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        self.create_options()
 | 
						|
        self.help_string = self.parser.format_help()
 | 
						|
 | 
						|
    def __call__(self, debugger, command, exe_ctx, result):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        # Use the Shell Lexer to properly parse up command options just like a
 | 
						|
        # shell would
 | 
						|
        command_args = shlex.split(command)
 | 
						|
 | 
						|
        try:
 | 
						|
            (options, _) = self.parser.parse_args(command_args)
 | 
						|
        except ValueError:
 | 
						|
            # if you don't handle exceptions, passing an incorrect argument
 | 
						|
            # to the OptionParser will cause LLDB to exit (courtesy of
 | 
						|
            # OptParse dealing with argument errors by throwing SystemExit)
 | 
						|
            result.SetError("option parsing failed")
 | 
						|
            return
 | 
						|
 | 
						|
        if options.help:
 | 
						|
            self.parser.print_help()
 | 
						|
            return
 | 
						|
 | 
						|
        address = arg_to_address(exe_ctx.frame, options.address)
 | 
						|
 | 
						|
        file = LldbFileObject(exe_ctx.process)
 | 
						|
        hob = EfiHob(file, address, options.verbose).get_hob_by_type(
 | 
						|
            options.type)
 | 
						|
        print(hob)
 | 
						|
 | 
						|
 | 
						|
class EfiTableCommand:
 | 
						|
 | 
						|
    def create_options(self):
 | 
						|
        ''' standard lldb command help/options parser'''
 | 
						|
        usage = "usage: %prog [options]"
 | 
						|
        description = '''Command that can display EFI Config Tables
 | 
						|
'''
 | 
						|
 | 
						|
        # Pass add_help_option = False, since this keeps the command in line
 | 
						|
        # with lldb commands, and we wire up "help command" to work by
 | 
						|
        # providing the long & short help methods below.
 | 
						|
        self.parser = optparse.OptionParser(
 | 
						|
            description=description,
 | 
						|
            prog='table',
 | 
						|
            usage=usage,
 | 
						|
            add_help_option=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-h',
 | 
						|
            '--help',
 | 
						|
            action='store_true',
 | 
						|
            dest='help',
 | 
						|
            help='Show help for the command',
 | 
						|
            default=False)
 | 
						|
 | 
						|
    def get_short_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return "Display EFI Tables"
 | 
						|
 | 
						|
    def get_long_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return self.help_string
 | 
						|
 | 
						|
    def __init__(self, debugger, internal_dict):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        self.create_options()
 | 
						|
        self.help_string = self.parser.format_help()
 | 
						|
 | 
						|
    def __call__(self, debugger, command, exe_ctx, result):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        # Use the Shell Lexer to properly parse up command options just like a
 | 
						|
        # shell would
 | 
						|
        command_args = shlex.split(command)
 | 
						|
 | 
						|
        try:
 | 
						|
            (options, _) = self.parser.parse_args(command_args)
 | 
						|
        except ValueError:
 | 
						|
            # if you don't handle exceptions, passing an incorrect argument
 | 
						|
            # to the OptionParser will cause LLDB to exit (courtesy of
 | 
						|
            # OptParse dealing with argument errors by throwing SystemExit)
 | 
						|
            result.SetError("option parsing failed")
 | 
						|
            return
 | 
						|
 | 
						|
        if options.help:
 | 
						|
            self.parser.print_help()
 | 
						|
            return
 | 
						|
 | 
						|
        gST = exe_ctx.target.FindFirstGlobalVariable('gST')
 | 
						|
        if gST.error.fail:
 | 
						|
            print('Error: This command requires symbols for gST to be loaded')
 | 
						|
            return
 | 
						|
 | 
						|
        file = LldbFileObject(exe_ctx.process)
 | 
						|
        table = EfiConfigurationTable(file, gST.unsigned)
 | 
						|
        if table:
 | 
						|
            print(table, '\n')
 | 
						|
 | 
						|
 | 
						|
class EfiGuidCommand:
 | 
						|
 | 
						|
    def create_options(self):
 | 
						|
        ''' standard lldb command help/options parser'''
 | 
						|
        usage = "usage: %prog [options]"
 | 
						|
        description = '''
 | 
						|
            Command that can display all EFI GUID's or give info on a
 | 
						|
            specific GUID's
 | 
						|
            '''
 | 
						|
        self.parser = optparse.OptionParser(
 | 
						|
            description=description,
 | 
						|
            prog='guid',
 | 
						|
            usage=usage,
 | 
						|
            add_help_option=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-n',
 | 
						|
            '--new',
 | 
						|
            action='store_true',
 | 
						|
            dest='new',
 | 
						|
            help='Generate a new GUID',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-v',
 | 
						|
            '--verbose',
 | 
						|
            action='store_true',
 | 
						|
            dest='verbose',
 | 
						|
            help='Also display GUID C structure values',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-h',
 | 
						|
            '--help',
 | 
						|
            action='store_true',
 | 
						|
            dest='help',
 | 
						|
            help='Show help for the command',
 | 
						|
            default=False)
 | 
						|
 | 
						|
    def get_short_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return "Display EFI GUID's"
 | 
						|
 | 
						|
    def get_long_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return self.help_string
 | 
						|
 | 
						|
    def __init__(self, debugger, internal_dict):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        self.create_options()
 | 
						|
        self.help_string = self.parser.format_help()
 | 
						|
 | 
						|
    def __call__(self, debugger, command, exe_ctx, result):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        # Use the Shell Lexer to properly parse up command options just like a
 | 
						|
        # shell would
 | 
						|
        command_args = shlex.split(command)
 | 
						|
 | 
						|
        try:
 | 
						|
            (options, args) = self.parser.parse_args(command_args)
 | 
						|
            if len(args) >= 1:
 | 
						|
                # guid { 0x414e6bdd, 0xe47b, 0x47cc,
 | 
						|
                #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
 | 
						|
                # this generates multiple args
 | 
						|
                arg = ' '.join(args)
 | 
						|
        except ValueError:
 | 
						|
            # if you don't handle exceptions, passing an incorrect argument
 | 
						|
            # to the OptionParser will cause LLDB to exit (courtesy of
 | 
						|
            # OptParse dealing with argument errors by throwing SystemExit)
 | 
						|
            result.SetError("option parsing failed")
 | 
						|
            return
 | 
						|
 | 
						|
        if options.help:
 | 
						|
            self.parser.print_help()
 | 
						|
            return
 | 
						|
 | 
						|
        if options.new:
 | 
						|
            guid = uuid.uuid4()
 | 
						|
            print(str(guid).upper())
 | 
						|
            print(GuidNames.to_c_guid(guid))
 | 
						|
            return
 | 
						|
 | 
						|
        if len(args) > 0:
 | 
						|
            if GuidNames.is_guid_str(arg):
 | 
						|
                # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9
 | 
						|
                key = arg.lower()
 | 
						|
                name = GuidNames.to_name(key)
 | 
						|
            elif GuidNames.is_c_guid(arg):
 | 
						|
                # guid { 0x414e6bdd, 0xe47b, 0x47cc,
 | 
						|
                #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
 | 
						|
                key = GuidNames.from_c_guid(arg)
 | 
						|
                name = GuidNames.to_name(key)
 | 
						|
            else:
 | 
						|
                # guid gEfiDxeServicesTableGuid
 | 
						|
                name = arg
 | 
						|
                try:
 | 
						|
                    key = GuidNames.to_guid(name)
 | 
						|
                    name = GuidNames.to_name(key)
 | 
						|
                except ValueError:
 | 
						|
                    return
 | 
						|
 | 
						|
            extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else ''
 | 
						|
            print(f'{key}: {extra}{name}')
 | 
						|
 | 
						|
        else:
 | 
						|
            for key, value in GuidNames._dict_.items():
 | 
						|
                if options.verbose:
 | 
						|
                    extra = f'{GuidNames.to_c_guid(key)}: '
 | 
						|
                else:
 | 
						|
                    extra = ''
 | 
						|
                print(f'{key}: {extra}{value}')
 | 
						|
 | 
						|
 | 
						|
class EfiSymbolicateCommand(object):
 | 
						|
    '''Class to abstract an lldb command'''
 | 
						|
 | 
						|
    def create_options(self):
 | 
						|
        ''' standard lldb command help/options parser'''
 | 
						|
        usage = "usage: %prog [options]"
 | 
						|
        description = '''Command that can load EFI PE/COFF and TE image
 | 
						|
        symbols. If you are having trouble in PEI try adding --pei.
 | 
						|
        '''
 | 
						|
 | 
						|
        # Pass add_help_option = False, since this keeps the command in line
 | 
						|
        # with lldb commands, and we wire up "help command" to work by
 | 
						|
        # providing the long & short help methods below.
 | 
						|
        self.parser = optparse.OptionParser(
 | 
						|
            description=description,
 | 
						|
            prog='efi_symbols',
 | 
						|
            usage=usage,
 | 
						|
            add_help_option=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-a',
 | 
						|
            '--address',
 | 
						|
            type="int",
 | 
						|
            dest='address',
 | 
						|
            help='Load symbols for image at address',
 | 
						|
            default=None)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-f',
 | 
						|
            '--frame',
 | 
						|
            action='store_true',
 | 
						|
            dest='frame',
 | 
						|
            help='Load symbols for current stack frame',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-p',
 | 
						|
            '--pc',
 | 
						|
            action='store_true',
 | 
						|
            dest='pc',
 | 
						|
            help='Load symbols for pc',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '--pei',
 | 
						|
            action='store_true',
 | 
						|
            dest='pei',
 | 
						|
            help='Load symbols for PEI (searches every 4 bytes)',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-e',
 | 
						|
            '--extended',
 | 
						|
            action='store_true',
 | 
						|
            dest='extended',
 | 
						|
            help='Try to load all symbols based on config tables.',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-r',
 | 
						|
            '--range',
 | 
						|
            type="long",
 | 
						|
            dest='range',
 | 
						|
            help='How far to search backward for start of PE/COFF Image',
 | 
						|
            default=None)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-s',
 | 
						|
            '--stride',
 | 
						|
            type="long",
 | 
						|
            dest='stride',
 | 
						|
            help='Boundary to search for PE/COFF header',
 | 
						|
            default=None)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-t',
 | 
						|
            '--thread',
 | 
						|
            action='store_true',
 | 
						|
            dest='thread',
 | 
						|
            help='Load symbols for the frames of all threads',
 | 
						|
            default=False)
 | 
						|
 | 
						|
        self.parser.add_option(
 | 
						|
            '-h',
 | 
						|
            '--help',
 | 
						|
            action='store_true',
 | 
						|
            dest='help',
 | 
						|
            help='Show help for the command',
 | 
						|
            default=False)
 | 
						|
 | 
						|
    def get_short_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return (
 | 
						|
            "Load symbols based on an address that is part of"
 | 
						|
            " a PE/COFF EFI image.")
 | 
						|
 | 
						|
    def get_long_help(self):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        return self.help_string
 | 
						|
 | 
						|
    def __init__(self, debugger, unused):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        self.create_options()
 | 
						|
        self.help_string = self.parser.format_help()
 | 
						|
 | 
						|
    def lldb_print(self, lldb_str):
 | 
						|
        # capture command out like an lldb command
 | 
						|
        self.result.PutCString(lldb_str)
 | 
						|
        # flush the output right away
 | 
						|
        self.result.SetImmediateOutputFile(
 | 
						|
            self.exe_ctx.target.debugger.GetOutputFile())
 | 
						|
 | 
						|
    def __call__(self, debugger, command, exe_ctx, result):
 | 
						|
        '''standard lldb function method'''
 | 
						|
        # Use the Shell Lexer to properly parse up command options just like a
 | 
						|
        # shell would
 | 
						|
        command_args = shlex.split(command)
 | 
						|
 | 
						|
        try:
 | 
						|
            (options, _) = self.parser.parse_args(command_args)
 | 
						|
        except ValueError:
 | 
						|
            # if you don't handle exceptions, passing an incorrect argument
 | 
						|
            # to the OptionParser will cause LLDB to exit (courtesy of
 | 
						|
            # OptParse dealing with argument errors by throwing SystemExit)
 | 
						|
            result.SetError("option parsing failed")
 | 
						|
            return
 | 
						|
 | 
						|
        if options.help:
 | 
						|
            self.parser.print_help()
 | 
						|
            return
 | 
						|
 | 
						|
        file = LldbFileObject(exe_ctx.process)
 | 
						|
        efi_symbols = EfiSymbols(exe_ctx.target)
 | 
						|
        self.result = result
 | 
						|
        self.exe_ctx = exe_ctx
 | 
						|
 | 
						|
        if options.pei:
 | 
						|
            # XIP code ends up on a 4 byte boundary.
 | 
						|
            options.stride = 4
 | 
						|
            options.range = 0x100000
 | 
						|
        efi_symbols.configure_search(options.stride, options.range)
 | 
						|
 | 
						|
        if not options.pc and options.address is None:
 | 
						|
            # default to
 | 
						|
            options.frame = True
 | 
						|
 | 
						|
        if options.frame:
 | 
						|
            if not exe_ctx.frame.IsValid():
 | 
						|
                result.SetError("invalid frame")
 | 
						|
                return
 | 
						|
 | 
						|
            threads = exe_ctx.process.threads if options.thread else [
 | 
						|
                exe_ctx.thread]
 | 
						|
 | 
						|
            for thread in threads:
 | 
						|
                for frame in thread:
 | 
						|
                    res = efi_symbols.address_to_symbols(frame.pc)
 | 
						|
                    self.lldb_print(res)
 | 
						|
 | 
						|
        else:
 | 
						|
            if options.address is not None:
 | 
						|
                address = options.address
 | 
						|
            elif options.pc:
 | 
						|
                try:
 | 
						|
                    address = exe_ctx.thread.GetSelectedFrame().pc
 | 
						|
                except ValueError:
 | 
						|
                    result.SetError("invalid pc")
 | 
						|
                    return
 | 
						|
            else:
 | 
						|
                address = 0
 | 
						|
 | 
						|
            res = efi_symbols.address_to_symbols(address.pc)
 | 
						|
            print(res)
 | 
						|
 | 
						|
        if options.extended:
 | 
						|
 | 
						|
            gST = exe_ctx.target.FindFirstGlobalVariable('gST')
 | 
						|
            if gST.error.fail:
 | 
						|
                print('Error: This command requires symbols to be loaded')
 | 
						|
            else:
 | 
						|
                table = EfiConfigurationTable(file, gST.unsigned)
 | 
						|
                for address, _ in table.DebugImageInfo():
 | 
						|
                    res = efi_symbols.address_to_symbols(address)
 | 
						|
                    self.lldb_print(res)
 | 
						|
 | 
						|
        # keep trying module file names until we find a GUID xref file
 | 
						|
        for m in exe_ctx.target.modules:
 | 
						|
            if GuidNames.add_build_guid_file(str(m.file)):
 | 
						|
                break
 | 
						|
 | 
						|
 | 
						|
def CHAR16_TypeSummary(valobj, internal_dict):
 | 
						|
    '''
 | 
						|
    Display CHAR16 as a String in the debugger.
 | 
						|
    Note: utf-8 is returned as that is the value for the debugger.
 | 
						|
    '''
 | 
						|
    SBError = lldb.SBError()
 | 
						|
    Str = ''
 | 
						|
    if valobj.TypeIsPointerType():
 | 
						|
        if valobj.GetValueAsUnsigned() == 0:
 | 
						|
            return "NULL"
 | 
						|
 | 
						|
        # CHAR16 *   max string size 1024
 | 
						|
        for i in range(1024):
 | 
						|
            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0)
 | 
						|
            if SBError.fail or Char == 0:
 | 
						|
                break
 | 
						|
            Str += chr(Char)
 | 
						|
        return 'L"' + Str + '"'
 | 
						|
 | 
						|
    if valobj.num_children == 0:
 | 
						|
        # CHAR16
 | 
						|
        return "L'" + chr(valobj.unsigned) + "'"
 | 
						|
 | 
						|
    else:
 | 
						|
        # CHAR16 []
 | 
						|
        for i in range(valobj.num_children):
 | 
						|
            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0)
 | 
						|
            if Char == 0:
 | 
						|
                break
 | 
						|
            Str += chr(Char)
 | 
						|
        return 'L"' + Str + '"'
 | 
						|
 | 
						|
    return Str
 | 
						|
 | 
						|
 | 
						|
def CHAR8_TypeSummary(valobj, internal_dict):
 | 
						|
    '''
 | 
						|
    Display CHAR8 as a String in the debugger.
 | 
						|
    Note: utf-8 is returned as that is the value for the debugger.
 | 
						|
    '''
 | 
						|
    SBError = lldb.SBError()
 | 
						|
    Str = ''
 | 
						|
    if valobj.TypeIsPointerType():
 | 
						|
        if valobj.GetValueAsUnsigned() == 0:
 | 
						|
            return "NULL"
 | 
						|
 | 
						|
        # CHAR8 *   max string size 1024
 | 
						|
        for i in range(1024):
 | 
						|
            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0)
 | 
						|
            if SBError.fail or Char == 0:
 | 
						|
                break
 | 
						|
            Str += chr(Char)
 | 
						|
        Str = '"' + Str + '"'
 | 
						|
        return Str
 | 
						|
 | 
						|
    if valobj.num_children == 0:
 | 
						|
        # CHAR8
 | 
						|
        return "'" + chr(valobj.unsigned) + "'"
 | 
						|
    else:
 | 
						|
        # CHAR8 []
 | 
						|
        for i in range(valobj.num_children):
 | 
						|
            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0)
 | 
						|
            if SBError.fail or Char == 0:
 | 
						|
                break
 | 
						|
            Str += chr(Char)
 | 
						|
        return '"' + Str + '"'
 | 
						|
 | 
						|
    return Str
 | 
						|
 | 
						|
 | 
						|
def EFI_STATUS_TypeSummary(valobj, internal_dict):
 | 
						|
    if valobj.TypeIsPointerType():
 | 
						|
        return ''
 | 
						|
    return str(EfiStatusClass(valobj.unsigned))
 | 
						|
 | 
						|
 | 
						|
def EFI_TPL_TypeSummary(valobj, internal_dict):
 | 
						|
    if valobj.TypeIsPointerType():
 | 
						|
        return ''
 | 
						|
    return str(EfiTpl(valobj.unsigned))
 | 
						|
 | 
						|
 | 
						|
def EFI_GUID_TypeSummary(valobj, internal_dict):
 | 
						|
    if valobj.TypeIsPointerType():
 | 
						|
        return ''
 | 
						|
    return str(GuidNames(bytes(valobj.data.uint8)))
 | 
						|
 | 
						|
 | 
						|
def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict):
 | 
						|
    if valobj.TypeIsPointerType():
 | 
						|
        return ''
 | 
						|
    '''Return #define name for EFI_BOOT_MODE'''
 | 
						|
    return str(EfiBootMode(valobj.unsigned))
 | 
						|
 | 
						|
 | 
						|
def lldb_type_formaters(debugger, mod_name):
 | 
						|
    '''Teach lldb about EFI types'''
 | 
						|
 | 
						|
    category = debugger.GetDefaultCategory()
 | 
						|
    FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool)
 | 
						|
 | 
						|
    FormatHex = lldb.SBTypeFormat(lldb.eFormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
 | 
						|
        "EFI_PHYSICAL_ADDRESS"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
 | 
						|
        "PHYSICAL_ADDRESS"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex)
 | 
						|
    category.AddTypeFormat(
 | 
						|
        lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex)
 | 
						|
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
 | 
						|
        "EFI_FV_FILETYPE"), FormatHex)
 | 
						|
 | 
						|
    #
 | 
						|
    # Smart type printing for EFI
 | 
						|
    #
 | 
						|
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add GUID - -python-function '
 | 
						|
        f'{mod_name}.EFI_GUID_TypeSummary')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add EFI_GUID --python-function '
 | 
						|
        f'{mod_name}.EFI_GUID_TypeSummary')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add EFI_STATUS --python-function '
 | 
						|
        f'{mod_name}.EFI_STATUS_TypeSummary')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add EFI_TPL - -python-function '
 | 
						|
        f'{mod_name}.EFI_TPL_TypeSummary')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add EFI_BOOT_MODE --python-function '
 | 
						|
        f'{mod_name}.EFI_BOOT_MODE_TypeSummary')
 | 
						|
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add CHAR16 --python-function '
 | 
						|
        f'{mod_name}.CHAR16_TypeSummary')
 | 
						|
 | 
						|
    # W605 this is the correct escape sequence for the lldb command
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add --regex "CHAR16 \[[0-9]+\]" '  # noqa: W605
 | 
						|
        f'--python-function {mod_name}.CHAR16_TypeSummary')
 | 
						|
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add CHAR8 --python-function '
 | 
						|
        f'{mod_name}.CHAR8_TypeSummary')
 | 
						|
 | 
						|
    # W605 this is the correct escape sequence for the lldb command
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'type summary add --regex "CHAR8 \[[0-9]+\]"  '  # noqa: W605
 | 
						|
        f'--python-function {mod_name}.CHAR8_TypeSummary')
 | 
						|
 | 
						|
 | 
						|
class LldbWorkaround:
 | 
						|
    needed = True
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def activate(cls):
 | 
						|
        if cls.needed:
 | 
						|
            lldb.debugger.HandleCommand("process handle SIGALRM -n false")
 | 
						|
            cls.needed = False
 | 
						|
 | 
						|
 | 
						|
def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict):
 | 
						|
    #
 | 
						|
    # This is an lldb breakpoint script, and assumes the breakpoint is on a
 | 
						|
    # function with the same prototype as SecGdbScriptBreak(). The
 | 
						|
    # argument names are important as lldb looks them up.
 | 
						|
    #
 | 
						|
    # VOID
 | 
						|
    # SecGdbScriptBreak (
 | 
						|
    #   char                *FileName,
 | 
						|
    #   int                 FileNameLength,
 | 
						|
    #   long unsigned int   LoadAddress,
 | 
						|
    #   int                 AddSymbolFlag
 | 
						|
    #   )
 | 
						|
    # {
 | 
						|
    #   return;
 | 
						|
    # }
 | 
						|
    #
 | 
						|
    # When the emulator loads a PE/COFF image, it calls the stub function with
 | 
						|
    # the filename of the symbol file, the length of the FileName, the
 | 
						|
    # load address and a flag to indicate if this is a load or unload operation
 | 
						|
    #
 | 
						|
    LldbWorkaround().activate()
 | 
						|
 | 
						|
    symbols = EfiSymbols(frame.thread.process.target)
 | 
						|
    LoadAddress = frame.FindVariable("LoadAddress").unsigned
 | 
						|
    if frame.FindVariable("AddSymbolFlag").unsigned == 1:
 | 
						|
        res = symbols.address_to_symbols(LoadAddress)
 | 
						|
    else:
 | 
						|
        res = symbols.unload_symbols(LoadAddress)
 | 
						|
    print(res)
 | 
						|
 | 
						|
    # make breakpoint command continue
 | 
						|
    return False
 | 
						|
 | 
						|
 | 
						|
def __lldb_init_module(debugger, internal_dict):
 | 
						|
    '''
 | 
						|
    This initializer is being run from LLDB in the embedded command interpreter
 | 
						|
    '''
 | 
						|
 | 
						|
    mod_name = Path(__file__).stem
 | 
						|
    lldb_type_formaters(debugger, mod_name)
 | 
						|
 | 
						|
    # Add any commands contained in this module to LLDB
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'command script add -c {mod_name}.EfiGuidCommand guid')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'command script add -c {mod_name}.EfiTableCommand table')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'command script add -c {mod_name}.EfiHobCommand hob')
 | 
						|
    debugger.HandleCommand(
 | 
						|
        f'command script add -c {mod_name}.EfiDevicePathCommand devicepath')
 | 
						|
 | 
						|
    print('EFI specific commands have been installed.')
 | 
						|
 | 
						|
    # patch the ctypes c_void_p values if the debuggers OS and EFI have
 | 
						|
    # different ideas on the size of the debug.
 | 
						|
    try:
 | 
						|
        patch_ctypes(debugger.GetSelectedTarget().addr_size)
 | 
						|
    except ValueError:
 | 
						|
        # incase the script is imported and the debugger has not target
 | 
						|
        # defaults to sizeof(UINTN) == sizeof(UINT64)
 | 
						|
        patch_ctypes()
 | 
						|
 | 
						|
    try:
 | 
						|
        target = debugger.GetSelectedTarget()
 | 
						|
        if target.FindFunctions('SecGdbScriptBreak').symbols:
 | 
						|
            breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak')
 | 
						|
            # Set the emulator breakpoints, if we are in the emulator
 | 
						|
            cmd = 'breakpoint command add -s python -F '
 | 
						|
            cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}'
 | 
						|
            debugger.HandleCommand(cmd)
 | 
						|
            print('Type r to run emulator.')
 | 
						|
        else:
 | 
						|
            raise ValueError("No Emulator Symbols")
 | 
						|
 | 
						|
    except ValueError:
 | 
						|
        # default action when the script is imported
 | 
						|
        debugger.HandleCommand("efi_symbols --frame --extended")
 | 
						|
        debugger.HandleCommand("register read")
 | 
						|
        debugger.HandleCommand("bt all")
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    pass
 |