public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Rebecca Cran" <quic_rcran@quicinc.com>
To: <devel@edk2.groups.io>, Leif Lindholm <quic_llindhol@quicinc.com>,
	Michael D Kinney <michael.d.kinney@intel.com>,
	Hao A Wu <hao.a.wu@intel.com>, Bob Feng <bob.c.feng@intel.com>,
	Liming Gao <gaoliming@byosoft.com.cn>,
	Yuwei Chen <yuwei.chen@intel.com>
Cc: Rebecca Cran <quic_rcran@quicinc.com>, Andrew Fish <afish@apple.com>
Subject: [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb EFI commands and pretty Print
Date: Mon, 21 Mar 2022 14:20:48 -0600	[thread overview]
Message-ID: <20220321202048.13567-4-quic_rcran@quicinc.com> (raw)
In-Reply-To: <20220321202048.13567-1-quic_rcran@quicinc.com>

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


  parent reply	other threads:[~2022-03-21 20:21 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 ` Rebecca Cran [this message]
2022-04-09  4:16   ` [edk2-devel] [PATCH v2 3/3] BaseTools: Scripts/efi_lldb.py: Add lldb " Bob Feng
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=20220321202048.13567-4-quic_rcran@quicinc.com \
    --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