#!/usr/bin/python3
'''
Copyright (c) Apple Inc. 2021
SPDX-License-Identifier: BSD-2-Clause-Patent

Example usage:
OvmfPkg/build.sh qemu -gdb tcp::9000
lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py"
'''

import optparse
import shlex
import subprocess
import uuid
import sys
import os
from pathlib import Path
from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl
from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode
from efi_debugging import PeTeImage, patch_ctypes

try:
    # Just try for LLDB in case PYTHONPATH is already correctly setup
    import lldb
except ImportError:
    try:
        env = os.environ.copy()
        env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
        lldb_python_path = subprocess.check_output(
            ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip()
        sys.path.append(lldb_python_path)
        import lldb
    except ValueError:
        print("Couldn't find LLDB.framework from lldb -P")
        print("PYTHONPATH should match the currently selected lldb")
        sys.exit(-1)


class LldbFileObject(object):
    '''
    Class that fakes out file object to abstract lldb from the generic code.
    For lldb this is memory so we don't have a concept of the end of the file.
    '''

    def __init__(self, process):
        # _exe_ctx is lldb.SBExecutionContext
        self._process = process
        self._offset = 0
        self._SBError = lldb.SBError()

    def tell(self):
        return self._offset

    def read(self, size=-1):
        if size == -1:
            # arbitrary default size
            size = 0x1000000

        data = self._process.ReadMemory(self._offset, size, self._SBError)
        if self._SBError.fail:
            raise MemoryError(
                f'lldb could not read memory 0x{size:x} '
                f' bytes from 0x{self._offset:08x}')
        else:
            return data

    def readable(self):
        return True

    def seek(self, offset, whence=0):
        if whence == 0:
            self._offset = offset
        elif whence == 1:
            self._offset += offset
        else:
            # whence == 2 is seek from end
            raise NotImplementedError

    def seekable(self):
        return True

    def write(self, data):
        result = self._process.WriteMemory(self._offset, data, self._SBError)
        if self._SBError.fail:
            raise MemoryError(
                f'lldb could not write memory to 0x{self._offset:08x}')
        return result

    def writable(self):
        return True

    def truncate(self, size=None):
        raise NotImplementedError

    def flush(self):
        raise NotImplementedError

    def fileno(self):
        raise NotImplementedError


class EfiSymbols:
    """
    Class to manage EFI Symbols
    You need to pass file, and exe_ctx to load symbols.
    You can print(EfiSymbols()) to see the currently loaded symbols
    """

    loaded = {}
    stride = None
    range = None
    verbose = False

    def __init__(self, target=None):
        if target:
            EfiSymbols.target = target
            EfiSymbols._file = LldbFileObject(target.process)

    @ classmethod
    def __str__(cls):
        return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values())

    @ classmethod
    def configure_search(cls, stride, range, verbose=False):
        cls.stride = stride
        cls.range = range
        cls.verbose = verbose

    @ classmethod
    def clear(cls):
        cls.loaded = {}

    @ classmethod
    def add_symbols_for_pecoff(cls, pecoff):
        '''Tell lldb the location of the .text and .data sections.'''

        if pecoff.LoadAddress in cls.loaded:
            return 'Already Loaded: '

        module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid))
        if not module:
            module = cls.target.AddModule(pecoff.CodeViewPdb,
                                          None,
                                          str(pecoff.CodeViewUuid))
        if module.IsValid():
            SBError = cls.target.SetModuleLoadAddress(
                module, pecoff.LoadAddress + pecoff.TeAdjust)
            if SBError.success:
                cls.loaded[pecoff.LoadAddress] = (pecoff, module)
                return ''

        return 'Symbols NOT FOUND: '

    @ classmethod
    def address_to_symbols(cls, address, reprobe=False):
        '''
        Given an address search backwards for a PE/COFF (or TE) header
        and load symbols. Return a status string.
        '''
        if not isinstance(address, int):
            address = int(address)

        pecoff, _ = cls.address_in_loaded_pecoff(address)
        if not reprobe and pecoff is not None:
            # skip the probe of the remote
            return f'{pecoff} is already loaded'

        pecoff = PeTeImage(cls._file, None)
        if pecoff.pcToPeCoff(address, cls.stride, cls.range):
            res = cls.add_symbols_for_pecoff(pecoff)
            return f'{res}{pecoff}'
        else:
            return f'0x{address:08x} not in a PE/COFF (or TE) image'

    @ classmethod
    def address_in_loaded_pecoff(cls, address):
        if not isinstance(address, int):
            address = int(address)

        for (pecoff, module) in cls.loaded.values():
            if (address >= pecoff.LoadAddress and
                    address <= pecoff.EndLoadAddress):

                return pecoff, module

        return None, None

    @ classmethod
    def unload_symbols(cls, address):
        pecoff, module = cls.address_in_loaded_pecoff(address)
        if module:
            name = str(module)
            cls.target.ClearModuleLoadAddress(module)
            cls.target.RemoveModule(module)
            del cls.loaded[pecoff.LoadAddress]
            return f'{name:s} was unloaded'
        return f'0x{address:x} was not in a loaded image'


def arg_to_address(frame, arg):
    ''' convert an lldb command arg into a memory address (addr_t)'''

    if arg is None:
        return None

    arg_str = arg if isinstance(arg, str) else str(arg)
    SBValue = frame.EvaluateExpression(arg_str)
    if SBValue.error.fail:
        return arg

    if (SBValue.TypeIsPointerType() or
            SBValue.value_type == lldb.eValueTypeRegister or
            SBValue.value_type == lldb.eValueTypeRegisterSet or
            SBValue.value_type == lldb.eValueTypeConstResult):
        try:
            addr = SBValue.GetValueAsAddress()
        except ValueError:
            addr = SBValue.unsigned
    else:
        try:
            addr = SBValue.address_of.GetValueAsAddress()
        except ValueError:
            addr = SBValue.address_of.unsigned

    return addr


