From mboxrd@z Thu Jan 1 00:00:00 1970 Subject: Re: [edk2-devel] [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print To: Rebecca Cran ,devel@edk2.groups.io From: "Bob Feng" X-Originating-Location: California, US (192.55.54.51) X-Originating-Platform: Windows Chrome 100 User-Agent: GROUPS.IO Web Poster MIME-Version: 1.0 Date: Fri, 08 Apr 2022 21:16:13 -0700 References: <20220321202048.13567-4-quic_rcran@quicinc.com> In-Reply-To: <20220321202048.13567-4-quic_rcran@quicinc.com> Message-ID: <9262.1649477773839547260@groups.io> Content-Type: multipart/alternative; boundary="yaRYGYBt0xwdomHI389E" --yaRYGYBt0xwdomHI389E Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Reviewed-by: Bob Feng On Tue, Mar 22, 2022 at 04:21 AM, Rebecca Cran wrote: >=20 > https://bugzilla.tianocore.org/show_bug.cgi?id=3D3500 >=20 > Use efi_debugging.py Python Classes to implement EFI gdb commands: > efi_symbols, guid, table, hob, and devicepath >=20 > You can attach to any standard gdb or kdp remote server and get EFI > symbols. No modifications of EFI are required. >=20 > Example usage: > OvmfPkg/build.sh qemu -gdb tcp::9000 > lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py= " >=20 > 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" >=20 > Cc: Leif Lindholm > Cc: Michael D Kinney > Cc: Hao A Wu > Cc: Bob Feng > Cc: Liming Gao > Cc: Yuwei Chen > Signed-off-by: Rebecca Cran > --- > BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++ > 1 file changed, 1044 insertions(+) >=20 > diff --git a/BaseTools/Scripts/efi_lldb.py b/BaseTools/Scripts/efi_lldb.p= y >=20 > 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 =3D os.environ.copy() > + env['LLDB_DEFAULT_PYTHON_VERSION'] =3D str(sys.version_info.major) > + lldb_python_path =3D subprocess.check_output( > + ["xcrun", "lldb", "-P"], env=3Denv).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= . >=20 > + 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 =3D process > + self._offset =3D 0 > + self._SBError =3D lldb.SBError() > + > + def tell(self): > + return self._offset > + > + def read(self, size=3D-1): > + if size =3D=3D -1: > + # arbitrary default size > + size =3D 0x1000000 > + > + data =3D 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=3D0): > + if whence =3D=3D 0: > + self._offset =3D offset > + elif whence =3D=3D 1: > + self._offset +=3D offset > + else: > + # whence =3D=3D 2 is seek from end > + raise NotImplementedError > + > + def seekable(self): > + return True > + > + def write(self, data): > + result =3D 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=3DNone): > + 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 =3D {} > + stride =3D None > + range =3D None > + verbose =3D False > + > + def __init__(self, target=3DNone): > + if target: > + EfiSymbols.target =3D target > + EfiSymbols._file =3D 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=3DFalse): > + cls.stride =3D stride > + cls.range =3D range > + cls.verbose =3D verbose > + > + @ classmethod > + def clear(cls): > + cls.loaded =3D {} > + > + @ 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 =3D cls.target.AddModule(None, None, str(pecoff.CodeViewUuid)) > + if not module: > + module =3D cls.target.AddModule(pecoff.CodeViewPdb, > + None, > + str(pecoff.CodeViewUuid)) > + if module.IsValid(): > + SBError =3D cls.target.SetModuleLoadAddress( > + module, pecoff.LoadAddress + pecoff.TeAdjust) > + if SBError.success: > + cls.loaded[pecoff.LoadAddress] =3D (pecoff, module) > + return '' > + > + return 'Symbols NOT FOUND: ' > + > + @ classmethod > + def address_to_symbols(cls, address, reprobe=3DFalse): > + ''' > + 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 =3D int(address) > + > + pecoff, _ =3D 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 =3D PeTeImage(cls._file, None) > + if pecoff.pcToPeCoff(address, cls.stride, cls.range): > + res =3D 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 =3D int(address) > + > + for (pecoff, module) in cls.loaded.values(): > + if (address >=3D pecoff.LoadAddress and > + address <=3D pecoff.EndLoadAddress): > + > + return pecoff, module > + > + return None, None > + > + @ classmethod > + def unload_symbols(cls, address): > + pecoff, module =3D cls.address_in_loaded_pecoff(address) > + if module: > + name =3D 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 =3D arg if isinstance(arg, str) else str(arg) > + SBValue =3D frame.EvaluateExpression(arg_str) > + if SBValue.error.fail: > + return arg > + > + if (SBValue.TypeIsPointerType() or > + SBValue.value_type =3D=3D lldb.eValueTypeRegister or > + SBValue.value_type =3D=3D lldb.eValueTypeRegisterSet or > + SBValue.value_type =3D=3D lldb.eValueTypeConstResult): > + try: > + addr =3D SBValue.GetValueAsAddress() > + except ValueError: > + addr =3D SBValue.unsigned > + else: > + try: > + addr =3D SBValue.address_of.GetValueAsAddress() > + except ValueError: > + addr =3D 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 =3D str(str) > + > + SBValue =3D frame.EvaluateExpression(arg_str) > + return SBValue.unsigned > + > + > +class EfiDevicePathCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage =3D "usage: %prog [options]" > + description =3D '''Command that can EFI Config Tables > +''' > + > + # Pass add_help_option =3D 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 =3D optparse.OptionParser( > + description=3Ddescription, > + prog=3D'devicepath', > + usage=3Dusage, > + add_help_option=3DFalse) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action=3D'store_true', > + dest=3D'verbose', > + help=3D'hex dump extra data', > + default=3DFalse) > + > + self.parser.add_option( > + '-n', > + '--node', > + action=3D'store_true', > + dest=3D'node', > + help=3D'dump a single device path node', > + default=3DFalse) > + > + self.parser.add_option( > + '-h', > + '--help', > + action=3D'store_true', > + dest=3D'help', > + help=3D'Show help for the command', > + default=3DFalse) > + > + 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 =3D 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 =3D shlex.split(command) > + > + try: > + (options, args) =3D self.parser.parse_args(command_args) > + dev_list =3D [] > + 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 =3D 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 =3D 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 =3D "usage: %prog [options]" > + description =3D '''Command that can EFI dump EFI HOBs''' > + > + # Pass add_help_option =3D 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 =3D optparse.OptionParser( > + description=3Ddescription, > + prog=3D'table', > + usage=3Dusage, > + add_help_option=3DFalse) > + > + self.parser.add_option( > + '-a', > + '--address', > + type=3D"int", > + dest=3D'address', > + help=3D'Parse HOBs from address', > + default=3DNone) > + > + self.parser.add_option( > + '-t', > + '--type', > + type=3D"int", > + dest=3D'type', > + help=3D'Only dump HOBS of his type', > + default=3DNone) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action=3D'store_true', > + dest=3D'verbose', > + help=3D'hex dump extra data', > + default=3DFalse) > + > + self.parser.add_option( > + '-h', > + '--help', > + action=3D'store_true', > + dest=3D'help', > + help=3D'Show help for the command', > + default=3DFalse) > + > + 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 =3D 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 =3D shlex.split(command) > + > + try: > + (options, _) =3D 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 =3D arg_to_address(exe_ctx.frame, options.address) > + > + file =3D LldbFileObject(exe_ctx.process) > + hob =3D 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 =3D "usage: %prog [options]" > + description =3D '''Command that can display EFI Config Tables > +''' > + > + # Pass add_help_option =3D 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 =3D optparse.OptionParser( > + description=3Ddescription, > + prog=3D'table', > + usage=3Dusage, > + add_help_option=3DFalse) > + > + self.parser.add_option( > + '-h', > + '--help', > + action=3D'store_true', > + dest=3D'help', > + help=3D'Show help for the command', > + default=3DFalse) > + > + 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 =3D 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 =3D shlex.split(command) > + > + try: > + (options, _) =3D 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 =3D exe_ctx.target.FindFirstGlobalVariable('gST') > + if gST.error.fail: > + print('Error: This command requires symbols for gST to be loaded') > + return > + > + file =3D LldbFileObject(exe_ctx.process) > + table =3D EfiConfigurationTable(file, gST.unsigned) > + if table: > + print(table, '\n') > + > + > +class EfiGuidCommand: > + > + def create_options(self): > + ''' standard lldb command help/options parser''' > + usage =3D "usage: %prog [options]" > + description =3D ''' > + Command that can display all EFI GUID's or give info on a > + specific GUID's > + ''' > + self.parser =3D optparse.OptionParser( > + description=3Ddescription, > + prog=3D'guid', > + usage=3Dusage, > + add_help_option=3DFalse) > + > + self.parser.add_option( > + '-n', > + '--new', > + action=3D'store_true', > + dest=3D'new', > + help=3D'Generate a new GUID', > + default=3DFalse) > + > + self.parser.add_option( > + '-v', > + '--verbose', > + action=3D'store_true', > + dest=3D'verbose', > + help=3D'Also display GUID C structure values', > + default=3DFalse) > + > + self.parser.add_option( > + '-h', > + '--help', > + action=3D'store_true', > + dest=3D'help', > + help=3D'Show help for the command', > + default=3DFalse) > + > + 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 =3D 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 =3D shlex.split(command) > + > + try: > + (options, args) =3D self.parser.parse_args(command_args) > + if len(args) >=3D 1: > + # guid { 0x414e6bdd, 0xe47b, 0x47cc, > + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} > + # this generates multiple args > + arg =3D ' '.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 =3D 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 =3D arg.lower() > + name =3D GuidNames.to_name(key) > + elif GuidNames.is_c_guid(arg): > + # guid { 0x414e6bdd, 0xe47b, 0x47cc, > + # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} > + key =3D GuidNames.from_c_guid(arg) > + name =3D GuidNames.to_name(key) > + else: > + # guid gEfiDxeServicesTableGuid > + name =3D arg > + try: > + key =3D GuidNames.to_guid(name) > + name =3D GuidNames.to_name(key) > + except ValueError: > + return > + > + extra =3D 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 =3D f'{GuidNames.to_c_guid(key)}: ' > + else: > + extra =3D '' > + 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 =3D "usage: %prog [options]" > + description =3D '''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 =3D 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 =3D optparse.OptionParser( > + description=3Ddescription, > + prog=3D'efi_symbols', > + usage=3Dusage, > + add_help_option=3DFalse) > + > + self.parser.add_option( > + '-a', > + '--address', > + type=3D"int", > + dest=3D'address', > + help=3D'Load symbols for image at address', > + default=3DNone) > + > + self.parser.add_option( > + '-f', > + '--frame', > + action=3D'store_true', > + dest=3D'frame', > + help=3D'Load symbols for current stack frame', > + default=3DFalse) > + > + self.parser.add_option( > + '-p', > + '--pc', > + action=3D'store_true', > + dest=3D'pc', > + help=3D'Load symbols for pc', > + default=3DFalse) > + > + self.parser.add_option( > + '--pei', > + action=3D'store_true', > + dest=3D'pei', > + help=3D'Load symbols for PEI (searches every 4 bytes)', > + default=3DFalse) > + > + self.parser.add_option( > + '-e', > + '--extended', > + action=3D'store_true', > + dest=3D'extended', > + help=3D'Try to load all symbols based on config tables.', > + default=3DFalse) > + > + self.parser.add_option( > + '-r', > + '--range', > + type=3D"long", > + dest=3D'range', > + help=3D'How far to search backward for start of PE/COFF Image', > + default=3DNone) > + > + self.parser.add_option( > + '-s', > + '--stride', > + type=3D"long", > + dest=3D'stride', > + help=3D'Boundary to search for PE/COFF header', > + default=3DNone) > + > + self.parser.add_option( > + '-t', > + '--thread', > + action=3D'store_true', > + dest=3D'thread', > + help=3D'Load symbols for the frames of all threads', > + default=3DFalse) > + > + self.parser.add_option( > + '-h', > + '--help', > + action=3D'store_true', > + dest=3D'help', > + help=3D'Show help for the command', > + default=3DFalse) > + > + 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 =3D 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 =3D shlex.split(command) > + > + try: > + (options, _) =3D 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 =3D LldbFileObject(exe_ctx.process) > + efi_symbols =3D EfiSymbols(exe_ctx.target) > + self.result =3D result > + self.exe_ctx =3D exe_ctx > + > + if options.pei: > + # XIP code ends up on a 4 byte boundary. > + options.stride =3D 4 > + options.range =3D 0x100000 > + efi_symbols.configure_search(options.stride, options.range) > + > + if not options.pc and options.address is None: > + # default to > + options.frame =3D True > + > + if options.frame: > + if not exe_ctx.frame.IsValid(): > + result.SetError("invalid frame") > + return > + > + threads =3D exe_ctx.process.threads if options.thread else [ > + exe_ctx.thread] > + > + for thread in threads: > + for frame in thread: > + res =3D efi_symbols.address_to_symbols(frame.pc) > + self.lldb_print(res) > + > + else: > + if options.address is not None: > + address =3D options.address > + elif options.pc: > + try: > + address =3D exe_ctx.thread.GetSelectedFrame().pc > + except ValueError: > + result.SetError("invalid pc") > + return > + else: > + address =3D 0 > + > + res =3D efi_symbols.address_to_symbols(address.pc) > + print(res) > + > + if options.extended: > + > + gST =3D exe_ctx.target.FindFirstGlobalVariable('gST') > + if gST.error.fail: > + print('Error: This command requires symbols to be loaded') > + else: > + table =3D EfiConfigurationTable(file, gST.unsigned) > + for address, _ in table.DebugImageInfo(): > + res =3D 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 =3D lldb.SBError() > + Str =3D '' > + if valobj.TypeIsPointerType(): > + if valobj.GetValueAsUnsigned() =3D=3D 0: > + return "NULL" > + > + # CHAR16 * max string size 1024 > + for i in range(1024): > + Char =3D valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0) > + if SBError.fail or Char =3D=3D 0: > + break > + Str +=3D chr(Char) > + return 'L"' + Str + '"' > + > + if valobj.num_children =3D=3D 0: > + # CHAR16 > + return "L'" + chr(valobj.unsigned) + "'" > + > + else: > + # CHAR16 [] > + for i in range(valobj.num_children): > + Char =3D valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0) > + if Char =3D=3D 0: > + break > + Str +=3D 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 =3D lldb.SBError() > + Str =3D '' > + if valobj.TypeIsPointerType(): > + if valobj.GetValueAsUnsigned() =3D=3D 0: > + return "NULL" > + > + # CHAR8 * max string size 1024 > + for i in range(1024): > + Char =3D valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0) > + if SBError.fail or Char =3D=3D 0: > + break > + Str +=3D chr(Char) > + Str =3D '"' + Str + '"' > + return Str > + > + if valobj.num_children =3D=3D 0: > + # CHAR8 > + return "'" + chr(valobj.unsigned) + "'" > + else: > + # CHAR8 [] > + for i in range(valobj.num_children): > + Char =3D valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0) > + if SBError.fail or Char =3D=3D 0: > + break > + Str +=3D 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 =3D debugger.GetDefaultCategory() > + FormatBool =3D lldb.SBTypeFormat(lldb.eFormatBoolean) > + category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool) > + > + FormatHex =3D 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 =3D True > + > + @classmethod > + def activate(cls): > + if cls.needed: > + lldb.debugger.HandleCommand("process handle SIGALRM -n false") > + cls.needed =3D 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 =3D EfiSymbols(frame.thread.process.target) > + LoadAddress =3D frame.FindVariable("LoadAddress").unsigned > + if frame.FindVariable("AddSymbolFlag").unsigned =3D=3D 1: > + res =3D symbols.address_to_symbols(LoadAddress) > + else: > + res =3D 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 =3D 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) =3D=3D sizeof(UINT64) > + patch_ctypes() > + > + try: > + target =3D debugger.GetSelectedTarget() > + if target.FindFunctions('SecGdbScriptBreak').symbols: > + breakpoint =3D target.BreakpointCreateByName('SecGdbScriptBreak') > + # Set the emulator breakpoints, if we are in the emulator > + cmd =3D 'breakpoint command add -s python -F ' > + cmd +=3D 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__ =3D=3D '__main__': > + pass > -- > 2.34.1 --yaRYGYBt0xwdomHI389E Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: quoted-printable Reviewed-by: Bob Feng <bob.c.feng@intel.com>

On Tue, Mar 2= 2, 2022 at 04:21 AM, Rebecca Cran wrote:
https://bugzilla.tianocore.org/show_b= ug.cgi?id=3D3500

Use efi_debugging.py Python Classes to impl= ement EFI gdb commands:
efi_symbols, guid, table, hob, and devicepath<= br />
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 l= ocalhost:9000" -o "command script import efi_lldb.py"
Note you may als= o have to teach lldb about QEMU:
-o "settings set plugin.process.gdb-r= emote.target-definition-file
x86_64_target_definition.py"

C= c: Leif Lindholm <quic_llindhol@quicinc.com>
Cc: Michael D Kinne= y <michael.d.kinney@intel.com>
Cc: Hao A Wu <hao.a.wu@intel.c= om>
Cc: Bob Feng <bob.c.feng@intel.com>
Cc: Liming Gao &= lt;gaoliming@byosoft.com.cn>
Cc: Yuwei Chen <yuwei.chen@intel.co= m>
Signed-off-by: Rebecca Cran <quic_rcran@quicinc.com>
= ---
BaseTools/Scripts/efi_lldb.py | 1044 ++++++++++++++++++++
1 f= ile changed, 1044 insertions(+)

diff --git a/BaseTools/Scripts/e= fi_lldb.py b/BaseTools/Scripts/efi_lldb.py
new file mode 100755
i= ndex 000000000000..089b6ba58ab8
--- /dev/null
+++ b/BaseTools/Scr= ipts/efi_lldb.py
@@ -0,0 +1,1044 @@
+#!/usr/bin/python3
+'''=
+Copyright (c) Apple Inc. 2021
+SPDX-License-Identifier: BSD-2-C= lause-Patent
+
+Example usage:
+OvmfPkg/build.sh qemu -gdb t= cp::9000
+lldb -o "gdb-remote localhost:9000" -o "command script impor= t 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, Guid= Names, EfiStatusClass, EfiBootMode
+from efi_debugging import PeTeImag= e, patch_ctypes
+
+try:
+ # Just try for LLDB in case PYTHON= PATH is already correctly setup
+ import lldb
+except ImportError= :
+ try:
+ env =3D os.environ.copy()
+ env['LLDB_DEFAULT_PYT= HON_VERSION'] =3D str(sys.version_info.major)
+ lldb_python_path =3D s= ubprocess.check_output(
+ ["xcrun", "lldb", "-P"], env=3Denv).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):<= br />+ # _exe_ctx is lldb.SBExecutionContext
+ self._process =3D proce= ss
+ self._offset =3D 0
+ self._SBError =3D lldb.SBError()
+=
+ def tell(self):
+ return self._offset
+
+ def read(s= elf, size=3D-1):
+ if size =3D=3D -1:
+ # arbitrary default size<= br />+ size =3D 0x1000000
+
+ data =3D self._process.ReadMemory(s= elf._offset, size, self._SBError)
+ if self._SBError.fail:
+ rais= e MemoryError(
+ f'lldb could not read memory 0x{size:x} '
+ f' b= ytes from 0x{self._offset:08x}')
+ else:
+ return data
+
+ def readable(self):
+ return True
+
+ def seek(self, off= set, whence=3D0):
+ if whence =3D=3D 0:
+ self._offset =3D offset=
+ elif whence =3D=3D 1:
+ self._offset +=3D offset
+ else:<= br />+ # whence =3D=3D 2 is seek from end
+ raise NotImplementedError<= br />+
+ def seekable(self):
+ return True
+
+ def writ= e(self, data):
+ result =3D self._process.WriteMemory(self._offset, da= ta, self._SBError)
+ if self._SBError.fail:
+ raise MemoryError(<= br />+ f'lldb could not write memory to 0x{self._offset:08x}')
+ retur= n result
+
+ def writable(self):
+ return True
+
+= def truncate(self, size=3DNone):
+ raise NotImplementedError
++ def flush(self):
+ raise NotImplementedError
+
+ def f= ileno(self):
+ raise NotImplementedError
+
+
+class Efi= Symbols:
+ """
+ Class to manage EFI Symbols
+ You need to p= ass file, and exe_ctx to load symbols.
+ You can print(EfiSymbols()) t= o see the currently loaded symbols
+ """
+
+ loaded =3D {}+ stride =3D None
+ range =3D None
+ verbose =3D False
+=
+ def __init__(self, target=3DNone):
+ if target:
+ EfiSymb= ols.target =3D target
+ EfiSymbols._file =3D LldbFileObject(target.pro= cess)
+
+ @ classmethod
+ def __str__(cls):
+ return ''= .join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values())
+
+ @= classmethod
+ def configure_search(cls, stride, range, verbose=3DFals= e):
+ cls.stride =3D stride
+ cls.range =3D range
+ cls.verb= ose =3D verbose
+
+ @ classmethod
+ def clear(cls):
+ c= ls.loaded =3D {}
+
+ @ classmethod
+ def add_symbols_for_pec= off(cls, pecoff):
+ '''Tell lldb the location of the .text and .data s= ections.'''
+
+ if pecoff.LoadAddress in cls.loaded:
+ retur= n 'Already Loaded: '
+
+ module =3D cls.target.AddModule(None, No= ne, str(pecoff.CodeViewUuid))
+ if not module:
+ module =3D cls.t= arget.AddModule(pecoff.CodeViewPdb,
+ None,
+ str(pecoff.CodeView= Uuid))
+ if module.IsValid():
+ SBError =3D cls.target.SetModuleL= oadAddress(
+ module, pecoff.LoadAddress + pecoff.TeAdjust)
+ if = SBError.success:
+ cls.loaded[pecoff.LoadAddress] =3D (pecoff, module)=
+ return ''
+
+ return 'Symbols NOT FOUND: '
+
+ = @ classmethod
+ def address_to_symbols(cls, address, reprobe=3DFalse):=
+ '''
+ Given an address search backwards for a PE/COFF (or TE) = header
+ and load symbols. Return a status string.
+ '''
+ i= f not isinstance(address, int):
+ address =3D int(address)
+
+ pecoff, _ =3D cls.address_in_loaded_pecoff(address)
+ if not reprob= e and pecoff is not None:
+ # skip the probe of the remote
+ retu= rn f'{pecoff} is already loaded'
+
+ pecoff =3D PeTeImage(cls._fi= le, None)
+ if pecoff.pcToPeCoff(address, cls.stride, cls.range):
+ res =3D cls.add_symbols_for_pecoff(pecoff)
+ return f'{res}{pecoff}= '
+ else:
+ return f'0x{address:08x} not in a PE/COFF (or TE) ima= ge'
+
+ @ classmethod
+ def address_in_loaded_pecoff(cls, ad= dress):
+ if not isinstance(address, int):
+ address =3D int(addr= ess)
+
+ for (pecoff, module) in cls.loaded.values():
+ if (= address >=3D pecoff.LoadAddress and
+ address <=3D pecoff.EndLoa= dAddress):
+
+ return pecoff, module
+
+ return None, N= one
+
+ @ classmethod
+ def unload_symbols(cls, address):+ pecoff, module =3D cls.address_in_loaded_pecoff(address)
+ if mod= ule:
+ name =3D str(module)
+ cls.target.ClearModuleLoadAddress(m= odule)
+ cls.target.RemoveModule(module)
+ del cls.loaded[pecoff.= LoadAddress]
+ return f'{name:s} was unloaded'
+ return f'0x{addr= ess:x} was not in a loaded image'
+
+
+def arg_to_address(fr= ame, arg):
+ ''' convert an lldb command arg into a memory address (ad= dr_t)'''
+
+ if arg is None:
+ return None
+
+ arg= _str =3D arg if isinstance(arg, str) else str(arg)
+ SBValue =3D frame= .EvaluateExpression(arg_str)
+ if SBValue.error.fail:
+ return ar= g
+
+ if (SBValue.TypeIsPointerType() or
+ SBValue.value_typ= e =3D=3D lldb.eValueTypeRegister or
+ SBValue.value_type =3D=3D lldb.e= ValueTypeRegisterSet or
+ SBValue.value_type =3D=3D lldb.eValueTypeCon= stResult):
+ try:
+ addr =3D SBValue.GetValueAsAddress()
+ e= xcept ValueError:
+ addr =3D SBValue.unsigned
+ else:
+ try:=
+ addr =3D SBValue.address_of.GetValueAsAddress()
+ except Value= Error:
+ addr =3D SBValue.address_of.unsigned
+
+ return add= r
+
+
+def arg_to_data(frame, arg):
+ ''' convert an ll= db command arg into a data vale (uint32_t/uint64_t)'''
+ if not isinst= ance(arg, str):
+ arg_str =3D str(str)
+
+ SBValue =3D frame= .EvaluateExpression(arg_str)
+ return SBValue.unsigned
+
++class EfiDevicePathCommand:
+
+ def create_options(self):+ ''' standard lldb command help/options parser'''
+ usage =3D "usa= ge: %prog [options]"
+ description =3D '''Command that can EFI Config = Tables
+'''
+
+ # Pass add_help_option =3D False, since this= keeps the command in line
+ # with lldb commands, and we wire up "hel= p command" to work by
+ # providing the long & short help methods = below.
+ self.parser =3D optparse.OptionParser(
+ description=3Dd= escription,
+ prog=3D'devicepath',
+ usage=3Dusage,
+ add_he= lp_option=3DFalse)
+
+ self.parser.add_option(
+ '-v',
= + '--verbose',
+ action=3D'store_true',
+ dest=3D'verbose',
= + help=3D'hex dump extra data',
+ default=3DFalse)
+
+ self.= parser.add_option(
+ '-n',
+ '--node',
+ action=3D'store_tru= e',
+ dest=3D'node',
+ help=3D'dump a single device path node',+ default=3DFalse)
+
+ self.parser.add_option(
+ '-h',+ '--help',
+ action=3D'store_true',
+ dest=3D'help',
+ = help=3D'Show help for the command',
+ default=3DFalse)
+
+ d= ef 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 f= unction method'''
+ self.create_options()
+ self.help_string =3D = self.parser.format_help()
+
+ def __call__(self, debugger, comman= d, exe_ctx, result):
+ '''standard lldb function method'''
+ # Us= e the Shell Lexer to properly parse up command options just like a
+ #= shell would
+ command_args =3D shlex.split(command)
+
+ try= :
+ (options, args) =3D self.parser.parse_args(command_args)
+ de= v_list =3D []
+ for arg in args:
+ dev_list.append(arg_to_address= (exe_ctx.frame, arg))
+ except ValueError:
+ # if you don't handl= e exceptions, passing an incorrect argument
+ # to the OptionParser wi= ll cause LLDB to exit (courtesy of
+ # OptParse dealing with argument = errors by throwing SystemExit)
+ result.SetError("option parsing faile= d")
+ return
+
+ if options.help:
+ self.parser.print_h= elp()
+ return
+
+ file =3D LldbFileObject(exe_ctx.process)<= br />+
+ for dev_addr in dev_list:
+ if options.node:
+ prin= t(EfiDevicePath(file).device_path_node_str(
+ dev_addr, options.verbos= e))
+ else:
+ device_path =3D EfiDevicePath(file, dev_addr, optio= ns.verbose)
+ if device_path.valid():
+ print(device_path)
+=
+
+class EfiHobCommand:
+ def create_options(self):
+ = ''' standard lldb command help/options parser'''
+ usage =3D "usage: %= prog [options]"
+ description =3D '''Command that can EFI dump EFI HOB= s'''
+
+ # Pass add_help_option =3D False, since this keeps the c= ommand in line
+ # with lldb commands, and we wire up "help command" t= o work by
+ # providing the long & short help methods below.
= + self.parser =3D optparse.OptionParser(
+ description=3Ddescription,<= br />+ prog=3D'table',
+ usage=3Dusage,
+ add_help_option=3DFalse= )
+
+ self.parser.add_option(
+ '-a',
+ '--address',+ type=3D"int",
+ dest=3D'address',
+ help=3D'Parse HOBs from = address',
+ default=3DNone)
+
+ self.parser.add_option(
+ '-t',
+ '--type',
+ type=3D"int",
+ dest=3D'type',
+= help=3D'Only dump HOBS of his type',
+ default=3DNone)
+
+ = self.parser.add_option(
+ '-v',
+ '--verbose',
+ action=3D's= tore_true',
+ dest=3D'verbose',
+ help=3D'hex dump extra data',+ default=3DFalse)
+
+ self.parser.add_option(
+ '-h',+ '--help',
+ action=3D'store_true',
+ dest=3D'help',
+ = help=3D'Show help for the command',
+ default=3DFalse)
+
+ d= ef 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 fun= ction method'''
+ self.create_options()
+ self.help_string =3D se= lf.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
+ # s= hell would
+ command_args =3D shlex.split(command)
+
+ try:<= br />+ (options, _) =3D self.parser.parse_args(command_args)
+ except = ValueError:
+ # if you don't handle exceptions, passing an incorrect a= rgument
+ # 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 o= ptions.help:
+ self.parser.print_help()
+ return
+
+ ad= dress =3D arg_to_address(exe_ctx.frame, options.address)
+
+ file= =3D LldbFileObject(exe_ctx.process)
+ hob =3D EfiHob(file, address, o= ptions.verbose).get_hob_by_type(
+ options.type)
+ print(hob)
+
+
+class EfiTableCommand:
+
+ def create_options(se= lf):
+ ''' standard lldb command help/options parser'''
+ usage = =3D "usage: %prog [options]"
+ description =3D '''Command that can dis= play EFI Config Tables
+'''
+
+ # Pass add_help_option =3D F= alse, since this keeps the command in line
+ # with lldb commands, and= we wire up "help command" to work by
+ # providing the long & sho= rt help methods below.
+ self.parser =3D optparse.OptionParser(
+= description=3Ddescription,
+ prog=3D'table',
+ usage=3Dusage,+ add_help_option=3DFalse)
+
+ self.parser.add_option(
+ = '-h',
+ '--help',
+ action=3D'store_true',
+ dest=3D'help',<= br />+ help=3D'Show help for the command',
+ default=3DFalse)
++ def get_short_help(self):
+ '''standard lldb function method'''<= br />+ 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_stri= ng =3D 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 =3D shlex.split(command)
+
+ try:
+ (options, _) =3D self.parser.parse_args(command_args)
= + except ValueError:
+ # if you don't handle exceptions, passing an in= correct argument
+ # to the OptionParser will cause LLDB to exit (cour= tesy of
+ # OptParse dealing with argument errors by throwing SystemEx= it)
+ result.SetError("option parsing failed")
+ return
++ if options.help:
+ self.parser.print_help()
+ return
+<= br />+ gST =3D exe_ctx.target.FindFirstGlobalVariable('gST')
+ if gST.= error.fail:
+ print('Error: This command requires symbols for gST to b= e loaded')
+ return
+
+ file =3D LldbFileObject(exe_ctx.proc= ess)
+ table =3D EfiConfigurationTable(file, gST.unsigned)
+ if t= able:
+ print(table, '\n')
+
+
+class EfiGuidCommand:+
+ def create_options(self):
+ ''' standard lldb command hel= p/options parser'''
+ usage =3D "usage: %prog [options]"
+ descri= ption =3D '''
+ Command that can display all EFI GUID's or give info o= n a
+ specific GUID's
+ '''
+ self.parser =3D optparse.Optio= nParser(
+ description=3Ddescription,
+ prog=3D'guid',
+ usa= ge=3Dusage,
+ add_help_option=3DFalse)
+
+ self.parser.add_o= ption(
+ '-n',
+ '--new',
+ action=3D'store_true',
+ de= st=3D'new',
+ help=3D'Generate a new GUID',
+ default=3DFalse)+
+ self.parser.add_option(
+ '-v',
+ '--verbose',
+= action=3D'store_true',
+ dest=3D'verbose',
+ help=3D'Also displa= y GUID C structure values',
+ default=3DFalse)
+
+ self.pars= er.add_option(
+ '-h',
+ '--help',
+ action=3D'store_true',<= br />+ dest=3D'help',
+ help=3D'Show help for the command',
+ def= ault=3DFalse)
+
+ def get_short_help(self):
+ '''standard ll= db function method'''
+ return "Display EFI GUID's"
+
+ def = get_long_help(self):
+ '''standard lldb function method'''
+ retu= rn self.help_string
+
+ def __init__(self, debugger, internal_dic= t):
+ '''standard lldb function method'''
+ self.create_options()=
+ self.help_string =3D self.parser.format_help()
+
+ def __= call__(self, debugger, command, exe_ctx, result):
+ '''standard lldb f= unction method'''
+ # Use the Shell Lexer to properly parse up command= options just like a
+ # shell would
+ command_args =3D shlex.spl= it(command)
+
+ try:
+ (options, args) =3D self.parser.parse= _args(command_args)
+ if len(args) >=3D 1:
+ # guid { 0x414e6b= dd, 0xe47b, 0x47cc,
+ # { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x1= 6 }}
+ # this generates multiple args
+ arg =3D ' '.join(args)+ except ValueError:
+ # if you don't handle exceptions, passing an= incorrect argument
+ # to the OptionParser will cause LLDB to exit (c= ourtesy of
+ # OptParse dealing with argument errors by throwing Syste= mExit)
+ result.SetError("option parsing failed")
+ return
+=
+ if options.help:
+ self.parser.print_help()
+ return
+
+ if options.new:
+ guid =3D uuid.uuid4()
+ print(str(gui= d).upper())
+ print(GuidNames.to_c_guid(guid))
+ return
++ if len(args) > 0:
+ if GuidNames.is_guid_str(arg):
+ # gu= id 05AD34BA-6F02-4214-952E-4DA0398E2BB9
+ key =3D arg.lower()
+ n= ame =3D GuidNames.to_name(key)
+ elif GuidNames.is_c_guid(arg):
+= # guid { 0x414e6bdd, 0xe47b, 0x47cc,
+ # { 0xb2, 0x44, 0xbb, 0x61, 0x= 02, 0x0c,0xf5, 0x16 }}
+ key =3D GuidNames.from_c_guid(arg)
+ nam= e =3D GuidNames.to_name(key)
+ else:
+ # guid gEfiDxeServicesTabl= eGuid
+ name =3D arg
+ try:
+ key =3D GuidNames.to_guid(name= )
+ name =3D GuidNames.to_name(key)
+ except ValueError:
+ r= eturn
+
+ extra =3D f'{GuidNames.to_c_guid(key)}: ' if options.ve= rbose else ''
+ print(f'{key}: {extra}{name}')
+
+ else:
+ for key, value in GuidNames._dict_.items():
+ if options.verbose:<= br />+ extra =3D f'{GuidNames.to_c_guid(key)}: '
+ else:
+ extra = =3D ''
+ print(f'{key}: {extra}{value}')
+
+
+class Efi= SymbolicateCommand(object):
+ '''Class to abstract an lldb command'''<= br />+
+ def create_options(self):
+ ''' standard lldb command he= lp/options parser'''
+ usage =3D "usage: %prog [options]"
+ descr= iption =3D '''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 =3D 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 = =3D optparse.OptionParser(
+ description=3Ddescription,
+ prog=3D= 'efi_symbols',
+ usage=3Dusage,
+ add_help_option=3DFalse)
+=
+ self.parser.add_option(
+ '-a',
+ '--address',
+ typ= e=3D"int",
+ dest=3D'address',
+ help=3D'Load symbols for image a= t address',
+ default=3DNone)
+
+ self.parser.add_option(+ '-f',
+ '--frame',
+ action=3D'store_true',
+ dest=3D'f= rame',
+ help=3D'Load symbols for current stack frame',
+ default= =3DFalse)
+
+ self.parser.add_option(
+ '-p',
+ '--pc',=
+ action=3D'store_true',
+ dest=3D'pc',
+ help=3D'Load symb= ols for pc',
+ default=3DFalse)
+
+ self.parser.add_option(<= br />+ '--pei',
+ action=3D'store_true',
+ dest=3D'pei',
+ h= elp=3D'Load symbols for PEI (searches every 4 bytes)',
+ default=3DFal= se)
+
+ self.parser.add_option(
+ '-e',
+ '--extended',=
+ action=3D'store_true',
+ dest=3D'extended',
+ help=3D'Try= to load all symbols based on config tables.',
+ default=3DFalse)
+
+ self.parser.add_option(
+ '-r',
+ '--range',
+ typ= e=3D"long",
+ dest=3D'range',
+ help=3D'How far to search backwar= d for start of PE/COFF Image',
+ default=3DNone)
+
+ self.pa= rser.add_option(
+ '-s',
+ '--stride',
+ type=3D"long",
+ dest=3D'stride',
+ help=3D'Boundary to search for PE/COFF header',<= br />+ default=3DNone)
+
+ self.parser.add_option(
+ '-t',+ '--thread',
+ action=3D'store_true',
+ dest=3D'thread',
+ help=3D'Load symbols for the frames of all threads',
+ default=3DF= alse)
+
+ self.parser.add_option(
+ '-h',
+ '--help',+ action=3D'store_true',
+ dest=3D'help',
+ help=3D'Show help= for the command',
+ default=3DFalse)
+
+ def get_short_help= (self):
+ '''standard lldb function method'''
+ return (
+ "= Load symbols based on an address that is part of"
+ " a PE/COFF EFI im= age.")
+
+ def get_long_help(self):
+ '''standard lldb funct= ion method'''
+ return self.help_string
+
+ def __init__(sel= f, debugger, unused):
+ '''standard lldb function method'''
+ sel= f.create_options()
+ self.help_string =3D self.parser.format_help()+
+ def lldb_print(self, lldb_str):
+ # capture command out li= ke an lldb command
+ self.result.PutCString(lldb_str)
+ # flush t= he output right away
+ self.result.SetImmediateOutputFile(
+ self= .exe_ctx.target.debugger.GetOutputFile())
+
+ def __call__(self, = debugger, command, exe_ctx, result):
+ '''standard lldb function metho= d'''
+ # Use the Shell Lexer to properly parse up command options just= like a
+ # shell would
+ command_args =3D shlex.split(command)+
+ try:
+ (options, _) =3D self.parser.parse_args(command_ar= gs)
+ except ValueError:
+ # if you don't handle exceptions, pass= ing an incorrect argument
+ # to the OptionParser will cause LLDB to e= xit (courtesy of
+ # OptParse dealing with argument errors by throwing= SystemExit)
+ result.SetError("option parsing failed")
+ return<= br />+
+ if options.help:
+ self.parser.print_help()
+ retur= n
+
+ file =3D LldbFileObject(exe_ctx.process)
+ efi_symbols= =3D EfiSymbols(exe_ctx.target)
+ self.result =3D result
+ self.e= xe_ctx =3D exe_ctx
+
+ if options.pei:
+ # XIP code ends up = on a 4 byte boundary.
+ options.stride =3D 4
+ options.range =3D = 0x100000
+ efi_symbols.configure_search(options.stride, options.range)=
+
+ if not options.pc and options.address is None:
+ # defa= ult to
+ options.frame =3D True
+
+ if options.frame:
+= if not exe_ctx.frame.IsValid():
+ result.SetError("invalid frame")+ return
+
+ threads =3D exe_ctx.process.threads if options.th= read else [
+ exe_ctx.thread]
+
+ for thread in threads:
+ for frame in thread:
+ res =3D efi_symbols.address_to_symbols(fram= e.pc)
+ self.lldb_print(res)
+
+ else:
+ if options.add= ress is not None:
+ address =3D options.address
+ elif options.pc= :
+ try:
+ address =3D exe_ctx.thread.GetSelectedFrame().pc
= + except ValueError:
+ result.SetError("invalid pc")
+ return
+ else:
+ address =3D 0
+
+ res =3D efi_symbols.address_to= _symbols(address.pc)
+ print(res)
+
+ if options.extended:+
+ gST =3D exe_ctx.target.FindFirstGlobalVariable('gST')
+ i= f gST.error.fail:
+ print('Error: This command requires symbols to be = loaded')
+ else:
+ table =3D EfiConfigurationTable(file, gST.unsi= gned)
+ for address, _ in table.DebugImageInfo():
+ res =3D efi_s= ymbols.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, in= ternal_dict):
+ '''
+ Display CHAR16 as a String in the debugger.=
+ Note: utf-8 is returned as that is the value for the debugger.
+ '''
+ SBError =3D lldb.SBError()
+ Str =3D ''
+ if valobj= .TypeIsPointerType():
+ if valobj.GetValueAsUnsigned() =3D=3D 0:
= + return "NULL"
+
+ # CHAR16 * max string size 1024
+ for i = in range(1024):
+ Char =3D valobj.GetPointeeData(i, 1).GetUnsignedInt1= 6(SBError, 0)
+ if SBError.fail or Char =3D=3D 0:
+ break
+ = Str +=3D chr(Char)
+ return 'L"' + Str + '"'
+
+ if valobj.n= um_children =3D=3D 0:
+ # CHAR16
+ return "L'" + chr(valobj.unsig= ned) + "'"
+
+ else:
+ # CHAR16 []
+ for i in range(val= obj.num_children):
+ Char =3D valobj.GetChildAtIndex(i).data.GetUnsign= edInt16(SBError, 0)
+ if Char =3D=3D 0:
+ break
+ Str +=3D c= hr(Char)
+ return 'L"' + Str + '"'
+
+ return Str
+
+
+def CHAR8_TypeSummary(valobj, internal_dict):
+ '''
+ D= isplay CHAR8 as a String in the debugger.
+ Note: utf-8 is returned as= that is the value for the debugger.
+ '''
+ SBError =3D lldb.SBE= rror()
+ Str =3D ''
+ if valobj.TypeIsPointerType():
+ if va= lobj.GetValueAsUnsigned() =3D=3D 0:
+ return "NULL"
+
+ # CH= AR8 * max string size 1024
+ for i in range(1024):
+ Char =3D val= obj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0)
+ if SBError.fail= or Char =3D=3D 0:
+ break
+ Str +=3D chr(Char)
+ Str =3D '"= ' + Str + '"'
+ return Str
+
+ if valobj.num_children =3D=3D= 0:
+ # CHAR8
+ return "'" + chr(valobj.unsigned) + "'"
+ el= se:
+ # CHAR8 []
+ for i in range(valobj.num_children):
+ Ch= ar =3D valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0)
+ if= SBError.fail or Char =3D=3D 0:
+ break
+ Str +=3D chr(Char)
+ return '"' + Str + '"'
+
+ return Str
+
+
+def = EFI_STATUS_TypeSummary(valobj, internal_dict):
+ if valobj.TypeIsPoint= erType():
+ 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, int= ernal_dict):
+ if valobj.TypeIsPointerType():
+ return ''
+ = return str(GuidNames(bytes(valobj.data.uint8)))
+
+
+def EFI= _BOOT_MODE_TypeSummary(valobj, internal_dict):
+ if valobj.TypeIsPoint= erType():
+ return ''
+ '''Return #define name for EFI_BOOT_MODE'= ''
+ return str(EfiBootMode(valobj.unsigned))
+
+
+def = lldb_type_formaters(debugger, mod_name):
+ '''Teach lldb about EFI typ= es'''
+
+ category =3D debugger.GetDefaultCategory()
+ Forma= tBool =3D lldb.SBTypeFormat(lldb.eFormatBoolean)
+ category.AddTypeFor= mat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool)
+
+ FormatHe= x =3D lldb.SBTypeFormat(lldb.eFormatHex)
+ category.AddTypeFormat(lldb= .SBTypeNameSpecifier("UINT64"), FormatHex)
+ category.AddTypeFormat(ll= db.SBTypeNameSpecifier("INT64"), FormatHex)
+ category.AddTypeFormat(l= ldb.SBTypeNameSpecifier("UINT32"), FormatHex)
+ category.AddTypeFormat= (lldb.SBTypeNameSpecifier("INT32"), FormatHex)
+ category.AddTypeForma= t(lldb.SBTypeNameSpecifier("UINT16"), FormatHex)
+ category.AddTypeFor= mat(lldb.SBTypeNameSpecifier("INT16"), FormatHex)
+ category.AddTypeFo= rmat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex)
+ category.AddTypeF= ormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex)
+ category.AddTypeF= ormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex)
+ category.AddType= Format(lldb.SBTypeNameSpecifier("INTN"), FormatHex)
+ category.AddType= Format(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex)
+ category.AddTyp= eFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex)
+ category.AddT= ypeFormat(lldb.SBTypeNameSpecifier(
+ "EFI_PHYSICAL_ADDRESS"), FormatH= ex)
+ category.AddTypeFormat(lldb.SBTypeNameSpecifier(
+ "PHYSICA= L_ADDRESS"), FormatHex)
+ category.AddTypeFormat(lldb.SBTypeNameSpecif= ier("EFI_LBA"), FormatHex)
+ category.AddTypeFormat(
+ lldb.SBTyp= eNameSpecifier("EFI_BOOT_MODE"), FormatHex)
+ category.AddTypeFormat(l= ldb.SBTypeNameSpecifier(
+ "EFI_FV_FILETYPE"), FormatHex)
+
= + #
+ # Smart type printing for EFI
+ #
+
+ debugger.Ha= ndleCommand(
+ f'type summary add GUID - -python-function '
+ f'{= mod_name}.EFI_GUID_TypeSummary')
+ debugger.HandleCommand(
+ f'ty= pe summary add EFI_GUID --python-function '
+ f'{mod_name}.EFI_GUID_Ty= peSummary')
+ debugger.HandleCommand(
+ f'type summary add EFI_ST= ATUS --python-function '
+ f'{mod_name}.EFI_STATUS_TypeSummary')
= + debugger.HandleCommand(
+ f'type summary add EFI_TPL - -python-funct= ion '
+ f'{mod_name}.EFI_TPL_TypeSummary')
+ debugger.HandleComma= nd(
+ f'type summary add EFI_BOOT_MODE --python-function '
+ f'{m= od_name}.EFI_BOOT_MODE_TypeSummary')
+
+ debugger.HandleCommand(<= br />+ f'type summary add CHAR16 --python-function '
+ f'{mod_name}.CH= AR16_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_Typ= eSummary')
+
+ # W605 this is the correct escape sequence for the= lldb command
+ debugger.HandleCommand(
+ f'type summary add --re= gex "CHAR8 \[[0-9]+\]" ' # noqa: W605
+ f'--python-function {mod_name}= .CHAR8_TypeSummary')
+
+
+class LldbWorkaround:
+ neede= d =3D True
+
+ @classmethod
+ def activate(cls):
+ if c= ls.needed:
+ lldb.debugger.HandleCommand("process handle SIGALRM -n fa= lse")
+ cls.needed =3D False
+
+
+def LoadEmulatorEfiSy= mbols(frame, bp_loc, internal_dict):
+ #
+ # This is an lldb brea= kpoint script, and assumes the breakpoint is on a
+ # function with th= e same prototype as SecGdbScriptBreak(). The
+ # argument names are im= portant as lldb looks them up.
+ #
+ # VOID
+ # SecGdbScript= Break (
+ # 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 s= ymbol file, the length of the FileName, the
+ # load address and a fla= g to indicate if this is a load or unload operation
+ #
+ LldbWor= karound().activate()
+
+ symbols =3D EfiSymbols(frame.thread.proc= ess.target)
+ LoadAddress =3D frame.FindVariable("LoadAddress").unsign= ed
+ if frame.FindVariable("AddSymbolFlag").unsigned =3D=3D 1:
+ = res =3D symbols.address_to_symbols(LoadAddress)
+ else:
+ res =3D= symbols.unload_symbols(LoadAddress)
+ print(res)
+
+ # make= breakpoint command continue
+ return False
+
+
+def __= lldb_init_module(debugger, internal_dict):
+ '''
+ This initializ= er is being run from LLDB in the embedded command interpreter
+ '''+
+ mod_name =3D Path(__file__).stem
+ lldb_type_formaters(deb= ugger, 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')
+ debugg= er.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')
+
+ pr= int('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.G= etSelectedTarget().addr_size)
+ except ValueError:
+ # incase the= script is imported and the debugger has not target
+ # defaults to si= zeof(UINTN) =3D=3D sizeof(UINT64)
+ patch_ctypes()
+
+ try:<= br />+ target =3D debugger.GetSelectedTarget()
+ if target.FindFunctio= ns('SecGdbScriptBreak').symbols:
+ breakpoint =3D target.BreakpointCre= ateByName('SecGdbScriptBreak')
+ # Set the emulator breakpoints, if we= are in the emulator
+ cmd =3D 'breakpoint command add -s python -F '<= br />+ cmd +=3D f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}'
+ debugger.HandleCommand(cmd)
+ print('Type r to run emulator.')
+ else:
+ raise ValueError("No Emulator Symbols")
+
+ exce= pt ValueError:
+ # default action when the script is imported
+ d= ebugger.HandleCommand("efi_symbols --frame --extended")
+ debugger.Han= dleCommand("register read")
+ debugger.HandleCommand("bt all")
+<= br />+
+if __name__ =3D=3D '__main__':
+ pass
--
2.34.= 1
--yaRYGYBt0xwdomHI389E--