public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Bob Feng" <bob.c.feng@intel.com>
To: Rebecca Cran <quic_rcran@quicinc.com>,devel@edk2.groups.io
Subject: Re: [edk2-devel] [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print
Date: Fri, 08 Apr 2022 21:16:13 -0700	[thread overview]
Message-ID: <9262.1649477773839547260@groups.io> (raw)
In-Reply-To: <20220321202048.13567-4-quic_rcran@quicinc.com>

[-- Attachment #1: Type: text/plain, Size: 32626 bytes --]

Reviewed-by: Bob Feng <bob.c.feng@intel.com>

On Tue, Mar 22, 2022 at 04:21 AM, Rebecca Cran wrote:

> 
> 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>
> ---
> BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++
> 1 file changed, 1044 insertions(+)
> 
> diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.py
> 
> new file mode 100755
> index 000000000000..089b6ba58ab8
> --- /dev/null
> +++ b/BaseTools/Scripts/efi_lldb.py
> @@ -0,0 +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
> --
> 2.34.1

[-- Attachment #2: Type: text/html, Size: 34958 bytes --]

  reply	other threads:[~2022-04-09  4:16 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-21 20:20 [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
2022-03-21 20:20 ` [PATCH v2 1/3] BaseTools: efi_debugging.py: Add debugger agnostic dbg Python Classes Rebecca Cran
2022-03-26 14:04   ` Bob Feng
2022-03-21 20:20 ` [PATCH v2 2/3] BaseTools: Scripts/efi_gdb.py: Add gdb EFI commands and pretty Print Rebecca Cran
2022-03-26 14:05   ` Bob Feng
2022-03-21 20:20 ` [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Rebecca Cran
2022-04-09  4:16   ` Bob Feng [this message]
2022-04-06 22:34 ` [PATCH v2 0/3] BaseTools: Add support for gdb and lldb Rebecca Cran
2022-04-07  5:02   ` 回复: " gaoliming
2022-04-09  4:47   ` Bob Feng

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9262.1649477773839547260@groups.io \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox