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