def arg_to_data(frame, arg):
    ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)'''
    if not isinstance(arg, str):
        arg_str = str(str)

    SBValue = frame.EvaluateExpression(arg_str)
    return SBValue.unsigned


class EfiDevicePathCommand:

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can EFI Config Tables
'''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='devicepath',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-v',
            '--verbose',
            action='store_true',
            dest='verbose',
            help='hex dump extra data',
            default=False)

        self.parser.add_option(
            '-n',
            '--node',
            action='store_true',
            dest='node',
            help='dump a single device path node',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI Tables"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, args) = self.parser.parse_args(command_args)
            dev_list = []
            for arg in args:
                dev_list.append(arg_to_address(exe_ctx.frame, arg))
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        file = LldbFileObject(exe_ctx.process)

        for dev_addr in dev_list:
            if options.node:
                print(EfiDevicePath(file).device_path_node_str(
                    dev_addr, options.verbose))
            else:
                device_path = EfiDevicePath(file, dev_addr, options.verbose)
                if device_path.valid():
                    print(device_path)


class EfiHobCommand:
    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can EFI dump EFI HOBs'''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='table',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-a',
            '--address',
            type="int",
            dest='address',
            help='Parse HOBs from address',
            default=None)

        self.parser.add_option(
            '-t',
            '--type',
            type="int",
            dest='type',
            help='Only dump HOBS of his type',
            default=None)

        self.parser.add_option(
            '-v',
            '--verbose',
            action='store_true',
            dest='verbose',
            help='hex dump extra data',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI Hobs"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, _) = self.parser.parse_args(command_args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        address = arg_to_address(exe_ctx.frame, options.address)

        file = LldbFileObject(exe_ctx.process)
        hob = EfiHob(file, address, options.verbose).get_hob_by_type(
            options.type)
        print(hob)


class EfiTableCommand:

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can display EFI Config Tables
'''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='table',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI Tables"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, _) = self.parser.parse_args(command_args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        gST = exe_ctx.target.FindFirstGlobalVariable('gST')
        if gST.error.fail:
            print('Error: This command requires symbols for gST to be loaded')
            return

        file = LldbFileObject(exe_ctx.process)
        table = EfiConfigurationTable(file, gST.unsigned)
        if table:
            print(table, '\n')


class EfiGuidCommand:

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''
            Command that can display all EFI GUID's or give info on a
            specific GUID's
            '''
        self.parser = optparse.OptionParser(
            description=description,
            prog='guid',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-n',
            '--new',
            action='store_true',
            dest='new',
            help='Generate a new GUID',
            default=False)

        self.parser.add_option(
            '-v',
            '--verbose',
            action='store_true',
            dest='verbose',
            help='Also display GUID C structure values',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI GUID's"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, args) = self.parser.parse_args(command_args)
            if len(args) >= 1:
                # guid { 0x414e6bdd, 0xe47b, 0x47cc,
                #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
                # this generates multiple args
                arg = ' '.join(args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        if options.new:
            guid = uuid.uuid4()
            print(str(guid).upper())
            print(GuidNames.to_c_guid(guid))
            return

        if len(args) > 0:
            if GuidNames.is_guid_str(arg):
                # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9
                key = arg.lower()
                name = GuidNames.to_name(key)
            elif GuidNames.is_c_guid(arg):
                # guid { 0x414e6bdd, 0xe47b, 0x47cc,
                #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
                key = GuidNames.from_c_guid(arg)
                name = GuidNames.to_name(key)
            else:
                # guid gEfiDxeServicesTableGuid
                name = arg
                try:
                    key = GuidNames.to_guid(name)
                    name = GuidNames.to_name(key)
                except ValueError:
                    return

            extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else ''
            print(f'{key}: {extra}{name}')

        else:
            for key, value in GuidNames._dict_.items():
                if options.verbose:
                    extra = f'{GuidNames.to_c_guid(key)}: '
                else:
                    extra = ''
                print(f'{key}: {extra}{value}')


class EfiSymbolicateCommand(object):
    '''Class to abstract an lldb command'''

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can load EFI PE/COFF and TE image
        symbols. If you are having trouble in PEI try adding --pei.
        '''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='efi_symbols',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-a',
            '--address',
            type="int",
            dest='address',
            help='Load symbols for image at address',
            default=None)

        self.parser.add_option(
            '-f',
            '--frame',
            action='store_true',
            dest='frame',
            help='Load symbols for current stack frame',
            default=False)

        self.parser.add_option(
            '-p',
            '--pc',
            action='store_true',
            dest='pc',
            help='Load symbols for pc',
            default=False)

        self.parser.add_option(
            '--pei',
            action='store_true',
            dest='pei',
            help='Load symbols for PEI (searches every 4 bytes)',
            default=False)

        self.parser.add_option(
            '-e',
            '--extended',
            action='store_true',
            dest='extended',
            help='Try to load all symbols based on config tables.',
            default=False)

        self.parser.add_option(
            '-r',
            '--range',
            type="long",
            dest='range',
            help='How far to search backward for start of PE/COFF Image',
            default=None)

        self.parser.add_option(
            '-s',
            '--stride',
            type="long",
            dest='stride',
            help='Boundary to search for PE/COFF header',
            default=None)

        self.parser.add_option(
            '-t',
            '--thread',
            action='store_true',
            dest='thread',
            help='Load symbols for the frames of all threads',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return (
            "Load symbols based on an address that is part of"
            " a PE/COFF EFI image.")

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, unused):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def lldb_print(self, lldb_str):
        # capture command out like an lldb command
        self.result.PutCString(lldb_str)
        # flush the output right away
        self.result.SetImmediateOutputFile(
            self.exe_ctx.target.debugger.GetOutputFile())

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, _) = self.parser.parse_args(command_args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        file = LldbFileObject(exe_ctx.process)
        efi_symbols = EfiSymbols(exe_ctx.target)
        self.result = result
        self.exe_ctx = exe_ctx

        if options.pei:
            # XIP code ends up on a 4 byte boundary.
            options.stride = 4
            options.range = 0x100000
        efi_symbols.configure_search(options.stride, options.range)

        if not options.pc and options.address is None:
            # default to
            options.frame = True

        if options.frame:
            if not exe_ctx.frame.IsValid():
                result.SetError("invalid frame")
                return

            threads = exe_ctx.process.threads if options.thread else [
                exe_ctx.thread]

            for thread in threads:
                for frame in thread:
                    res = efi_symbols.address_to_symbols(frame.pc)
                    self.lldb_print(res)

        else:
            if options.address is not None:
                address = options.address
            elif options.pc:
                try:
                    address = exe_ctx.thread.GetSelectedFrame().pc
                except ValueError:
                    result.SetError("invalid pc")
                    return
            else:
                address = 0

            res = efi_symbols.address_to_symbols(address.pc)
            print(res)

        if options.extended:

            gST = exe_ctx.target.FindFirstGlobalVariable('gST')
            if gST.error.fail:
                print('Error: This command requires symbols to be loaded')
            else:
                table = EfiConfigurationTable(file, gST.unsigned)
                for address, _ in table.DebugImageInfo():
                    res = efi_symbols.address_to_symbols(address)
                    self.lldb_print(res)

        # keep trying module file names until we find a GUID xref file
        for m in exe_ctx.target.modules:
            if GuidNames.add_build_guid_file(str(m.file)):
                break


def CHAR16_TypeSummary(valobj, internal_dict):
    '''
    Display CHAR16 as a String in the debugger.
    Note: utf-8 is returned as that is the value for the debugger.
    '''
    SBError = lldb.SBError()
    Str = ''
    if valobj.TypeIsPointerType():
        if valobj.GetValueAsUnsigned() == 0:
            return "NULL"

        # CHAR16 *   max string size 1024
        for i in range(1024):
            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0)
            if SBError.fail or Char == 0:
                break
            Str += chr(Char)
        return 'L"' + Str + '"'

    if valobj.num_children == 0:
        # CHAR16
        return "L'" + chr(valobj.unsigned) + "'"

    else:
        # CHAR16 []
        for i in range(valobj.num_children):
            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0)
            if Char == 0:
                break
            Str += chr(Char)
        return 'L"' + Str + '"'

    return Str


