diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h index 92cfca4227cf..a21a631f0300 100644 --- a/lldb/include/lldb/Target/StackFrameRecognizer.h +++ b/lldb/include/lldb/Target/StackFrameRecognizer.h @@ -1,167 +1,167 @@ //===-- StackFrameRecognizer.h ----------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef liblldb_StackFrameRecognizer_h_ #define liblldb_StackFrameRecognizer_h_ #include "lldb/Core/ValueObject.h" #include "lldb/Core/ValueObjectList.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/StopInfo.h" #include "lldb/Utility/StructuredData.h" #include "lldb/lldb-private-forward.h" #include "lldb/lldb-public.h" namespace lldb_private { /// \class RecognizedStackFrame /// /// This class provides extra information about a stack frame that was /// provided by a specific stack frame recognizer. Right now, this class only /// holds recognized arguments (via GetRecognizedArguments). class RecognizedStackFrame : public std::enable_shared_from_this { public: virtual lldb::ValueObjectListSP GetRecognizedArguments() { return m_arguments; } virtual lldb::ValueObjectSP GetExceptionObject() { return lldb::ValueObjectSP(); } virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; }; virtual ~RecognizedStackFrame(){}; std::string GetStopDescription() { return m_stop_desc; } protected: lldb::ValueObjectListSP m_arguments; std::string m_stop_desc; }; /// \class StackFrameRecognizer /// /// A base class for frame recognizers. Subclasses (actual frame recognizers) /// should implement RecognizeFrame to provide a RecognizedStackFrame for a /// given stack frame. class StackFrameRecognizer : public std::enable_shared_from_this { public: virtual lldb::RecognizedStackFrameSP RecognizeFrame( lldb::StackFrameSP frame) { return lldb::RecognizedStackFrameSP(); }; virtual std::string GetName() { return ""; } virtual ~StackFrameRecognizer(){}; }; /// \class ScriptedStackFrameRecognizer /// /// Python implementation for frame recognizers. An instance of this class /// tracks a particular Python classobject, which will be asked to recognize /// stack frames. class ScriptedStackFrameRecognizer : public StackFrameRecognizer { lldb_private::ScriptInterpreter *m_interpreter; lldb_private::StructuredData::ObjectSP m_python_object_sp; std::string m_python_class; public: ScriptedStackFrameRecognizer(lldb_private::ScriptInterpreter *interpreter, const char *pclass); ~ScriptedStackFrameRecognizer() override {} std::string GetName() override { return GetPythonClassName(); } const char *GetPythonClassName() { return m_python_class.c_str(); } lldb::RecognizedStackFrameSP RecognizeFrame( lldb::StackFrameSP frame) override; private: DISALLOW_COPY_AND_ASSIGN(ScriptedStackFrameRecognizer); }; /// \class StackFrameRecognizerManager /// /// Static class that provides a registry of known stack frame recognizers. /// Has static methods to add, enumerate, remove, query and invoke recognizers. class StackFrameRecognizerManager { public: static void AddRecognizer(lldb::StackFrameRecognizerSP recognizer, - ConstString module, ConstString symbol, - ConstString alternate_symbol, + ConstString module, + llvm::ArrayRef symbols, bool first_instruction_only = true); static void AddRecognizer(lldb::StackFrameRecognizerSP recognizer, lldb::RegularExpressionSP module, lldb::RegularExpressionSP symbol, bool first_instruction_only = true); - static void ForEach( - std::function const - &callback); + static void + ForEach(std::function symbols, + bool regexp)> const &callback); static bool RemoveRecognizerWithID(uint32_t recognizer_id); static void RemoveAllRecognizers(); static lldb::StackFrameRecognizerSP GetRecognizerForFrame( lldb::StackFrameSP frame); static lldb::RecognizedStackFrameSP RecognizeFrame(lldb::StackFrameSP frame); }; /// \class ValueObjectRecognizerSynthesizedValue /// /// ValueObject subclass that presents the passed ValueObject as a recognized /// value with the specified ValueType. Frame recognizers should return /// instances of this class as the returned objects in GetRecognizedArguments(). class ValueObjectRecognizerSynthesizedValue : public ValueObject { public: static lldb::ValueObjectSP Create(ValueObject &parent, lldb::ValueType type) { return (new ValueObjectRecognizerSynthesizedValue(parent, type))->GetSP(); } ValueObjectRecognizerSynthesizedValue(ValueObject &parent, lldb::ValueType type) : ValueObject(parent), m_type(type) { SetName(parent.GetName()); } uint64_t GetByteSize() override { return m_parent->GetByteSize(); } lldb::ValueType GetValueType() const override { return m_type; } bool UpdateValue() override { if (!m_parent->UpdateValueIfNeeded()) return false; m_value = m_parent->GetValue(); return true; } size_t CalculateNumChildren(uint32_t max = UINT32_MAX) override { return m_parent->GetNumChildren(max); } CompilerType GetCompilerTypeImpl() override { return m_parent->GetCompilerType(); } bool IsSynthetic() override { return true; } private: lldb::ValueType m_type; }; } // namespace lldb_private #endif // liblldb_StackFrameRecognizer_h_ diff --git a/lldb/packages/Python/lldbsuite/test/lldbutil.py b/lldb/packages/Python/lldbsuite/test/lldbutil.py index 006362a4479c..98d7b7e13cba 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbutil.py +++ b/lldb/packages/Python/lldbsuite/test/lldbutil.py @@ -1,1422 +1,1422 @@ """ This LLDB module contains miscellaneous utilities. Some of the test suite takes advantage of the utility functions defined here. They can also be useful for general purpose lldb scripting. """ from __future__ import print_function from __future__ import absolute_import # System modules import errno import os import re import sys # Third-party modules from six import StringIO as SixStringIO import six # LLDB modules import lldb from . import lldbtest_config # =================================================== # Utilities for locating/checking executable programs # =================================================== def is_exe(fpath): """Returns True if fpath is an executable.""" return os.path.isfile(fpath) and os.access(fpath, os.X_OK) def which(program): """Returns the full path to a program; None otherwise.""" fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def mkdir_p(path): try: os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST: raise if not os.path.isdir(path): raise OSError(errno.ENOTDIR, "%s is not a directory"%path) # =================================================== # Disassembly for an SBFunction or an SBSymbol object # =================================================== def disassemble(target, function_or_symbol): """Disassemble the function or symbol given a target. It returns the disassembly content in a string object. """ buf = SixStringIO() insts = function_or_symbol.GetInstructions(target) for i in insts: print(i, file=buf) return buf.getvalue() # ========================================================== # Integer (byte size 1, 2, 4, and 8) to bytearray conversion # ========================================================== def int_to_bytearray(val, bytesize): """Utility function to convert an integer into a bytearray. It returns the bytearray in the little endian format. It is easy to get the big endian format, just do ba.reverse() on the returned object. """ import struct if bytesize == 1: return bytearray([val]) # Little endian followed by a format character. template = "<%c" if bytesize == 2: fmt = template % 'h' elif bytesize == 4: fmt = template % 'i' elif bytesize == 4: fmt = template % 'q' else: return None packed = struct.pack(fmt, val) return bytearray(packed) def bytearray_to_int(bytes, bytesize): """Utility function to convert a bytearray into an integer. It interprets the bytearray in the little endian format. For a big endian bytearray, just do ba.reverse() on the object before passing it in. """ import struct if bytesize == 1: return bytes[0] # Little endian followed by a format character. template = "<%c" if bytesize == 2: fmt = template % 'h' elif bytesize == 4: fmt = template % 'i' elif bytesize == 4: fmt = template % 'q' else: return None unpacked = struct.unpack_from(fmt, bytes) return unpacked[0] # ============================================================== # Get the description of an lldb object or None if not available # ============================================================== def get_description(obj, option=None): """Calls lldb_obj.GetDescription() and returns a string, or None. For SBTarget, SBBreakpointLocation, and SBWatchpoint lldb objects, an extra option can be passed in to describe the detailed level of description desired: o lldb.eDescriptionLevelBrief o lldb.eDescriptionLevelFull o lldb.eDescriptionLevelVerbose """ method = getattr(obj, 'GetDescription') if not method: return None tuple = (lldb.SBTarget, lldb.SBBreakpointLocation, lldb.SBWatchpoint) if isinstance(obj, tuple): if option is None: option = lldb.eDescriptionLevelBrief stream = lldb.SBStream() if option is None: success = method(stream) else: success = method(stream, option) if not success: return None return stream.GetData() # ================================================= # Convert some enum value to its string counterpart # ================================================= def state_type_to_str(enum): """Returns the stateType string given an enum.""" if enum == lldb.eStateInvalid: return "invalid" elif enum == lldb.eStateUnloaded: return "unloaded" elif enum == lldb.eStateConnected: return "connected" elif enum == lldb.eStateAttaching: return "attaching" elif enum == lldb.eStateLaunching: return "launching" elif enum == lldb.eStateStopped: return "stopped" elif enum == lldb.eStateRunning: return "running" elif enum == lldb.eStateStepping: return "stepping" elif enum == lldb.eStateCrashed: return "crashed" elif enum == lldb.eStateDetached: return "detached" elif enum == lldb.eStateExited: return "exited" elif enum == lldb.eStateSuspended: return "suspended" else: raise Exception("Unknown StateType enum") def stop_reason_to_str(enum): """Returns the stopReason string given an enum.""" if enum == lldb.eStopReasonInvalid: return "invalid" elif enum == lldb.eStopReasonNone: return "none" elif enum == lldb.eStopReasonTrace: return "trace" elif enum == lldb.eStopReasonBreakpoint: return "breakpoint" elif enum == lldb.eStopReasonWatchpoint: return "watchpoint" elif enum == lldb.eStopReasonExec: return "exec" elif enum == lldb.eStopReasonSignal: return "signal" elif enum == lldb.eStopReasonException: return "exception" elif enum == lldb.eStopReasonPlanComplete: return "plancomplete" elif enum == lldb.eStopReasonThreadExiting: return "threadexiting" else: raise Exception("Unknown StopReason enum") def symbol_type_to_str(enum): """Returns the symbolType string given an enum.""" if enum == lldb.eSymbolTypeInvalid: return "invalid" elif enum == lldb.eSymbolTypeAbsolute: return "absolute" elif enum == lldb.eSymbolTypeCode: return "code" elif enum == lldb.eSymbolTypeData: return "data" elif enum == lldb.eSymbolTypeTrampoline: return "trampoline" elif enum == lldb.eSymbolTypeRuntime: return "runtime" elif enum == lldb.eSymbolTypeException: return "exception" elif enum == lldb.eSymbolTypeSourceFile: return "sourcefile" elif enum == lldb.eSymbolTypeHeaderFile: return "headerfile" elif enum == lldb.eSymbolTypeObjectFile: return "objectfile" elif enum == lldb.eSymbolTypeCommonBlock: return "commonblock" elif enum == lldb.eSymbolTypeBlock: return "block" elif enum == lldb.eSymbolTypeLocal: return "local" elif enum == lldb.eSymbolTypeParam: return "param" elif enum == lldb.eSymbolTypeVariable: return "variable" elif enum == lldb.eSymbolTypeVariableType: return "variabletype" elif enum == lldb.eSymbolTypeLineEntry: return "lineentry" elif enum == lldb.eSymbolTypeLineHeader: return "lineheader" elif enum == lldb.eSymbolTypeScopeBegin: return "scopebegin" elif enum == lldb.eSymbolTypeScopeEnd: return "scopeend" elif enum == lldb.eSymbolTypeAdditional: return "additional" elif enum == lldb.eSymbolTypeCompiler: return "compiler" elif enum == lldb.eSymbolTypeInstrumentation: return "instrumentation" elif enum == lldb.eSymbolTypeUndefined: return "undefined" def value_type_to_str(enum): """Returns the valueType string given an enum.""" if enum == lldb.eValueTypeInvalid: return "invalid" elif enum == lldb.eValueTypeVariableGlobal: return "global_variable" elif enum == lldb.eValueTypeVariableStatic: return "static_variable" elif enum == lldb.eValueTypeVariableArgument: return "argument_variable" elif enum == lldb.eValueTypeVariableLocal: return "local_variable" elif enum == lldb.eValueTypeRegister: return "register" elif enum == lldb.eValueTypeRegisterSet: return "register_set" elif enum == lldb.eValueTypeConstResult: return "constant_result" else: raise Exception("Unknown ValueType enum") # ================================================== # Get stopped threads due to each stop reason. # ================================================== def sort_stopped_threads(process, breakpoint_threads=None, crashed_threads=None, watchpoint_threads=None, signal_threads=None, exiting_threads=None, other_threads=None): """ Fills array *_threads with threads stopped for the corresponding stop reason. """ for lst in [breakpoint_threads, watchpoint_threads, signal_threads, exiting_threads, other_threads]: if lst is not None: lst[:] = [] for thread in process: dispatched = False for (reason, list) in [(lldb.eStopReasonBreakpoint, breakpoint_threads), (lldb.eStopReasonException, crashed_threads), (lldb.eStopReasonWatchpoint, watchpoint_threads), (lldb.eStopReasonSignal, signal_threads), (lldb.eStopReasonThreadExiting, exiting_threads), (None, other_threads)]: if not dispatched and list is not None: if thread.GetStopReason() == reason or reason is None: list.append(thread) dispatched = True # ================================================== # Utility functions for setting breakpoints # ================================================== def run_break_set_by_script( test, class_name, extra_options=None, num_expected_locations=1): """Set a scripted breakpoint. Check that it got the right number of locations.""" test.assertTrue(class_name is not None, "Must pass in a class name.") command = "breakpoint set -P " + class_name if extra_options is not None: command += " " + extra_options break_results = run_break_set_command(test, command) check_breakpoint_result(test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) def run_break_set_by_file_and_line( test, file_name, line_number, extra_options=None, num_expected_locations=1, loc_exact=False, module_name=None): """Set a breakpoint by file and line, returning the breakpoint number. If extra_options is not None, then we append it to the breakpoint set command. If num_expected_locations is -1, we check that we got AT LEAST one location. If num_expected_locations is -2, we don't check the actual number at all. Otherwise, we check that num_expected_locations equals the number of locations. If loc_exact is true, we check that there is one location, and that location must be at the input file and line number.""" if file_name is None: command = 'breakpoint set -l %d' % (line_number) else: command = 'breakpoint set -f "%s" -l %d' % (file_name, line_number) if module_name: command += " --shlib '%s'" % (module_name) if extra_options: command += " " + extra_options break_results = run_break_set_command(test, command) if num_expected_locations == 1 and loc_exact: check_breakpoint_result( test, break_results, num_locations=num_expected_locations, file_name=file_name, line_number=line_number, module_name=module_name) else: check_breakpoint_result( test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) def run_break_set_by_symbol( test, symbol, extra_options=None, num_expected_locations=-1, sym_exact=False, module_name=None): """Set a breakpoint by symbol name. Common options are the same as run_break_set_by_file_and_line. If sym_exact is true, then the output symbol must match the input exactly, otherwise we do a substring match.""" command = 'breakpoint set -n "%s"' % (symbol) if module_name: command += " --shlib '%s'" % (module_name) if extra_options: command += " " + extra_options break_results = run_break_set_command(test, command) if num_expected_locations == 1 and sym_exact: check_breakpoint_result( test, break_results, num_locations=num_expected_locations, symbol_name=symbol, module_name=module_name) else: check_breakpoint_result( test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) def run_break_set_by_selector( test, selector, extra_options=None, num_expected_locations=-1, module_name=None): """Set a breakpoint by selector. Common options are the same as run_break_set_by_file_and_line.""" command = 'breakpoint set -S "%s"' % (selector) if module_name: command += ' --shlib "%s"' % (module_name) if extra_options: command += " " + extra_options break_results = run_break_set_command(test, command) if num_expected_locations == 1: check_breakpoint_result( test, break_results, num_locations=num_expected_locations, symbol_name=selector, symbol_match_exact=False, module_name=module_name) else: check_breakpoint_result( test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) def run_break_set_by_regexp( test, regexp, extra_options=None, num_expected_locations=-1): """Set a breakpoint by regular expression match on symbol name. Common options are the same as run_break_set_by_file_and_line.""" command = 'breakpoint set -r "%s"' % (regexp) if extra_options: command += " " + extra_options break_results = run_break_set_command(test, command) check_breakpoint_result( test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) def run_break_set_by_source_regexp( test, regexp, extra_options=None, num_expected_locations=-1): """Set a breakpoint by source regular expression. Common options are the same as run_break_set_by_file_and_line.""" command = 'breakpoint set -p "%s"' % (regexp) if extra_options: command += " " + extra_options break_results = run_break_set_command(test, command) check_breakpoint_result( test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) def run_break_set_command(test, command): """Run the command passed in - it must be some break set variant - and analyze the result. Returns a dictionary of information gleaned from the command-line results. Will assert if the breakpoint setting fails altogether. Dictionary will contain: bpno - breakpoint of the newly created breakpoint, -1 on error. num_locations - number of locations set for the breakpoint. If there is only one location, the dictionary MAY contain: file - source file name line_no - source line number symbol - symbol name inline_symbol - inlined symbol name offset - offset from the original symbol module - module address - address at which the breakpoint was set.""" patterns = [ r"^Breakpoint (?P[0-9]+): (?P[0-9]+) locations\.$", r"^Breakpoint (?P[0-9]+): (?Pno) locations \(pending\)\.", r"^Breakpoint (?P[0-9]+): where = (?P.*)`(?P[+\-]{0,1}[^+]+)( \+ (?P[0-9]+)){0,1}( \[inlined\] (?P.*)){0,1} at (?P[^:]+):(?P[0-9]+)(?P(:[0-9]+)?), address = (?P
0x[0-9a-fA-F]+)$", r"^Breakpoint (?P[0-9]+): where = (?P.*)`(?P.*)( \+ (?P[0-9]+)){0,1}, address = (?P
0x[0-9a-fA-F]+)$"] match_object = test.match(command, patterns) break_results = match_object.groupdict() # We always insert the breakpoint number, setting it to -1 if we couldn't find it # Also, make sure it gets stored as an integer. if not 'bpno' in break_results: break_results['bpno'] = -1 else: break_results['bpno'] = int(break_results['bpno']) # We always insert the number of locations # If ONE location is set for the breakpoint, then the output doesn't mention locations, but it has to be 1... # We also make sure it is an integer. if not 'num_locations' in break_results: num_locations = 1 else: num_locations = break_results['num_locations'] if num_locations == 'no': num_locations = 0 else: num_locations = int(break_results['num_locations']) break_results['num_locations'] = num_locations if 'line_no' in break_results: break_results['line_no'] = int(break_results['line_no']) return break_results def get_bpno_from_match(break_results): return int(break_results['bpno']) def check_breakpoint_result( test, break_results, file_name=None, line_number=-1, symbol_name=None, symbol_match_exact=True, module_name=None, offset=-1, num_locations=-1): out_num_locations = break_results['num_locations'] if num_locations == -1: test.assertTrue(out_num_locations > 0, "Expecting one or more locations, got none.") elif num_locations != -2: test.assertTrue( num_locations == out_num_locations, "Expecting %d locations, got %d." % (num_locations, out_num_locations)) if file_name: out_file_name = "" if 'file' in break_results: out_file_name = break_results['file'] test.assertTrue( file_name.endswith(out_file_name), "Breakpoint file name '%s' doesn't match resultant name '%s'." % (file_name, out_file_name)) if line_number != -1: out_line_number = -1 if 'line_no' in break_results: out_line_number = break_results['line_no'] test.assertTrue( line_number == out_line_number, "Breakpoint line number %s doesn't match resultant line %s." % (line_number, out_line_number)) if symbol_name: out_symbol_name = "" # Look first for the inlined symbol name, otherwise use the symbol # name: if 'inline_symbol' in break_results and break_results['inline_symbol']: out_symbol_name = break_results['inline_symbol'] elif 'symbol' in break_results: out_symbol_name = break_results['symbol'] if symbol_match_exact: test.assertTrue( symbol_name == out_symbol_name, "Symbol name '%s' doesn't match resultant symbol '%s'." % (symbol_name, out_symbol_name)) else: test.assertTrue( out_symbol_name.find(symbol_name) != - 1, "Symbol name '%s' isn't in resultant symbol '%s'." % (symbol_name, out_symbol_name)) if module_name: out_module_name = None if 'module' in break_results: out_module_name = break_results['module'] test.assertTrue( module_name.find(out_module_name) != - 1, "Symbol module name '%s' isn't in expected module name '%s'." % (out_module_name, module_name)) # ================================================== # Utility functions related to Threads and Processes # ================================================== def get_stopped_threads(process, reason): """Returns the thread(s) with the specified stop reason in a list. The list can be empty if no such thread exists. """ threads = [] for t in process: if t.GetStopReason() == reason: threads.append(t) return threads def get_stopped_thread(process, reason): """A convenience function which returns the first thread with the given stop reason or None. Example usages: 1. Get the stopped thread due to a breakpoint condition ... from lldbutil import get_stopped_thread thread = get_stopped_thread(process, lldb.eStopReasonPlanComplete) self.assertTrue(thread.IsValid(), "There should be a thread stopped due to breakpoint condition") ... 2. Get the thread stopped due to a breakpoint ... from lldbutil import get_stopped_thread thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) self.assertTrue(thread.IsValid(), "There should be a thread stopped due to breakpoint") ... """ threads = get_stopped_threads(process, reason) if len(threads) == 0: return None return threads[0] def get_threads_stopped_at_breakpoint_id(process, bpid): """ For a stopped process returns the thread stopped at the breakpoint passed in bkpt""" stopped_threads = [] threads = [] stopped_threads = get_stopped_threads(process, lldb.eStopReasonBreakpoint) if len(stopped_threads) == 0: return threads for thread in stopped_threads: # Make sure we've hit our breakpoint... break_id = thread.GetStopReasonDataAtIndex(0) if break_id == bpid: threads.append(thread) return threads def get_threads_stopped_at_breakpoint(process, bkpt): return get_threads_stopped_at_breakpoint_id(process, bkpt.GetID()) def get_one_thread_stopped_at_breakpoint_id( process, bpid, require_exactly_one=True): threads = get_threads_stopped_at_breakpoint_id(process, bpid) if len(threads) == 0: return None if require_exactly_one and len(threads) != 1: return None return threads[0] def get_one_thread_stopped_at_breakpoint( process, bkpt, require_exactly_one=True): return get_one_thread_stopped_at_breakpoint_id( process, bkpt.GetID(), require_exactly_one) def is_thread_crashed(test, thread): """In the test suite we dereference a null pointer to simulate a crash. The way this is reported depends on the platform.""" if test.platformIsDarwin(): return thread.GetStopReason( ) == lldb.eStopReasonException and "EXC_BAD_ACCESS" in thread.GetStopDescription(100) elif test.getPlatform() == "linux": return thread.GetStopReason() == lldb.eStopReasonSignal and thread.GetStopReasonDataAtIndex( 0) == thread.GetProcess().GetUnixSignals().GetSignalNumberFromName("SIGSEGV") elif test.getPlatform() == "windows": return "Exception 0xc0000005" in thread.GetStopDescription(200) else: return "invalid address" in thread.GetStopDescription(100) def get_crashed_threads(test, process): threads = [] if process.GetState() != lldb.eStateStopped: return threads for thread in process: if is_thread_crashed(test, thread): threads.append(thread) return threads # Helper functions for run_to_{source,name}_breakpoint: def run_to_breakpoint_make_target(test, exe_name = "a.out", in_cwd = True): if in_cwd: exe = test.getBuildArtifact(exe_name) # Create the target target = test.dbg.CreateTarget(exe) test.assertTrue(target, "Target: %s is not valid."%(exe_name)) # Set environment variables for the inferior. if lldbtest_config.inferior_env: test.runCmd('settings set target.env-vars {}'.format( lldbtest_config.inferior_env)) return target def run_to_breakpoint_do_run(test, target, bkpt, launch_info = None, only_one_thread = True, extra_images = None): # Launch the process, and do not stop at the entry point. if not launch_info: launch_info = target.GetLaunchInfo() launch_info.SetWorkingDirectory(test.get_process_working_directory()) if extra_images and lldb.remote_platform: environ = test.registerSharedLibrariesWithTarget(target, extra_images) launch_info.SetEnvironmentEntries(environ, True) error = lldb.SBError() process = target.Launch(launch_info, error) test.assertTrue(process, "Could not create a valid process for %s: %s"%(target.GetExecutable().GetFilename(), error.GetCString())) test.assertFalse(error.Fail(), "Process launch failed: %s" % (error.GetCString())) # Frame #0 should be at our breakpoint. threads = get_threads_stopped_at_breakpoint( process, bkpt) num_threads = len(threads) if only_one_thread: test.assertEqual(num_threads, 1, "Expected 1 thread to stop at breakpoint, %d did."%(num_threads)) else: test.assertGreater(num_threads, 0, "No threads stopped at breakpoint") - + thread = threads[0] return (target, process, thread, bkpt) def run_to_name_breakpoint (test, bkpt_name, launch_info = None, exe_name = "a.out", bkpt_module = None, in_cwd = True, only_one_thread = True, extra_images = None): """Start up a target, using exe_name as the executable, and run it to a breakpoint set by name on bkpt_name restricted to bkpt_module. If you want to pass in launch arguments or environment variables, you can optionally pass in an SBLaunchInfo. If you do that, remember to set the working directory as well. If your executable isn't called a.out, you can pass that in. And if your executable isn't in the CWD, pass in the absolute path to the executable in exe_name, and set in_cwd to False. If you need to restrict the breakpoint to a particular module, pass the module name (a string not a FileSpec) in bkpt_module. If nothing is passed in setting will be unrestricted. If the target isn't valid, the breakpoint isn't found, or hit, the function will cause a testsuite failure. If successful it returns a tuple with the target process and thread that hit the breakpoint, and the breakpoint that we set for you. If only_one_thread is true, we require that there be only one thread stopped at the breakpoint. Otherwise we only require one or more threads stop there. If there are more than one, we return the first thread that stopped. """ target = run_to_breakpoint_make_target(test, exe_name, in_cwd) breakpoint = target.BreakpointCreateByName(bkpt_name, bkpt_module) test.assertTrue(breakpoint.GetNumLocations() > 0, "No locations found for name breakpoint: '%s'."%(bkpt_name)) return run_to_breakpoint_do_run(test, target, breakpoint, launch_info, only_one_thread, extra_images) def run_to_source_breakpoint(test, bkpt_pattern, source_spec, launch_info = None, exe_name = "a.out", bkpt_module = None, in_cwd = True, only_one_thread = True, extra_images = None): """Start up a target, using exe_name as the executable, and run it to a breakpoint set by source regex bkpt_pattern. The rest of the behavior is the same as run_to_name_breakpoint. """ target = run_to_breakpoint_make_target(test, exe_name, in_cwd) # Set the breakpoints breakpoint = target.BreakpointCreateBySourceRegex( bkpt_pattern, source_spec, bkpt_module) test.assertTrue(breakpoint.GetNumLocations() > 0, 'No locations found for source breakpoint: "%s", file: "%s", dir: "%s"' %(bkpt_pattern, source_spec.GetFilename(), source_spec.GetDirectory())) return run_to_breakpoint_do_run(test, target, breakpoint, launch_info, only_one_thread, extra_images) def run_to_line_breakpoint(test, source_spec, line_number, column = 0, launch_info = None, exe_name = "a.out", bkpt_module = None, in_cwd = True, only_one_thread = True, extra_images = None): """Start up a target, using exe_name as the executable, and run it to a breakpoint set by (source_spec, line_number(, column)). The rest of the behavior is the same as run_to_name_breakpoint. """ target = run_to_breakpoint_make_target(test, exe_name, in_cwd) # Set the breakpoints breakpoint = target.BreakpointCreateByLocation( source_spec, line_number, column, 0, lldb.SBFileSpecList()) test.assertTrue(breakpoint.GetNumLocations() > 0, 'No locations found for line breakpoint: "%s:%d(:%d)", dir: "%s"' %(source_spec.GetFilename(), line_number, column, source_spec.GetDirectory())) return run_to_breakpoint_do_run(test, target, breakpoint, launch_info, only_one_thread, extra_images) def continue_to_breakpoint(process, bkpt): """ Continues the process, if it stops, returns the threads stopped at bkpt; otherwise, returns None""" process.Continue() if process.GetState() != lldb.eStateStopped: return None else: return get_threads_stopped_at_breakpoint(process, bkpt) def get_caller_symbol(thread): """ Returns the symbol name for the call site of the leaf function. """ depth = thread.GetNumFrames() if depth <= 1: return None caller = thread.GetFrameAtIndex(1).GetSymbol() if caller: return caller.GetName() else: return None def get_function_names(thread): """ Returns a sequence of function names from the stack frames of this thread. """ def GetFuncName(i): return thread.GetFrameAtIndex(i).GetFunctionName() return list(map(GetFuncName, list(range(thread.GetNumFrames())))) def get_symbol_names(thread): """ Returns a sequence of symbols for this thread. """ def GetSymbol(i): return thread.GetFrameAtIndex(i).GetSymbol().GetName() return list(map(GetSymbol, list(range(thread.GetNumFrames())))) def get_pc_addresses(thread): """ Returns a sequence of pc addresses for this thread. """ def GetPCAddress(i): return thread.GetFrameAtIndex(i).GetPCAddress() return list(map(GetPCAddress, list(range(thread.GetNumFrames())))) def get_filenames(thread): """ Returns a sequence of file names from the stack frames of this thread. """ def GetFilename(i): return thread.GetFrameAtIndex( i).GetLineEntry().GetFileSpec().GetFilename() return list(map(GetFilename, list(range(thread.GetNumFrames())))) def get_line_numbers(thread): """ Returns a sequence of line numbers from the stack frames of this thread. """ def GetLineNumber(i): return thread.GetFrameAtIndex(i).GetLineEntry().GetLine() return list(map(GetLineNumber, list(range(thread.GetNumFrames())))) def get_module_names(thread): """ Returns a sequence of module names from the stack frames of this thread. """ def GetModuleName(i): return thread.GetFrameAtIndex( i).GetModule().GetFileSpec().GetFilename() return list(map(GetModuleName, list(range(thread.GetNumFrames())))) def get_stack_frames(thread): """ Returns a sequence of stack frames for this thread. """ def GetStackFrame(i): return thread.GetFrameAtIndex(i) return list(map(GetStackFrame, list(range(thread.GetNumFrames())))) def print_stacktrace(thread, string_buffer=False): """Prints a simple stack trace of this thread.""" output = SixStringIO() if string_buffer else sys.stdout target = thread.GetProcess().GetTarget() depth = thread.GetNumFrames() mods = get_module_names(thread) funcs = get_function_names(thread) symbols = get_symbol_names(thread) files = get_filenames(thread) lines = get_line_numbers(thread) addrs = get_pc_addresses(thread) if thread.GetStopReason() != lldb.eStopReasonInvalid: desc = "stop reason=" + stop_reason_to_str(thread.GetStopReason()) else: desc = "" print( "Stack trace for thread id={0:#x} name={1} queue={2} ".format( thread.GetThreadID(), thread.GetName(), thread.GetQueueName()) + desc, file=output) for i in range(depth): frame = thread.GetFrameAtIndex(i) function = frame.GetFunction() load_addr = addrs[i].GetLoadAddress(target) if not function: file_addr = addrs[i].GetFileAddress() start_addr = frame.GetSymbol().GetStartAddress().GetFileAddress() symbol_offset = file_addr - start_addr print( " frame #{num}: {addr:#016x} {mod}`{symbol} + {offset}".format( num=i, addr=load_addr, mod=mods[i], symbol=symbols[i], offset=symbol_offset), file=output) else: print( " frame #{num}: {addr:#016x} {mod}`{func} at {file}:{line} {args}".format( num=i, addr=load_addr, mod=mods[i], func='%s [inlined]' % funcs[i] if frame.IsInlined() else funcs[i], file=files[i], line=lines[i], args=get_args_as_string( frame, showFuncName=False) if not frame.IsInlined() else '()'), file=output) if string_buffer: return output.getvalue() def print_stacktraces(process, string_buffer=False): """Prints the stack traces of all the threads.""" output = SixStringIO() if string_buffer else sys.stdout print("Stack traces for " + str(process), file=output) for thread in process: print(print_stacktrace(thread, string_buffer=True), file=output) if string_buffer: return output.getvalue() def expect_state_changes(test, listener, process, states, timeout=5): """Listens for state changed events on the listener and makes sure they match what we expect. Stop-and-restart events (where GetRestartedFromEvent() returns true) are ignored.""" for expected_state in states: def get_next_event(): event = lldb.SBEvent() if not listener.WaitForEventForBroadcasterWithType( timeout, process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged, event): test.fail( "Timed out while waiting for a transition to state %s" % lldb.SBDebugger.StateAsCString(expected_state)) return event event = get_next_event() while (lldb.SBProcess.GetStateFromEvent(event) == lldb.eStateStopped and lldb.SBProcess.GetRestartedFromEvent(event)): # Ignore restarted event and the subsequent running event. event = get_next_event() test.assertEqual( lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning, "Restarted event followed by a running event") event = get_next_event() test.assertEqual( lldb.SBProcess.GetStateFromEvent(event), expected_state) # =================================== # Utility functions related to Frames # =================================== def get_parent_frame(frame): """ Returns the parent frame of the input frame object; None if not available. """ thread = frame.GetThread() parent_found = False for f in thread: if parent_found: return f if f.GetFrameID() == frame.GetFrameID(): parent_found = True # If we reach here, no parent has been found, return None. return None def get_args_as_string(frame, showFuncName=True): """ Returns the args of the input frame object as a string. """ # arguments => True # locals => False # statics => False # in_scope_only => True vars = frame.GetVariables(True, False, False, True) # type of SBValueList args = [] # list of strings for var in vars: args.append("(%s)%s=%s" % (var.GetTypeName(), var.GetName(), var.GetValue())) if frame.GetFunction(): name = frame.GetFunction().GetName() elif frame.GetSymbol(): name = frame.GetSymbol().GetName() else: name = "" if showFuncName: return "%s(%s)" % (name, ", ".join(args)) else: return "(%s)" % (", ".join(args)) def print_registers(frame, string_buffer=False): """Prints all the register sets of the frame.""" output = SixStringIO() if string_buffer else sys.stdout print("Register sets for " + str(frame), file=output) registerSet = frame.GetRegisters() # Return type of SBValueList. print("Frame registers (size of register set = %d):" % registerSet.GetSize(), file=output) for value in registerSet: #print(value, file=output) print("%s (number of children = %d):" % (value.GetName(), value.GetNumChildren()), file=output) for child in value: print( "Name: %s, Value: %s" % (child.GetName(), child.GetValue()), file=output) if string_buffer: return output.getvalue() def get_registers(frame, kind): """Returns the registers given the frame and the kind of registers desired. Returns None if there's no such kind. """ registerSet = frame.GetRegisters() # Return type of SBValueList. for value in registerSet: if kind.lower() in value.GetName().lower(): return value return None def get_GPRs(frame): """Returns the general purpose registers of the frame as an SBValue. The returned SBValue object is iterable. An example: ... from lldbutil import get_GPRs regs = get_GPRs(frame) for reg in regs: print("%s => %s" % (reg.GetName(), reg.GetValue())) ... """ return get_registers(frame, "general purpose") def get_FPRs(frame): """Returns the floating point registers of the frame as an SBValue. The returned SBValue object is iterable. An example: ... from lldbutil import get_FPRs regs = get_FPRs(frame) for reg in regs: print("%s => %s" % (reg.GetName(), reg.GetValue())) ... """ return get_registers(frame, "floating point") def get_ESRs(frame): """Returns the exception state registers of the frame as an SBValue. The returned SBValue object is iterable. An example: ... from lldbutil import get_ESRs regs = get_ESRs(frame) for reg in regs: print("%s => %s" % (reg.GetName(), reg.GetValue())) ... """ return get_registers(frame, "exception state") # ====================================== # Utility classes/functions for SBValues # ====================================== class BasicFormatter(object): """The basic formatter inspects the value object and prints the value.""" def format(self, value, buffer=None, indent=0): if not buffer: output = SixStringIO() else: output = buffer # If there is a summary, it suffices. val = value.GetSummary() # Otherwise, get the value. if val is None: val = value.GetValue() if val is None and value.GetNumChildren() > 0: val = "%s (location)" % value.GetLocation() print("{indentation}({type}) {name} = {value}".format( indentation=' ' * indent, type=value.GetTypeName(), name=value.GetName(), value=val), file=output) return output.getvalue() class ChildVisitingFormatter(BasicFormatter): """The child visiting formatter prints the value and its immediate children. The constructor takes a keyword arg: indent_child, which defaults to 2. """ def __init__(self, indent_child=2): """Default indentation of 2 SPC's for the children.""" self.cindent = indent_child def format(self, value, buffer=None): if not buffer: output = SixStringIO() else: output = buffer BasicFormatter.format(self, value, buffer=output) for child in value: BasicFormatter.format( self, child, buffer=output, indent=self.cindent) return output.getvalue() class RecursiveDecentFormatter(BasicFormatter): """The recursive decent formatter prints the value and the decendents. The constructor takes two keyword args: indent_level, which defaults to 0, and indent_child, which defaults to 2. The current indentation level is determined by indent_level, while the immediate children has an additional indentation by inden_child. """ def __init__(self, indent_level=0, indent_child=2): self.lindent = indent_level self.cindent = indent_child def format(self, value, buffer=None): if not buffer: output = SixStringIO() else: output = buffer BasicFormatter.format(self, value, buffer=output, indent=self.lindent) new_indent = self.lindent + self.cindent for child in value: if child.GetSummary() is not None: BasicFormatter.format( self, child, buffer=output, indent=new_indent) else: if child.GetNumChildren() > 0: rdf = RecursiveDecentFormatter(indent_level=new_indent) rdf.format(child, buffer=output) else: BasicFormatter.format( self, child, buffer=output, indent=new_indent) return output.getvalue() # =========================================================== # Utility functions for path manipulation on remote platforms # =========================================================== def join_remote_paths(*paths): # TODO: update with actual platform name for remote windows once it exists if lldb.remote_platform.GetName() == 'remote-windows': return os.path.join(*paths).replace(os.path.sep, '\\') return os.path.join(*paths).replace(os.path.sep, '/') def append_to_process_working_directory(test, *paths): remote = lldb.remote_platform if remote: return join_remote_paths(remote.GetWorkingDirectory(), *paths) return os.path.join(test.getBuildDir(), *paths) # ================================================== # Utility functions to get the correct signal number # ================================================== import signal def get_signal_number(signal_name): platform = lldb.remote_platform if platform and platform.IsValid(): signals = platform.GetUnixSignals() if signals.IsValid(): signal_number = signals.GetSignalNumberFromName(signal_name) if signal_number > 0: return signal_number # No remote platform; fall back to using local python signals. return getattr(signal, signal_name) class PrintableRegex(object): def __init__(self, text): self.regex = re.compile(text) self.text = text def match(self, str): return self.regex.match(str) def __str__(self): return "%s" % (self.text) def __repr__(self): return "re.compile(%s) -> %s" % (self.text, self.regex) def skip_if_callable(test, mycallable, reason): if six.callable(mycallable): if mycallable(test): test.skipTest(reason) return True return False def skip_if_library_missing(test, target, library): def find_library(target, library): for module in target.modules: filename = module.file.GetFilename() if isinstance(library, str): if library == filename: return False elif hasattr(library, 'match'): if library.match(filename): return False return True def find_library_callable(test): return find_library(target, library) return skip_if_callable( test, find_library_callable, "could not find library matching '%s' in target %s" % (library, target)) def read_file_on_target(test, remote): if lldb.remote_platform: local = test.getBuildArtifact("file_from_target") error = lldb.remote_platform.Get(lldb.SBFileSpec(remote, False), lldb.SBFileSpec(local, True)) test.assertTrue(error.Success(), "Reading file {0} failed: {1}".format(remote, error)) else: local = remote with open(local, 'r') as f: return f.read() def read_file_from_process_wd(test, name): path = append_to_process_working_directory(test, name) return read_file_on_target(test, path) def wait_for_file_on_target(testcase, file_path, max_attempts=6): for i in range(max_attempts): err, retcode, msg = testcase.run_platform_command("ls %s" % file_path) if err.Success() and retcode == 0: break if i < max_attempts: # Exponential backoff! import time time.sleep(pow(2, i) * 0.25) else: testcase.fail( "File %s not found even after %d attempts." % (file_path, max_attempts)) return read_file_on_target(testcase, file_path) diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp index f460b7b05fc7..475a3d660e0e 100644 --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -1,1116 +1,1125 @@ //===-- CommandObjectFrame.cpp ----------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "CommandObjectFrame.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/ValueObject.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/DataFormatters/ValueObjectPrinter.h" #include "lldb/Host/Config.h" #include "lldb/Host/OptionParser.h" #include "lldb/Host/StringConvert.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionGroupFormat.h" #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" #include "lldb/Interpreter/OptionGroupVariable.h" #include "lldb/Interpreter/Options.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Symbol/Variable.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/StackFrame.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/Args.h" #include #include using namespace lldb; using namespace lldb_private; #pragma mark CommandObjectFrameDiagnose // CommandObjectFrameInfo // CommandObjectFrameDiagnose #define LLDB_OPTIONS_frame_diag #include "CommandOptions.inc" class CommandObjectFrameDiagnose : public CommandObjectParsed { public: class CommandOptions : public Options { public: CommandOptions() : Options() { OptionParsingStarting(nullptr); } ~CommandOptions() override = default; Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) override { Status error; const int short_option = m_getopt_table[option_idx].val; switch (short_option) { case 'r': reg = ConstString(option_arg); break; case 'a': { address.emplace(); if (option_arg.getAsInteger(0, *address)) { address.reset(); error.SetErrorStringWithFormat("invalid address argument '%s'", option_arg.str().c_str()); } } break; case 'o': { offset.emplace(); if (option_arg.getAsInteger(0, *offset)) { offset.reset(); error.SetErrorStringWithFormat("invalid offset argument '%s'", option_arg.str().c_str()); } } break; default: llvm_unreachable("Unimplemented option"); } return error; } void OptionParsingStarting(ExecutionContext *execution_context) override { address.reset(); reg.reset(); offset.reset(); } llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_frame_diag_options); } // Options. llvm::Optional address; llvm::Optional reg; llvm::Optional offset; }; CommandObjectFrameDiagnose(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "frame diagnose", "Try to determine what path path the current stop " "location used to get to a register or address", nullptr, eCommandRequiresThread | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), m_options() { CommandArgumentEntry arg; CommandArgumentData index_arg; // Define the first (and only) variant of this arg. index_arg.arg_type = eArgTypeFrameIndex; index_arg.arg_repetition = eArgRepeatOptional; // There is only one variant this argument could be; put it into the // argument entry. arg.push_back(index_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg); } ~CommandObjectFrameDiagnose() override = default; Options *GetOptions() override { return &m_options; } protected: bool DoExecute(Args &command, CommandReturnObject &result) override { Thread *thread = m_exe_ctx.GetThreadPtr(); StackFrameSP frame_sp = thread->GetSelectedFrame(); ValueObjectSP valobj_sp; if (m_options.address.hasValue()) { if (m_options.reg.hasValue() || m_options.offset.hasValue()) { result.AppendError( "`frame diagnose --address` is incompatible with other arguments."); result.SetStatus(eReturnStatusFailed); return false; } valobj_sp = frame_sp->GuessValueForAddress(m_options.address.getValue()); } else if (m_options.reg.hasValue()) { valobj_sp = frame_sp->GuessValueForRegisterAndOffset( m_options.reg.getValue(), m_options.offset.getValueOr(0)); } else { StopInfoSP stop_info_sp = thread->GetStopInfo(); if (!stop_info_sp) { result.AppendError("No arguments provided, and no stop info."); result.SetStatus(eReturnStatusFailed); return false; } valobj_sp = StopInfo::GetCrashingDereference(stop_info_sp); } if (!valobj_sp) { result.AppendError("No diagnosis available."); result.SetStatus(eReturnStatusFailed); return false; } DumpValueObjectOptions::DeclPrintingHelper helper = [&valobj_sp](ConstString type, ConstString var, const DumpValueObjectOptions &opts, Stream &stream) -> bool { const ValueObject::GetExpressionPathFormat format = ValueObject:: GetExpressionPathFormat::eGetExpressionPathFormatHonorPointers; const bool qualify_cxx_base_classes = false; valobj_sp->GetExpressionPath(stream, qualify_cxx_base_classes, format); stream.PutCString(" ="); return true; }; DumpValueObjectOptions options; options.SetDeclPrintingHelper(helper); ValueObjectPrinter printer(valobj_sp.get(), &result.GetOutputStream(), options); printer.PrintValueObject(); return true; } protected: CommandOptions m_options; }; #pragma mark CommandObjectFrameInfo // CommandObjectFrameInfo class CommandObjectFrameInfo : public CommandObjectParsed { public: CommandObjectFrameInfo(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "frame info", "List information about the current " "stack frame in the current thread.", "frame info", eCommandRequiresFrame | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) {} ~CommandObjectFrameInfo() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { m_exe_ctx.GetFrameRef().DumpUsingSettingsFormat(&result.GetOutputStream()); result.SetStatus(eReturnStatusSuccessFinishResult); return result.Succeeded(); } }; #pragma mark CommandObjectFrameSelect // CommandObjectFrameSelect #define LLDB_OPTIONS_frame_select #include "CommandOptions.inc" class CommandObjectFrameSelect : public CommandObjectParsed { public: class CommandOptions : public Options { public: CommandOptions() : Options() { OptionParsingStarting(nullptr); } ~CommandOptions() override = default; Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) override { Status error; const int short_option = m_getopt_table[option_idx].val; switch (short_option) { case 'r': { int32_t offset = 0; if (option_arg.getAsInteger(0, offset) || offset == INT32_MIN) { error.SetErrorStringWithFormat("invalid frame offset argument '%s'", option_arg.str().c_str()); } else relative_frame_offset = offset; break; } default: llvm_unreachable("Unimplemented option"); } return error; } void OptionParsingStarting(ExecutionContext *execution_context) override { relative_frame_offset.reset(); } llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_frame_select_options); } llvm::Optional relative_frame_offset; }; CommandObjectFrameSelect(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "frame select", "Select the current stack frame by " "index from within the current thread " "(see 'thread backtrace'.)", nullptr, eCommandRequiresThread | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), m_options() { CommandArgumentEntry arg; CommandArgumentData index_arg; // Define the first (and only) variant of this arg. index_arg.arg_type = eArgTypeFrameIndex; index_arg.arg_repetition = eArgRepeatOptional; // There is only one variant this argument could be; put it into the // argument entry. arg.push_back(index_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg); } ~CommandObjectFrameSelect() override = default; Options *GetOptions() override { return &m_options; } protected: bool DoExecute(Args &command, CommandReturnObject &result) override { // No need to check "thread" for validity as eCommandRequiresThread ensures // it is valid Thread *thread = m_exe_ctx.GetThreadPtr(); uint32_t frame_idx = UINT32_MAX; if (m_options.relative_frame_offset.hasValue()) { // The one and only argument is a signed relative frame index frame_idx = thread->GetSelectedFrameIndex(); if (frame_idx == UINT32_MAX) frame_idx = 0; if (*m_options.relative_frame_offset < 0) { if (static_cast(frame_idx) >= -*m_options.relative_frame_offset) frame_idx += *m_options.relative_frame_offset; else { if (frame_idx == 0) { // If you are already at the bottom of the stack, then just warn // and don't reset the frame. result.AppendError("Already at the bottom of the stack."); result.SetStatus(eReturnStatusFailed); return false; } else frame_idx = 0; } } else if (*m_options.relative_frame_offset > 0) { // I don't want "up 20" where "20" takes you past the top of the stack // to produce // an error, but rather to just go to the top. So I have to count the // stack here... const uint32_t num_frames = thread->GetStackFrameCount(); if (static_cast(num_frames - frame_idx) > *m_options.relative_frame_offset) frame_idx += *m_options.relative_frame_offset; else { if (frame_idx == num_frames - 1) { // If we are already at the top of the stack, just warn and don't // reset the frame. result.AppendError("Already at the top of the stack."); result.SetStatus(eReturnStatusFailed); return false; } else frame_idx = num_frames - 1; } } } else { if (command.GetArgumentCount() > 1) { result.AppendErrorWithFormat( "too many arguments; expected frame-index, saw '%s'.\n", command[0].c_str()); m_options.GenerateOptionUsage( result.GetErrorStream(), this, GetCommandInterpreter().GetDebugger().GetTerminalWidth()); return false; } if (command.GetArgumentCount() == 1) { if (command[0].ref().getAsInteger(0, frame_idx)) { result.AppendErrorWithFormat("invalid frame index argument '%s'.", command[0].c_str()); result.SetStatus(eReturnStatusFailed); return false; } } else if (command.GetArgumentCount() == 0) { frame_idx = thread->GetSelectedFrameIndex(); if (frame_idx == UINT32_MAX) { frame_idx = 0; } } } bool success = thread->SetSelectedFrameByIndexNoisily( frame_idx, result.GetOutputStream()); if (success) { m_exe_ctx.SetFrameSP(thread->GetSelectedFrame()); result.SetStatus(eReturnStatusSuccessFinishResult); } else { result.AppendErrorWithFormat("Frame index (%u) out of range.\n", frame_idx); result.SetStatus(eReturnStatusFailed); } return result.Succeeded(); } protected: CommandOptions m_options; }; #pragma mark CommandObjectFrameVariable // List images with associated information class CommandObjectFrameVariable : public CommandObjectParsed { public: CommandObjectFrameVariable(CommandInterpreter &interpreter) : CommandObjectParsed( interpreter, "frame variable", "Show variables for the current stack frame. Defaults to all " "arguments and local variables in scope. Names of argument, " "local, file static and file global variables can be specified. " "Children of aggregate variables can be specified such as " "'var->child.x'. The -> and [] operators in 'frame variable' do " "not invoke operator overloads if they exist, but directly access " "the specified element. If you want to trigger operator overloads " "use the expression command to print the variable instead." "\nIt is worth noting that except for overloaded " "operators, when printing local variables 'expr local_var' and " "'frame var local_var' produce the same " "results. However, 'frame variable' is more efficient, since it " "uses debug information and memory reads directly, rather than " "parsing and evaluating an expression, which may even involve " "JITing and running code in the target program.", nullptr, eCommandRequiresFrame | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused | eCommandRequiresProcess), m_option_group(), m_option_variable( true), // Include the frame specific options by passing "true" m_option_format(eFormatDefault), m_varobj_options() { CommandArgumentEntry arg; CommandArgumentData var_name_arg; // Define the first (and only) variant of this arg. var_name_arg.arg_type = eArgTypeVarName; var_name_arg.arg_repetition = eArgRepeatStar; // There is only one variant this argument could be; put it into the // argument entry. arg.push_back(var_name_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg); m_option_group.Append(&m_option_variable, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); m_option_group.Append(&m_option_format, OptionGroupFormat::OPTION_GROUP_FORMAT | OptionGroupFormat::OPTION_GROUP_GDB_FMT, LLDB_OPT_SET_1); m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); m_option_group.Finalize(); } ~CommandObjectFrameVariable() override = default; Options *GetOptions() override { return &m_option_group; } void HandleArgumentCompletion(CompletionRequest &request, OptionElementVector &opt_element_vector) override { // Arguments are the standard source file completer. CommandCompletions::InvokeCommonCompletionCallbacks( GetCommandInterpreter(), CommandCompletions::eVariablePathCompletion, request, nullptr); } protected: llvm::StringRef GetScopeString(VariableSP var_sp) { if (!var_sp) return llvm::StringRef::withNullAsEmpty(nullptr); switch (var_sp->GetScope()) { case eValueTypeVariableGlobal: return "GLOBAL: "; case eValueTypeVariableStatic: return "STATIC: "; case eValueTypeVariableArgument: return "ARG: "; case eValueTypeVariableLocal: return "LOCAL: "; case eValueTypeVariableThreadLocal: return "THREAD: "; default: break; } return llvm::StringRef::withNullAsEmpty(nullptr); } bool DoExecute(Args &command, CommandReturnObject &result) override { // No need to check "frame" for validity as eCommandRequiresFrame ensures // it is valid StackFrame *frame = m_exe_ctx.GetFramePtr(); Stream &s = result.GetOutputStream(); // Be careful about the stack frame, if any summary formatter runs code, it // might clear the StackFrameList for the thread. So hold onto a shared // pointer to the frame so it stays alive. VariableList *variable_list = frame->GetVariableList(m_option_variable.show_globals); VariableSP var_sp; ValueObjectSP valobj_sp; TypeSummaryImplSP summary_format_sp; if (!m_option_variable.summary.IsCurrentValueEmpty()) DataVisualization::NamedSummaryFormats::GetSummaryFormat( ConstString(m_option_variable.summary.GetCurrentValue()), summary_format_sp); else if (!m_option_variable.summary_string.IsCurrentValueEmpty()) summary_format_sp = std::make_shared( TypeSummaryImpl::Flags(), m_option_variable.summary_string.GetCurrentValue()); DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions( eLanguageRuntimeDescriptionDisplayVerbosityFull, eFormatDefault, summary_format_sp)); const SymbolContext &sym_ctx = frame->GetSymbolContext(eSymbolContextFunction); if (sym_ctx.function && sym_ctx.function->IsTopLevelFunction()) m_option_variable.show_globals = true; if (variable_list) { const Format format = m_option_format.GetFormat(); options.SetFormat(format); if (!command.empty()) { VariableList regex_var_list; // If we have any args to the variable command, we will make variable // objects from them... for (auto &entry : command) { if (m_option_variable.use_regex) { const size_t regex_start_index = regex_var_list.GetSize(); llvm::StringRef name_str = entry.ref(); RegularExpression regex(name_str); if (regex.IsValid()) { size_t num_matches = 0; const size_t num_new_regex_vars = variable_list->AppendVariablesIfUnique(regex, regex_var_list, num_matches); if (num_new_regex_vars > 0) { for (size_t regex_idx = regex_start_index, end_index = regex_var_list.GetSize(); regex_idx < end_index; ++regex_idx) { var_sp = regex_var_list.GetVariableAtIndex(regex_idx); if (var_sp) { valobj_sp = frame->GetValueObjectForFrameVariable( var_sp, m_varobj_options.use_dynamic); if (valobj_sp) { std::string scope_string; if (m_option_variable.show_scope) scope_string = GetScopeString(var_sp).str(); if (!scope_string.empty()) s.PutCString(scope_string); if (m_option_variable.show_decl && var_sp->GetDeclaration().GetFile()) { bool show_fullpaths = false; bool show_module = true; if (var_sp->DumpDeclaration(&s, show_fullpaths, show_module)) s.PutCString(": "); } valobj_sp->Dump(result.GetOutputStream(), options); } } } } else if (num_matches == 0) { result.GetErrorStream().Printf("error: no variables matched " "the regular expression '%s'.\n", entry.c_str()); } } else { if (llvm::Error err = regex.GetError()) result.GetErrorStream().Printf( "error: %s\n", llvm::toString(std::move(err)).c_str()); else result.GetErrorStream().Printf( "error: unknown regex error when compiling '%s'\n", entry.c_str()); } } else // No regex, either exact variable names or variable // expressions. { Status error; uint32_t expr_path_options = StackFrame::eExpressionPathOptionCheckPtrVsMember | StackFrame::eExpressionPathOptionsAllowDirectIVarAccess | StackFrame::eExpressionPathOptionsInspectAnonymousUnions; lldb::VariableSP var_sp; valobj_sp = frame->GetValueForVariableExpressionPath( entry.ref(), m_varobj_options.use_dynamic, expr_path_options, var_sp, error); if (valobj_sp) { std::string scope_string; if (m_option_variable.show_scope) scope_string = GetScopeString(var_sp).str(); if (!scope_string.empty()) s.PutCString(scope_string); if (m_option_variable.show_decl && var_sp && var_sp->GetDeclaration().GetFile()) { var_sp->GetDeclaration().DumpStopContext(&s, false); s.PutCString(": "); } options.SetFormat(format); options.SetVariableFormatDisplayLanguage( valobj_sp->GetPreferredDisplayLanguage()); Stream &output_stream = result.GetOutputStream(); options.SetRootValueObjectName( valobj_sp->GetParent() ? entry.c_str() : nullptr); valobj_sp->Dump(output_stream, options); } else { const char *error_cstr = error.AsCString(nullptr); if (error_cstr) result.GetErrorStream().Printf("error: %s\n", error_cstr); else result.GetErrorStream().Printf("error: unable to find any " "variable expression path that " "matches '%s'.\n", entry.c_str()); } } } } else // No command arg specified. Use variable_list, instead. { const size_t num_variables = variable_list->GetSize(); if (num_variables > 0) { for (size_t i = 0; i < num_variables; i++) { var_sp = variable_list->GetVariableAtIndex(i); switch (var_sp->GetScope()) { case eValueTypeVariableGlobal: if (!m_option_variable.show_globals) continue; break; case eValueTypeVariableStatic: if (!m_option_variable.show_globals) continue; break; case eValueTypeVariableArgument: if (!m_option_variable.show_args) continue; break; case eValueTypeVariableLocal: if (!m_option_variable.show_locals) continue; break; default: continue; break; } std::string scope_string; if (m_option_variable.show_scope) scope_string = GetScopeString(var_sp).str(); // Use the variable object code to make sure we are using the same // APIs as the public API will be using... valobj_sp = frame->GetValueObjectForFrameVariable( var_sp, m_varobj_options.use_dynamic); if (valobj_sp) { // When dumping all variables, don't print any variables that are // not in scope to avoid extra unneeded output if (valobj_sp->IsInScope()) { if (!valobj_sp->GetTargetSP() ->GetDisplayRuntimeSupportValues() && valobj_sp->IsRuntimeSupportValue()) continue; if (!scope_string.empty()) s.PutCString(scope_string); if (m_option_variable.show_decl && var_sp->GetDeclaration().GetFile()) { var_sp->GetDeclaration().DumpStopContext(&s, false); s.PutCString(": "); } options.SetFormat(format); options.SetVariableFormatDisplayLanguage( valobj_sp->GetPreferredDisplayLanguage()); options.SetRootValueObjectName( var_sp ? var_sp->GetName().AsCString() : nullptr); valobj_sp->Dump(result.GetOutputStream(), options); } } } } } result.SetStatus(eReturnStatusSuccessFinishResult); } if (m_option_variable.show_recognized_args) { auto recognized_frame = frame->GetRecognizedFrame(); if (recognized_frame) { ValueObjectListSP recognized_arg_list = recognized_frame->GetRecognizedArguments(); if (recognized_arg_list) { for (auto &rec_value_sp : recognized_arg_list->GetObjects()) { options.SetFormat(m_option_format.GetFormat()); options.SetVariableFormatDisplayLanguage( rec_value_sp->GetPreferredDisplayLanguage()); options.SetRootValueObjectName(rec_value_sp->GetName().AsCString()); rec_value_sp->Dump(result.GetOutputStream(), options); } } } } if (m_interpreter.TruncationWarningNecessary()) { result.GetOutputStream().Printf(m_interpreter.TruncationWarningText(), m_cmd_name.c_str()); m_interpreter.TruncationWarningGiven(); } // Increment statistics. bool res = result.Succeeded(); Target &target = GetSelectedOrDummyTarget(); if (res) target.IncrementStats(StatisticKind::FrameVarSuccess); else target.IncrementStats(StatisticKind::FrameVarFailure); return res; } protected: OptionGroupOptions m_option_group; OptionGroupVariable m_option_variable; OptionGroupFormat m_option_format; OptionGroupValueObjectDisplay m_varobj_options; }; #pragma mark CommandObjectFrameRecognizer #define LLDB_OPTIONS_frame_recognizer_add #include "CommandOptions.inc" class CommandObjectFrameRecognizerAdd : public CommandObjectParsed { private: class CommandOptions : public Options { public: CommandOptions() : Options() {} ~CommandOptions() override = default; Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) override { Status error; const int short_option = m_getopt_table[option_idx].val; switch (short_option) { case 'l': m_class_name = std::string(option_arg); break; case 's': m_module = std::string(option_arg); break; case 'n': - m_function = std::string(option_arg); + m_symbols.push_back(std::string(option_arg)); break; case 'x': m_regex = true; break; default: llvm_unreachable("Unimplemented option"); } return error; } void OptionParsingStarting(ExecutionContext *execution_context) override { m_module = ""; - m_function = ""; + m_symbols.clear(); m_class_name = ""; m_regex = false; } llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_frame_recognizer_add_options); } // Instance variables to hold the values for command options. std::string m_class_name; std::string m_module; - std::string m_function; + std::vector m_symbols; bool m_regex; }; CommandOptions m_options; Options *GetOptions() override { return &m_options; } protected: bool DoExecute(Args &command, CommandReturnObject &result) override; public: CommandObjectFrameRecognizerAdd(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "frame recognizer add", "Add a new frame recognizer.", nullptr), m_options() { SetHelpLong(R"( Frame recognizers allow for retrieving information about special frames based on ABI, arguments or other special properties of that frame, even without source code or debug info. Currently, one use case is to extract function arguments that would otherwise be unaccesible, or augment existing arguments. Adding a custom frame recognizer is possible by implementing a Python class and using the 'frame recognizer add' command. The Python class should have a 'get_recognized_arguments' method and it will receive an argument of type lldb.SBFrame representing the current frame that we are trying to recognize. The method should return a (possibly empty) list of lldb.SBValue objects that represent the recognized arguments. An example of a recognizer that retrieves the file descriptor values from libc functions 'read', 'write' and 'close' follows: class LibcFdRecognizer(object): def get_recognized_arguments(self, frame): if frame.name in ["read", "write", "close"]: fd = frame.EvaluateExpression("$arg1").unsigned value = lldb.target.CreateValueFromExpression("fd", "(int)%d" % fd) return [value] return [] The file containing this implementation can be imported via 'command script import' and then we can register this recognizer with 'frame recognizer add'. It's important to restrict the recognizer to the libc library (which is libsystem_kernel.dylib on macOS) to avoid matching functions with the same name in other modules: (lldb) command script import .../fd_recognizer.py (lldb) frame recognizer add -l fd_recognizer.LibcFdRecognizer -n read -s libsystem_kernel.dylib When the program is stopped at the beginning of the 'read' function in libc, we can view the recognizer arguments in 'frame variable': (lldb) b read (lldb) r Process 1234 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3 frame #0: 0x00007fff06013ca0 libsystem_kernel.dylib`read (lldb) frame variable (int) fd = 3 )"); } ~CommandObjectFrameRecognizerAdd() override = default; }; bool CommandObjectFrameRecognizerAdd::DoExecute(Args &command, CommandReturnObject &result) { #if LLDB_ENABLE_PYTHON if (m_options.m_class_name.empty()) { result.AppendErrorWithFormat( "%s needs a Python class name (-l argument).\n", m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } if (m_options.m_module.empty()) { result.AppendErrorWithFormat("%s needs a module name (-s argument).\n", m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } - if (m_options.m_function.empty()) { - result.AppendErrorWithFormat("%s needs a function name (-n argument).\n", - m_cmd_name.c_str()); + if (m_options.m_symbols.empty()) { + result.AppendErrorWithFormat( + "%s needs at least one symbol name (-n argument).\n", + m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + if (m_options.m_regex && m_options.m_symbols.size() > 1) { + result.AppendErrorWithFormat( + "%s needs only one symbol regular expression (-n argument).\n", + m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter(); if (interpreter && !interpreter->CheckObjectExists(m_options.m_class_name.c_str())) { result.AppendWarning("The provided class does not exist - please define it " "before attempting to use this frame recognizer"); } StackFrameRecognizerSP recognizer_sp = StackFrameRecognizerSP(new ScriptedStackFrameRecognizer( interpreter, m_options.m_class_name.c_str())); if (m_options.m_regex) { auto module = RegularExpressionSP(new RegularExpression(m_options.m_module)); auto func = - RegularExpressionSP(new RegularExpression(m_options.m_function)); + RegularExpressionSP(new RegularExpression(m_options.m_symbols.front())); StackFrameRecognizerManager::AddRecognizer(recognizer_sp, module, func); } else { auto module = ConstString(m_options.m_module); - auto func = ConstString(m_options.m_function); - StackFrameRecognizerManager::AddRecognizer(recognizer_sp, module, func, {}); + std::vector symbols(m_options.m_symbols.begin(), + m_options.m_symbols.end()); + StackFrameRecognizerManager::AddRecognizer(recognizer_sp, module, symbols); } #endif result.SetStatus(eReturnStatusSuccessFinishNoResult); return result.Succeeded(); } class CommandObjectFrameRecognizerClear : public CommandObjectParsed { public: CommandObjectFrameRecognizerClear(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "frame recognizer clear", "Delete all frame recognizers.", nullptr) {} ~CommandObjectFrameRecognizerClear() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { StackFrameRecognizerManager::RemoveAllRecognizers(); result.SetStatus(eReturnStatusSuccessFinishResult); return result.Succeeded(); } }; class CommandObjectFrameRecognizerDelete : public CommandObjectParsed { public: CommandObjectFrameRecognizerDelete(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "frame recognizer delete", "Delete an existing frame recognizer.", nullptr) {} ~CommandObjectFrameRecognizerDelete() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { if (command.GetArgumentCount() == 0) { if (!m_interpreter.Confirm( "About to delete all frame recognizers, do you want to do that?", true)) { result.AppendMessage("Operation cancelled..."); result.SetStatus(eReturnStatusFailed); return false; } StackFrameRecognizerManager::RemoveAllRecognizers(); result.SetStatus(eReturnStatusSuccessFinishResult); return result.Succeeded(); } if (command.GetArgumentCount() != 1) { result.AppendErrorWithFormat("'%s' takes zero or one arguments.\n", m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } uint32_t recognizer_id = StringConvert::ToUInt32(command.GetArgumentAtIndex(0), 0, 0); StackFrameRecognizerManager::RemoveRecognizerWithID(recognizer_id); result.SetStatus(eReturnStatusSuccessFinishResult); return result.Succeeded(); } }; class CommandObjectFrameRecognizerList : public CommandObjectParsed { public: CommandObjectFrameRecognizerList(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "frame recognizer list", "Show a list of active frame recognizers.", nullptr) {} ~CommandObjectFrameRecognizerList() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { bool any_printed = false; StackFrameRecognizerManager::ForEach( - [&result, &any_printed](uint32_t recognizer_id, std::string name, - std::string module, std::string symbol, - std::string alternate_symbol, bool regexp) { + [&result, &any_printed]( + uint32_t recognizer_id, std::string name, std::string module, + llvm::ArrayRef symbols, bool regexp) { Stream &stream = result.GetOutputStream(); if (name.empty()) name = "(internal)"; stream << std::to_string(recognizer_id) << ": " << name; if (!module.empty()) stream << ", module " << module; - if (!symbol.empty()) - stream << ", function " << symbol; - if (!alternate_symbol.empty()) - stream << ", symbol " << alternate_symbol; + if (!symbols.empty()) + for (auto &symbol : symbols) + stream << ", symbol " << symbol; if (regexp) stream << " (regexp)"; stream.EOL(); stream.Flush(); any_printed = true; }); if (any_printed) result.SetStatus(eReturnStatusSuccessFinishResult); else { result.GetOutputStream().PutCString("no matching results found.\n"); result.SetStatus(eReturnStatusSuccessFinishNoResult); } return result.Succeeded(); } }; class CommandObjectFrameRecognizerInfo : public CommandObjectParsed { public: CommandObjectFrameRecognizerInfo(CommandInterpreter &interpreter) : CommandObjectParsed( interpreter, "frame recognizer info", "Show which frame recognizer is applied a stack frame (if any).", nullptr) { CommandArgumentEntry arg; CommandArgumentData index_arg; // Define the first (and only) variant of this arg. index_arg.arg_type = eArgTypeFrameIndex; index_arg.arg_repetition = eArgRepeatPlain; // There is only one variant this argument could be; put it into the // argument entry. arg.push_back(index_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg); } ~CommandObjectFrameRecognizerInfo() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { Process *process = m_exe_ctx.GetProcessPtr(); if (process == nullptr) { result.AppendError("no process"); result.SetStatus(eReturnStatusFailed); return false; } Thread *thread = m_exe_ctx.GetThreadPtr(); if (thread == nullptr) { result.AppendError("no thread"); result.SetStatus(eReturnStatusFailed); return false; } if (command.GetArgumentCount() != 1) { result.AppendErrorWithFormat( "'%s' takes exactly one frame index argument.\n", m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } uint32_t frame_index = StringConvert::ToUInt32(command.GetArgumentAtIndex(0), 0, 0); StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_index); if (!frame_sp) { result.AppendErrorWithFormat("no frame with index %u", frame_index); result.SetStatus(eReturnStatusFailed); return false; } auto recognizer = StackFrameRecognizerManager::GetRecognizerForFrame(frame_sp); Stream &output_stream = result.GetOutputStream(); output_stream.Printf("frame %d ", frame_index); if (recognizer) { output_stream << "is recognized by "; output_stream << recognizer->GetName(); } else { output_stream << "not recognized by any recognizer"; } output_stream.EOL(); result.SetStatus(eReturnStatusSuccessFinishResult); return result.Succeeded(); } }; class CommandObjectFrameRecognizer : public CommandObjectMultiword { public: CommandObjectFrameRecognizer(CommandInterpreter &interpreter) : CommandObjectMultiword( interpreter, "frame recognizer", "Commands for editing and viewing frame recognizers.", "frame recognizer [] ") { LoadSubCommand("add", CommandObjectSP(new CommandObjectFrameRecognizerAdd( interpreter))); LoadSubCommand( "clear", CommandObjectSP(new CommandObjectFrameRecognizerClear(interpreter))); LoadSubCommand( "delete", CommandObjectSP(new CommandObjectFrameRecognizerDelete(interpreter))); LoadSubCommand("list", CommandObjectSP(new CommandObjectFrameRecognizerList( interpreter))); LoadSubCommand("info", CommandObjectSP(new CommandObjectFrameRecognizerInfo( interpreter))); } ~CommandObjectFrameRecognizer() override = default; }; #pragma mark CommandObjectMultiwordFrame // CommandObjectMultiwordFrame CommandObjectMultiwordFrame::CommandObjectMultiwordFrame( CommandInterpreter &interpreter) : CommandObjectMultiword(interpreter, "frame", "Commands for selecting and " "examing the current " "thread's stack frames.", "frame []") { LoadSubCommand("diagnose", CommandObjectSP(new CommandObjectFrameDiagnose(interpreter))); LoadSubCommand("info", CommandObjectSP(new CommandObjectFrameInfo(interpreter))); LoadSubCommand("select", CommandObjectSP(new CommandObjectFrameSelect(interpreter))); LoadSubCommand("variable", CommandObjectSP(new CommandObjectFrameVariable(interpreter))); #if LLDB_ENABLE_PYTHON LoadSubCommand("recognizer", CommandObjectSP(new CommandObjectFrameRecognizer( interpreter))); #endif } CommandObjectMultiwordFrame::~CommandObjectMultiwordFrame() = default; diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 1456630c892f..5953ed445223 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1,1140 +1,1141 @@ include "OptionsBase.td" let Command = "target modules dump symtab" in { def tm_sort : Option<"sort", "s">, Group<1>, Desc<"Supply a sort order when dumping the symbol table.">, EnumArg<"SortOrder", "OptionEnumValues(g_sort_option_enumeration)">; def tm_smn : Option<"show-mangled-names", "m">, Group<1>, Desc<"Do not demangle symbol names before showing them.">; } let Command = "help" in { def help_hide_aliases : Option<"hide-aliases", "a">, Desc<"Hide aliases in the command list.">; def help_hide_user : Option<"hide-user-commands", "u">, Desc<"Hide user-defined commands from the list.">; def help_show_hidden : Option<"show-hidden-commands", "h">, Desc<"Include commands prefixed with an underscore.">; } let Command = "settings set" in { def setset_global : Option<"global", "g">, Arg<"Filename">, Completion<"DiskFile">, Desc<"Apply the new value to the global default value.">; def setset_force : Option<"force", "f">, Desc<"Force an empty value to be accepted as the default.">; } let Command = "settings write" in { def setwrite_file : Option<"file", "f">, Required, Arg<"Filename">, Completion<"DiskFile">, Desc<"The file into which to write the settings.">; def setwrite_append : Option<"append", "a">, Desc<"Append to saved settings file if it exists.">; } let Command = "settings read" in { def setread_file : Option<"file", "f">, Required, Arg<"Filename">, Completion<"DiskFile">, Desc<"The file from which to read the settings.">; } let Command = "breakpoint list" in { // FIXME: We need to add an "internal" command, and then add this sort of // thing to it. But I need to see it for now, and don't want to wait. def blist_internal : Option<"internal", "i">, Desc<"Show debugger internal breakpoints">; def blist_brief : Option<"brief", "b">, Group<1>, Desc<"Give a brief description of the breakpoint (no location info).">; def blist_full : Option<"full", "f">, Group<2>, Desc<"Give a full description of the breakpoint and its locations.">; def blist_verbose : Option<"verbose", "v">, Group<3>, Desc<"Explain everything we know about the breakpoint (for debugging " "debugger bugs).">; def blist_dummy_bp : Option<"dummy-breakpoints", "D">, Desc<"List Dummy breakpoints - i.e. breakpoints set before a file is " "provided, which prime new targets.">; } let Command = "breakpoint modify" in { def breakpoint_modify_ignore_count : Option<"ignore-count", "i">, Group<1>, Arg<"Count">, Desc<"Set the number of times this breakpoint is skipped before stopping.">; def breakpoint_modify_one_shot : Option<"one-shot", "o">, Group<1>, Arg<"Boolean">, Desc<"The breakpoint is deleted the first time it stop causes a stop.">; def breakpoint_modify_thread_index : Option<"thread-index", "x">, Group<1>, Arg<"ThreadIndex">, Desc<"The breakpoint stops only for the thread whose " "index matches this argument.">; def breakpoint_modify_thread_id : Option<"thread-id", "t">, Group<1>, Arg<"ThreadID">, Desc<"The breakpoint stops only for the thread whose TID " "matches this argument.">; def breakpoint_modify_thread_name : Option<"thread-name", "T">, Group<1>, Arg<"ThreadName">, Desc<"The breakpoint stops only for the thread whose " "thread name matches this argument.">; def breakpoint_modify_queue_name : Option<"queue-name", "q">, Group<1>, Arg<"QueueName">, Desc<"The breakpoint stops only for threads in the queue " "whose name is given by this argument.">; def breakpoint_modify_condition : Option<"condition", "c">, Group<1>, Arg<"Expression">, Desc<"The breakpoint stops only if this condition " "expression evaluates to true.">; def breakpoint_modify_auto_continue : Option<"auto-continue", "G">, Group<1>, Arg<"Boolean">, Desc<"The breakpoint will auto-continue after running its commands.">; def breakpoint_modify_enable : Option<"enable", "e">, Group<2>, Desc<"Enable the breakpoint.">; def breakpoint_modify_disable : Option<"disable", "d">, Group<3>, Desc<"Disable the breakpoint.">; def breakpoint_modify_command : Option<"command", "C">, Group<4>, Arg<"Command">, Desc<"A command to run when the breakpoint is hit, can be provided more " "than once, the commands will get run in order left to right.">; } let Command = "breakpoint dummy" in { def breakpoint_dummy_options_dummy_breakpoints : Option<"dummy-breakpoints", "D">, Group<1>, Desc<"Act on Dummy breakpoints - i.e. breakpoints set before a file is " "provided, which prime new targets.">; } let Command = "breakpoint set" in { def breakpoint_set_shlib : Option<"shlib", "s">, Arg<"ShlibName">, Completion<"Module">, Groups<[1,2,3,4,5,6,7,8,9,11]>, // *not* in group 10 Desc<"Set the breakpoint only in this shared library. Can repeat this " "option multiple times to specify multiple shared libraries.">; def breakpoint_set_hardware : Option<"hardware", "H">, Desc<"Require the breakpoint to use hardware breakpoints.">; def breakpoint_set_file : Option<"file", "f">, Arg<"Filename">, Completion<"SourceFile">, Groups<[1,3,4,5,6,7,8,9,11]>, Desc<"Specifies the source file in which to set this breakpoint. Note, by " "default lldb only looks for files that are #included if they use the " "standard include file extensions. To set breakpoints on .c/.cpp/.m/.mm " "files that are #included, set target.inline-breakpoint-strategy to " "\"always\".">; def breakpoint_set_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, Required, Desc<"Specifies the line number on which to set this breakpoint.">; def breakpoint_set_column : Option<"column", "u">, Group<1>, Arg<"ColumnNum">, Desc<"Specifies the column number on which to set this breakpoint.">; def breakpoint_set_address : Option<"address", "a">, Group<2>, Arg<"AddressOrExpression">, Required, Desc<"Set the breakpoint at the specified address. If the address maps " "uniquely toa particular binary, then the address will be converted to " "a \"file\"address, so that the breakpoint will track that binary+offset " "no matter where the binary eventually loads. Alternately, if you also " "specify the module - with the -s option - then the address will be " "treated as a file address in that module, and resolved accordingly. " "Again, this will allow lldb to track that offset on subsequent reloads. " " The module need not have been loaded at the time you specify this " "breakpoint, and will get resolved when the module is loaded.">; def breakpoint_set_name : Option<"name", "n">, Group<3>, Arg<"FunctionName">, Completion<"Symbol">, Required, Desc<"Set the breakpoint by function name. Can be repeated multiple times " "to makeone breakpoint for multiple names">; def breakpoint_set_source_regexp_function : Option<"source-regexp-function", "X">, Group<9>, Arg<"FunctionName">, Completion<"Symbol">, Desc<"When used with '-p' limits the source regex to source contained in " "the namedfunctions. Can be repeated multiple times.">; def breakpoint_set_fullname : Option<"fullname", "F">, Group<4>, Arg<"FullName">, Required, Completion<"Symbol">, Desc<"Set the breakpoint by fully qualified function names. For C++ this " "means namespaces and all arguments, and for Objective-C this means a full " "functionprototype with class and selector. Can be repeated multiple times" " to make one breakpoint for multiple names.">; def breakpoint_set_selector : Option<"selector", "S">, Group<5>, Arg<"Selector">, Required, Desc<"Set the breakpoint by ObjC selector name. Can be repeated multiple " "times tomake one breakpoint for multiple Selectors.">; def breakpoint_set_method : Option<"method", "M">, Group<6>, Arg<"Method">, Required, Desc<"Set the breakpoint by C++ method names. Can be repeated " "multiple times tomake one breakpoint for multiple methods.">; def breakpoint_set_func_regex : Option<"func-regex", "r">, Group<7>, Arg<"RegularExpression">, Required, Desc<"Set the breakpoint by function " "name, evaluating a regular-expression to findthe function name(s).">; def breakpoint_set_basename : Option<"basename", "b">, Group<8>, Arg<"FunctionName">, Required, Completion<"Symbol">, Desc<"Set the breakpoint by function basename (C++ namespaces and arguments" " will beignored). Can be repeated multiple times to make one breakpoint " "for multiplesymbols.">; def breakpoint_set_source_pattern_regexp : Option<"source-pattern-regexp", "p">, Group<9>, Arg<"RegularExpression">, Required, Desc<"Set the breakpoint by specifying a regular expression which" " is matched against the source text in a source file or files specified " "with the -f can be specified more than once. If no source files " "are specified, uses the current \"default source file\". If you want to " "match against all source files, pass the \"--all-files\" option.">; def breakpoint_set_all_files : Option<"all-files", "A">, Group<9>, Desc<"All files are searched for source pattern matches.">; def breakpoint_set_language_exception : Option<"language-exception", "E">, Group<10>, Arg<"Language">, Required, Desc<"Set the breakpoint on exceptions thrown by the specified language " "(without options, on throw but not catch.)">; def breakpoint_set_on_throw : Option<"on-throw", "w">, Group<10>, Arg<"Boolean">, Desc<"Set the breakpoint on exception throW.">; def breakpoint_set_on_catch : Option<"on-catch", "h">, Group<10>, Arg<"Boolean">, Desc<"Set the breakpoint on exception catcH.">; def breakpoint_set_language : Option<"language", "L">, GroupRange<3, 8>, Arg<"Language">, Desc<"Specifies the Language to use when interpreting the breakpoint's " "expression (note: currently only implemented for setting breakpoints on " "identifiers). If not set the target.language setting is used.">; def breakpoint_set_skip_prologue : Option<"skip-prologue", "K">, Arg<"Boolean">, Groups<[1,3,4,5,6,7,8]>, Desc<"sKip the prologue if the breakpoint is at the beginning of a " "function. If not set the target.skip-prologue setting is used.">; def breakpoint_set_breakpoint_name : Option<"breakpoint-name", "N">, Arg<"BreakpointName">, Desc<"Adds this to the list of names for this breakpoint.">; def breakpoint_set_address_slide : Option<"address-slide", "R">, Arg<"Address">, Groups<[1,3,4,5,6,7,8]>, Desc<"Add the specified offset to whatever address(es) the breakpoint " "resolves to. At present this applies the offset directly as given, and " "doesn't try to align it to instruction boundaries.">; def breakpoint_set_move_to_nearest_code : Option<"move-to-nearest-code", "m">, Groups<[1, 9]>, Arg<"Boolean">, Desc<"Move breakpoints to nearest code. If not set the " "target.move-to-nearest-codesetting is used.">; /* Don't add this option till it actually does something useful... def breakpoint_set_exception_typename : Option<"exception-typename", "O">, Arg<"TypeName">, Desc<"The breakpoint will only stop if an " "exception Object of this type is thrown. Can be repeated multiple times " "to stop for multiple object types">; */ } let Command = "breakpoint clear" in { def breakpoint_clear_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Completion<"SourceFile">, Desc<"Specify the breakpoint by source location in this particular file.">; def breakpoint_clear_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, Required, Desc<"Specify the breakpoint by source location at this particular line.">; } let Command = "breakpoint delete" in { def breakpoint_delete_force : Option<"force", "f">, Group<1>, Desc<"Delete all breakpoints without querying for confirmation.">; def breakpoint_delete_dummy_breakpoints : Option<"dummy-breakpoints", "D">, Group<1>, Desc<"Delete Dummy breakpoints - i.e. breakpoints set before a " "file is provided, which prime new targets.">; } let Command = "breakpoint name" in { def breakpoint_name_name : Option<"name", "N">, Group<1>, Arg<"BreakpointName">, Desc<"Specifies a breakpoint name to use.">; def breakpoint_name_breakpoint_id : Option<"breakpoint-id", "B">, Group<2>, Arg<"BreakpointID">, Desc<"Specify a breakpoint ID to use.">; def breakpoint_name_dummy_breakpoints : Option<"dummy-breakpoints", "D">, Group<3>, Desc<"Operate on Dummy breakpoints - i.e. breakpoints set before " "a file is provided, which prime new targets.">; def breakpoint_name_help_string : Option<"help-string", "H">, Group<4>, Arg<"None">, Desc<"A help string describing the purpose of this name.">; } let Command = "breakpoint access" in { def breakpoint_access_allow_list : Option<"allow-list", "L">, Group<1>, Arg<"Boolean">, Desc<"Determines whether the breakpoint will show up in " "break list if not referred to explicitly.">; def breakpoint_access_allow_disable : Option<"allow-disable", "A">, Group<2>, Arg<"Boolean">, Desc<"Determines whether the breakpoint can be disabled by " "name or when all breakpoints are disabled.">; def breakpoint_access_allow_delete : Option<"allow-delete", "D">, Group<3>, Arg<"Boolean">, Desc<"Determines whether the breakpoint can be deleted by " "name or when all breakpoints are deleted.">; } let Command = "breakpoint read" in { def breakpoint_read_file : Option<"file", "f">, Arg<"Filename">, Required, Completion<"DiskFile">, Desc<"The file from which to read the breakpoints.">; def breakpoint_read_breakpoint_name : Option<"breakpoint-name", "N">, Arg<"BreakpointName">, Desc<"Only read in breakpoints with this name.">; } let Command = "breakpoint write" in { def breakpoint_write_file : Option<"file", "f">, Arg<"Filename">, Required, Completion<"DiskFile">, Desc<"The file into which to write the breakpoints.">; def breakpoint_write_append : Option<"append", "a">, Desc<"Append to saved breakpoints file if it exists.">; } let Command = "breakpoint command add" in { def breakpoint_add_one_liner : Option<"one-liner", "o">, Group<1>, Arg<"OneLiner">, Desc<"Specify a one-line breakpoint command inline. Be " "sure to surround it with quotes.">; def breakpoint_add_stop_on_error : Option<"stop-on-error", "e">, Arg<"Boolean">, Desc<"Specify whether breakpoint command execution should " "terminate on error.">; def breakpoint_add_script_type : Option<"script-type", "s">, EnumArg<"None", "ScriptOptionEnum()">, Desc<"Specify the language for the commands - if none is specified, the " "lldb command interpreter will be used.">; def breakpoint_add_dummy_breakpoints : Option<"dummy-breakpoints", "D">, Desc<"Sets Dummy breakpoints - i.e. breakpoints set before a file is " "provided, which prime new targets.">; } let Command = "breakpoint command delete" in { def breakpoint_command_delete_dummy_breakpoints : Option<"dummy-breakpoints", "D">, Group<1>, Desc<"Delete commands from Dummy breakpoints - i.e. breakpoints set before " "a file is provided, which prime new targets.">; } let Command = "disassemble" in { def disassemble_options_bytes : Option<"bytes", "b">, Desc<"Show opcode bytes when disassembling.">; def disassemble_options_context : Option<"context", "C">, Arg<"NumLines">, Desc<"Number of context lines of source to show.">; def disassemble_options_mixed : Option<"mixed", "m">, Desc<"Enable mixed source and assembly display.">; def disassemble_options_raw : Option<"raw", "r">, Desc<"Print raw disassembly with no symbol information.">; def disassemble_options_plugin : Option<"plugin", "P">, Arg<"Plugin">, Desc<"Name of the disassembler plugin you want to use.">; def disassemble_options_flavor : Option<"flavor", "F">, Arg<"DisassemblyFlavor">, Desc<"Name of the disassembly flavor you want to " "use. Currently the only valid options are default, and for Intel " "architectures, att and intel.">; def disassemble_options_arch : Option<"arch", "A">, Arg<"Architecture">, Desc<"Specify the architecture to use from cross disassembly.">; def disassemble_options_start_address : Option<"start-address", "s">, Groups<[1,2]>, Arg<"AddressOrExpression">, Required, Desc<"Address at which to start disassembling.">; def disassemble_options_end_address : Option<"end-address", "e">, Group<1>, Arg<"AddressOrExpression">, Desc<"Address at which to end disassembling.">; def disassemble_options_count : Option<"count", "c">, Groups<[2,3,4,5]>, Arg<"NumLines">, Desc<"Number of instructions to display.">; def disassemble_options_name : Option<"name", "n">, Group<3>, Arg<"FunctionName">, Completion<"Symbol">, Desc<"Disassemble entire contents of the given function name.">; def disassemble_options_frame : Option<"frame", "f">, Group<4>, Desc<"Disassemble from the start of the current frame's function.">; def disassemble_options_pc : Option<"pc", "p">, Group<5>, Desc<"Disassemble around the current pc.">; def disassemble_options_line : Option<"line", "l">, Group<6>, Desc<"Disassemble the current frame's current source line instructions if" "there is debug line table information, else disassemble around the pc.">; def disassemble_options_address : Option<"address", "a">, Group<7>, Arg<"AddressOrExpression">, Desc<"Disassemble function containing this address.">; } let Command = "expression" in { def expression_options_all_threads : Option<"all-threads", "a">, Groups<[1,2]>, Arg<"Boolean">, Desc<"Should we run all threads if the " "execution doesn't complete on one thread.">; def expression_options_ignore_breakpoints : Option<"ignore-breakpoints", "i">, Groups<[1,2]>, Arg<"Boolean">, Desc<"Ignore breakpoint hits while running expressions">; def expression_options_timeout : Option<"timeout", "t">, Groups<[1,2]>, Arg<"UnsignedInteger">, Desc<"Timeout value (in microseconds) for running the expression.">; def expression_options_unwind_on_error : Option<"unwind-on-error", "u">, Groups<[1,2]>, Arg<"Boolean">, Desc<"Clean up program state if the expression causes a crash, or raises a " "signal. Note, unlike gdb hitting a breakpoint is controlled by another " "option (-i).">; def expression_options_debug : Option<"debug", "g">, Groups<[1,2]>, Desc<"When specified, debug the JIT code by setting a breakpoint on the " "first instruction and forcing breakpoints to not be ignored (-i0) and no " "unwinding to happen on error (-u0).">; def expression_options_language : Option<"language", "l">, Groups<[1,2]>, Arg<"Language">, Desc<"Specifies the Language to use when parsing the " "expression. If not set the target.language setting is used.">; def expression_options_apply_fixits : Option<"apply-fixits", "X">, Groups<[1,2]>, Arg<"Language">, Desc<"If true, simple fix-it hints will be " "automatically applied to the expression.">; def expression_options_description_verbosity : Option<"description-verbosity", "v">, Group<1>, OptionalEnumArg<"DescriptionVerbosity", "DescriptionVerbosityTypes()">, Desc<"How verbose should the output of this expression be, if the object " "description is asked for.">; def expression_options_top_level : Option<"top-level", "p">, Groups<[1,2]>, Desc<"Interpret the expression as a complete translation unit, without " "injecting it into the local context. Allows declaration of persistent, " "top-level entities without a $ prefix.">; def expression_options_allow_jit : Option<"allow-jit", "j">, Groups<[1,2]>, Arg<"Boolean">, Desc<"Controls whether the expression can fall back to being JITted if it's" "not supported by the interpreter (defaults to true).">; } let Command = "frame diag" in { def frame_diag_register : Option<"register", "r">, Group<1>, Arg<"RegisterName">, Desc<"A register to diagnose.">; def frame_diag_address : Option<"address", "a">, Group<1>, Arg<"Address">, Desc<"An address to diagnose.">; def frame_diag_offset : Option<"offset", "o">, Group<1>, Arg<"Offset">, Desc<"An optional offset. Requires --register.">; } let Command = "frame select" in { def frame_select_relative : Option<"relative", "r">, Group<1>, Arg<"Offset">, Desc<"A relative frame index offset from the current frame index.">; } let Command = "frame recognizer add" in { def frame_recognizer_shlib : Option<"shlib", "s">, Arg<"ShlibName">, Completion<"Module">, Desc<"Name of the module or shared library that this recognizer applies " "to.">; def frame_recognizer_function : Option<"function", "n">, Arg<"Name">, Completion<"Symbol">, - Desc<"Name of the function that this recognizer applies to.">; + Desc<"Name of the function that this recognizer applies to. " + "Can be specified more than once except if -x|--regex is provided.">; def frame_recognizer_python_class : Option<"python-class", "l">, Group<2>, Arg<"PythonClass">, Desc<"Give the name of a Python class to use for this frame recognizer.">; def frame_recognizer_regex : Option<"regex", "x">, Desc<"Function name and module name are actually regular expressions.">; } let Command = "history" in { def history_count : Option<"count", "c">, Group<1>, Arg<"UnsignedInteger">, Desc<"How many history commands to print.">; def history_start_index : Option<"start-index", "s">, Group<1>, Arg<"UnsignedInteger">, Desc<"Index at which to start printing history " "commands (or end to mean tail mode).">; def history_end_index : Option<"end-index", "e">, Group<1>, Arg<"UnsignedInteger">, Desc<"Index at which to stop printing history commands.">; def history_clear : Option<"clear", "C">, Group<2>, Desc<"Clears the current command history.">; } let Command = "log" in { def log_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Desc<"Set the destination file to log to.">; def log_threadsafe : Option<"threadsafe", "t">, Group<1>, Desc<"Enable thread safe logging to avoid interweaved log lines.">; def log_verbose : Option<"verbose", "v">, Group<1>, Desc<"Enable verbose logging.">; def log_sequence : Option<"sequence", "s">, Group<1>, Desc<"Prepend all log lines with an increasing integer sequence id.">; def log_timestamp : Option<"timestamp", "T">, Group<1>, Desc<"Prepend all log lines with a timestamp.">; def log_pid_tid : Option<"pid-tid", "p">, Group<1>, Desc<"Prepend all log lines with the process and thread ID that generates " "the log line.">; def log_thread_name : Option<"thread-name", "n">, Group<1>, Desc<"Prepend all log lines with the thread name for the thread that " "generates the log line.">; def log_stack : Option<"stack", "S">, Group<1>, Desc<"Append a stack backtrace to each log line.">; def log_append : Option<"append", "a">, Group<1>, Desc<"Append to the log file instead of overwriting.">; def log_file_function : Option<"file-function", "F">, Group<1>, Desc<"Prepend the names of files and function that generate the logs.">; } let Command = "reproducer dump" in { def reproducer_provider : Option<"provider", "p">, Group<1>, EnumArg<"None", "ReproducerProviderType()">, Required, Desc<"The reproducer provider to dump.">; def reproducer_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Desc<"The reproducer path. If a reproducer is replayed and no path is " "provided, that reproducer is dumped.">; } let Command = "reproducer xcrash" in { def reproducer_signal : Option<"signal", "s">, Group<1>, EnumArg<"None", "ReproducerSignalType()">, Required, Desc<"The signal to crash the debugger.">; } let Command = "memory read" in { def memory_read_num_per_line : Option<"num-per-line", "l">, Group<1>, Arg<"NumberPerLine">, Desc<"The number of items per line to display.">; def memory_read_binary : Option<"binary", "b">, Group<2>, Desc<"If true, memory will be saved as binary. If false, the memory is " "saved save as an ASCII dump that uses the format, size, count and number " "per line settings.">; def memory_read_type : Option<"type", "t">, Groups<[3,4]>, Arg<"Name">, Required, Desc<"The name of a type to view memory as.">; def memory_read_language : Option<"language", "x">, Group<4>, Arg<"Language">, Desc<"The language of the type to view memory as.">; def memory_read_offset : Option<"offset", "E">, Group<3>, Arg<"Count">, Desc<"How many elements of the specified type to skip before starting to " "display data.">; def memory_read_force : Option<"force", "r">, Groups<[1,2,3]>, Desc<"Necessary if reading over target.max-memory-read-size bytes.">; } let Command = "memory find" in { def memory_find_expression : Option<"expression", "e">, Group<1>, Arg<"Expression">, Required, Desc<"Evaluate an expression to obtain a byte pattern.">; def memory_find_string : Option<"string", "s">, Group<2>, Arg<"Name">, Required, Desc<"Use text to find a byte pattern.">; def memory_find_count : Option<"count", "c">, Arg<"Count">, Desc<"How many times to perform the search.">; def memory_find_dump_offset : Option<"dump-offset", "o">, Arg<"Offset">, Desc<"When dumping memory for a match, an offset from the match location to" " start dumping from.">; } let Command = "memory write" in { def memory_write_infile : Option<"infile", "i">, Group<1>, Arg<"Filename">, Required, Desc<"Write memory using the contents of a file.">; def memory_write_offset : Option<"offset", "o">, Group<1>, Arg<"Offset">, Desc<"Start writing bytes from an offset within the input file.">; } let Command = "register read" in { def register_read_alternate : Option<"alternate", "A">, Desc<"Display register names using the alternate register name if there " "is one.">; def register_read_set : Option<"set", "s">, Group<1>, Arg<"Index">, Desc<"Specify which register sets to dump by index.">; def register_read_all : Option<"all", "a">, Group<2>, Desc<"Show all register sets.">; } let Command = "source" in { def source_stop_on_error : Option<"stop-on-error", "e">, Arg<"Boolean">, Desc<"If true, stop executing commands on error.">; def source_stop_on_continue : Option<"stop-on-continue", "c">, Arg<"Boolean">, Desc<"If true, stop executing commands on continue.">; def source_silent_run : Option<"silent-run", "s">, Arg<"Boolean">, Desc<"If true don't echo commands while executing.">; } let Command = "alias" in { def alias_help : Option<"help", "h">, Arg<"HelpText">, Desc<"Help text for this command">; def alias_long_help : Option<"long-help", "H">, Arg<"HelpText">, Desc<"Long help text for this command">; } let Command = "regex" in { def regex_help : Option<"help", "h">, Group<1>, Arg<"None">, Desc<"The help text to display for this command.">; def regex_syntax : Option<"syntax", "s">, Group<1>, Arg<"None">, Desc<"A syntax string showing the typical usage syntax.">; } let Command = "permissions" in { def permissions_permissions_value : Option<"permissions-value", "v">, Arg<"PermissionsNumber">, Desc<"Give out the numeric value for permissions (e.g. 757)">; def permissions_permissions_string : Option<"permissions-string", "s">, Arg<"PermissionsString">, Desc<"Give out the string value for permissions (e.g. rwxr-xr--).">; def permissions_user_read : Option<"user-read", "r">, Desc<"Allow user to read.">; def permissions_user_write : Option<"user-write", "w">, Desc<"Allow user to write.">; def permissions_user_exec : Option<"user-exec", "x">, Desc<"Allow user to execute.">; def permissions_group_read : Option<"group-read", "R">, Desc<"Allow group to read.">; def permissions_group_write : Option<"group-write", "W">, Desc<"Allow group to write.">; def permissions_group_exec : Option<"group-exec", "X">, Desc<"Allow group to execute.">; def permissions_world_read : Option<"world-read", "d">, Desc<"Allow world to read.">; def permissions_world_write : Option<"world-write", "t">, Desc<"Allow world to write.">; def permissions_world_exec : Option<"world-exec", "e">, Desc<"Allow world to execute.">; } let Command = "platform fread" in { def platform_fread_offset : Option<"offset", "o">, Group<1>, Arg<"Index">, Desc<"Offset into the file at which to start reading.">; def platform_fread_count : Option<"count", "c">, Group<1>, Arg<"Count">, Desc<"Number of bytes to read from the file.">; } let Command = "platform fwrite" in { def platform_fwrite_offset : Option<"offset", "o">, Group<1>, Arg<"Index">, Desc<"Offset into the file at which to start reading.">; def platform_fwrite_data : Option<"data", "d">, Group<1>, Arg<"Value">, Desc<"Text to write to the file.">; } let Command = "platform process list" in { def platform_process_list_pid : Option<"pid", "p">, Group<1>, Arg<"Pid">, Desc<"List the process info for a specific process ID.">; def platform_process_list_name : Option<"name", "n">, Group<2>, Arg<"ProcessName">, Required, Desc<"Find processes with executable basenames that match a string.">; def platform_process_list_ends_with : Option<"ends-with", "e">, Group<3>, Arg<"ProcessName">, Required, Desc<"Find processes with executable basenames that end with a string.">; def platform_process_list_starts_with : Option<"starts-with", "s">, Group<4>, Arg<"ProcessName">, Required, Desc<"Find processes with executable basenames that start with a string.">; def platform_process_list_contains : Option<"contains", "c">, Group<5>, Arg<"ProcessName">, Required, Desc<"Find processes with executable basenames that contain a string.">; def platform_process_list_regex : Option<"regex", "r">, Group<6>, Arg<"RegularExpression">, Required, Desc<"Find processes with executable basenames that match a regular " "expression.">; def platform_process_list_parent : Option<"parent", "P">, GroupRange<2, 6>, Arg<"Pid">, Desc<"Find processes that have a matching parent process ID.">; def platform_process_list_uid : Option<"uid", "u">, GroupRange<2, 6>, Arg<"UnsignedInteger">, Validator<"&posix_validator">, Desc<"Find processes that have a matching user ID.">; def platform_process_list_euid : Option<"euid", "U">, GroupRange<2, 6>, Arg<"UnsignedInteger">, Validator<"&posix_validator">, Desc<"Find processes that have a matching effective user ID.">; def platform_process_list_gid : Option<"gid", "g">, GroupRange<2, 6>, Arg<"UnsignedInteger">, Validator<"&posix_validator">, Desc<"Find processes that have a matching group ID.">; def platform_process_list_egid : Option<"egid", "G">, GroupRange<2, 6>, Arg<"UnsignedInteger">, Validator<"&posix_validator">, Desc<"Find processes that have a matching effective group ID.">; def platform_process_list_arch : Option<"arch", "a">, GroupRange<2, 6>, Arg<"Architecture">, Desc<"Find processes that have a matching architecture.">; def platform_process_list_show_args : Option<"show-args", "A">, GroupRange<1, 6>, Desc<"Show process arguments instead of the process executable basename.">; def platform_process_list_all_users: Option<"all-users", "x">, GroupRange<1,6>, Desc<"Show processes matching all user IDs.">; def platform_process_list_verbose : Option<"verbose", "v">, GroupRange<1, 6>, Desc<"Enable verbose output.">; } let Command = "platform process attach" in { def platform_process_attach_plugin : Option<"plugin", "P">, Arg<"Plugin">, Desc<"Name of the process plugin you want to use.">; def platform_process_attach_pid : Option<"pid", "p">, Group<1>, Arg<"Pid">, Desc<"The process ID of an existing process to attach to.">; def platform_process_attach_name : Option<"name", "n">, Group<2>, Arg<"ProcessName">, Desc<"The name of the process to attach to.">; def platform_process_attach_waitfor : Option<"waitfor", "w">, Group<2>, Desc<"Wait for the process with to launch.">; } let Command = "platform shell" in { def platform_shell_timeout : Option<"timeout", "t">, Arg<"Value">, Desc<"Seconds to wait for the remote host to finish running the command.">; } let Command = "process attach" in { def process_attach_continue : Option<"continue", "c">, Desc<"Immediately continue the process once attached.">; def process_attach_plugin : Option<"plugin", "P">, Arg<"Plugin">, Desc<"Name of the process plugin you want to use.">; def process_attach_pid : Option<"pid", "p">, Group<1>, Arg<"Pid">, Desc<"The process ID of an existing process to attach to.">; def process_attach_name : Option<"name", "n">, Group<2>, Arg<"ProcessName">, Desc<"The name of the process to attach to.">; def process_attach_include_existing : Option<"include-existing", "i">, Group<2>, Desc<"Include existing processes when doing attach -w.">; def process_attach_waitfor : Option<"waitfor", "w">, Group<2>, Desc<"Wait for the process with to launch.">; } let Command = "process continue" in { def process_continue_ignore_count : Option<"ignore-count", "i">, Arg<"UnsignedInteger">, Desc<"Ignore crossings of the breakpoint (if it" " exists) for the currently selected thread.">; } let Command = "process detach" in { def process_detach_keep_stopped : Option<"keep-stopped", "s">, Group<1>, Arg<"Boolean">, Desc<"Whether or not the process should be kept stopped on" " detach (if possible).">; } let Command = "process connect" in { def process_connect_plugin : Option<"plugin", "p">, Arg<"Plugin">, Desc<"Name of the process plugin you want to use.">; } let Command = "process load" in { def process_load_install : Option<"install", "i">, OptionalArg<"Path">, Desc<"Install the shared library to the target. If specified without an " "argument then the library will installed in the current working " "directory.">; } let Command = "process handle" in { def process_handle_stop : Option<"stop", "s">, Group<1>, Arg<"Boolean">, Desc<"Whether or not the process should be stopped if the signal is " "received.">; def process_handle_notify : Option<"notify", "n">, Group<1>, Arg<"Boolean">, Desc<"Whether or not the debugger should notify the user if the signal is " "received.">; def process_handle_pass : Option<"pass", "p">, Group<1>, Arg<"Boolean">, Desc<"Whether or not the signal should be passed to the process.">; } let Command = "process status" in { def process_status_verbose : Option<"verbose", "v">, Group<1>, Desc<"Show verbose process status including extended crash information.">; } let Command = "script import" in { def script_import_allow_reload : Option<"allow-reload", "r">, Group<1>, Desc<"Allow the script to be loaded even if it was already loaded before. " "This argument exists for backwards compatibility, but reloading is always " "allowed, whether you specify it or not.">; } let Command = "script add" in { def script_add_function : Option<"function", "f">, Group<1>, Arg<"PythonFunction">, Desc<"Name of the Python function to bind to this command name.">; def script_add_class : Option<"class", "c">, Group<2>, Arg<"PythonClass">, Desc<"Name of the Python class to bind to this command name.">; def script_add_help : Option<"help", "h">, Group<1>, Arg<"HelpText">, Desc<"The help text to display for this command.">; def script_add_synchronicity : Option<"synchronicity", "s">, EnumArg<"ScriptedCommandSynchronicity", "ScriptSynchroType()">, Desc<"Set the synchronicity of this command's executions with regard to " "LLDB event system.">; } let Command = "source info" in { def source_info_count : Option<"count", "c">, Arg<"Count">, Desc<"The number of line entries to display.">; def source_info_shlib : Option<"shlib", "s">, Groups<[1,2]>, Arg<"ShlibName">, Completion<"Module">, Desc<"Look up the source in the given module or " "shared library (can be specified more than once).">; def source_info_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Completion<"SourceFile">, Desc<"The file from which to display source.">; def source_info_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, Desc<"The line number at which to start the displaying lines.">; def source_info_end_line : Option<"end-line", "e">, Group<1>, Arg<"LineNum">, Desc<"The line number at which to stop displaying lines.">; def source_info_name : Option<"name", "n">, Group<2>, Arg<"Symbol">, Completion<"Symbol">, Desc<"The name of a function whose source to display.">; def source_info_address : Option<"address", "a">, Group<3>, Arg<"AddressOrExpression">, Desc<"Lookup the address and display the source" " information for the corresponding file and line.">; } let Command = "source list" in { def source_list_count : Option<"count", "c">, Arg<"Count">, Desc<"The number of source lines to display.">; def source_list_shlib : Option<"shlib", "s">, Groups<[1,2]>, Arg<"ShlibName">, Completion<"Module">, Desc<"Look up the source file in the given shared library.">; def source_list_show_breakpoints : Option<"show-breakpoints", "b">, Desc<"Show the line table locations from the debug information that " "indicate valid places to set source level breakpoints.">; def source_list_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Completion<"SourceFile">, Desc<"The file from which to display source.">; def source_list_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, Desc<"The line number at which to start the display source.">; def source_list_name : Option<"name", "n">, Group<2>, Arg<"Symbol">, Completion<"Symbol">, Desc<"The name of a function whose source to display.">; def source_list_address : Option<"address", "a">, Group<3>, Arg<"AddressOrExpression">, Desc<"Lookup the address and display the source" " information for the corresponding file and line.">; def source_list_reverse : Option<"reverse", "r">, Group<4>, Desc<"Reverse the" " listing to look backwards from the last displayed block of source.">; } let Command = "target dependents" in { def dependents_no_dependents : Option<"no-dependents", "d">, Group<1>, OptionalEnumArg<"Value", "OptionEnumValues(g_dependents_enumaration)">, Desc<"Whether or not to load dependents when creating a target. If the " "option is not specified, the value is implicitly 'default'. If the " "option is specified but without a value, the value is implicitly " "'true'.">; } let Command = "target modules dump" in { def target_modules_dump_verbose : Option<"verbose", "v">, Desc<"Enable verbose dump.">; } let Command = "target modules list" in { def target_modules_list_address : Option<"address", "a">, Group<1>, Arg<"AddressOrExpression">, Desc<"Display the image at this address.">; def target_modules_list_arch : Option<"arch", "A">, Group<1>, OptionalArg<"Width">, Desc<"Display the architecture when listing images.">; def target_modules_list_triple : Option<"triple", "t">, Group<1>, OptionalArg<"Width">, Desc<"Display the triple when listing images.">; def target_modules_list_header : Option<"header", "h">, Group<1>, Desc<"Display the image base address as a load address if debugging, a file" " address otherwise.">; def target_modules_list_offset : Option<"offset", "o">, Group<1>, Desc<"Display the image load address offset from the base file address " "(the slide amount).">; def target_modules_list_uuid : Option<"uuid", "u">, Group<1>, Desc<"Display the UUID when listing images.">; def target_modules_list_fullpath : Option<"fullpath", "f">, Group<1>, OptionalArg<"Width">, Desc<"Display the fullpath to the image object file.">; def target_modules_list_directory : Option<"directory", "d">, Group<1>, OptionalArg<"Width">, Desc<"Display the directory with optional width for " "the image object file.">; def target_modules_list_basename : Option<"basename", "b">, Group<1>, OptionalArg<"Width">, Desc<"Display the basename with optional width for " "the image object file.">; def target_modules_list_symfile : Option<"symfile", "s">, Group<1>, OptionalArg<"Width">, Desc<"Display the fullpath to the image symbol file " "with optional width.">; def target_modules_list_symfile_unique : Option<"symfile-unique", "S">, Group<1>, OptionalArg<"Width">, Desc<"Display the symbol file with optional" " width only if it is different from the executable object file.">; def target_modules_list_mod_time : Option<"mod-time", "m">, Group<1>, OptionalArg<"Width">, Desc<"Display the modification time with optional " "width of the module.">; def target_modules_list_ref_count : Option<"ref-count", "r">, Group<1>, OptionalArg<"Width">, Desc<"Display the reference count if the module is " "still in the shared module cache.">; def target_modules_list_pointer : Option<"pointer", "p">, Group<1>, OptionalArg<"None">, Desc<"Display the module pointer.">; def target_modules_list_global : Option<"global", "g">, Group<1>, Desc<"Display the modules from the global module list, not just the " "current target.">; } let Command = "target modules show unwind" in { def target_modules_show_unwind_name : Option<"name", "n">, Group<1>, Arg<"FunctionName">, Desc<"Show unwind instructions for a function or symbol name.">; def target_modules_show_unwind_address : Option<"address", "a">, Group<2>, Arg<"AddressOrExpression">, Desc<"Show unwind instructions for a function " "or symbol containing an address">; } let Command = "target modules lookup" in { def target_modules_lookup_address : Option<"address", "a">, Group<1>, Arg<"AddressOrExpression">, Required, Desc<"Lookup an address in one or " "more target modules.">; def target_modules_lookup_offset : Option<"offset", "o">, Group<1>, Arg<"Offset">, Desc<"When looking up an address subtract from any " "addresses before doing the lookup.">; // FIXME: re-enable regex for types when the LookupTypeInModule actually uses // the regex option by adding to group 6. def target_modules_lookup_regex : Option<"regex", "r">, Groups<[2,4,5]>, Desc<"The argument for name lookups are regular expressions.">; def target_modules_lookup_symbol : Option<"symbol", "s">, Group<2>, Arg<"Symbol">, Required, Desc<"Lookup a symbol by name in the symbol tables" " in one or more target modules.">; def target_modules_lookup_file : Option<"file", "f">, Group<3>, Arg<"Filename">, Required, Desc<"Lookup a file by fullpath or basename in " "one or more target modules.">; def target_modules_lookup_line : Option<"line", "l">, Group<3>, Arg<"LineNum">, Desc<"Lookup a line number in a file (must be used in " "conjunction with --file).">; def target_modules_lookup_no_inlines : Option<"no-inlines", "i">, GroupRange<3,5>, Desc<"Ignore inline entries (must be used in conjunction with --file or " "--function).">; def target_modules_lookup_function : Option<"function", "F">, Group<4>, Arg<"FunctionName">, Required, Desc<"Lookup a function by name in the debug" " symbols in one or more target modules.">; def target_modules_lookup_name : Option<"name", "n">, Group<5>, Arg<"FunctionOrSymbol">, Required, Desc<"Lookup a function or symbol by " "name in one or more target modules.">; def target_modules_lookup_type : Option<"type", "t">, Group<6>, Arg<"Name">, Required, Desc<"Lookup a type by name in the debug symbols in one or more " "target modules.">; def target_modules_lookup_verbose : Option<"verbose", "v">, Desc<"Enable verbose lookup information.">; def target_modules_lookup_all : Option<"all", "A">, Desc<"Print all matches, " "not just the best match, if a best match is available.">; } let Command = "target stop hook add" in { def target_stop_hook_add_one_liner : Option<"one-liner", "o">, Arg<"OneLiner">, Desc<"Add a command for the stop hook. Can be specified " "more than once, and commands will be run in the order they appear.">; def target_stop_hook_add_shlib : Option<"shlib", "s">, Arg<"ShlibName">, Completion<"Module">, Desc<"Set the module within which the stop-hook is to be run.">; def target_stop_hook_add_thread_index : Option<"thread-index", "x">, Arg<"ThreadIndex">, Desc<"The stop hook is run only for the thread whose " "index matches this argument.">; def target_stop_hook_add_thread_id : Option<"thread-id", "t">, Arg<"ThreadID">, Desc<"The stop hook is run only for the thread whose TID " "matches this argument.">; def target_stop_hook_add_thread_name : Option<"thread-name", "T">, Arg<"ThreadName">, Desc<"The stop hook is run only for the thread whose " "thread name matches this argument.">; def target_stop_hook_add_queue_name : Option<"queue-name", "q">, Arg<"QueueName">, Desc<"The stop hook is run only for threads in the queue " "whose name is given by this argument.">; def target_stop_hook_add_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Desc<"Specify the source file within which the stop-hook " "is to be run.">, Completion<"SourceFile">; def target_stop_hook_add_start_line : Option<"start-line", "l">, Group<1>, Arg<"LineNum">, Desc<"Set the start of the line range for which the " "stop-hook is to be run.">; def target_stop_hook_add_end_line : Option<"end-line", "e">, Group<1>, Arg<"LineNum">, Desc<"Set the end of the line range for which the stop-hook" " is to be run.">; def target_stop_hook_add_classname : Option<"classname", "c">, Group<2>, Arg<"ClassName">, Desc<"Specify the class within which the stop-hook is to be run.">; def target_stop_hook_add_name : Option<"name", "n">, Group<3>, Arg<"FunctionName">, Desc<"Set the function name within which the stop hook" " will be run.">, Completion<"Symbol">; def target_stop_hook_add_auto_continue : Option<"auto-continue", "G">, Arg<"Boolean">, Desc<"The breakpoint will auto-continue after running its" " commands.">; } let Command = "thread backtrace" in { def thread_backtrace_count : Option<"count", "c">, Group<1>, Arg<"Count">, Desc<"How many frames to display (-1 for all)">; def thread_backtrace_start : Option<"start", "s">, Group<1>, Arg<"FrameIndex">, Desc<"Frame in which to start the backtrace">; def thread_backtrace_extended : Option<"extended", "e">, Group<1>, Arg<"Boolean">, Desc<"Show the extended backtrace, if available">; } let Command = "thread step scope" in { def thread_step_scope_step_in_avoids_no_debug : Option<"step-in-avoids-no-debug", "a">, Group<1>, Arg<"Boolean">, Desc<"A boolean value that sets whether stepping into functions will step " "over functions with no debug information.">; def thread_step_scope_step_out_avoids_no_debug : Option<"step-out-avoids-no-debug", "A">, Group<1>, Arg<"Boolean">, Desc<"A boolean value, if true stepping out of functions will continue to" " step out till it hits a function with debug information.">; def thread_step_scope_count : Option<"count", "c">, Group<1>, Arg<"Count">, Desc<"How many times to perform the stepping operation - currently only " "supported for step-inst and next-inst.">; def thread_step_scope_end_linenumber : Option<"end-linenumber", "e">, Group<1>, Arg<"LineNum">, Desc<"The line at which to stop stepping - " "defaults to the next line and only supported for step-in and step-over." " You can also pass the string 'block' to step to the end of the current" " block. This is particularly use in conjunction with --step-target to" " step through a complex calling sequence.">; def thread_step_scope_run_mode : Option<"run-mode", "m">, Group<1>, EnumArg<"RunMode", "TriRunningModes()">, Desc<"Determine how to run other " "threads while stepping the current thread.">; def thread_step_scope_step_over_regexp : Option<"step-over-regexp", "r">, Group<1>, Arg<"RegularExpression">, Desc<"A regular expression that defines" "function names to not to stop at when stepping in.">; def thread_step_scope_step_in_target : Option<"step-in-target", "t">, Group<1>, Arg<"FunctionName">, Desc<"The name of the directly called " "function step in should stop at when stepping into.">; } let Command = "thread until" in { def thread_until_frame : Option<"frame", "f">, Group<1>, Arg<"FrameIndex">, Desc<"Frame index for until operation - defaults to 0">; def thread_until_thread : Option<"thread", "t">, Group<1>, Arg<"ThreadIndex">, Desc<"Thread index for the thread for until operation">; def thread_until_run_mode : Option<"run-mode", "m">, Group<1>, EnumArg<"RunMode", "DuoRunningModes()">, Desc<"Determine how to run other" "threads while stepping this one">; def thread_until_address : Option<"address", "a">, Group<1>, Arg<"AddressOrExpression">, Desc<"Run until we reach the specified address," "or leave the function - can be specified multiple times.">; } let Command = "thread info" in { def thread_info_json : Option<"json", "j">, Desc<"Display the thread info in" " JSON format.">; def thread_info_stop_info : Option<"stop-info", "s">, Desc<"Display the " "extended stop info in JSON format.">; } let Command = "thread return" in { def thread_return_from_expression : Option<"from-expression", "x">, Desc<"Return from the innermost expression evaluation.">; } let Command = "thread jump" in { def thread_jump_file : Option<"file", "f">, Group<1>, Arg<"Filename">, Completion<"SourceFile">, Desc<"Specifies the source file to jump to.">; def thread_jump_line : Option<"line", "l">, Group<1>, Arg<"LineNum">, Required, Desc<"Specifies the line number to jump to.">; def thread_jump_by : Option<"by", "b">, Group<2>, Arg<"Offset">, Required, Desc<"Jumps by a relative line offset from the current line.">; def thread_jump_address : Option<"address", "a">, Group<3>, Arg<"AddressOrExpression">, Required, Desc<"Jumps to a specific address.">; def thread_jump_force : Option<"force", "r">, Groups<[1,2,3]>, Desc<"Allows the PC to leave the current function.">; } let Command = "thread plan list" in { def thread_plan_list_verbose : Option<"verbose", "v">, Group<1>, Desc<"Display more information about the thread plans">; def thread_plan_list_internal : Option<"internal", "i">, Group<1>, Desc<"Display internal as well as user thread plans">; } let Command = "type summary add" in { def type_summary_add_category : Option<"category", "w">, Arg<"Name">, Desc<"Add this to the given category instead of the default one.">; def type_summary_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, Desc<"If true, cascade through typedef chains.">; def type_summary_add_no_value : Option<"no-value", "v">, Desc<"Don't show the value, just show the summary, for this type.">; def type_summary_add_skip_pointers : Option<"skip-pointers", "p">, Desc<"Don't use this format for pointers-to-type objects.">; def type_summary_add_skip_references : Option<"skip-references", "r">, Desc<"Don't use this format for references-to-type objects.">; def type_summary_add_regex : Option<"regex", "x">, Desc<"Type names are actually regular expressions.">; def type_summary_add_inline_children : Option<"inline-children", "c">, Group<1>, Required, Desc<"If true, inline all child values into summary string.">; def type_summary_add_omit_names : Option<"omit-names", "O">, Group<1>, Desc<"If true, omit value names in the summary display.">; def type_summary_add_summary_string : Option<"summary-string", "s">, Group<2>, Arg<"SummaryString">, Required, Desc<"Summary string used to display text and object contents.">; def type_summary_add_python_script : Option<"python-script", "o">, Group<3>, Arg<"PythonScript">, Desc<"Give a one-liner Python script as part of the command.">; def type_summary_add_python_function : Option<"python-function", "F">, Group<3>, Arg<"PythonFunction">, Desc<"Give the name of a Python function to use for this type.">; def type_summary_add_input_python : Option<"input-python", "P">, Group<3>, Desc<"Input Python code to use for this type manually.">; def type_summary_add_expand : Option<"expand", "e">, Groups<[2,3]>, Desc<"Expand aggregate data types to show children on separate lines.">; def type_summary_add_hide_empty : Option<"hide-empty", "h">, Groups<[2,3]>, Desc<"Do not expand aggregate data types with no children.">; def type_summary_add_name : Option<"name", "n">, Groups<[2,3]>, Arg<"Name">, Desc<"A name for this summary string.">; } let Command = "type synth add" in { def type_synth_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, Desc<"If true, cascade through typedef chains.">; def type_synth_add_skip_pointers : Option<"skip-pointers", "p">, Desc<"Don't use this format for pointers-to-type objects.">; def type_synth_add_skip_references : Option<"skip-references", "r">, Desc<"Don't use this format for references-to-type objects.">; def type_synth_add_category : Option<"category", "w">, Arg<"Name">, Desc<"Add this to the given category instead of the default one.">; def type_synth_add_python_class : Option<"python-class", "l">, Group<2>, Arg<"PythonClass">, Desc<"Use this Python class to produce synthetic children.">; def type_synth_add_input_python : Option<"input-python", "P">, Group<3>, Desc<"Type Python code to generate a class that provides synthetic " "children.">; def type_synth_add_regex : Option<"regex", "x">, Desc<"Type names are actually regular expressions.">; } let Command = "type format add" in { def type_format_add_category : Option<"category", "w">, Arg<"Name">, Desc<"Add this to the given category instead of the default one.">; def type_format_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, Desc<"If true, cascade through typedef chains.">; def type_format_add_skip_pointers : Option<"skip-pointers", "p">, Desc<"Don't use this format for pointers-to-type objects.">; def type_format_add_skip_references : Option<"skip-references", "r">, Desc<"Don't use this format for references-to-type objects.">; def type_format_add_regex : Option<"regex", "x">, Desc<"Type names are actually regular expressions.">; def type_format_add_type : Option<"type", "t">, Group<2>, Arg<"Name">, Desc<"Format variables as if they were of this type.">; } let Command = "type formatter delete" in { def type_formatter_delete_all : Option<"all", "a">, Group<1>, Desc<"Delete from every category.">; def type_formatter_delete_category : Option<"category", "w">, Group<2>, Arg<"Name">, Desc<"Delete from given category.">; def type_formatter_delete_language : Option<"language", "l">, Group<3>, Arg<"Language">, Desc<"Delete from given language's category.">; } let Command = "type formatter clear" in { def type_formatter_clear_all : Option<"all", "a">, Desc<"Clear every category.">; } let Command = "type formatter list" in { def type_formatter_list_category_regex : Option<"category-regex", "w">, Group<1>, Arg<"Name">, Desc<"Only show categories matching this filter.">; def type_formatter_list_language : Option<"language", "l">, Group<2>, Arg<"Language">, Desc<"Only show the category for a specific language.">; } let Command = "type category define" in { def type_category_define_enabled : Option<"enabled", "e">, Desc<"If specified, this category will be created enabled.">; def type_category_define_language : Option<"language", "l">, Arg<"Language">, Desc<"Specify the language that this category is supported for.">; } let Command = "type category enable" in { def type_category_enable_language : Option<"language", "l">, Arg<"Language">, Desc<"Enable the category for this language.">; } let Command = "type category disable" in { def type_category_disable_language : Option<"language", "l">, Arg<"Language">, Desc<"Enable the category for this language.">; } let Command = "type filter add" in { def type_filter_add_cascade : Option<"cascade", "C">, Arg<"Boolean">, Desc<"If true, cascade through typedef chains.">; def type_filter_add_skip_pointers : Option<"skip-pointers", "p">, Desc<"Don't use this format for pointers-to-type objects.">; def type_filter_add_skip_references : Option<"skip-references", "r">, Desc<"Don't use this format for references-to-type objects.">; def type_filter_add_category : Option<"category", "w">, Arg<"Name">, Desc<"Add this to the given category instead of the default one.">; def type_filter_add_child : Option<"child", "c">, Arg<"ExpressionPath">, Desc<"Include this expression path in the synthetic view.">; def type_filter_add_regex : Option<"regex", "x">, Desc<"Type names are actually regular expressions.">; } let Command = "type lookup" in { def type_lookup_show_help : Option<"show-help", "h">, Desc<"Display available help for types">; def type_lookup_language : Option<"language", "l">, Arg<"Language">, Desc<"Which language's types should the search scope be">; } let Command = "watchpoint list" in { def watchpoint_list_brief : Option<"brief", "b">, Group<1>, Desc<"Give a " "brief description of the watchpoint (no location info).">; def watchpoint_list_full : Option<"full", "f">, Group<2>, Desc<"Give a full " "description of the watchpoint and its locations.">; def watchpoint_list_verbose : Option<"verbose", "v">, Group<3>, Desc<"Explain" "everything we know about the watchpoint (for debugging debugger bugs).">; } let Command = "watchpoint ignore" in { def watchpoint_ignore_ignore_count : Option<"ignore-count", "i">, Arg<"Count">, Required, Desc<"Set the number of times this watchpoint is" " skipped before stopping.">; } let Command = "watchpoint modify" in { def watchpoint_modify_condition : Option<"condition", "c">, Arg<"Expression">, Desc<"The watchpoint stops only if this condition expression evaluates " "to true.">; } let Command = "watchpoint command add" in { def watchpoint_command_add_one_liner : Option<"one-liner", "o">, Group<1>, Arg<"OneLiner">, Desc<"Specify a one-line watchpoint command inline. Be " "sure to surround it with quotes.">; def watchpoint_command_add_stop_on_error : Option<"stop-on-error", "e">, Arg<"Boolean">, Desc<"Specify whether watchpoint command execution should " "terminate on error.">; def watchpoint_command_add_script_type : Option<"script-type", "s">, EnumArg<"None", "ScriptOptionEnum()">, Desc<"Specify the language for the" " commands - if none is specified, the lldb command interpreter will be " "used.">; def watchpoint_command_add_python_function : Option<"python-function", "F">, Group<2>, Arg<"PythonFunction">, Desc<"Give the name of a Python function " "to run as command for this watchpoint. Be sure to give a module name if " "appropriate.">; } let Command = "watchpoint delete" in { def watchpoint_delete_force : Option<"force", "f">, Group<1>, Desc<"Delete all watchpoints without querying for confirmation.">; } diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp index 1840c42caf8e..5d77082420e6 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -1,2729 +1,2730 @@ //===-- AppleObjCRuntimeV2.cpp ----------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include #include #include #include #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "lldb/Core/ClangForward.h" #include "lldb/Host/OptionParser.h" #include "lldb/Symbol/CompilerType.h" #include "lldb/lldb-enumerations.h" #include "lldb/Core/ClangForward.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/Section.h" #include "lldb/Core/ValueObjectConstResult.h" #include "lldb/Core/ValueObjectVariable.h" #include "lldb/Expression/DiagnosticManager.h" #include "lldb/Expression/FunctionCaller.h" #include "lldb/Expression/UtilityFunction.h" #include "lldb/Interpreter/CommandObject.h" #include "lldb/Interpreter/CommandObjectMultiword.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionArgParser.h" #include "lldb/Interpreter/OptionValueBoolean.h" #include "lldb/Symbol/TypeSystemClang.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Symbol/TypeList.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/ABI.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Platform.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Scalar.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/Timer.h" #include "AppleObjCClassDescriptorV2.h" #include "AppleObjCDeclVendor.h" #include "AppleObjCRuntimeV2.h" #include "AppleObjCTrampolineHandler.h" #include "AppleObjCTypeEncodingParser.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" #include using namespace lldb; using namespace lldb_private; char AppleObjCRuntimeV2::ID = 0; static const char *g_get_dynamic_class_info_name = "__lldb_apple_objc_v2_get_dynamic_class_info"; // Testing using the new C++11 raw string literals. If this breaks GCC then we // will need to revert to the code above... static const char *g_get_dynamic_class_info_body = R"( extern "C" { size_t strlen(const char *); char *strncpy (char * s1, const char * s2, size_t n); int printf(const char * format, ...); } #define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) typedef struct _NXMapTable { void *prototype; unsigned num_classes; unsigned num_buckets_minus_one; void *buckets; } NXMapTable; #define NX_MAPNOTAKEY ((void *)(-1)) typedef struct BucketInfo { const char *name_ptr; Class isa; } BucketInfo; struct ClassInfo { Class isa; uint32_t hash; } __attribute__((__packed__)); uint32_t __lldb_apple_objc_v2_get_dynamic_class_info (void *gdb_objc_realized_classes_ptr, void *class_infos_ptr, uint32_t class_infos_byte_size, uint32_t should_log) { DEBUG_PRINTF ("gdb_objc_realized_classes_ptr = %p\n", gdb_objc_realized_classes_ptr); DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); DEBUG_PRINTF ("class_infos_byte_size = %u\n", class_infos_byte_size); const NXMapTable *grc = (const NXMapTable *)gdb_objc_realized_classes_ptr; if (grc) { const unsigned num_classes = grc->num_classes; if (class_infos_ptr) { const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; BucketInfo *buckets = (BucketInfo *)grc->buckets; uint32_t idx = 0; for (unsigned i=0; i<=grc->num_buckets_minus_one; ++i) { if (buckets[i].name_ptr != NX_MAPNOTAKEY) { if (idx < max_class_infos) { const char *s = buckets[i].name_ptr; uint32_t h = 5381; for (unsigned char c = *s; c; c = *++s) h = ((h << 5) + h) + c; class_infos[idx].hash = h; class_infos[idx].isa = buckets[i].isa; } ++idx; } } if (idx < max_class_infos) { class_infos[idx].isa = NULL; class_infos[idx].hash = 0; } } return num_classes; } return 0; } )"; // We'll substitute in class_getName or class_getNameRaw depending // on which is present. static const char *g_shared_cache_class_name_funcptr = R"( extern "C" { const char *%s(void *objc_class); const char *(*class_name_lookup_func)(void *) = %s; } )"; static const char *g_get_shared_cache_class_info_name = "__lldb_apple_objc_v2_get_shared_cache_class_info"; // Testing using the new C++11 raw string literals. If this breaks GCC then we // will need to revert to the code above... static const char *g_get_shared_cache_class_info_body = R"( extern "C" { size_t strlen(const char *); char *strncpy (char * s1, const char * s2, size_t n); int printf(const char * format, ...); } #define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) struct objc_classheader_t { int32_t clsOffset; int32_t hiOffset; }; struct objc_clsopt_t { uint32_t capacity; uint32_t occupied; uint32_t shift; uint32_t mask; uint32_t zero; uint32_t unused; uint64_t salt; uint32_t scramble[256]; uint8_t tab[0]; // tab[mask+1] // uint8_t checkbytes[capacity]; // int32_t offset[capacity]; // objc_classheader_t clsOffsets[capacity]; // uint32_t duplicateCount; // objc_classheader_t duplicateOffsets[duplicateCount]; }; struct objc_opt_t { uint32_t version; int32_t selopt_offset; int32_t headeropt_offset; int32_t clsopt_offset; }; struct objc_opt_v14_t { uint32_t version; uint32_t flags; int32_t selopt_offset; int32_t headeropt_offset; int32_t clsopt_offset; }; struct ClassInfo { Class isa; uint32_t hash; } __attribute__((__packed__)); uint32_t __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, void *class_infos_ptr, uint32_t class_infos_byte_size, uint32_t should_log) { uint32_t idx = 0; DEBUG_PRINTF ("objc_opt_ro_ptr = %p\n", objc_opt_ro_ptr); DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); DEBUG_PRINTF ("class_infos_byte_size = %u (%llu class infos)\n", class_infos_byte_size, (uint64_t)(class_infos_byte_size/sizeof(ClassInfo))); if (objc_opt_ro_ptr) { const objc_opt_t *objc_opt = (objc_opt_t *)objc_opt_ro_ptr; const objc_opt_v14_t* objc_opt_v14 = (objc_opt_v14_t*)objc_opt_ro_ptr; const bool is_v14_format = objc_opt->version >= 14; if (is_v14_format) { DEBUG_PRINTF ("objc_opt->version = %u\n", objc_opt_v14->version); DEBUG_PRINTF ("objc_opt->flags = %u\n", objc_opt_v14->flags); DEBUG_PRINTF ("objc_opt->selopt_offset = %d\n", objc_opt_v14->selopt_offset); DEBUG_PRINTF ("objc_opt->headeropt_offset = %d\n", objc_opt_v14->headeropt_offset); DEBUG_PRINTF ("objc_opt->clsopt_offset = %d\n", objc_opt_v14->clsopt_offset); } else { DEBUG_PRINTF ("objc_opt->version = %u\n", objc_opt->version); DEBUG_PRINTF ("objc_opt->selopt_offset = %d\n", objc_opt->selopt_offset); DEBUG_PRINTF ("objc_opt->headeropt_offset = %d\n", objc_opt->headeropt_offset); DEBUG_PRINTF ("objc_opt->clsopt_offset = %d\n", objc_opt->clsopt_offset); } if (objc_opt->version == 12 || objc_opt->version == 13 || objc_opt->version == 14 || objc_opt->version == 15) { const objc_clsopt_t* clsopt = NULL; if (is_v14_format) clsopt = (const objc_clsopt_t*)((uint8_t *)objc_opt_v14 + objc_opt_v14->clsopt_offset); else clsopt = (const objc_clsopt_t*)((uint8_t *)objc_opt + objc_opt->clsopt_offset); const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); DEBUG_PRINTF("max_class_infos = %llu\n", (uint64_t)max_class_infos); ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; int32_t invalidEntryOffset = 0; // this is safe to do because the version field order is invariant if (objc_opt->version == 12) invalidEntryOffset = 16; const uint8_t *checkbytes = &clsopt->tab[clsopt->mask+1]; const int32_t *offsets = (const int32_t *)(checkbytes + clsopt->capacity); const objc_classheader_t *classOffsets = (const objc_classheader_t *)(offsets + clsopt->capacity); DEBUG_PRINTF ("clsopt->capacity = %u\n", clsopt->capacity); DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask); DEBUG_PRINTF ("classOffsets = %p\n", classOffsets); DEBUG_PRINTF("invalidEntryOffset = %d\n", invalidEntryOffset); for (uint32_t i=0; icapacity; ++i) { const int32_t clsOffset = classOffsets[i].clsOffset; DEBUG_PRINTF("clsOffset[%u] = %u\n", i, clsOffset); if (clsOffset & 1) { DEBUG_PRINTF("clsOffset & 1\n"); continue; // duplicate } else if (clsOffset == invalidEntryOffset) { DEBUG_PRINTF("clsOffset == invalidEntryOffset\n"); continue; // invalid offset } if (class_infos && idx < max_class_infos) { class_infos[idx].isa = (Class)((uint8_t *)clsopt + clsOffset); const char *name = class_name_lookup_func (class_infos[idx].isa); DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); // Hash the class name so we don't have to read it const char *s = name; uint32_t h = 5381; for (unsigned char c = *s; c; c = *++s) { // class_getName demangles swift names and the hash must // be calculated on the mangled name. hash==0 means lldb // will fetch the mangled name and compute the hash in // ParseClassInfoArray. if (c == '.') { h = 0; break; } h = ((h << 5) + h) + c; } class_infos[idx].hash = h; } else { DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n"); } ++idx; } const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; const uint32_t duplicate_count = *duplicate_count_ptr; const objc_classheader_t *duplicateClassOffsets = (const objc_classheader_t *)(&duplicate_count_ptr[1]); DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); for (uint32_t i=0; iGetAddressByteSize(); const Symbol *symbol = module_sp->FindFirstSymbolWithNameAndType(name, lldb::eSymbolTypeData); if (symbol && symbol->ValueIsAddress()) { lldb::addr_t symbol_load_addr = symbol->GetAddressRef().GetLoadAddress(&process->GetTarget()); if (symbol_load_addr != LLDB_INVALID_ADDRESS) { if (read_value) return process->ReadUnsignedIntegerFromMemory( symbol_load_addr, byte_size, default_value, error); else return symbol_load_addr; } else { error.SetErrorString("symbol address invalid"); return default_value; } } else { error.SetErrorString("no symbol"); return default_value; } } static void RegisterObjCExceptionRecognizer(); AppleObjCRuntimeV2::AppleObjCRuntimeV2(Process *process, const ModuleSP &objc_module_sp) : AppleObjCRuntime(process), m_get_class_info_code(), m_get_class_info_args(LLDB_INVALID_ADDRESS), m_get_class_info_args_mutex(), m_get_shared_cache_class_info_code(), m_get_shared_cache_class_info_args(LLDB_INVALID_ADDRESS), m_get_shared_cache_class_info_args_mutex(), m_decl_vendor_up(), m_tagged_pointer_obfuscator(LLDB_INVALID_ADDRESS), m_isa_hash_table_ptr(LLDB_INVALID_ADDRESS), m_hash_signature(), m_has_object_getClass(false), m_loaded_objc_opt(false), m_non_pointer_isa_cache_up( NonPointerISACache::CreateInstance(*this, objc_module_sp)), m_tagged_pointer_vendor_up( TaggedPointerVendorV2::CreateInstance(*this, objc_module_sp)), m_encoding_to_type_sp(), m_noclasses_warning_emitted(false), m_CFBoolean_values() { static const ConstString g_gdb_object_getClass("gdb_object_getClass"); m_has_object_getClass = (objc_module_sp->FindFirstSymbolWithNameAndType( g_gdb_object_getClass, eSymbolTypeCode) != nullptr); RegisterObjCExceptionRecognizer(); } bool AppleObjCRuntimeV2::GetDynamicTypeAndAddress( ValueObject &in_value, lldb::DynamicValueType use_dynamic, TypeAndOrName &class_type_or_name, Address &address, Value::ValueType &value_type) { // We should never get here with a null process... assert(m_process != nullptr); // The Runtime is attached to a particular process, you shouldn't pass in a // value from another process. Note, however, the process might be NULL (e.g. // if the value was made with SBTarget::EvaluateExpression...) in which case // it is sufficient if the target's match: Process *process = in_value.GetProcessSP().get(); if (process) assert(process == m_process); else assert(in_value.GetTargetSP().get() == m_process->CalculateTarget().get()); class_type_or_name.Clear(); value_type = Value::ValueType::eValueTypeScalar; // Make sure we can have a dynamic value before starting... if (CouldHaveDynamicValue(in_value)) { // First job, pull out the address at 0 offset from the object That will // be the ISA pointer. ClassDescriptorSP objc_class_sp(GetNonKVOClassDescriptor(in_value)); if (objc_class_sp) { const addr_t object_ptr = in_value.GetPointerValue(); address.SetRawAddress(object_ptr); ConstString class_name(objc_class_sp->GetClassName()); class_type_or_name.SetName(class_name); TypeSP type_sp(objc_class_sp->GetType()); if (type_sp) class_type_or_name.SetTypeSP(type_sp); else { type_sp = LookupInCompleteClassCache(class_name); if (type_sp) { objc_class_sp->SetType(type_sp); class_type_or_name.SetTypeSP(type_sp); } else { // try to go for a CompilerType at least if (auto *vendor = GetDeclVendor()) { auto types = vendor->FindTypes(class_name, /*max_matches*/ 1); if (!types.empty()) class_type_or_name.SetCompilerType(types.front()); } } } } } return !class_type_or_name.IsEmpty(); } // Static Functions LanguageRuntime *AppleObjCRuntimeV2::CreateInstance(Process *process, LanguageType language) { // FIXME: This should be a MacOS or iOS process, and we need to look for the // OBJC section to make // sure we aren't using the V1 runtime. if (language == eLanguageTypeObjC) { ModuleSP objc_module_sp; if (AppleObjCRuntime::GetObjCVersion(process, objc_module_sp) == ObjCRuntimeVersions::eAppleObjC_V2) return new AppleObjCRuntimeV2(process, objc_module_sp); else return nullptr; } else return nullptr; } static constexpr OptionDefinition g_objc_classtable_dump_options[] = { {LLDB_OPT_SET_ALL, false, "verbose", 'v', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Print ivar and method information in detail"}}; class CommandObjectObjC_ClassTable_Dump : public CommandObjectParsed { public: class CommandOptions : public Options { public: CommandOptions() : Options(), m_verbose(false, false) {} ~CommandOptions() override = default; Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) override { Status error; const int short_option = m_getopt_table[option_idx].val; switch (short_option) { case 'v': m_verbose.SetCurrentValue(true); m_verbose.SetOptionWasSet(); break; default: error.SetErrorStringWithFormat("unrecognized short option '%c'", short_option); break; } return error; } void OptionParsingStarting(ExecutionContext *execution_context) override { m_verbose.Clear(); } llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_objc_classtable_dump_options); } OptionValueBoolean m_verbose; }; CommandObjectObjC_ClassTable_Dump(CommandInterpreter &interpreter) : CommandObjectParsed( interpreter, "dump", "Dump information on Objective-C classes " "known to the current process.", "language objc class-table dump", eCommandRequiresProcess | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), m_options() { CommandArgumentEntry arg; CommandArgumentData index_arg; // Define the first (and only) variant of this arg. index_arg.arg_type = eArgTypeRegularExpression; index_arg.arg_repetition = eArgRepeatOptional; // There is only one variant this argument could be; put it into the // argument entry. arg.push_back(index_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg); } ~CommandObjectObjC_ClassTable_Dump() override = default; Options *GetOptions() override { return &m_options; } protected: bool DoExecute(Args &command, CommandReturnObject &result) override { std::unique_ptr regex_up; switch (command.GetArgumentCount()) { case 0: break; case 1: { regex_up.reset(new RegularExpression( llvm::StringRef::withNullAsEmpty(command.GetArgumentAtIndex(0)))); if (!regex_up->IsValid()) { result.AppendError( "invalid argument - please provide a valid regular expression"); result.SetStatus(lldb::eReturnStatusFailed); return false; } break; } default: { result.AppendError("please provide 0 or 1 arguments"); result.SetStatus(lldb::eReturnStatusFailed); return false; } } Process *process = m_exe_ctx.GetProcessPtr(); ObjCLanguageRuntime *objc_runtime = ObjCLanguageRuntime::Get(*process); if (objc_runtime) { auto iterators_pair = objc_runtime->GetDescriptorIteratorPair(); auto iterator = iterators_pair.first; auto &std_out = result.GetOutputStream(); for (; iterator != iterators_pair.second; iterator++) { if (iterator->second) { const char *class_name = iterator->second->GetClassName().AsCString(""); if (regex_up && class_name && !regex_up->Execute(llvm::StringRef(class_name))) continue; std_out.Printf("isa = 0x%" PRIx64, iterator->first); std_out.Printf(" name = %s", class_name); std_out.Printf(" instance size = %" PRIu64, iterator->second->GetInstanceSize()); std_out.Printf(" num ivars = %" PRIuPTR, (uintptr_t)iterator->second->GetNumIVars()); if (auto superclass = iterator->second->GetSuperclass()) { std_out.Printf(" superclass = %s", superclass->GetClassName().AsCString("")); } std_out.Printf("\n"); if (m_options.m_verbose) { for (size_t i = 0; i < iterator->second->GetNumIVars(); i++) { auto ivar = iterator->second->GetIVarAtIndex(i); std_out.Printf( " ivar name = %s type = %s size = %" PRIu64 " offset = %" PRId32 "\n", ivar.m_name.AsCString(""), ivar.m_type.GetDisplayTypeName().AsCString(""), ivar.m_size, ivar.m_offset); } iterator->second->Describe( nullptr, [&std_out](const char *name, const char *type) -> bool { std_out.Printf(" instance method name = %s type = %s\n", name, type); return false; }, [&std_out](const char *name, const char *type) -> bool { std_out.Printf(" class method name = %s type = %s\n", name, type); return false; }, nullptr); } } else { if (regex_up && !regex_up->Execute(llvm::StringRef())) continue; std_out.Printf("isa = 0x%" PRIx64 " has no associated class.\n", iterator->first); } } result.SetStatus(lldb::eReturnStatusSuccessFinishResult); return true; } else { result.AppendError("current process has no Objective-C runtime loaded"); result.SetStatus(lldb::eReturnStatusFailed); return false; } } CommandOptions m_options; }; class CommandObjectMultiwordObjC_TaggedPointer_Info : public CommandObjectParsed { public: CommandObjectMultiwordObjC_TaggedPointer_Info(CommandInterpreter &interpreter) : CommandObjectParsed( interpreter, "info", "Dump information on a tagged pointer.", "language objc tagged-pointer info", eCommandRequiresProcess | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) { CommandArgumentEntry arg; CommandArgumentData index_arg; // Define the first (and only) variant of this arg. index_arg.arg_type = eArgTypeAddress; index_arg.arg_repetition = eArgRepeatPlus; // There is only one variant this argument could be; put it into the // argument entry. arg.push_back(index_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg); } ~CommandObjectMultiwordObjC_TaggedPointer_Info() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { if (command.GetArgumentCount() == 0) { result.AppendError("this command requires arguments"); result.SetStatus(lldb::eReturnStatusFailed); return false; } Process *process = m_exe_ctx.GetProcessPtr(); ExecutionContext exe_ctx(process); ObjCLanguageRuntime *objc_runtime = ObjCLanguageRuntime::Get(*process); if (objc_runtime) { ObjCLanguageRuntime::TaggedPointerVendor *tagged_ptr_vendor = objc_runtime->GetTaggedPointerVendor(); if (tagged_ptr_vendor) { for (size_t i = 0; i < command.GetArgumentCount(); i++) { const char *arg_str = command.GetArgumentAtIndex(i); if (!arg_str) continue; Status error; lldb::addr_t arg_addr = OptionArgParser::ToAddress( &exe_ctx, arg_str, LLDB_INVALID_ADDRESS, &error); if (arg_addr == 0 || arg_addr == LLDB_INVALID_ADDRESS || error.Fail()) continue; auto descriptor_sp = tagged_ptr_vendor->GetClassDescriptor(arg_addr); if (!descriptor_sp) continue; uint64_t info_bits = 0; uint64_t value_bits = 0; uint64_t payload = 0; if (descriptor_sp->GetTaggedPointerInfo(&info_bits, &value_bits, &payload)) { result.GetOutputStream().Printf( "0x%" PRIx64 " is tagged.\n\tpayload = 0x%" PRIx64 "\n\tvalue = 0x%" PRIx64 "\n\tinfo bits = 0x%" PRIx64 "\n\tclass = %s\n", (uint64_t)arg_addr, payload, value_bits, info_bits, descriptor_sp->GetClassName().AsCString("")); } else { result.GetOutputStream().Printf("0x%" PRIx64 " is not tagged.\n", (uint64_t)arg_addr); } } } else { result.AppendError("current process has no tagged pointer support"); result.SetStatus(lldb::eReturnStatusFailed); return false; } result.SetStatus(lldb::eReturnStatusSuccessFinishResult); return true; } else { result.AppendError("current process has no Objective-C runtime loaded"); result.SetStatus(lldb::eReturnStatusFailed); return false; } } }; class CommandObjectMultiwordObjC_ClassTable : public CommandObjectMultiword { public: CommandObjectMultiwordObjC_ClassTable(CommandInterpreter &interpreter) : CommandObjectMultiword( interpreter, "class-table", "Commands for operating on the Objective-C class table.", "class-table []") { LoadSubCommand( "dump", CommandObjectSP(new CommandObjectObjC_ClassTable_Dump(interpreter))); } ~CommandObjectMultiwordObjC_ClassTable() override = default; }; class CommandObjectMultiwordObjC_TaggedPointer : public CommandObjectMultiword { public: CommandObjectMultiwordObjC_TaggedPointer(CommandInterpreter &interpreter) : CommandObjectMultiword( interpreter, "tagged-pointer", "Commands for operating on Objective-C tagged pointers.", "class-table []") { LoadSubCommand( "info", CommandObjectSP( new CommandObjectMultiwordObjC_TaggedPointer_Info(interpreter))); } ~CommandObjectMultiwordObjC_TaggedPointer() override = default; }; class CommandObjectMultiwordObjC : public CommandObjectMultiword { public: CommandObjectMultiwordObjC(CommandInterpreter &interpreter) : CommandObjectMultiword( interpreter, "objc", "Commands for operating on the Objective-C language runtime.", "objc []") { LoadSubCommand("class-table", CommandObjectSP( new CommandObjectMultiwordObjC_ClassTable(interpreter))); LoadSubCommand("tagged-pointer", CommandObjectSP(new CommandObjectMultiwordObjC_TaggedPointer( interpreter))); } ~CommandObjectMultiwordObjC() override = default; }; void AppleObjCRuntimeV2::Initialize() { PluginManager::RegisterPlugin( GetPluginNameStatic(), "Apple Objective-C Language Runtime - Version 2", CreateInstance, [](CommandInterpreter &interpreter) -> lldb::CommandObjectSP { return CommandObjectSP(new CommandObjectMultiwordObjC(interpreter)); }, GetBreakpointExceptionPrecondition); } void AppleObjCRuntimeV2::Terminate() { PluginManager::UnregisterPlugin(CreateInstance); } lldb_private::ConstString AppleObjCRuntimeV2::GetPluginNameStatic() { static ConstString g_name("apple-objc-v2"); return g_name; } // PluginInterface protocol lldb_private::ConstString AppleObjCRuntimeV2::GetPluginName() { return GetPluginNameStatic(); } uint32_t AppleObjCRuntimeV2::GetPluginVersion() { return 1; } BreakpointResolverSP AppleObjCRuntimeV2::CreateExceptionResolver(Breakpoint *bkpt, bool catch_bp, bool throw_bp) { BreakpointResolverSP resolver_sp; if (throw_bp) resolver_sp = std::make_shared( bkpt, std::get<1>(GetExceptionThrowLocation()).AsCString(), eFunctionNameTypeBase, eLanguageTypeUnknown, Breakpoint::Exact, 0, eLazyBoolNo); // FIXME: We don't do catch breakpoints for ObjC yet. // Should there be some way for the runtime to specify what it can do in this // regard? return resolver_sp; } UtilityFunction *AppleObjCRuntimeV2::CreateObjectChecker(const char *name) { char check_function_code[2048]; int len = 0; if (m_has_object_getClass) { len = ::snprintf(check_function_code, sizeof(check_function_code), R"( extern "C" void *gdb_object_getClass(void *); extern "C" int printf(const char *format, ...); extern "C" void %s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) { if ($__lldb_arg_obj == (void *)0) return; // nil is ok if (!gdb_object_getClass($__lldb_arg_obj)) { *((volatile int *)0) = 'ocgc'; } else if ($__lldb_arg_selector != (void *)0) { signed char $responds = (signed char) [(id)$__lldb_arg_obj respondsToSelector: (void *) $__lldb_arg_selector]; if ($responds == (signed char) 0) *((volatile int *)0) = 'ocgc'; } })", name); } else { len = ::snprintf(check_function_code, sizeof(check_function_code), R"( extern "C" void *gdb_class_getClass(void *); extern "C" int printf(const char *format, ...); extern "C" void %s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) { if ($__lldb_arg_obj == (void *)0) return; // nil is ok void **$isa_ptr = (void **)$__lldb_arg_obj; if (*$isa_ptr == (void *)0 || !gdb_class_getClass(*$isa_ptr)) *((volatile int *)0) = 'ocgc'; else if ($__lldb_arg_selector != (void *)0) { signed char $responds = (signed char) [(id)$__lldb_arg_obj respondsToSelector: (void *) $__lldb_arg_selector]; if ($responds == (signed char) 0) *((volatile int *)0) = 'ocgc'; } })", name); } assert(len < (int)sizeof(check_function_code)); UNUSED_IF_ASSERT_DISABLED(len); Status error; return GetTargetRef().GetUtilityFunctionForLanguage( check_function_code, eLanguageTypeObjC, name, error); } size_t AppleObjCRuntimeV2::GetByteOffsetForIvar(CompilerType &parent_ast_type, const char *ivar_name) { uint32_t ivar_offset = LLDB_INVALID_IVAR_OFFSET; const char *class_name = parent_ast_type.GetConstTypeName().AsCString(); if (class_name && class_name[0] && ivar_name && ivar_name[0]) { // Make the objective C V2 mangled name for the ivar offset from the class // name and ivar name std::string buffer("OBJC_IVAR_$_"); buffer.append(class_name); buffer.push_back('.'); buffer.append(ivar_name); ConstString ivar_const_str(buffer.c_str()); // Try to get the ivar offset address from the symbol table first using the // name we created above SymbolContextList sc_list; Target &target = m_process->GetTarget(); target.GetImages().FindSymbolsWithNameAndType(ivar_const_str, eSymbolTypeObjCIVar, sc_list); addr_t ivar_offset_address = LLDB_INVALID_ADDRESS; Status error; SymbolContext ivar_offset_symbol; if (sc_list.GetSize() == 1 && sc_list.GetContextAtIndex(0, ivar_offset_symbol)) { if (ivar_offset_symbol.symbol) ivar_offset_address = ivar_offset_symbol.symbol->GetLoadAddress(&target); } // If we didn't get the ivar offset address from the symbol table, fall // back to getting it from the runtime if (ivar_offset_address == LLDB_INVALID_ADDRESS) ivar_offset_address = LookupRuntimeSymbol(ivar_const_str); if (ivar_offset_address != LLDB_INVALID_ADDRESS) ivar_offset = m_process->ReadUnsignedIntegerFromMemory( ivar_offset_address, 4, LLDB_INVALID_IVAR_OFFSET, error); } return ivar_offset; } // tagged pointers are special not-a-real-pointer values that contain both type // and value information this routine attempts to check with as little // computational effort as possible whether something could possibly be a // tagged pointer - false positives are possible but false negatives shouldn't bool AppleObjCRuntimeV2::IsTaggedPointer(addr_t ptr) { if (!m_tagged_pointer_vendor_up) return false; return m_tagged_pointer_vendor_up->IsPossibleTaggedPointer(ptr); } class RemoteNXMapTable { public: RemoteNXMapTable() : m_count(0), m_num_buckets_minus_one(0), m_buckets_ptr(LLDB_INVALID_ADDRESS), m_process(nullptr), m_end_iterator(*this, -1), m_load_addr(LLDB_INVALID_ADDRESS), m_map_pair_size(0), m_invalid_key(0) {} void Dump() { printf("RemoteNXMapTable.m_load_addr = 0x%" PRIx64 "\n", m_load_addr); printf("RemoteNXMapTable.m_count = %u\n", m_count); printf("RemoteNXMapTable.m_num_buckets_minus_one = %u\n", m_num_buckets_minus_one); printf("RemoteNXMapTable.m_buckets_ptr = 0x%" PRIX64 "\n", m_buckets_ptr); } bool ParseHeader(Process *process, lldb::addr_t load_addr) { m_process = process; m_load_addr = load_addr; m_map_pair_size = m_process->GetAddressByteSize() * 2; m_invalid_key = m_process->GetAddressByteSize() == 8 ? UINT64_MAX : UINT32_MAX; Status err; // This currently holds true for all platforms we support, but we might // need to change this to use get the actually byte size of "unsigned" from // the target AST... const uint32_t unsigned_byte_size = sizeof(uint32_t); // Skip the prototype as we don't need it (const struct // +NXMapTablePrototype *prototype) bool success = true; if (load_addr == LLDB_INVALID_ADDRESS) success = false; else { lldb::addr_t cursor = load_addr + m_process->GetAddressByteSize(); // unsigned count; m_count = m_process->ReadUnsignedIntegerFromMemory( cursor, unsigned_byte_size, 0, err); if (m_count) { cursor += unsigned_byte_size; // unsigned nbBucketsMinusOne; m_num_buckets_minus_one = m_process->ReadUnsignedIntegerFromMemory( cursor, unsigned_byte_size, 0, err); cursor += unsigned_byte_size; // void *buckets; m_buckets_ptr = m_process->ReadPointerFromMemory(cursor, err); success = m_count > 0 && m_buckets_ptr != LLDB_INVALID_ADDRESS; } } if (!success) { m_count = 0; m_num_buckets_minus_one = 0; m_buckets_ptr = LLDB_INVALID_ADDRESS; } return success; } // const_iterator mimics NXMapState and its code comes from NXInitMapState // and NXNextMapState. typedef std::pair element; friend class const_iterator; class const_iterator { public: const_iterator(RemoteNXMapTable &parent, int index) : m_parent(parent), m_index(index) { AdvanceToValidIndex(); } const_iterator(const const_iterator &rhs) : m_parent(rhs.m_parent), m_index(rhs.m_index) { // AdvanceToValidIndex() has been called by rhs already. } const_iterator &operator=(const const_iterator &rhs) { // AdvanceToValidIndex() has been called by rhs already. assert(&m_parent == &rhs.m_parent); m_index = rhs.m_index; return *this; } bool operator==(const const_iterator &rhs) const { if (&m_parent != &rhs.m_parent) return false; if (m_index != rhs.m_index) return false; return true; } bool operator!=(const const_iterator &rhs) const { return !(operator==(rhs)); } const_iterator &operator++() { AdvanceToValidIndex(); return *this; } const element operator*() const { if (m_index == -1) { // TODO find a way to make this an error, but not an assert return element(); } lldb::addr_t pairs_ptr = m_parent.m_buckets_ptr; size_t map_pair_size = m_parent.m_map_pair_size; lldb::addr_t pair_ptr = pairs_ptr + (m_index * map_pair_size); Status err; lldb::addr_t key = m_parent.m_process->ReadPointerFromMemory(pair_ptr, err); if (!err.Success()) return element(); lldb::addr_t value = m_parent.m_process->ReadPointerFromMemory( pair_ptr + m_parent.m_process->GetAddressByteSize(), err); if (!err.Success()) return element(); std::string key_string; m_parent.m_process->ReadCStringFromMemory(key, key_string, err); if (!err.Success()) return element(); return element(ConstString(key_string.c_str()), (ObjCLanguageRuntime::ObjCISA)value); } private: void AdvanceToValidIndex() { if (m_index == -1) return; const lldb::addr_t pairs_ptr = m_parent.m_buckets_ptr; const size_t map_pair_size = m_parent.m_map_pair_size; const lldb::addr_t invalid_key = m_parent.m_invalid_key; Status err; while (m_index--) { lldb::addr_t pair_ptr = pairs_ptr + (m_index * map_pair_size); lldb::addr_t key = m_parent.m_process->ReadPointerFromMemory(pair_ptr, err); if (!err.Success()) { m_index = -1; return; } if (key != invalid_key) return; } } RemoteNXMapTable &m_parent; int m_index; }; const_iterator begin() { return const_iterator(*this, m_num_buckets_minus_one + 1); } const_iterator end() { return m_end_iterator; } uint32_t GetCount() const { return m_count; } uint32_t GetBucketCount() const { return m_num_buckets_minus_one; } lldb::addr_t GetBucketDataPointer() const { return m_buckets_ptr; } lldb::addr_t GetTableLoadAddress() const { return m_load_addr; } private: // contents of _NXMapTable struct uint32_t m_count; uint32_t m_num_buckets_minus_one; lldb::addr_t m_buckets_ptr; lldb_private::Process *m_process; const_iterator m_end_iterator; lldb::addr_t m_load_addr; size_t m_map_pair_size; lldb::addr_t m_invalid_key; }; AppleObjCRuntimeV2::HashTableSignature::HashTableSignature() : m_count(0), m_num_buckets(0), m_buckets_ptr(0) {} void AppleObjCRuntimeV2::HashTableSignature::UpdateSignature( const RemoteNXMapTable &hash_table) { m_count = hash_table.GetCount(); m_num_buckets = hash_table.GetBucketCount(); m_buckets_ptr = hash_table.GetBucketDataPointer(); } bool AppleObjCRuntimeV2::HashTableSignature::NeedsUpdate( Process *process, AppleObjCRuntimeV2 *runtime, RemoteNXMapTable &hash_table) { if (!hash_table.ParseHeader(process, runtime->GetISAHashTablePointer())) { return false; // Failed to parse the header, no need to update anything } // Check with out current signature and return true if the count, number of // buckets or the hash table address changes. if (m_count == hash_table.GetCount() && m_num_buckets == hash_table.GetBucketCount() && m_buckets_ptr == hash_table.GetBucketDataPointer()) { // Hash table hasn't changed return false; } // Hash table data has changed, we need to update return true; } ObjCLanguageRuntime::ClassDescriptorSP AppleObjCRuntimeV2::GetClassDescriptorFromISA(ObjCISA isa) { ObjCLanguageRuntime::ClassDescriptorSP class_descriptor_sp; if (m_non_pointer_isa_cache_up) class_descriptor_sp = m_non_pointer_isa_cache_up->GetClassDescriptor(isa); if (!class_descriptor_sp) class_descriptor_sp = ObjCLanguageRuntime::GetClassDescriptorFromISA(isa); return class_descriptor_sp; } ObjCLanguageRuntime::ClassDescriptorSP AppleObjCRuntimeV2::GetClassDescriptor(ValueObject &valobj) { ClassDescriptorSP objc_class_sp; if (valobj.IsBaseClass()) { ValueObject *parent = valobj.GetParent(); // if I am my own parent, bail out of here fast.. if (parent && parent != &valobj) { ClassDescriptorSP parent_descriptor_sp = GetClassDescriptor(*parent); if (parent_descriptor_sp) return parent_descriptor_sp->GetSuperclass(); } return nullptr; } // if we get an invalid VO (which might still happen when playing around with // pointers returned by the expression parser, don't consider this a valid // ObjC object) if (valobj.GetCompilerType().IsValid()) { addr_t isa_pointer = valobj.GetPointerValue(); // tagged pointer if (IsTaggedPointer(isa_pointer)) { return m_tagged_pointer_vendor_up->GetClassDescriptor(isa_pointer); } else { ExecutionContext exe_ctx(valobj.GetExecutionContextRef()); Process *process = exe_ctx.GetProcessPtr(); if (process) { Status error; ObjCISA isa = process->ReadPointerFromMemory(isa_pointer, error); if (isa != LLDB_INVALID_ADDRESS) { objc_class_sp = GetClassDescriptorFromISA(isa); if (isa && !objc_class_sp) { Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); LLDB_LOGF(log, "0x%" PRIx64 ": AppleObjCRuntimeV2::GetClassDescriptor() ISA was " "not in class descriptor cache 0x%" PRIx64, isa_pointer, isa); } } } } } return objc_class_sp; } lldb::addr_t AppleObjCRuntimeV2::GetTaggedPointerObfuscator() { if (m_tagged_pointer_obfuscator != LLDB_INVALID_ADDRESS) return m_tagged_pointer_obfuscator; Process *process = GetProcess(); ModuleSP objc_module_sp(GetObjCModule()); if (!objc_module_sp) return LLDB_INVALID_ADDRESS; static ConstString g_gdb_objc_obfuscator("objc_debug_taggedpointer_obfuscator"); const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( g_gdb_objc_obfuscator, lldb::eSymbolTypeAny); if (symbol) { lldb::addr_t g_gdb_obj_obfuscator_ptr = symbol->GetLoadAddress(&process->GetTarget()); if (g_gdb_obj_obfuscator_ptr != LLDB_INVALID_ADDRESS) { Status error; m_tagged_pointer_obfuscator = process->ReadPointerFromMemory( g_gdb_obj_obfuscator_ptr, error); } } // If we don't have a correct value at this point, there must be no obfuscation. if (m_tagged_pointer_obfuscator == LLDB_INVALID_ADDRESS) m_tagged_pointer_obfuscator = 0; return m_tagged_pointer_obfuscator; } lldb::addr_t AppleObjCRuntimeV2::GetISAHashTablePointer() { if (m_isa_hash_table_ptr == LLDB_INVALID_ADDRESS) { Process *process = GetProcess(); ModuleSP objc_module_sp(GetObjCModule()); if (!objc_module_sp) return LLDB_INVALID_ADDRESS; static ConstString g_gdb_objc_realized_classes("gdb_objc_realized_classes"); const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( g_gdb_objc_realized_classes, lldb::eSymbolTypeAny); if (symbol) { lldb::addr_t gdb_objc_realized_classes_ptr = symbol->GetLoadAddress(&process->GetTarget()); if (gdb_objc_realized_classes_ptr != LLDB_INVALID_ADDRESS) { Status error; m_isa_hash_table_ptr = process->ReadPointerFromMemory( gdb_objc_realized_classes_ptr, error); } } } return m_isa_hash_table_ptr; } AppleObjCRuntimeV2::DescriptorMapUpdateResult AppleObjCRuntimeV2::UpdateISAToDescriptorMapDynamic( RemoteNXMapTable &hash_table) { Process *process = GetProcess(); if (process == nullptr) return DescriptorMapUpdateResult::Fail(); uint32_t num_class_infos = 0; Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); ExecutionContext exe_ctx; ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread(); if (!thread_sp) return DescriptorMapUpdateResult::Fail(); thread_sp->CalculateExecutionContext(exe_ctx); TypeSystemClang *ast = TypeSystemClang::GetScratch(process->GetTarget()); if (!ast) return DescriptorMapUpdateResult::Fail(); Address function_address; DiagnosticManager diagnostics; const uint32_t addr_size = process->GetAddressByteSize(); Status err; // Read the total number of classes from the hash table const uint32_t num_classes = hash_table.GetCount(); if (num_classes == 0) { LLDB_LOGF(log, "No dynamic classes found in gdb_objc_realized_classes."); return DescriptorMapUpdateResult::Success(0); } // Make some types for our arguments CompilerType clang_uint32_t_type = ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); CompilerType clang_void_pointer_type = ast->GetBasicType(eBasicTypeVoid).GetPointerType(); ValueList arguments; FunctionCaller *get_class_info_function = nullptr; if (!m_get_class_info_code) { Status error; m_get_class_info_code.reset(GetTargetRef().GetUtilityFunctionForLanguage( g_get_dynamic_class_info_body, eLanguageTypeObjC, g_get_dynamic_class_info_name, error)); if (error.Fail()) { LLDB_LOGF(log, "Failed to get Utility Function for implementation lookup: %s", error.AsCString()); m_get_class_info_code.reset(); } else { diagnostics.Clear(); if (!m_get_class_info_code->Install(diagnostics, exe_ctx)) { if (log) { LLDB_LOGF(log, "Failed to install implementation lookup"); diagnostics.Dump(log); } m_get_class_info_code.reset(); } } if (!m_get_class_info_code) return DescriptorMapUpdateResult::Fail(); // Next make the runner function for our implementation utility function. Value value; value.SetValueType(Value::eValueTypeScalar); value.SetCompilerType(clang_void_pointer_type); arguments.PushValue(value); arguments.PushValue(value); value.SetValueType(Value::eValueTypeScalar); value.SetCompilerType(clang_uint32_t_type); arguments.PushValue(value); arguments.PushValue(value); get_class_info_function = m_get_class_info_code->MakeFunctionCaller( clang_uint32_t_type, arguments, thread_sp, error); if (error.Fail()) { LLDB_LOGF(log, "Failed to make function caller for implementation lookup: %s.", error.AsCString()); return DescriptorMapUpdateResult::Fail(); } } else { get_class_info_function = m_get_class_info_code->GetFunctionCaller(); if (!get_class_info_function) { if (log) { LLDB_LOGF(log, "Failed to get implementation lookup function caller."); diagnostics.Dump(log); } return DescriptorMapUpdateResult::Fail(); } arguments = get_class_info_function->GetArgumentValues(); } diagnostics.Clear(); const uint32_t class_info_byte_size = addr_size + 4; const uint32_t class_infos_byte_size = num_classes * class_info_byte_size; lldb::addr_t class_infos_addr = process->AllocateMemory( class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); if (class_infos_addr == LLDB_INVALID_ADDRESS) { LLDB_LOGF(log, "unable to allocate %" PRIu32 " bytes in process for shared cache read", class_infos_byte_size); return DescriptorMapUpdateResult::Fail(); } std::lock_guard guard(m_get_class_info_args_mutex); // Fill in our function argument values arguments.GetValueAtIndex(0)->GetScalar() = hash_table.GetTableLoadAddress(); arguments.GetValueAtIndex(1)->GetScalar() = class_infos_addr; arguments.GetValueAtIndex(2)->GetScalar() = class_infos_byte_size; // Only dump the runtime classes from the expression evaluation if the log is // verbose: Log *type_log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES); bool dump_log = type_log && type_log->GetVerbose(); arguments.GetValueAtIndex(3)->GetScalar() = dump_log ? 1 : 0; bool success = false; diagnostics.Clear(); // Write our function arguments into the process so we can run our function if (get_class_info_function->WriteFunctionArguments( exe_ctx, m_get_class_info_args, arguments, diagnostics)) { EvaluateExpressionOptions options; options.SetUnwindOnError(true); options.SetTryAllThreads(false); options.SetStopOthers(true); options.SetIgnoreBreakpoints(true); options.SetTimeout(process->GetUtilityExpressionTimeout()); options.SetIsForUtilityExpr(true); Value return_value; return_value.SetValueType(Value::eValueTypeScalar); // return_value.SetContext (Value::eContextTypeClangType, // clang_uint32_t_type); return_value.SetCompilerType(clang_uint32_t_type); return_value.GetScalar() = 0; diagnostics.Clear(); // Run the function ExpressionResults results = get_class_info_function->ExecuteFunction( exe_ctx, &m_get_class_info_args, options, diagnostics, return_value); if (results == eExpressionCompleted) { // The result is the number of ClassInfo structures that were filled in num_class_infos = return_value.GetScalar().ULong(); LLDB_LOGF(log, "Discovered %u ObjC classes\n", num_class_infos); if (num_class_infos > 0) { // Read the ClassInfo structures DataBufferHeap buffer(num_class_infos * class_info_byte_size, 0); if (process->ReadMemory(class_infos_addr, buffer.GetBytes(), buffer.GetByteSize(), err) == buffer.GetByteSize()) { DataExtractor class_infos_data(buffer.GetBytes(), buffer.GetByteSize(), process->GetByteOrder(), addr_size); ParseClassInfoArray(class_infos_data, num_class_infos); } } success = true; } else { if (log) { LLDB_LOGF(log, "Error evaluating our find class name function."); diagnostics.Dump(log); } } } else { if (log) { LLDB_LOGF(log, "Error writing function arguments."); diagnostics.Dump(log); } } // Deallocate the memory we allocated for the ClassInfo array process->DeallocateMemory(class_infos_addr); return DescriptorMapUpdateResult(success, num_class_infos); } uint32_t AppleObjCRuntimeV2::ParseClassInfoArray(const DataExtractor &data, uint32_t num_class_infos) { // Parses an array of "num_class_infos" packed ClassInfo structures: // // struct ClassInfo // { // Class isa; // uint32_t hash; // } __attribute__((__packed__)); Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES)); bool should_log = log && log->GetVerbose(); uint32_t num_parsed = 0; // Iterate through all ClassInfo structures lldb::offset_t offset = 0; for (uint32_t i = 0; i < num_class_infos; ++i) { ObjCISA isa = data.GetPointer(&offset); if (isa == 0) { if (should_log) LLDB_LOGF( log, "AppleObjCRuntimeV2 found NULL isa, ignoring this class info"); continue; } // Check if we already know about this ISA, if we do, the info will never // change, so we can just skip it. if (ISAIsCached(isa)) { if (should_log) LLDB_LOGF(log, "AppleObjCRuntimeV2 found cached isa=0x%" PRIx64 ", ignoring this class info", isa); offset += 4; } else { // Read the 32 bit hash for the class name const uint32_t name_hash = data.GetU32(&offset); ClassDescriptorSP descriptor_sp( new ClassDescriptorV2(*this, isa, nullptr)); // The code in g_get_shared_cache_class_info_body sets the value of the hash // to 0 to signal a demangled symbol. We use class_getName() in that code to // find the class name, but this returns a demangled name for Swift symbols. // For those symbols, recompute the hash here by reading their name from the // runtime. if (name_hash) AddClass(isa, descriptor_sp, name_hash); else AddClass(isa, descriptor_sp, descriptor_sp->GetClassName().AsCString(nullptr)); num_parsed++; if (should_log) LLDB_LOGF(log, "AppleObjCRuntimeV2 added isa=0x%" PRIx64 ", hash=0x%8.8x, name=%s", isa, name_hash, descriptor_sp->GetClassName().AsCString("")); } } if (should_log) LLDB_LOGF(log, "AppleObjCRuntimeV2 parsed %" PRIu32 " class infos", num_parsed); return num_parsed; } AppleObjCRuntimeV2::DescriptorMapUpdateResult AppleObjCRuntimeV2::UpdateISAToDescriptorMapSharedCache() { Process *process = GetProcess(); if (process == nullptr) return DescriptorMapUpdateResult::Fail(); Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); ExecutionContext exe_ctx; ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread(); if (!thread_sp) return DescriptorMapUpdateResult::Fail(); thread_sp->CalculateExecutionContext(exe_ctx); TypeSystemClang *ast = TypeSystemClang::GetScratch(process->GetTarget()); if (!ast) return DescriptorMapUpdateResult::Fail(); Address function_address; DiagnosticManager diagnostics; const uint32_t addr_size = process->GetAddressByteSize(); Status err; uint32_t num_class_infos = 0; const lldb::addr_t objc_opt_ptr = GetSharedCacheReadOnlyAddress(); if (objc_opt_ptr == LLDB_INVALID_ADDRESS) return DescriptorMapUpdateResult::Fail(); const uint32_t num_classes = 128 * 1024; // Make some types for our arguments CompilerType clang_uint32_t_type = ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); CompilerType clang_void_pointer_type = ast->GetBasicType(eBasicTypeVoid).GetPointerType(); ValueList arguments; FunctionCaller *get_shared_cache_class_info_function = nullptr; if (!m_get_shared_cache_class_info_code) { Status error; // If the inferior objc.dylib has the class_getNameRaw function, // use that in our jitted expression. Else fall back to the old // class_getName. static ConstString g_class_getName_symbol_name("class_getName"); static ConstString g_class_getNameRaw_symbol_name("objc_debug_class_getNameRaw"); ConstString class_name_getter_function_name = g_class_getName_symbol_name; ObjCLanguageRuntime *objc_runtime = ObjCLanguageRuntime::Get(*process); if (objc_runtime) { const ModuleList &images = process->GetTarget().GetImages(); std::lock_guard guard(images.GetMutex()); for (size_t i = 0; i < images.GetSize(); ++i) { lldb::ModuleSP mod_sp = images.GetModuleAtIndexUnlocked(i); if (objc_runtime->IsModuleObjCLibrary(mod_sp)) { const Symbol *symbol = mod_sp->FindFirstSymbolWithNameAndType(g_class_getNameRaw_symbol_name, lldb::eSymbolTypeCode); if (symbol && (symbol->ValueIsAddress() || symbol->GetAddressRef().IsValid())) { class_name_getter_function_name = g_class_getNameRaw_symbol_name; } } } } // Substitute in the correct class_getName / class_getNameRaw function name, // concatenate the two parts of our expression text. The format string // has two %s's, so provide the name twice. std::string shared_class_expression; llvm::raw_string_ostream(shared_class_expression) << llvm::format( g_shared_cache_class_name_funcptr, class_name_getter_function_name.AsCString(), class_name_getter_function_name.AsCString()); shared_class_expression += g_get_shared_cache_class_info_body; m_get_shared_cache_class_info_code.reset( GetTargetRef().GetUtilityFunctionForLanguage( shared_class_expression.c_str(), eLanguageTypeObjC, g_get_shared_cache_class_info_name, error)); if (error.Fail()) { LLDB_LOGF(log, "Failed to get Utility function for implementation lookup: %s.", error.AsCString()); m_get_shared_cache_class_info_code.reset(); } else { diagnostics.Clear(); if (!m_get_shared_cache_class_info_code->Install(diagnostics, exe_ctx)) { if (log) { LLDB_LOGF(log, "Failed to install implementation lookup."); diagnostics.Dump(log); } m_get_shared_cache_class_info_code.reset(); } } if (!m_get_shared_cache_class_info_code) return DescriptorMapUpdateResult::Fail(); // Next make the function caller for our implementation utility function. Value value; value.SetValueType(Value::eValueTypeScalar); // value.SetContext (Value::eContextTypeClangType, clang_void_pointer_type); value.SetCompilerType(clang_void_pointer_type); arguments.PushValue(value); arguments.PushValue(value); value.SetValueType(Value::eValueTypeScalar); // value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); value.SetCompilerType(clang_uint32_t_type); arguments.PushValue(value); arguments.PushValue(value); get_shared_cache_class_info_function = m_get_shared_cache_class_info_code->MakeFunctionCaller( clang_uint32_t_type, arguments, thread_sp, error); if (get_shared_cache_class_info_function == nullptr) return DescriptorMapUpdateResult::Fail(); } else { get_shared_cache_class_info_function = m_get_shared_cache_class_info_code->GetFunctionCaller(); if (get_shared_cache_class_info_function == nullptr) return DescriptorMapUpdateResult::Fail(); arguments = get_shared_cache_class_info_function->GetArgumentValues(); } diagnostics.Clear(); const uint32_t class_info_byte_size = addr_size + 4; const uint32_t class_infos_byte_size = num_classes * class_info_byte_size; lldb::addr_t class_infos_addr = process->AllocateMemory( class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); if (class_infos_addr == LLDB_INVALID_ADDRESS) { LLDB_LOGF(log, "unable to allocate %" PRIu32 " bytes in process for shared cache read", class_infos_byte_size); return DescriptorMapUpdateResult::Fail(); } std::lock_guard guard(m_get_shared_cache_class_info_args_mutex); // Fill in our function argument values arguments.GetValueAtIndex(0)->GetScalar() = objc_opt_ptr; arguments.GetValueAtIndex(1)->GetScalar() = class_infos_addr; arguments.GetValueAtIndex(2)->GetScalar() = class_infos_byte_size; // Only dump the runtime classes from the expression evaluation if the log is // verbose: Log *type_log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES); bool dump_log = type_log && type_log->GetVerbose(); arguments.GetValueAtIndex(3)->GetScalar() = dump_log ? 1 : 0; bool success = false; diagnostics.Clear(); // Write our function arguments into the process so we can run our function if (get_shared_cache_class_info_function->WriteFunctionArguments( exe_ctx, m_get_shared_cache_class_info_args, arguments, diagnostics)) { EvaluateExpressionOptions options; options.SetUnwindOnError(true); options.SetTryAllThreads(false); options.SetStopOthers(true); options.SetIgnoreBreakpoints(true); options.SetTimeout(process->GetUtilityExpressionTimeout()); options.SetIsForUtilityExpr(true); Value return_value; return_value.SetValueType(Value::eValueTypeScalar); // return_value.SetContext (Value::eContextTypeClangType, // clang_uint32_t_type); return_value.SetCompilerType(clang_uint32_t_type); return_value.GetScalar() = 0; diagnostics.Clear(); // Run the function ExpressionResults results = get_shared_cache_class_info_function->ExecuteFunction( exe_ctx, &m_get_shared_cache_class_info_args, options, diagnostics, return_value); if (results == eExpressionCompleted) { // The result is the number of ClassInfo structures that were filled in num_class_infos = return_value.GetScalar().ULong(); LLDB_LOGF(log, "Discovered %u ObjC classes in shared cache\n", num_class_infos); assert(num_class_infos <= num_classes); if (num_class_infos > 0) { if (num_class_infos > num_classes) { num_class_infos = num_classes; success = false; } else { success = true; } // Read the ClassInfo structures DataBufferHeap buffer(num_class_infos * class_info_byte_size, 0); if (process->ReadMemory(class_infos_addr, buffer.GetBytes(), buffer.GetByteSize(), err) == buffer.GetByteSize()) { DataExtractor class_infos_data(buffer.GetBytes(), buffer.GetByteSize(), process->GetByteOrder(), addr_size); ParseClassInfoArray(class_infos_data, num_class_infos); } } else { success = true; } } else { if (log) { LLDB_LOGF(log, "Error evaluating our find class name function."); diagnostics.Dump(log); } } } else { if (log) { LLDB_LOGF(log, "Error writing function arguments."); diagnostics.Dump(log); } } // Deallocate the memory we allocated for the ClassInfo array process->DeallocateMemory(class_infos_addr); return DescriptorMapUpdateResult(success, num_class_infos); } bool AppleObjCRuntimeV2::UpdateISAToDescriptorMapFromMemory( RemoteNXMapTable &hash_table) { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); Process *process = GetProcess(); if (process == nullptr) return false; uint32_t num_map_table_isas = 0; ModuleSP objc_module_sp(GetObjCModule()); if (objc_module_sp) { for (RemoteNXMapTable::element elt : hash_table) { ++num_map_table_isas; if (ISAIsCached(elt.second)) continue; ClassDescriptorSP descriptor_sp = ClassDescriptorSP( new ClassDescriptorV2(*this, elt.second, elt.first.AsCString())); if (log && log->GetVerbose()) LLDB_LOGF(log, "AppleObjCRuntimeV2 added (ObjCISA)0x%" PRIx64 " (%s) from dynamic table to isa->descriptor cache", elt.second, elt.first.AsCString()); AddClass(elt.second, descriptor_sp, elt.first.AsCString()); } } return num_map_table_isas > 0; } lldb::addr_t AppleObjCRuntimeV2::GetSharedCacheReadOnlyAddress() { Process *process = GetProcess(); if (process) { ModuleSP objc_module_sp(GetObjCModule()); if (objc_module_sp) { ObjectFile *objc_object = objc_module_sp->GetObjectFile(); if (objc_object) { SectionList *section_list = objc_module_sp->GetSectionList(); if (section_list) { SectionSP text_segment_sp( section_list->FindSectionByName(ConstString("__TEXT"))); if (text_segment_sp) { SectionSP objc_opt_section_sp( text_segment_sp->GetChildren().FindSectionByName( ConstString("__objc_opt_ro"))); if (objc_opt_section_sp) { return objc_opt_section_sp->GetLoadBaseAddress( &process->GetTarget()); } } } } } } return LLDB_INVALID_ADDRESS; } void AppleObjCRuntimeV2::UpdateISAToDescriptorMapIfNeeded() { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_TYPES)); static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION); // Else we need to check with our process to see when the map was updated. Process *process = GetProcess(); if (process) { RemoteNXMapTable hash_table; // Update the process stop ID that indicates the last time we updated the // map, whether it was successful or not. m_isa_to_descriptor_stop_id = process->GetStopID(); if (!m_hash_signature.NeedsUpdate(process, this, hash_table)) return; m_hash_signature.UpdateSignature(hash_table); // Grab the dynamically loaded objc classes from the hash table in memory DescriptorMapUpdateResult dynamic_update_result = UpdateISAToDescriptorMapDynamic(hash_table); // Now get the objc classes that are baked into the Objective-C runtime in // the shared cache, but only once per process as this data never changes if (!m_loaded_objc_opt) { // it is legitimately possible for the shared cache to be empty - in that // case, the dynamic hash table will contain all the class information we // need; the situation we're trying to detect is one where we aren't // seeing class information from the runtime - in order to detect that // vs. just the shared cache being empty or sparsely populated, we set an // arbitrary (very low) threshold for the number of classes that we want // to see in a "good" scenario - anything below that is suspicious // (Foundation alone has thousands of classes) const uint32_t num_classes_to_warn_at = 500; DescriptorMapUpdateResult shared_cache_update_result = UpdateISAToDescriptorMapSharedCache(); LLDB_LOGF(log, "attempted to read objc class data - results: " "[dynamic_update]: ran: %s, count: %" PRIu32 " [shared_cache_update]: ran: %s, count: %" PRIu32, dynamic_update_result.m_update_ran ? "yes" : "no", dynamic_update_result.m_num_found, shared_cache_update_result.m_update_ran ? "yes" : "no", shared_cache_update_result.m_num_found); // warn if: // - we could not run either expression // - we found fewer than num_classes_to_warn_at classes total if ((!shared_cache_update_result.m_update_ran) || (!dynamic_update_result.m_update_ran)) WarnIfNoClassesCached( SharedCacheWarningReason::eExpressionExecutionFailure); else if (dynamic_update_result.m_num_found + shared_cache_update_result.m_num_found < num_classes_to_warn_at) WarnIfNoClassesCached(SharedCacheWarningReason::eNotEnoughClassesRead); else m_loaded_objc_opt = true; } } else { m_isa_to_descriptor_stop_id = UINT32_MAX; } } static bool DoesProcessHaveSharedCache(Process &process) { PlatformSP platform_sp = process.GetTarget().GetPlatform(); if (!platform_sp) return true; // this should not happen ConstString platform_plugin_name = platform_sp->GetPluginName(); if (platform_plugin_name) { llvm::StringRef platform_plugin_name_sr = platform_plugin_name.GetStringRef(); if (platform_plugin_name_sr.endswith("-simulator")) return false; } return true; } void AppleObjCRuntimeV2::WarnIfNoClassesCached( SharedCacheWarningReason reason) { if (m_noclasses_warning_emitted) return; if (GetProcess() && !DoesProcessHaveSharedCache(*GetProcess())) { // Simulators do not have the objc_opt_ro class table so don't actually // complain to the user m_noclasses_warning_emitted = true; return; } Debugger &debugger(GetProcess()->GetTarget().GetDebugger()); if (auto stream = debugger.GetAsyncOutputStream()) { switch (reason) { case SharedCacheWarningReason::eNotEnoughClassesRead: stream->PutCString("warning: could not find Objective-C class data in " "the process. This may reduce the quality of type " "information available.\n"); m_noclasses_warning_emitted = true; break; case SharedCacheWarningReason::eExpressionExecutionFailure: stream->PutCString("warning: could not execute support code to read " "Objective-C class data in the process. This may " "reduce the quality of type information available.\n"); m_noclasses_warning_emitted = true; break; } } } ConstString AppleObjCRuntimeV2::GetActualTypeName(ObjCLanguageRuntime::ObjCISA isa) { if (isa == g_objc_Tagged_ISA) { static const ConstString g_objc_tagged_isa_name("_lldb_Tagged_ObjC_ISA"); return g_objc_tagged_isa_name; } if (isa == g_objc_Tagged_ISA_NSAtom) { static const ConstString g_objc_tagged_isa_nsatom_name("NSAtom"); return g_objc_tagged_isa_nsatom_name; } if (isa == g_objc_Tagged_ISA_NSNumber) { static const ConstString g_objc_tagged_isa_nsnumber_name("NSNumber"); return g_objc_tagged_isa_nsnumber_name; } if (isa == g_objc_Tagged_ISA_NSDateTS) { static const ConstString g_objc_tagged_isa_nsdatets_name("NSDateTS"); return g_objc_tagged_isa_nsdatets_name; } if (isa == g_objc_Tagged_ISA_NSManagedObject) { static const ConstString g_objc_tagged_isa_nsmanagedobject_name( "NSManagedObject"); return g_objc_tagged_isa_nsmanagedobject_name; } if (isa == g_objc_Tagged_ISA_NSDate) { static const ConstString g_objc_tagged_isa_nsdate_name("NSDate"); return g_objc_tagged_isa_nsdate_name; } return ObjCLanguageRuntime::GetActualTypeName(isa); } DeclVendor *AppleObjCRuntimeV2::GetDeclVendor() { if (!m_decl_vendor_up) m_decl_vendor_up.reset(new AppleObjCDeclVendor(*this)); return m_decl_vendor_up.get(); } lldb::addr_t AppleObjCRuntimeV2::LookupRuntimeSymbol(ConstString name) { lldb::addr_t ret = LLDB_INVALID_ADDRESS; const char *name_cstr = name.AsCString(); if (name_cstr) { llvm::StringRef name_strref(name_cstr); llvm::StringRef ivar_prefix("OBJC_IVAR_$_"); llvm::StringRef class_prefix("OBJC_CLASS_$_"); if (name_strref.startswith(ivar_prefix)) { llvm::StringRef ivar_skipped_prefix = name_strref.substr(ivar_prefix.size()); std::pair class_and_ivar = ivar_skipped_prefix.split('.'); if (class_and_ivar.first.size() && class_and_ivar.second.size()) { const ConstString class_name_cs(class_and_ivar.first); ClassDescriptorSP descriptor = ObjCLanguageRuntime::GetClassDescriptorFromClassName(class_name_cs); if (descriptor) { const ConstString ivar_name_cs(class_and_ivar.second); const char *ivar_name_cstr = ivar_name_cs.AsCString(); auto ivar_func = [&ret, ivar_name_cstr]( const char *name, const char *type, lldb::addr_t offset_addr, uint64_t size) -> lldb::addr_t { if (!strcmp(name, ivar_name_cstr)) { ret = offset_addr; return true; } return false; }; descriptor->Describe( std::function(nullptr), std::function(nullptr), std::function(nullptr), ivar_func); } } } else if (name_strref.startswith(class_prefix)) { llvm::StringRef class_skipped_prefix = name_strref.substr(class_prefix.size()); const ConstString class_name_cs(class_skipped_prefix); ClassDescriptorSP descriptor = GetClassDescriptorFromClassName(class_name_cs); if (descriptor) ret = descriptor->GetISA(); } } return ret; } AppleObjCRuntimeV2::NonPointerISACache * AppleObjCRuntimeV2::NonPointerISACache::CreateInstance( AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { Process *process(runtime.GetProcess()); Status error; Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES)); auto objc_debug_isa_magic_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_isa_magic_mask"), objc_module_sp, error); if (error.Fail()) return nullptr; auto objc_debug_isa_magic_value = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_isa_magic_value"), objc_module_sp, error); if (error.Fail()) return nullptr; auto objc_debug_isa_class_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_isa_class_mask"), objc_module_sp, error); if (error.Fail()) return nullptr; if (log) log->PutCString("AOCRT::NPI: Found all the non-indexed ISA masks"); bool foundError = false; auto objc_debug_indexed_isa_magic_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_indexed_isa_magic_mask"), objc_module_sp, error); foundError |= error.Fail(); auto objc_debug_indexed_isa_magic_value = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_indexed_isa_magic_value"), objc_module_sp, error); foundError |= error.Fail(); auto objc_debug_indexed_isa_index_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_indexed_isa_index_mask"), objc_module_sp, error); foundError |= error.Fail(); auto objc_debug_indexed_isa_index_shift = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_indexed_isa_index_shift"), objc_module_sp, error); foundError |= error.Fail(); auto objc_indexed_classes = ExtractRuntimeGlobalSymbol(process, ConstString("objc_indexed_classes"), objc_module_sp, error, false); foundError |= error.Fail(); if (log) log->PutCString("AOCRT::NPI: Found all the indexed ISA masks"); // we might want to have some rules to outlaw these other values (e.g if the // mask is zero but the value is non-zero, ...) return new NonPointerISACache( runtime, objc_module_sp, objc_debug_isa_class_mask, objc_debug_isa_magic_mask, objc_debug_isa_magic_value, objc_debug_indexed_isa_magic_mask, objc_debug_indexed_isa_magic_value, objc_debug_indexed_isa_index_mask, objc_debug_indexed_isa_index_shift, foundError ? 0 : objc_indexed_classes); } AppleObjCRuntimeV2::TaggedPointerVendorV2 * AppleObjCRuntimeV2::TaggedPointerVendorV2::CreateInstance( AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { Process *process(runtime.GetProcess()); Status error; auto objc_debug_taggedpointer_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_mask"), objc_module_sp, error); if (error.Fail()) return new TaggedPointerVendorLegacy(runtime); auto objc_debug_taggedpointer_slot_shift = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_slot_shift"), objc_module_sp, error, true, 4); if (error.Fail()) return new TaggedPointerVendorLegacy(runtime); auto objc_debug_taggedpointer_slot_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_slot_mask"), objc_module_sp, error, true, 4); if (error.Fail()) return new TaggedPointerVendorLegacy(runtime); auto objc_debug_taggedpointer_payload_lshift = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_payload_lshift"), objc_module_sp, error, true, 4); if (error.Fail()) return new TaggedPointerVendorLegacy(runtime); auto objc_debug_taggedpointer_payload_rshift = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_payload_rshift"), objc_module_sp, error, true, 4); if (error.Fail()) return new TaggedPointerVendorLegacy(runtime); auto objc_debug_taggedpointer_classes = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_classes"), objc_module_sp, error, false); if (error.Fail()) return new TaggedPointerVendorLegacy(runtime); // try to detect the "extended tagged pointer" variables - if any are // missing, use the non-extended vendor do { auto objc_debug_taggedpointer_ext_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_ext_mask"), objc_module_sp, error); if (error.Fail()) break; auto objc_debug_taggedpointer_ext_slot_shift = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_ext_slot_shift"), objc_module_sp, error, true, 4); if (error.Fail()) break; auto objc_debug_taggedpointer_ext_slot_mask = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_ext_slot_mask"), objc_module_sp, error, true, 4); if (error.Fail()) break; auto objc_debug_taggedpointer_ext_classes = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_ext_classes"), objc_module_sp, error, false); if (error.Fail()) break; auto objc_debug_taggedpointer_ext_payload_lshift = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_ext_payload_lshift"), objc_module_sp, error, true, 4); if (error.Fail()) break; auto objc_debug_taggedpointer_ext_payload_rshift = ExtractRuntimeGlobalSymbol( process, ConstString("objc_debug_taggedpointer_ext_payload_rshift"), objc_module_sp, error, true, 4); if (error.Fail()) break; return new TaggedPointerVendorExtended( runtime, objc_debug_taggedpointer_mask, objc_debug_taggedpointer_ext_mask, objc_debug_taggedpointer_slot_shift, objc_debug_taggedpointer_ext_slot_shift, objc_debug_taggedpointer_slot_mask, objc_debug_taggedpointer_ext_slot_mask, objc_debug_taggedpointer_payload_lshift, objc_debug_taggedpointer_payload_rshift, objc_debug_taggedpointer_ext_payload_lshift, objc_debug_taggedpointer_ext_payload_rshift, objc_debug_taggedpointer_classes, objc_debug_taggedpointer_ext_classes); } while (false); // we might want to have some rules to outlaw these values (e.g if the // table's address is zero) return new TaggedPointerVendorRuntimeAssisted( runtime, objc_debug_taggedpointer_mask, objc_debug_taggedpointer_slot_shift, objc_debug_taggedpointer_slot_mask, objc_debug_taggedpointer_payload_lshift, objc_debug_taggedpointer_payload_rshift, objc_debug_taggedpointer_classes); } bool AppleObjCRuntimeV2::TaggedPointerVendorLegacy::IsPossibleTaggedPointer( lldb::addr_t ptr) { return (ptr & 1); } ObjCLanguageRuntime::ClassDescriptorSP AppleObjCRuntimeV2::TaggedPointerVendorLegacy::GetClassDescriptor( lldb::addr_t ptr) { if (!IsPossibleTaggedPointer(ptr)) return ObjCLanguageRuntime::ClassDescriptorSP(); uint32_t foundation_version = m_runtime.GetFoundationVersion(); if (foundation_version == LLDB_INVALID_MODULE_VERSION) return ObjCLanguageRuntime::ClassDescriptorSP(); uint64_t class_bits = (ptr & 0xE) >> 1; ConstString name; static ConstString g_NSAtom("NSAtom"); static ConstString g_NSNumber("NSNumber"); static ConstString g_NSDateTS("NSDateTS"); static ConstString g_NSManagedObject("NSManagedObject"); static ConstString g_NSDate("NSDate"); if (foundation_version >= 900) { switch (class_bits) { case 0: name = g_NSAtom; break; case 3: name = g_NSNumber; break; case 4: name = g_NSDateTS; break; case 5: name = g_NSManagedObject; break; case 6: name = g_NSDate; break; default: return ObjCLanguageRuntime::ClassDescriptorSP(); } } else { switch (class_bits) { case 1: name = g_NSNumber; break; case 5: name = g_NSManagedObject; break; case 6: name = g_NSDate; break; case 7: name = g_NSDateTS; break; default: return ObjCLanguageRuntime::ClassDescriptorSP(); } } lldb::addr_t unobfuscated = ptr ^ m_runtime.GetTaggedPointerObfuscator(); return ClassDescriptorSP(new ClassDescriptorV2Tagged(name, unobfuscated)); } AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted:: TaggedPointerVendorRuntimeAssisted( AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, uint32_t objc_debug_taggedpointer_slot_shift, uint32_t objc_debug_taggedpointer_slot_mask, uint32_t objc_debug_taggedpointer_payload_lshift, uint32_t objc_debug_taggedpointer_payload_rshift, lldb::addr_t objc_debug_taggedpointer_classes) : TaggedPointerVendorV2(runtime), m_cache(), m_objc_debug_taggedpointer_mask(objc_debug_taggedpointer_mask), m_objc_debug_taggedpointer_slot_shift( objc_debug_taggedpointer_slot_shift), m_objc_debug_taggedpointer_slot_mask(objc_debug_taggedpointer_slot_mask), m_objc_debug_taggedpointer_payload_lshift( objc_debug_taggedpointer_payload_lshift), m_objc_debug_taggedpointer_payload_rshift( objc_debug_taggedpointer_payload_rshift), m_objc_debug_taggedpointer_classes(objc_debug_taggedpointer_classes) {} bool AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted:: IsPossibleTaggedPointer(lldb::addr_t ptr) { return (ptr & m_objc_debug_taggedpointer_mask) != 0; } ObjCLanguageRuntime::ClassDescriptorSP AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted::GetClassDescriptor( lldb::addr_t ptr) { ClassDescriptorSP actual_class_descriptor_sp; uint64_t data_payload; uint64_t unobfuscated = (ptr) ^ m_runtime.GetTaggedPointerObfuscator(); if (!IsPossibleTaggedPointer(unobfuscated)) return ObjCLanguageRuntime::ClassDescriptorSP(); uintptr_t slot = (ptr >> m_objc_debug_taggedpointer_slot_shift) & m_objc_debug_taggedpointer_slot_mask; CacheIterator iterator = m_cache.find(slot), end = m_cache.end(); if (iterator != end) { actual_class_descriptor_sp = iterator->second; } else { Process *process(m_runtime.GetProcess()); uintptr_t slot_ptr = slot * process->GetAddressByteSize() + m_objc_debug_taggedpointer_classes; Status error; uintptr_t slot_data = process->ReadPointerFromMemory(slot_ptr, error); if (error.Fail() || slot_data == 0 || slot_data == uintptr_t(LLDB_INVALID_ADDRESS)) return nullptr; actual_class_descriptor_sp = m_runtime.GetClassDescriptorFromISA((ObjCISA)slot_data); if (!actual_class_descriptor_sp) return ObjCLanguageRuntime::ClassDescriptorSP(); m_cache[slot] = actual_class_descriptor_sp; } data_payload = (((uint64_t)unobfuscated << m_objc_debug_taggedpointer_payload_lshift) >> m_objc_debug_taggedpointer_payload_rshift); return ClassDescriptorSP( new ClassDescriptorV2Tagged(actual_class_descriptor_sp, data_payload)); } AppleObjCRuntimeV2::TaggedPointerVendorExtended::TaggedPointerVendorExtended( AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, uint64_t objc_debug_taggedpointer_ext_mask, uint32_t objc_debug_taggedpointer_slot_shift, uint32_t objc_debug_taggedpointer_ext_slot_shift, uint32_t objc_debug_taggedpointer_slot_mask, uint32_t objc_debug_taggedpointer_ext_slot_mask, uint32_t objc_debug_taggedpointer_payload_lshift, uint32_t objc_debug_taggedpointer_payload_rshift, uint32_t objc_debug_taggedpointer_ext_payload_lshift, uint32_t objc_debug_taggedpointer_ext_payload_rshift, lldb::addr_t objc_debug_taggedpointer_classes, lldb::addr_t objc_debug_taggedpointer_ext_classes) : TaggedPointerVendorRuntimeAssisted( runtime, objc_debug_taggedpointer_mask, objc_debug_taggedpointer_slot_shift, objc_debug_taggedpointer_slot_mask, objc_debug_taggedpointer_payload_lshift, objc_debug_taggedpointer_payload_rshift, objc_debug_taggedpointer_classes), m_ext_cache(), m_objc_debug_taggedpointer_ext_mask(objc_debug_taggedpointer_ext_mask), m_objc_debug_taggedpointer_ext_slot_shift( objc_debug_taggedpointer_ext_slot_shift), m_objc_debug_taggedpointer_ext_slot_mask( objc_debug_taggedpointer_ext_slot_mask), m_objc_debug_taggedpointer_ext_payload_lshift( objc_debug_taggedpointer_ext_payload_lshift), m_objc_debug_taggedpointer_ext_payload_rshift( objc_debug_taggedpointer_ext_payload_rshift), m_objc_debug_taggedpointer_ext_classes( objc_debug_taggedpointer_ext_classes) {} bool AppleObjCRuntimeV2::TaggedPointerVendorExtended:: IsPossibleExtendedTaggedPointer(lldb::addr_t ptr) { if (!IsPossibleTaggedPointer(ptr)) return false; if (m_objc_debug_taggedpointer_ext_mask == 0) return false; return ((ptr & m_objc_debug_taggedpointer_ext_mask) == m_objc_debug_taggedpointer_ext_mask); } ObjCLanguageRuntime::ClassDescriptorSP AppleObjCRuntimeV2::TaggedPointerVendorExtended::GetClassDescriptor( lldb::addr_t ptr) { ClassDescriptorSP actual_class_descriptor_sp; uint64_t data_payload; uint64_t unobfuscated = (ptr) ^ m_runtime.GetTaggedPointerObfuscator(); if (!IsPossibleTaggedPointer(unobfuscated)) return ObjCLanguageRuntime::ClassDescriptorSP(); if (!IsPossibleExtendedTaggedPointer(unobfuscated)) return this->TaggedPointerVendorRuntimeAssisted::GetClassDescriptor(ptr); uintptr_t slot = (ptr >> m_objc_debug_taggedpointer_ext_slot_shift) & m_objc_debug_taggedpointer_ext_slot_mask; CacheIterator iterator = m_ext_cache.find(slot), end = m_ext_cache.end(); if (iterator != end) { actual_class_descriptor_sp = iterator->second; } else { Process *process(m_runtime.GetProcess()); uintptr_t slot_ptr = slot * process->GetAddressByteSize() + m_objc_debug_taggedpointer_ext_classes; Status error; uintptr_t slot_data = process->ReadPointerFromMemory(slot_ptr, error); if (error.Fail() || slot_data == 0 || slot_data == uintptr_t(LLDB_INVALID_ADDRESS)) return nullptr; actual_class_descriptor_sp = m_runtime.GetClassDescriptorFromISA((ObjCISA)slot_data); if (!actual_class_descriptor_sp) return ObjCLanguageRuntime::ClassDescriptorSP(); m_ext_cache[slot] = actual_class_descriptor_sp; } data_payload = (((uint64_t)unobfuscated << m_objc_debug_taggedpointer_ext_payload_lshift) >> m_objc_debug_taggedpointer_ext_payload_rshift); return ClassDescriptorSP( new ClassDescriptorV2Tagged(actual_class_descriptor_sp, data_payload)); } AppleObjCRuntimeV2::NonPointerISACache::NonPointerISACache( AppleObjCRuntimeV2 &runtime, const ModuleSP &objc_module_sp, uint64_t objc_debug_isa_class_mask, uint64_t objc_debug_isa_magic_mask, uint64_t objc_debug_isa_magic_value, uint64_t objc_debug_indexed_isa_magic_mask, uint64_t objc_debug_indexed_isa_magic_value, uint64_t objc_debug_indexed_isa_index_mask, uint64_t objc_debug_indexed_isa_index_shift, lldb::addr_t objc_indexed_classes) : m_runtime(runtime), m_cache(), m_objc_module_wp(objc_module_sp), m_objc_debug_isa_class_mask(objc_debug_isa_class_mask), m_objc_debug_isa_magic_mask(objc_debug_isa_magic_mask), m_objc_debug_isa_magic_value(objc_debug_isa_magic_value), m_objc_debug_indexed_isa_magic_mask(objc_debug_indexed_isa_magic_mask), m_objc_debug_indexed_isa_magic_value(objc_debug_indexed_isa_magic_value), m_objc_debug_indexed_isa_index_mask(objc_debug_indexed_isa_index_mask), m_objc_debug_indexed_isa_index_shift(objc_debug_indexed_isa_index_shift), m_objc_indexed_classes(objc_indexed_classes), m_indexed_isa_cache() {} ObjCLanguageRuntime::ClassDescriptorSP AppleObjCRuntimeV2::NonPointerISACache::GetClassDescriptor(ObjCISA isa) { ObjCISA real_isa = 0; if (!EvaluateNonPointerISA(isa, real_isa)) return ObjCLanguageRuntime::ClassDescriptorSP(); auto cache_iter = m_cache.find(real_isa); if (cache_iter != m_cache.end()) return cache_iter->second; auto descriptor_sp = m_runtime.ObjCLanguageRuntime::GetClassDescriptorFromISA(real_isa); if (descriptor_sp) // cache only positive matches since the table might grow m_cache[real_isa] = descriptor_sp; return descriptor_sp; } bool AppleObjCRuntimeV2::NonPointerISACache::EvaluateNonPointerISA( ObjCISA isa, ObjCISA &ret_isa) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES)); LLDB_LOGF(log, "AOCRT::NPI Evalulate(isa = 0x%" PRIx64 ")", (uint64_t)isa); if ((isa & ~m_objc_debug_isa_class_mask) == 0) return false; // If all of the indexed ISA variables are set, then its possible that this // ISA is indexed, and we should first try to get its value using the index. // Note, we check these variables first as the ObjC runtime will set at least // one of their values to 0 if they aren't needed. if (m_objc_debug_indexed_isa_magic_mask && m_objc_debug_indexed_isa_magic_value && m_objc_debug_indexed_isa_index_mask && m_objc_debug_indexed_isa_index_shift && m_objc_indexed_classes) { if ((isa & ~m_objc_debug_indexed_isa_index_mask) == 0) return false; if ((isa & m_objc_debug_indexed_isa_magic_mask) == m_objc_debug_indexed_isa_magic_value) { // Magic bits are correct, so try extract the index. uintptr_t index = (isa & m_objc_debug_indexed_isa_index_mask) >> m_objc_debug_indexed_isa_index_shift; // If the index is out of bounds of the length of the array then check if // the array has been updated. If that is the case then we should try // read the count again, and update the cache if the count has been // updated. if (index > m_indexed_isa_cache.size()) { LLDB_LOGF(log, "AOCRT::NPI (index = %" PRIu64 ") exceeds cache (size = %" PRIu64 ")", (uint64_t)index, (uint64_t)m_indexed_isa_cache.size()); Process *process(m_runtime.GetProcess()); ModuleSP objc_module_sp(m_objc_module_wp.lock()); if (!objc_module_sp) return false; Status error; auto objc_indexed_classes_count = ExtractRuntimeGlobalSymbol( process, ConstString("objc_indexed_classes_count"), objc_module_sp, error); if (error.Fail()) return false; LLDB_LOGF(log, "AOCRT::NPI (new class count = %" PRIu64 ")", (uint64_t)objc_indexed_classes_count); if (objc_indexed_classes_count > m_indexed_isa_cache.size()) { // Read the class entries we don't have. We should just read all of // them instead of just the one we need as then we can cache those we // may need later. auto num_new_classes = objc_indexed_classes_count - m_indexed_isa_cache.size(); const uint32_t addr_size = process->GetAddressByteSize(); DataBufferHeap buffer(num_new_classes * addr_size, 0); lldb::addr_t last_read_class = m_objc_indexed_classes + (m_indexed_isa_cache.size() * addr_size); size_t bytes_read = process->ReadMemory( last_read_class, buffer.GetBytes(), buffer.GetByteSize(), error); if (error.Fail() || bytes_read != buffer.GetByteSize()) return false; LLDB_LOGF(log, "AOCRT::NPI (read new classes count = %" PRIu64 ")", (uint64_t)num_new_classes); // Append the new entries to the existing cache. DataExtractor data(buffer.GetBytes(), buffer.GetByteSize(), process->GetByteOrder(), process->GetAddressByteSize()); lldb::offset_t offset = 0; for (unsigned i = 0; i != num_new_classes; ++i) m_indexed_isa_cache.push_back(data.GetPointer(&offset)); } } // If the index is still out of range then this isn't a pointer. if (index > m_indexed_isa_cache.size()) return false; LLDB_LOGF(log, "AOCRT::NPI Evalulate(ret_isa = 0x%" PRIx64 ")", (uint64_t)m_indexed_isa_cache[index]); ret_isa = m_indexed_isa_cache[index]; return (ret_isa != 0); // this is a pointer so 0 is not a valid value } return false; } // Definitely not an indexed ISA, so try to use a mask to extract the pointer // from the ISA. if ((isa & m_objc_debug_isa_magic_mask) == m_objc_debug_isa_magic_value) { ret_isa = isa & m_objc_debug_isa_class_mask; return (ret_isa != 0); // this is a pointer so 0 is not a valid value } return false; } ObjCLanguageRuntime::EncodingToTypeSP AppleObjCRuntimeV2::GetEncodingToType() { if (!m_encoding_to_type_sp) m_encoding_to_type_sp = std::make_shared(*this); return m_encoding_to_type_sp; } lldb_private::AppleObjCRuntime::ObjCISA AppleObjCRuntimeV2::GetPointerISA(ObjCISA isa) { ObjCISA ret = isa; if (m_non_pointer_isa_cache_up) m_non_pointer_isa_cache_up->EvaluateNonPointerISA(isa, ret); return ret; } bool AppleObjCRuntimeV2::GetCFBooleanValuesIfNeeded() { if (m_CFBoolean_values) return true; static ConstString g_kCFBooleanFalse("__kCFBooleanFalse"); static ConstString g_kCFBooleanTrue("__kCFBooleanTrue"); std::function get_symbol = [this](ConstString sym) -> lldb::addr_t { SymbolContextList sc_list; GetProcess()->GetTarget().GetImages().FindSymbolsWithNameAndType( sym, lldb::eSymbolTypeData, sc_list); if (sc_list.GetSize() == 1) { SymbolContext sc; sc_list.GetContextAtIndex(0, sc); if (sc.symbol) return sc.symbol->GetLoadAddress(&GetProcess()->GetTarget()); } return LLDB_INVALID_ADDRESS; }; lldb::addr_t false_addr = get_symbol(g_kCFBooleanFalse); lldb::addr_t true_addr = get_symbol(g_kCFBooleanTrue); return (m_CFBoolean_values = {false_addr, true_addr}).operator bool(); } void AppleObjCRuntimeV2::GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, lldb::addr_t &cf_false) { if (GetCFBooleanValuesIfNeeded()) { cf_true = m_CFBoolean_values->second; cf_false = m_CFBoolean_values->first; } else this->AppleObjCRuntime::GetValuesForGlobalCFBooleans(cf_true, cf_false); } #pragma mark Frame recognizers class ObjCExceptionRecognizedStackFrame : public RecognizedStackFrame { public: ObjCExceptionRecognizedStackFrame(StackFrameSP frame_sp) { ThreadSP thread_sp = frame_sp->GetThread(); ProcessSP process_sp = thread_sp->GetProcess(); const lldb::ABISP &abi = process_sp->GetABI(); if (!abi) return; TypeSystemClang *clang_ast_context = TypeSystemClang::GetScratch(process_sp->GetTarget()); if (!clang_ast_context) return; CompilerType voidstar = clang_ast_context->GetBasicType(lldb::eBasicTypeVoid).GetPointerType(); ValueList args; Value input_value; input_value.SetCompilerType(voidstar); args.PushValue(input_value); if (!abi->GetArgumentValues(*thread_sp, args)) return; addr_t exception_addr = args.GetValueAtIndex(0)->GetScalar().ULongLong(); Value value(exception_addr); value.SetCompilerType(voidstar); exception = ValueObjectConstResult::Create(frame_sp.get(), value, ConstString("exception")); exception = ValueObjectRecognizerSynthesizedValue::Create( *exception, eValueTypeVariableArgument); exception = exception->GetDynamicValue(eDynamicDontRunTarget); m_arguments = ValueObjectListSP(new ValueObjectList()); m_arguments->Append(exception); m_stop_desc = "hit Objective-C exception"; } ValueObjectSP exception; lldb::ValueObjectSP GetExceptionObject() override { return exception; } }; class ObjCExceptionThrowFrameRecognizer : public StackFrameRecognizer { lldb::RecognizedStackFrameSP RecognizeFrame(lldb::StackFrameSP frame) override { return lldb::RecognizedStackFrameSP( new ObjCExceptionRecognizedStackFrame(frame)); }; }; static void RegisterObjCExceptionRecognizer() { static llvm::once_flag g_once_flag; llvm::call_once(g_once_flag, []() { FileSpec module; ConstString function; std::tie(module, function) = AppleObjCRuntime::GetExceptionThrowLocation(); + std::vector symbols = {function}; StackFrameRecognizerManager::AddRecognizer( StackFrameRecognizerSP(new ObjCExceptionThrowFrameRecognizer()), - module.GetFilename(), function, /*alternate_symbol*/ {}, + module.GetFilename(), symbols, /*first_instruction_only*/ true); }); } diff --git a/lldb/source/Target/AssertFrameRecognizer.cpp b/lldb/source/Target/AssertFrameRecognizer.cpp index ebeff6c39391..d87459ac2fdd 100644 --- a/lldb/source/Target/AssertFrameRecognizer.cpp +++ b/lldb/source/Target/AssertFrameRecognizer.cpp @@ -1,172 +1,168 @@ #include "lldb/Core/Module.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Target/Process.h" #include "lldb/Target/StackFrameList.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Logging.h" #include "lldb/Target/AssertFrameRecognizer.h" using namespace llvm; using namespace lldb; using namespace lldb_private; namespace lldb_private { /// Stores a function module spec, symbol name and possibly an alternate symbol /// name. struct SymbolLocation { FileSpec module_spec; - ConstString symbol_name; - ConstString alternate_symbol_name; + std::vector symbols; }; /// Fetches the abort frame location depending on the current platform. /// /// \param[in] os /// The target's os type. /// \param[in,out] location /// The struct that will contain the abort module spec and symbol names. /// \return /// \b true, if the platform is supported /// \b false, otherwise. bool GetAbortLocation(llvm::Triple::OSType os, SymbolLocation &location) { switch (os) { case llvm::Triple::Darwin: case llvm::Triple::MacOSX: location.module_spec = FileSpec("libsystem_kernel.dylib"); - location.symbol_name.SetString("__pthread_kill"); + location.symbols.push_back(ConstString("__pthread_kill")); break; case llvm::Triple::Linux: location.module_spec = FileSpec("libc.so.6"); - location.symbol_name.SetString("raise"); - location.alternate_symbol_name.SetString("__GI_raise"); + location.symbols.push_back(ConstString("raise")); + location.symbols.push_back(ConstString("__GI_raise")); + location.symbols.push_back(ConstString("gsignal")); break; default: Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND)); LLDB_LOG(log, "AssertFrameRecognizer::GetAbortLocation Unsupported OS"); return false; } return true; } /// Fetches the assert frame location depending on the current platform. /// /// \param[in] os /// The target's os type. /// \param[in,out] location /// The struct that will contain the assert module spec and symbol names. /// \return /// \b true, if the platform is supported /// \b false, otherwise. bool GetAssertLocation(llvm::Triple::OSType os, SymbolLocation &location) { switch (os) { case llvm::Triple::Darwin: case llvm::Triple::MacOSX: location.module_spec = FileSpec("libsystem_c.dylib"); - location.symbol_name.SetString("__assert_rtn"); + location.symbols.push_back(ConstString("__assert_rtn")); break; case llvm::Triple::Linux: location.module_spec = FileSpec("libc.so.6"); - location.symbol_name.SetString("__assert_fail"); - location.alternate_symbol_name.SetString("__GI___assert_fail"); + location.symbols.push_back(ConstString("__assert_fail")); + location.symbols.push_back(ConstString("__GI___assert_fail")); break; default: Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND)); LLDB_LOG(log, "AssertFrameRecognizer::GetAssertLocation Unsupported OS"); return false; } return true; } void RegisterAssertFrameRecognizer(Process *process) { static llvm::once_flag g_once_flag; llvm::call_once(g_once_flag, [process]() { Target &target = process->GetTarget(); llvm::Triple::OSType os = target.GetArchitecture().GetTriple().getOS(); SymbolLocation location; if (!GetAbortLocation(os, location)) return; StackFrameRecognizerManager::AddRecognizer( StackFrameRecognizerSP(new AssertFrameRecognizer()), - location.module_spec.GetFilename(), ConstString(location.symbol_name), - ConstString(location.alternate_symbol_name), + location.module_spec.GetFilename(), location.symbols, /*first_instruction_only*/ false); }); } } // namespace lldb_private lldb::RecognizedStackFrameSP AssertFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) { ThreadSP thread_sp = frame_sp->GetThread(); ProcessSP process_sp = thread_sp->GetProcess(); Target &target = process_sp->GetTarget(); llvm::Triple::OSType os = target.GetArchitecture().GetTriple().getOS(); SymbolLocation location; if (!GetAssertLocation(os, location)) return RecognizedStackFrameSP(); const uint32_t frames_to_fetch = 5; const uint32_t last_frame_index = frames_to_fetch - 1; StackFrameSP prev_frame_sp = nullptr; // Fetch most relevant frame for (uint32_t frame_index = 0; frame_index < frames_to_fetch; frame_index++) { prev_frame_sp = thread_sp->GetStackFrameAtIndex(frame_index); if (!prev_frame_sp) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND)); LLDB_LOG(log, "Abort Recognizer: Hit unwinding bound ({1} frames)!", frames_to_fetch); break; } SymbolContext sym_ctx = prev_frame_sp->GetSymbolContext(eSymbolContextEverything); if (!sym_ctx.module_sp->GetFileSpec().FileEquals(location.module_spec)) continue; ConstString func_name = sym_ctx.GetFunctionName(); - if (func_name == location.symbol_name || - (!location.alternate_symbol_name.IsEmpty() && - func_name == location.alternate_symbol_name)) { - + if (llvm::is_contained(location.symbols, func_name)) { // We go a frame beyond the assert location because the most relevant // frame for the user is the one in which the assert function was called. // If the assert location is the last frame fetched, then it is set as // the most relevant frame. StackFrameSP most_relevant_frame_sp = thread_sp->GetStackFrameAtIndex( std::min(frame_index + 1, last_frame_index)); // Pass assert location to AbortRecognizedStackFrame to set as most // relevant frame. return lldb::RecognizedStackFrameSP( new AssertRecognizedStackFrame(most_relevant_frame_sp)); } } return RecognizedStackFrameSP(); } AssertRecognizedStackFrame::AssertRecognizedStackFrame( StackFrameSP most_relevant_frame_sp) : m_most_relevant_frame(most_relevant_frame_sp) { m_stop_desc = "hit program assert"; } lldb::StackFrameSP AssertRecognizedStackFrame::GetMostRelevantFrame() { return m_most_relevant_frame; } diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp index dfc811da0a50..32b8c94d942c 100644 --- a/lldb/source/Target/StackFrameRecognizer.cpp +++ b/lldb/source/Target/StackFrameRecognizer.cpp @@ -1,216 +1,205 @@ //===-- StackFrameRecognizer.cpp --------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include #include "lldb/Core/Module.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Target/StackFrame.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Utility/RegularExpression.h" using namespace lldb; using namespace lldb_private; class ScriptedRecognizedStackFrame : public RecognizedStackFrame { public: ScriptedRecognizedStackFrame(ValueObjectListSP args) { m_arguments = args; } }; ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer( ScriptInterpreter *interpreter, const char *pclass) : m_interpreter(interpreter), m_python_class(pclass) { m_python_object_sp = m_interpreter->CreateFrameRecognizer(m_python_class.c_str()); } RecognizedStackFrameSP ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) { if (!m_python_object_sp || !m_interpreter) return RecognizedStackFrameSP(); ValueObjectListSP args = m_interpreter->GetRecognizedArguments(m_python_object_sp, frame); auto args_synthesized = ValueObjectListSP(new ValueObjectList()); for (const auto &o : args->GetObjects()) { args_synthesized->Append(ValueObjectRecognizerSynthesizedValue::Create( *o, eValueTypeVariableArgument)); } return RecognizedStackFrameSP( new ScriptedRecognizedStackFrame(args_synthesized)); } class StackFrameRecognizerManagerImpl { public: void AddRecognizer(StackFrameRecognizerSP recognizer, ConstString module, - ConstString symbol, ConstString alternate_symbol, + llvm::ArrayRef symbols, bool first_instruction_only) { m_recognizers.push_front({(uint32_t)m_recognizers.size(), false, recognizer, - false, module, RegularExpressionSP(), symbol, - alternate_symbol, RegularExpressionSP(), - first_instruction_only}); + false, module, RegularExpressionSP(), symbols, + RegularExpressionSP(), first_instruction_only}); } void AddRecognizer(StackFrameRecognizerSP recognizer, RegularExpressionSP module, RegularExpressionSP symbol, bool first_instruction_only) { - m_recognizers.push_front({(uint32_t)m_recognizers.size(), false, recognizer, - true, ConstString(), module, ConstString(), - ConstString(), symbol, first_instruction_only}); + m_recognizers.push_front( + {(uint32_t)m_recognizers.size(), false, recognizer, true, ConstString(), + module, std::vector(), symbol, first_instruction_only}); } - void ForEach( - std::function const - &callback) { + void ForEach(std::function< + void(uint32_t recognized_id, std::string recognizer_name, + std::string module, llvm::ArrayRef symbols, + bool regexp)> const &callback) { for (auto entry : m_recognizers) { if (entry.is_regexp) { std::string module_name; std::string symbol_name; if (entry.module_regexp) module_name = entry.module_regexp->GetText().str(); if (entry.symbol_regexp) symbol_name = entry.symbol_regexp->GetText().str(); callback(entry.recognizer_id, entry.recognizer->GetName(), module_name, - symbol_name, {}, true); + llvm::makeArrayRef(ConstString(symbol_name)), true); } else { - std::string alternate_symbol; - if (!entry.alternate_symbol.IsEmpty()) - alternate_symbol.append(entry.alternate_symbol.GetCString()); - callback(entry.recognizer_id, entry.recognizer->GetName(), - entry.module.GetCString(), entry.symbol.GetCString(), - alternate_symbol, false); + entry.module.GetCString(), entry.symbols, false); } } } bool RemoveRecognizerWithID(uint32_t recognizer_id) { if (recognizer_id >= m_recognizers.size()) return false; if (m_recognizers[recognizer_id].deleted) return false; m_recognizers[recognizer_id].deleted = true; return true; } void RemoveAllRecognizers() { m_recognizers.clear(); } StackFrameRecognizerSP GetRecognizerForFrame(StackFrameSP frame) { const SymbolContext &symctx = frame->GetSymbolContext( eSymbolContextModule | eSymbolContextFunction | eSymbolContextSymbol); ConstString function_name = symctx.GetFunctionName(); ModuleSP module_sp = symctx.module_sp; if (!module_sp) return StackFrameRecognizerSP(); ConstString module_name = module_sp->GetFileSpec().GetFilename(); Symbol *symbol = symctx.symbol; if (!symbol) return StackFrameRecognizerSP(); Address start_addr = symbol->GetAddress(); Address current_addr = frame->GetFrameCodeAddress(); for (auto entry : m_recognizers) { if (entry.deleted) continue; if (entry.module) if (entry.module != module_name) continue; if (entry.module_regexp) if (!entry.module_regexp->Execute(module_name.GetStringRef())) continue; - if (entry.symbol) - if (entry.symbol != function_name && - (!entry.alternate_symbol || - entry.alternate_symbol != function_name)) + if (!entry.symbols.empty()) + if (!llvm::is_contained(entry.symbols, function_name)) continue; if (entry.symbol_regexp) if (!entry.symbol_regexp->Execute(function_name.GetStringRef())) continue; if (entry.first_instruction_only) if (start_addr != current_addr) continue; return entry.recognizer; } return StackFrameRecognizerSP(); } RecognizedStackFrameSP RecognizeFrame(StackFrameSP frame) { auto recognizer = GetRecognizerForFrame(frame); if (!recognizer) return RecognizedStackFrameSP(); return recognizer->RecognizeFrame(frame); } private: struct RegisteredEntry { uint32_t recognizer_id; bool deleted; StackFrameRecognizerSP recognizer; bool is_regexp; ConstString module; RegularExpressionSP module_regexp; - ConstString symbol; - ConstString alternate_symbol; + std::vector symbols; RegularExpressionSP symbol_regexp; bool first_instruction_only; }; std::deque m_recognizers; }; StackFrameRecognizerManagerImpl &GetStackFrameRecognizerManagerImpl() { static StackFrameRecognizerManagerImpl instance = StackFrameRecognizerManagerImpl(); return instance; } void StackFrameRecognizerManager::AddRecognizer( - StackFrameRecognizerSP recognizer, ConstString module, ConstString symbol, - ConstString alternate_symbol, bool first_instruction_only) { + StackFrameRecognizerSP recognizer, ConstString module, + llvm::ArrayRef symbols, bool first_instruction_only) { GetStackFrameRecognizerManagerImpl().AddRecognizer( - recognizer, module, symbol, alternate_symbol, first_instruction_only); + recognizer, module, symbols, first_instruction_only); } void StackFrameRecognizerManager::AddRecognizer( StackFrameRecognizerSP recognizer, RegularExpressionSP module, RegularExpressionSP symbol, bool first_instruction_only) { GetStackFrameRecognizerManagerImpl().AddRecognizer(recognizer, module, symbol, first_instruction_only); } void StackFrameRecognizerManager::ForEach( std::function const - &callback) { + std::string module, llvm::ArrayRef symbols, + bool regexp)> const &callback) { GetStackFrameRecognizerManagerImpl().ForEach(callback); } void StackFrameRecognizerManager::RemoveAllRecognizers() { GetStackFrameRecognizerManagerImpl().RemoveAllRecognizers(); } bool StackFrameRecognizerManager::RemoveRecognizerWithID(uint32_t recognizer_id) { return GetStackFrameRecognizerManagerImpl().RemoveRecognizerWithID(recognizer_id); } RecognizedStackFrameSP StackFrameRecognizerManager::RecognizeFrame( StackFrameSP frame) { return GetStackFrameRecognizerManagerImpl().RecognizeFrame(frame); } StackFrameRecognizerSP StackFrameRecognizerManager::GetRecognizerForFrame( lldb::StackFrameSP frame) { return GetStackFrameRecognizerManagerImpl().GetRecognizerForFrame(frame); } diff --git a/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py index 3a2faa48e767..04b940049496 100644 --- a/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py +++ b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py @@ -1,122 +1,146 @@ # encoding: utf-8 """ Test lldb's frame recognizers. """ import lldb from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil import recognizer class FrameRecognizerTestCase(TestBase): mydir = TestBase.compute_mydir(__file__) NO_DEBUG_INFO_TESTCASE = True @skipUnlessDarwin def test_frame_recognizer_1(self): self.build() - - target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) - self.assertTrue(target, VALID_TARGET) + exe = self.getBuildArtifact("a.out") # Clear internal & plugins recognizers that get initialized at launch self.runCmd("frame recognizer clear") self.runCmd("command script import " + os.path.join(self.getSourceDir(), "recognizer.py")) self.expect("frame recognizer list", substrs=['no matching results found.']) self.runCmd("frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo") self.expect("frame recognizer list", - substrs=['0: recognizer.MyFrameRecognizer, module a.out, function foo']) + substrs=['0: recognizer.MyFrameRecognizer, module a.out, symbol foo']) self.runCmd("frame recognizer add -l recognizer.MyOtherFrameRecognizer -s a.out -n bar -x") - self.expect("frame recognizer list", - substrs=['0: recognizer.MyFrameRecognizer, module a.out, function foo', - '1: recognizer.MyOtherFrameRecognizer, module a.out, function bar (regexp)' - ]) + self.expect( + "frame recognizer list", + substrs=[ + '1: recognizer.MyOtherFrameRecognizer, module a.out, symbol bar (regexp)', + '0: recognizer.MyFrameRecognizer, module a.out, symbol foo' + ]) self.runCmd("frame recognizer delete 0") self.expect("frame recognizer list", - substrs=['1: recognizer.MyOtherFrameRecognizer, module a.out, function bar (regexp)']) + substrs=['1: recognizer.MyOtherFrameRecognizer, module a.out, symbol bar (regexp)']) self.runCmd("frame recognizer clear") self.expect("frame recognizer list", substrs=['no matching results found.']) self.runCmd("frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo") - lldbutil.run_break_set_by_symbol(self, "foo") - self.runCmd("r") - - self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, - substrs=['stopped', 'stop reason = breakpoint']) - - process = target.GetProcess() - thread = process.GetSelectedThread() + target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "foo", + exe_name = exe) frame = thread.GetSelectedFrame() - self.assertEqual(frame.GetSymbol().GetName(), "foo") - self.assertFalse(frame.GetLineEntry().IsValid()) - self.expect("frame variable", substrs=['(int) a = 42', '(int) b = 56']) # Recognized arguments don't show up by default... variables = frame.GetVariables(lldb.SBVariablesOptions()) self.assertEqual(variables.GetSize(), 0) # ...unless you set target.display-recognized-arguments to 1... self.runCmd("settings set target.display-recognized-arguments 1") variables = frame.GetVariables(lldb.SBVariablesOptions()) self.assertEqual(variables.GetSize(), 2) # ...and you can reset it back to 0 to hide them again... self.runCmd("settings set target.display-recognized-arguments 0") variables = frame.GetVariables(lldb.SBVariablesOptions()) self.assertEqual(variables.GetSize(), 0) # ... or explicitly ask for them with SetIncludeRecognizedArguments(True). opts = lldb.SBVariablesOptions() opts.SetIncludeRecognizedArguments(True) variables = frame.GetVariables(opts) self.assertEqual(variables.GetSize(), 2) self.assertEqual(variables.GetValueAtIndex(0).name, "a") self.assertEqual(variables.GetValueAtIndex(0).signed, 42) self.assertEqual(variables.GetValueAtIndex(0).GetValueType(), lldb.eValueTypeVariableArgument) self.assertEqual(variables.GetValueAtIndex(1).name, "b") self.assertEqual(variables.GetValueAtIndex(1).signed, 56) self.assertEqual(variables.GetValueAtIndex(1).GetValueType(), lldb.eValueTypeVariableArgument) self.expect("frame recognizer info 0", substrs=['frame 0 is recognized by recognizer.MyFrameRecognizer']) self.expect("frame recognizer info 999", error=True, substrs=['no frame with index 999']) self.expect("frame recognizer info 1", substrs=['frame 1 not recognized by any recognizer']) # FIXME: The following doesn't work yet, but should be fixed. """ - lldbutil.run_break_set_by_symbol(self, "bar") - self.runCmd("c") + target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "bar", + exe_name = exe) + frame = thread.GetSelectedFrame() self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, substrs=['stopped', 'stop reason = breakpoint']) self.expect("frame variable -t", substrs=['(int *) a = ']) self.expect("frame variable -t *a", substrs=['*a = 78']) """ + + @skipUnlessDarwin + def test_frame_recognizer_multi_symbol(self): + self.build() + exe = self.getBuildArtifact("a.out") + + # Clear internal & plugins recognizers that get initialized at launch + self.runCmd("frame recognizer clear") + + self.runCmd("command script import " + os.path.join(self.getSourceDir(), "recognizer.py")) + + self.expect("frame recognizer list", + substrs=['no matching results found.']) + + self.runCmd("frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo -n bar") + + self.expect("frame recognizer list", + substrs=['recognizer.MyFrameRecognizer, module a.out, symbol foo, symbol bar']) + + target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "foo", + exe_name = exe) + frame = thread.GetSelectedFrame() + + self.expect("frame recognizer info 0", + substrs=['frame 0 is recognized by recognizer.MyFrameRecognizer']) + + target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "bar", + exe_name = exe) + frame = thread.GetSelectedFrame() + + self.expect("frame recognizer info 0", + substrs=['frame 0 is recognized by recognizer.MyFrameRecognizer']) diff --git a/lldb/test/API/commands/frame/recognizer/main.m b/lldb/test/API/commands/frame/recognizer/main.m index 9c6ce9d21023..a5ef73c43b44 100644 --- a/lldb/test/API/commands/frame/recognizer/main.m +++ b/lldb/test/API/commands/frame/recognizer/main.m @@ -1,27 +1,24 @@ //===-- main.m ------------------------------------------------*- ObjC -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #import void foo(int a, int b) { printf("%d %d\n", a, b); } -void bar(int *ptr) -{ - printf("%d\n", *ptr); -} +void bar(int *ptr) { printf("%d\n", *ptr); } int main (int argc, const char * argv[]) { foo(42, 56); int i = 78; bar(&i); return 0; } diff --git a/lldb/unittests/Target/StackFrameRecognizerTest.cpp b/lldb/unittests/Target/StackFrameRecognizerTest.cpp index f7b7829e2bb8..067a56a19902 100644 --- a/lldb/unittests/Target/StackFrameRecognizerTest.cpp +++ b/lldb/unittests/Target/StackFrameRecognizerTest.cpp @@ -1,84 +1,83 @@ //===-- StackFrameRecognizerTest.cpp --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Target/StackFrameRecognizer.h" #include "Plugins/Platform/Linux/PlatformLinux.h" #include "lldb/Core/Debugger.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/HostInfo.h" #include "lldb/Utility/Reproducer.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private-enumerations.h" #include "lldb/lldb-private.h" #include "llvm/Support/FormatVariadic.h" #include "gtest/gtest.h" using namespace lldb_private; using namespace lldb_private::repro; using namespace lldb; namespace { class StackFrameRecognizerTest : public ::testing::Test { public: void SetUp() override { llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None)); FileSystem::Initialize(); HostInfo::Initialize(); // Pretend Linux is the host platform. platform_linux::PlatformLinux::Initialize(); ArchSpec arch("powerpc64-pc-linux"); Platform::SetHostPlatform( platform_linux::PlatformLinux::CreateInstance(true, &arch)); } void TearDown() override { platform_linux::PlatformLinux::Terminate(); HostInfo::Terminate(); FileSystem::Terminate(); Reproducer::Terminate(); } }; class DummyStackFrameRecognizer : public StackFrameRecognizer { public: std::string GetName() override { return "Dummy StackFrame Recognizer"; } }; void RegisterDummyStackFrameRecognizer() { static llvm::once_flag g_once_flag; llvm::call_once(g_once_flag, []() { RegularExpressionSP module_regex_sp = nullptr; RegularExpressionSP symbol_regex_sp(new RegularExpression("boom")); StackFrameRecognizerSP dummy_recognizer_sp(new DummyStackFrameRecognizer()); StackFrameRecognizerManager::AddRecognizer( dummy_recognizer_sp, module_regex_sp, symbol_regex_sp, false); }); } } // namespace TEST_F(StackFrameRecognizerTest, NullModuleRegex) { DebuggerSP debugger_sp = Debugger::CreateInstance(); ASSERT_TRUE(debugger_sp); RegisterDummyStackFrameRecognizer(); bool any_printed = false; StackFrameRecognizerManager::ForEach( [&any_printed](uint32_t recognizer_id, std::string name, - std::string function, std::string symbol, - std::string alternate_symbol, + std::string function, llvm::ArrayRef symbols, bool regexp) { any_printed = true; }); EXPECT_TRUE(any_printed); }