def CHAR8_TypeSummary(valobj, internal_dict):
    '''
    Display CHAR8 as a String in the debugger.
    Note: utf-8 is returned as that is the value for the debugger.
    '''
    SBError = lldb.SBError()
    Str = ''
    if valobj.TypeIsPointerType():
        if valobj.GetValueAsUnsigned() == 0:
            return "NULL"

        # CHAR8 *   max string size 1024
        for i in range(1024):
            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0)
            if SBError.fail or Char == 0:
                break
            Str += chr(Char)
        Str = '"' + Str + '"'
        return Str

    if valobj.num_children == 0:
        # CHAR8
        return "'" + chr(valobj.unsigned) + "'"
    else:
        # CHAR8 []
        for i in range(valobj.num_children):
            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0)
            if SBError.fail or Char == 0:
                break
            Str += chr(Char)
        return '"' + Str + '"'

    return Str


def EFI_STATUS_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    return str(EfiStatusClass(valobj.unsigned))


def EFI_TPL_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    return str(EfiTpl(valobj.unsigned))


def EFI_GUID_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    return str(GuidNames(bytes(valobj.data.uint8)))


def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    '''Return #define name for EFI_BOOT_MODE'''
    return str(EfiBootMode(valobj.unsigned))


def lldb_type_formaters(debugger, mod_name):
    '''Teach lldb about EFI types'''

    category = debugger.GetDefaultCategory()
    FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool)

    FormatHex = lldb.SBTypeFormat(lldb.eFormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
        "EFI_PHYSICAL_ADDRESS"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
        "PHYSICAL_ADDRESS"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex)
    category.AddTypeFormat(
        lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
        "EFI_FV_FILETYPE"), FormatHex)

    #
    # Smart type printing for EFI
    #

    debugger.HandleCommand(
        f'type summary add GUID - -python-function '
        f'{mod_name}.EFI_GUID_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_GUID --python-function '
        f'{mod_name}.EFI_GUID_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_STATUS --python-function '
        f'{mod_name}.EFI_STATUS_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_TPL - -python-function '
        f'{mod_name}.EFI_TPL_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_BOOT_MODE --python-function '
        f'{mod_name}.EFI_BOOT_MODE_TypeSummary')

    debugger.HandleCommand(
        f'type summary add CHAR16 --python-function '
        f'{mod_name}.CHAR16_TypeSummary')

    # W605 this is the correct escape sequence for the lldb command
    debugger.HandleCommand(
        f'type summary add --regex "CHAR16 \[[0-9]+\]" '  # noqa: W605
        f'--python-function {mod_name}.CHAR16_TypeSummary')

    debugger.HandleCommand(
        f'type summary add CHAR8 --python-function '
        f'{mod_name}.CHAR8_TypeSummary')

    # W605 this is the correct escape sequence for the lldb command
    debugger.HandleCommand(
        f'type summary add --regex "CHAR8 \[[0-9]+\]"  '  # noqa: W605
        f'--python-function {mod_name}.CHAR8_TypeSummary')


class LldbWorkaround:
    needed = True

    @classmethod
    def activate(cls):
        if cls.needed:
            lldb.debugger.HandleCommand("process handle SIGALRM -n false")
            cls.needed = False


def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict):
    #
    # This is an lldb breakpoint script, and assumes the breakpoint is on a
    # function with the same prototype as SecGdbScriptBreak(). The
    # argument names are important as lldb looks them up.
    #
    # VOID
    # SecGdbScriptBreak (
    #   char                *FileName,
    #   int                 FileNameLength,
    #   long unsigned int   LoadAddress,
    #   int                 AddSymbolFlag
    #   )
    # {
    #   return;
    # }
    #
    # When the emulator loads a PE/COFF image, it calls the stub function with
    # the filename of the symbol file, the length of the FileName, the
    # load address and a flag to indicate if this is a load or unload operation
    #
    LldbWorkaround().activate()

    symbols = EfiSymbols(frame.thread.process.target)
    LoadAddress = frame.FindVariable("LoadAddress").unsigned
    if frame.FindVariable("AddSymbolFlag").unsigned == 1:
        res = symbols.address_to_symbols(LoadAddress)
    else:
        res = symbols.unload_symbols(LoadAddress)
    print(res)

    # make breakpoint command continue
    return False


def __lldb_init_module(debugger, internal_dict):
    '''
    This initializer is being run from LLDB in the embedded command interpreter
    '''

    mod_name = Path(__file__).stem
    lldb_type_formaters(debugger, mod_name)

    # Add any commands contained in this module to LLDB
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiGuidCommand guid')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiTableCommand table')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiHobCommand hob')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiDevicePathCommand devicepath')

    print('EFI specific commands have been installed.')

    # patch the ctypes c_void_p values if the debuggers OS and EFI have
    # different ideas on the size of the debug.
    try:
        patch_ctypes(debugger.GetSelectedTarget().addr_size)
    except ValueError:
        # incase the script is imported and the debugger has not target
        # defaults to sizeof(UINTN) == sizeof(UINT64)
        patch_ctypes()

    try:
        target = debugger.GetSelectedTarget()
        if target.FindFunctions('SecGdbScriptBreak').symbols:
            breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak')
            # Set the emulator breakpoints, if we are in the emulator
            cmd = 'breakpoint command add -s python -F '
            cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}'
            debugger.HandleCommand(cmd)
            print('Type r to run emulator.')
        else:
            raise ValueError("No Emulator Symbols")

    except ValueError:
        # default action when the script is imported
        debugger.HandleCommand("efi_symbols --frame --extended")
        debugger.HandleCommand("register read")
        debugger.HandleCommand("bt all")


if __name__ == '__main__':
    pass

#!/usr/bin/python3
'''
Copyright (c) Apple Inc. 2021
SPDX-License-Identifier: BSD-2-Clause-Patent

Example usage:
OvmfPkg/build.sh qemu -gdb tcp::9000
lldb -o "gdb-remote localhost:9000" -o "command script import efi_lldb.py"
'''

import optparse
import shlex
import subprocess
import uuid
import sys
import os
from pathlib import Path
from efi_debugging import EfiDevicePath, EfiConfigurationTable, EfiTpl
from efi_debugging import EfiHob, GuidNames, EfiStatusClass, EfiBootMode
from efi_debugging import PeTeImage, patch_ctypes

try:
    # Just try for LLDB in case PYTHONPATH is already correctly setup
    import lldb
except ImportError:
    try:
        env = os.environ.copy()
        env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
        lldb_python_path = subprocess.check_output(
            ["xcrun", "lldb", "-P"], env=env).decode("utf-8").strip()
        sys.path.append(lldb_python_path)
        import lldb
    except ValueError:
        print("Couldn't find LLDB.framework from lldb -P")
        print("PYTHONPATH should match the currently selected lldb")
        sys.exit(-1)


class LldbFileObject(object):
    '''
    Class that fakes out file object to abstract lldb from the generic code.
    For lldb this is memory so we don't have a concept of the end of the file.
    '''

    def __init__(self, process):
        # _exe_ctx is lldb.SBExecutionContext
        self._process = process
        self._offset = 0
        self._SBError = lldb.SBError()

    def tell(self):
        return self._offset

    def read(self, size=-1):
        if size == -1:
            # arbitrary default size
            size = 0x1000000

        data = self._process.ReadMemory(self._offset, size, self._SBError)
        if self._SBError.fail:
            raise MemoryError(
                f'lldb could not read memory 0x{size:x} '
                f' bytes from 0x{self._offset:08x}')
        else:
            return data

    def readable(self):
        return True

    def seek(self, offset, whence=0):
        if whence == 0:
            self._offset = offset
        elif whence == 1:
            self._offset += offset
        else:
            # whence == 2 is seek from end
            raise NotImplementedError

    def seekable(self):
        return True

    def write(self, data):
        result = self._process.WriteMemory(self._offset, data, self._SBError)
        if self._SBError.fail:
            raise MemoryError(
                f'lldb could not write memory to 0x{self._offset:08x}')
        return result

    def writable(self):
        return True

    def truncate(self, size=None):
        raise NotImplementedError

    def flush(self):
        raise NotImplementedError

    def fileno(self):
        raise NotImplementedError


class EfiSymbols:
    """
    Class to manage EFI Symbols
    You need to pass file, and exe_ctx to load symbols.
    You can print(EfiSymbols()) to see the currently loaded symbols
    """

    loaded = {}
    stride = None
    range = None
    verbose = False

    def __init__(self, target=None):
        if target:
            EfiSymbols.target = target
            EfiSymbols._file = LldbFileObject(target.process)

    @ classmethod
    def __str__(cls):
        return ''.join(f'{pecoff}\n' for (pecoff, _) in cls.loaded.values())

    @ classmethod
    def configure_search(cls, stride, range, verbose=False):
        cls.stride = stride
        cls.range = range
        cls.verbose = verbose

    @ classmethod
    def clear(cls):
        cls.loaded = {}

    @ classmethod
    def add_symbols_for_pecoff(cls, pecoff):
        '''Tell lldb the location of the .text and .data sections.'''

        if pecoff.LoadAddress in cls.loaded:
            return 'Already Loaded: '

        module = cls.target.AddModule(None, None, str(pecoff.CodeViewUuid))
        if not module:
            module = cls.target.AddModule(pecoff.CodeViewPdb,
                                          None,
                                          str(pecoff.CodeViewUuid))
        if module.IsValid():
            SBError = cls.target.SetModuleLoadAddress(
                module, pecoff.LoadAddress + pecoff.TeAdjust)
            if SBError.success:
                cls.loaded[pecoff.LoadAddress] = (pecoff, module)
                return ''

        return 'Symbols NOT FOUND: '

    @ classmethod
    def address_to_symbols(cls, address, reprobe=False):
        '''
        Given an address search backwards for a PE/COFF (or TE) header
        and load symbols. Return a status string.
        '''
        if not isinstance(address, int):
            address = int(address)

        pecoff, _ = cls.address_in_loaded_pecoff(address)
        if not reprobe and pecoff is not None:
            # skip the probe of the remote
            return f'{pecoff} is already loaded'

        pecoff = PeTeImage(cls._file, None)
        if pecoff.pcToPeCoff(address, cls.stride, cls.range):
            res = cls.add_symbols_for_pecoff(pecoff)
            return f'{res}{pecoff}'
        else:
            return f'0x{address:08x} not in a PE/COFF (or TE) image'

    @ classmethod
    def address_in_loaded_pecoff(cls, address):
        if not isinstance(address, int):
            address = int(address)

        for (pecoff, module) in cls.loaded.values():
            if (address >= pecoff.LoadAddress and
                    address <= pecoff.EndLoadAddress):

                return pecoff, module

        return None, None

    @ classmethod
    def unload_symbols(cls, address):
        pecoff, module = cls.address_in_loaded_pecoff(address)
        if module:
            name = str(module)
            cls.target.ClearModuleLoadAddress(module)
            cls.target.RemoveModule(module)
            del cls.loaded[pecoff.LoadAddress]
            return f'{name:s} was unloaded'
        return f'0x{address:x} was not in a loaded image'


def arg_to_address(frame, arg):
    ''' convert an lldb command arg into a memory address (addr_t)'''

    if arg is None:
        return None

    arg_str = arg if isinstance(arg, str) else str(arg)
    SBValue = frame.EvaluateExpression(arg_str)
    if SBValue.error.fail:
        return arg

    if (SBValue.TypeIsPointerType() or
            SBValue.value_type == lldb.eValueTypeRegister or
            SBValue.value_type == lldb.eValueTypeRegisterSet or
            SBValue.value_type == lldb.eValueTypeConstResult):
        try:
            addr = SBValue.GetValueAsAddress()
        except ValueError:
            addr = SBValue.unsigned
    else:
        try:
            addr = SBValue.address_of.GetValueAsAddress()
        except ValueError:
            addr = SBValue.address_of.unsigned

    return addr


def arg_to_data(frame, arg):
    ''' convert an lldb command arg into a data vale (uint32_t/uint64_t)'''
    if not isinstance(arg, str):
        arg_str = str(str)

    SBValue = frame.EvaluateExpression(arg_str)
    return SBValue.unsigned


class EfiDevicePathCommand:

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can EFI Config Tables
'''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='devicepath',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-v',
            '--verbose',
            action='store_true',
            dest='verbose',
            help='hex dump extra data',
            default=False)

        self.parser.add_option(
            '-n',
            '--node',
            action='store_true',
            dest='node',
            help='dump a single device path node',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI Tables"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, args) = self.parser.parse_args(command_args)
            dev_list = []
            for arg in args:
                dev_list.append(arg_to_address(exe_ctx.frame, arg))
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        file = LldbFileObject(exe_ctx.process)

        for dev_addr in dev_list:
            if options.node:
                print(EfiDevicePath(file).device_path_node_str(
                    dev_addr, options.verbose))
            else:
                device_path = EfiDevicePath(file, dev_addr, options.verbose)
                if device_path.valid():
                    print(device_path)


class EfiHobCommand:
    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can EFI dump EFI HOBs'''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='table',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-a',
            '--address',
            type="int",
            dest='address',
            help='Parse HOBs from address',
            default=None)

        self.parser.add_option(
            '-t',
            '--type',
            type="int",
            dest='type',
            help='Only dump HOBS of his type',
            default=None)

        self.parser.add_option(
            '-v',
            '--verbose',
            action='store_true',
            dest='verbose',
            help='hex dump extra data',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI Hobs"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, _) = self.parser.parse_args(command_args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        address = arg_to_address(exe_ctx.frame, options.address)

        file = LldbFileObject(exe_ctx.process)
        hob = EfiHob(file, address, options.verbose).get_hob_by_type(
            options.type)
        print(hob)


class EfiTableCommand:

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can display EFI Config Tables
'''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='table',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI Tables"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, _) = self.parser.parse_args(command_args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        gST = exe_ctx.target.FindFirstGlobalVariable('gST')
        if gST.error.fail:
            print('Error: This command requires symbols for gST to be loaded')
            return

        file = LldbFileObject(exe_ctx.process)
        table = EfiConfigurationTable(file, gST.unsigned)
        if table:
            print(table, '\n')


class EfiGuidCommand:

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''
            Command that can display all EFI GUID's or give info on a
            specific GUID's
            '''
        self.parser = optparse.OptionParser(
            description=description,
            prog='guid',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-n',
            '--new',
            action='store_true',
            dest='new',
            help='Generate a new GUID',
            default=False)

        self.parser.add_option(
            '-v',
            '--verbose',
            action='store_true',
            dest='verbose',
            help='Also display GUID C structure values',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return "Display EFI GUID's"

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, internal_dict):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, args) = self.parser.parse_args(command_args)
            if len(args) >= 1:
                # guid { 0x414e6bdd, 0xe47b, 0x47cc,
                #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
                # this generates multiple args
                arg = ' '.join(args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        if options.new:
            guid = uuid.uuid4()
            print(str(guid).upper())
            print(GuidNames.to_c_guid(guid))
            return

        if len(args) > 0:
            if GuidNames.is_guid_str(arg):
                # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9
                key = arg.lower()
                name = GuidNames.to_name(key)
            elif GuidNames.is_c_guid(arg):
                # guid { 0x414e6bdd, 0xe47b, 0x47cc,
                #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }}
                key = GuidNames.from_c_guid(arg)
                name = GuidNames.to_name(key)
            else:
                # guid gEfiDxeServicesTableGuid
                name = arg
                try:
                    key = GuidNames.to_guid(name)
                    name = GuidNames.to_name(key)
                except ValueError:
                    return

            extra = f'{GuidNames.to_c_guid(key)}: ' if options.verbose else ''
            print(f'{key}: {extra}{name}')

        else:
            for key, value in GuidNames._dict_.items():
                if options.verbose:
                    extra = f'{GuidNames.to_c_guid(key)}: '
                else:
                    extra = ''
                print(f'{key}: {extra}{value}')


class EfiSymbolicateCommand(object):
    '''Class to abstract an lldb command'''

    def create_options(self):
        ''' standard lldb command help/options parser'''
        usage = "usage: %prog [options]"
        description = '''Command that can load EFI PE/COFF and TE image
        symbols. If you are having trouble in PEI try adding --pei.
        '''

        # Pass add_help_option = False, since this keeps the command in line
        # with lldb commands, and we wire up "help command" to work by
        # providing the long & short help methods below.
        self.parser = optparse.OptionParser(
            description=description,
            prog='efi_symbols',
            usage=usage,
            add_help_option=False)

        self.parser.add_option(
            '-a',
            '--address',
            type="int",
            dest='address',
            help='Load symbols for image at address',
            default=None)

        self.parser.add_option(
            '-f',
            '--frame',
            action='store_true',
            dest='frame',
            help='Load symbols for current stack frame',
            default=False)

        self.parser.add_option(
            '-p',
            '--pc',
            action='store_true',
            dest='pc',
            help='Load symbols for pc',
            default=False)

        self.parser.add_option(
            '--pei',
            action='store_true',
            dest='pei',
            help='Load symbols for PEI (searches every 4 bytes)',
            default=False)

        self.parser.add_option(
            '-e',
            '--extended',
            action='store_true',
            dest='extended',
            help='Try to load all symbols based on config tables.',
            default=False)

        self.parser.add_option(
            '-r',
            '--range',
            type="long",
            dest='range',
            help='How far to search backward for start of PE/COFF Image',
            default=None)

        self.parser.add_option(
            '-s',
            '--stride',
            type="long",
            dest='stride',
            help='Boundary to search for PE/COFF header',
            default=None)

        self.parser.add_option(
            '-t',
            '--thread',
            action='store_true',
            dest='thread',
            help='Load symbols for the frames of all threads',
            default=False)

        self.parser.add_option(
            '-h',
            '--help',
            action='store_true',
            dest='help',
            help='Show help for the command',
            default=False)

    def get_short_help(self):
        '''standard lldb function method'''
        return (
            "Load symbols based on an address that is part of"
            " a PE/COFF EFI image.")

    def get_long_help(self):
        '''standard lldb function method'''
        return self.help_string

    def __init__(self, debugger, unused):
        '''standard lldb function method'''
        self.create_options()
        self.help_string = self.parser.format_help()

    def lldb_print(self, lldb_str):
        # capture command out like an lldb command
        self.result.PutCString(lldb_str)
        # flush the output right away
        self.result.SetImmediateOutputFile(
            self.exe_ctx.target.debugger.GetOutputFile())

    def __call__(self, debugger, command, exe_ctx, result):
        '''standard lldb function method'''
        # Use the Shell Lexer to properly parse up command options just like a
        # shell would
        command_args = shlex.split(command)

        try:
            (options, _) = self.parser.parse_args(command_args)
        except ValueError:
            # if you don't handle exceptions, passing an incorrect argument
            # to the OptionParser will cause LLDB to exit (courtesy of
            # OptParse dealing with argument errors by throwing SystemExit)
            result.SetError("option parsing failed")
            return

        if options.help:
            self.parser.print_help()
            return

        file = LldbFileObject(exe_ctx.process)
        efi_symbols = EfiSymbols(exe_ctx.target)
        self.result = result
        self.exe_ctx = exe_ctx

        if options.pei:
            # XIP code ends up on a 4 byte boundary.
            options.stride = 4
            options.range = 0x100000
        efi_symbols.configure_search(options.stride, options.range)

        if not options.pc and options.address is None:
            # default to
            options.frame = True

        if options.frame:
            if not exe_ctx.frame.IsValid():
                result.SetError("invalid frame")
                return

            threads = exe_ctx.process.threads if options.thread else [
                exe_ctx.thread]

            for thread in threads:
                for frame in thread:
                    res = efi_symbols.address_to_symbols(frame.pc)
                    self.lldb_print(res)

        else:
            if options.address is not None:
                address = options.address
            elif options.pc:
                try:
                    address = exe_ctx.thread.GetSelectedFrame().pc
                except ValueError:
                    result.SetError("invalid pc")
                    return
            else:
                address = 0

            res = efi_symbols.address_to_symbols(address.pc)
            print(res)

        if options.extended:

            gST = exe_ctx.target.FindFirstGlobalVariable('gST')
            if gST.error.fail:
                print('Error: This command requires symbols to be loaded')
            else:
                table = EfiConfigurationTable(file, gST.unsigned)
                for address, _ in table.DebugImageInfo():
                    res = efi_symbols.address_to_symbols(address)
                    self.lldb_print(res)

        # keep trying module file names until we find a GUID xref file
        for m in exe_ctx.target.modules:
            if GuidNames.add_build_guid_file(str(m.file)):
                break


def CHAR16_TypeSummary(valobj, internal_dict):
    '''
    Display CHAR16 as a String in the debugger.
    Note: utf-8 is returned as that is the value for the debugger.
    '''
    SBError = lldb.SBError()
    Str = ''
    if valobj.TypeIsPointerType():
        if valobj.GetValueAsUnsigned() == 0:
            return "NULL"

        # CHAR16 *   max string size 1024
        for i in range(1024):
            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt16(SBError, 0)
            if SBError.fail or Char == 0:
                break
            Str += chr(Char)
        return 'L"' + Str + '"'

    if valobj.num_children == 0:
        # CHAR16
        return "L'" + chr(valobj.unsigned) + "'"

    else:
        # CHAR16 []
        for i in range(valobj.num_children):
            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt16(SBError, 0)
            if Char == 0:
                break
            Str += chr(Char)
        return 'L"' + Str + '"'

    return Str


def CHAR8_TypeSummary(valobj, internal_dict):
    '''
    Display CHAR8 as a String in the debugger.
    Note: utf-8 is returned as that is the value for the debugger.
    '''
    SBError = lldb.SBError()
    Str = ''
    if valobj.TypeIsPointerType():
        if valobj.GetValueAsUnsigned() == 0:
            return "NULL"

        # CHAR8 *   max string size 1024
        for i in range(1024):
            Char = valobj.GetPointeeData(i, 1).GetUnsignedInt8(SBError, 0)
            if SBError.fail or Char == 0:
                break
            Str += chr(Char)
        Str = '"' + Str + '"'
        return Str

    if valobj.num_children == 0:
        # CHAR8
        return "'" + chr(valobj.unsigned) + "'"
    else:
        # CHAR8 []
        for i in range(valobj.num_children):
            Char = valobj.GetChildAtIndex(i).data.GetUnsignedInt8(SBError, 0)
            if SBError.fail or Char == 0:
                break
            Str += chr(Char)
        return '"' + Str + '"'

    return Str


def EFI_STATUS_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    return str(EfiStatusClass(valobj.unsigned))


def EFI_TPL_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    return str(EfiTpl(valobj.unsigned))


def EFI_GUID_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    return str(GuidNames(bytes(valobj.data.uint8)))


def EFI_BOOT_MODE_TypeSummary(valobj, internal_dict):
    if valobj.TypeIsPointerType():
        return ''
    '''Return #define name for EFI_BOOT_MODE'''
    return str(EfiBootMode(valobj.unsigned))


def lldb_type_formaters(debugger, mod_name):
    '''Teach lldb about EFI types'''

    category = debugger.GetDefaultCategory()
    FormatBool = lldb.SBTypeFormat(lldb.eFormatBoolean)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("BOOLEAN"), FormatBool)

    FormatHex = lldb.SBTypeFormat(lldb.eFormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT64"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT64"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT32"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT32"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT16"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT16"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINT8"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INT8"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("UINTN"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("INTN"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR8"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("CHAR16"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
        "EFI_PHYSICAL_ADDRESS"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
        "PHYSICAL_ADDRESS"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier("EFI_LBA"), FormatHex)
    category.AddTypeFormat(
        lldb.SBTypeNameSpecifier("EFI_BOOT_MODE"), FormatHex)
    category.AddTypeFormat(lldb.SBTypeNameSpecifier(
        "EFI_FV_FILETYPE"), FormatHex)

    #
    # Smart type printing for EFI
    #

    debugger.HandleCommand(
        f'type summary add GUID - -python-function '
        f'{mod_name}.EFI_GUID_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_GUID --python-function '
        f'{mod_name}.EFI_GUID_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_STATUS --python-function '
        f'{mod_name}.EFI_STATUS_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_TPL - -python-function '
        f'{mod_name}.EFI_TPL_TypeSummary')
    debugger.HandleCommand(
        f'type summary add EFI_BOOT_MODE --python-function '
        f'{mod_name}.EFI_BOOT_MODE_TypeSummary')

    debugger.HandleCommand(
        f'type summary add CHAR16 --python-function '
        f'{mod_name}.CHAR16_TypeSummary')

    # W605 this is the correct escape sequence for the lldb command
    debugger.HandleCommand(
        f'type summary add --regex "CHAR16 \[[0-9]+\]" '  # noqa: W605
        f'--python-function {mod_name}.CHAR16_TypeSummary')

    debugger.HandleCommand(
        f'type summary add CHAR8 --python-function '
        f'{mod_name}.CHAR8_TypeSummary')

    # W605 this is the correct escape sequence for the lldb command
    debugger.HandleCommand(
        f'type summary add --regex "CHAR8 \[[0-9]+\]"  '  # noqa: W605
        f'--python-function {mod_name}.CHAR8_TypeSummary')


class LldbWorkaround:
    needed = True

    @classmethod
    def activate(cls):
        if cls.needed:
            lldb.debugger.HandleCommand("process handle SIGALRM -n false")
            cls.needed = False


def LoadEmulatorEfiSymbols(frame, bp_loc, internal_dict):
    #
    # This is an lldb breakpoint script, and assumes the breakpoint is on a
    # function with the same prototype as SecGdbScriptBreak(). The
    # argument names are important as lldb looks them up.
    #
    # VOID
    # SecGdbScriptBreak (
    #   char                *FileName,
    #   int                 FileNameLength,
    #   long unsigned int   LoadAddress,
    #   int                 AddSymbolFlag
    #   )
    # {
    #   return;
    # }
    #
    # When the emulator loads a PE/COFF image, it calls the stub function with
    # the filename of the symbol file, the length of the FileName, the
    # load address and a flag to indicate if this is a load or unload operation
    #
    LldbWorkaround().activate()

    symbols = EfiSymbols(frame.thread.process.target)
    LoadAddress = frame.FindVariable("LoadAddress").unsigned
    if frame.FindVariable("AddSymbolFlag").unsigned == 1:
        res = symbols.address_to_symbols(LoadAddress)
    else:
        res = symbols.unload_symbols(LoadAddress)
    print(res)

    # make breakpoint command continue
    return False


def __lldb_init_module(debugger, internal_dict):
    '''
    This initializer is being run from LLDB in the embedded command interpreter
    '''

    mod_name = Path(__file__).stem
    lldb_type_formaters(debugger, mod_name)

    # Add any commands contained in this module to LLDB
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiSymbolicateCommand efi_symbols')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiGuidCommand guid')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiTableCommand table')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiHobCommand hob')
    debugger.HandleCommand(
        f'command script add -c {mod_name}.EfiDevicePathCommand devicepath')

    print('EFI specific commands have been installed.')

    # patch the ctypes c_void_p values if the debuggers OS and EFI have
    # different ideas on the size of the debug.
    try:
        patch_ctypes(debugger.GetSelectedTarget().addr_size)
    except ValueError:
        # incase the script is imported and the debugger has not target
        # defaults to sizeof(UINTN) == sizeof(UINT64)
        patch_ctypes()

    try:
        target = debugger.GetSelectedTarget()
        if target.FindFunctions('SecGdbScriptBreak').symbols:
            breakpoint = target.BreakpointCreateByName('SecGdbScriptBreak')
            # Set the emulator breakpoints, if we are in the emulator
            cmd = 'breakpoint command add -s python -F '
            cmd += f'efi_lldb.LoadEmulatorEfiSymbols {breakpoint.GetID()}'
            debugger.HandleCommand(cmd)
            print('Type r to run emulator.')
        else:
            raise ValueError("No Emulator Symbols")

    except ValueError:
        # default action when the script is imported
        debugger.HandleCommand("efi_symbols --frame --extended")
        debugger.HandleCommand("register read")
        debugger.HandleCommand("bt all")


if __name__ == '__main__':
    pass

#!/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" # Just try for LLDB in case PYTHONPATH is already correctly setup 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. # _exe_ctx is lldb.SBExecutionContext # arbitrary default size # whence == 2 is seek from end 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 Tell lldb the location of the .text and .data sections. Given an address search backwards for a PE/COFF (or TE) header
         and load symbols. Return a status string. # skip the probe of the remote convert an lldb command arg into a memory address (addr_t) convert an lldb command arg into a data vale (uint32_t/uint64_t) standard lldb command help/options parser 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. standard lldb function method standard lldb function method standard lldb function method standard lldb function method # Use the Shell Lexer to properly parse up command options just like a # shell would # 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) standard lldb command help/options parser 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. standard lldb function method standard lldb function method standard lldb function method standard lldb function method # Use the Shell Lexer to properly parse up command options just like a # shell would # 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) standard lldb command help/options parser 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. standard lldb function method standard lldb function method standard lldb function method standard lldb function method # Use the Shell Lexer to properly parse up command options just like a # shell would # 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) standard lldb command help/options parser Command that can display all EFI GUID's or give info on a
             specific GUID's standard lldb function method standard lldb function method standard lldb function method standard lldb function method # Use the Shell Lexer to properly parse up command options just like a # shell would # guid { 0x414e6bdd, 0xe47b, 0x47cc, #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} # this generates multiple args # 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) # guid 05AD34BA-6F02-4214-952E-4DA0398E2BB9 # guid { 0x414e6bdd, 0xe47b, 0x47cc, #      { 0xb2, 0x44, 0xbb, 0x61, 0x02, 0x0c,0xf5, 0x16 }} # guid gEfiDxeServicesTableGuid Class to abstract an lldb command standard lldb command help/options parser 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. standard lldb function method standard lldb function method standard lldb function method # capture command out like an lldb command # flush the output right away standard lldb function method # Use the Shell Lexer to properly parse up command options just like a # shell would # 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) # XIP code ends up on a 4 byte boundary. # default to # keep trying module file names until we find a GUID xref file Display CHAR16 as a String in the debugger.
     Note: utf-8 is returned as that is the value for the debugger. # CHAR16 *   max string size 1024 # CHAR16 # CHAR16 [] Display CHAR8 as a String in the debugger.
     Note: utf-8 is returned as that is the value for the debugger. # CHAR8 *   max string size 1024 # CHAR8 # CHAR8 [] Return #define name for EFI_BOOT_MODE Teach lldb about EFI types # # Smart type printing for EFI # # W605 this is the correct escape sequence for the lldb command # noqa: W605 # W605 this is the correct escape sequence for the lldb command # noqa: W605 # # 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 # # make breakpoint command continue This initializer is being run from LLDB in the embedded command interpreter # Add any commands contained in this module to LLDB # patch the ctypes c_void_p values if the debuggers OS and EFI have # different ideas on the size of the debug. # incase the script is imported and the debugger has not target # defaults to sizeof(UINTN) == sizeof(UINT64) # Set the emulator breakpoints, if we are in the emulator # default action when the script is imported