From f01f80ce6ca7640bb0e267b84b1ed0e89b57e2d9 Mon Sep 17 00:00:00 2001 From: Adrian Prantl Date: Tue, 20 Aug 2024 16:01:22 -0700 Subject: [PATCH] [lldb] Extend frame recognizers to hide frames from backtraces (#104523) Compilers and language runtimes often use helper functions that are fundamentally uninteresting when debugging anything but the compiler/runtime itself. This patch introduces a user-extensible mechanism that allows for these frames to be hidden from backtraces and automatically skipped over when navigating the stack with `up` and `down`. This does not affect the numbering of frames, so `f ` will still provide access to the hidden frames. The `bt` output will also print a hint that frames have been hidden. My primary motivation for this feature is to hide thunks in the Swift programming language, but I'm including an example recognizer for `std::function::operator()` that I wished for myself many times while debugging LLDB. rdar://126629381 Example output. (Yes, my proof-of-concept recognizer could hide even more frames if we had a method that returned the function name without the return type or I used something that isn't based off regex, but it's really only meant as an example). before: ``` (lldb) thread backtrace --filtered=false * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10 frame #1: 0x0000000100003a00 a.out`decltype(std::declval()(std::declval(), std::declval())) std::__1::__invoke[abi:se200000](__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25 frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper::__call[abi:se200000](__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12 frame #3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12 frame #4: 0x00000001000026bc a.out`std::__1::__function::__func, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10 frame #5: 0x0000000100003c38 a.out`std::__1::__function::__value_func::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12 frame #6: 0x0000000100002038 a.out`std::__1::function::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10 frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10 frame #8: 0x0000000183cdf154 dyld`start + 2476 (lldb) ``` after ``` (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10 frame #1: 0x0000000100003a00 a.out`decltype(std::declval()(std::declval(), std::declval())) std::__1::__invoke[abi:se200000](__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25 frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper::__call[abi:se200000](__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12 frame #6: 0x0000000100002038 a.out`std::__1::function::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10 frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10 frame #8: 0x0000000183cdf154 dyld`start + 2476 Note: Some frames were hidden by frame recognizers ``` --- lldb/bindings/python/python-wrapper.swig | 18 +++- lldb/include/lldb/API/SBFrame.h | 4 + .../lldb/Interpreter/ScriptInterpreter.h | 5 ++ lldb/include/lldb/Target/StackFrame.h | 36 ++++---- lldb/include/lldb/Target/StackFrameList.h | 2 +- .../lldb/Target/StackFrameRecognizer.h | 21 +++-- lldb/include/lldb/Target/Thread.h | 4 +- lldb/source/API/SBFrame.cpp | 15 +++- lldb/source/API/SBThread.cpp | 3 +- lldb/source/Commands/CommandCompletions.cpp | 4 +- lldb/source/Commands/CommandObjectFrame.cpp | 24 ++++++ lldb/source/Commands/CommandObjectMemory.cpp | 3 +- lldb/source/Commands/CommandObjectThread.cpp | 19 ++++- lldb/source/Commands/Options.td | 2 + lldb/source/Core/Debugger.cpp | 3 +- .../source/Interpreter/CommandInterpreter.cpp | 9 +- .../CPlusPlus/CPPLanguageRuntime.cpp | 44 +++++++++- .../Python/SWIGPythonBridge.h | 3 + .../Python/ScriptInterpreterPython.cpp | 29 +++++++ .../Python/ScriptInterpreterPythonImpl.h | 3 + lldb/source/Target/Process.cpp | 7 +- lldb/source/Target/StackFrame.cpp | 26 ++++-- lldb/source/Target/StackFrameList.cpp | 8 +- lldb/source/Target/StackFrameRecognizer.cpp | 29 +++++-- lldb/source/Target/Thread.cpp | 12 +-- lldb/source/Target/ThreadPlanStepOut.cpp | 2 +- .../frame/recognizer/TestFrameRecognizer.py | 40 +++++++++ .../test/API/commands/frame/recognizer/main.m | 21 ++--- .../commands/frame/recognizer/recognizer.py | 5 ++ .../lang/cpp/std-function-recognizer/Makefile | 4 + .../TestStdFunctionRecognizer.py | 84 +++++++++++++++++++ .../lang/cpp/std-function-recognizer/main.cpp | 10 +++ 32 files changed, 424 insertions(+), 75 deletions(-) create mode 100644 lldb/test/API/lang/cpp/std-function-recognizer/Makefile create mode 100644 lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py create mode 100644 lldb/test/API/lang/cpp/std-function-recognizer/main.cpp diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index 8f050643fa68b3..2ce42e3e017d5b 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -813,7 +813,7 @@ PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecogni } PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments( - PyObject * implementor, const lldb::StackFrameSP &frame_sp) { + PyObject *implementor, const lldb::StackFrameSP &frame_sp) { static char callee_name[] = "get_recognized_arguments"; PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp); @@ -824,6 +824,22 @@ PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArgument return result; } +bool lldb_private::python::SWIGBridge::LLDBSwigPython_ShouldHide( + PyObject *implementor, const lldb::StackFrameSP &frame_sp) { + static char callee_name[] = "should_hide"; + + PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp); + + PythonString str(callee_name); + + PyObject *result = + PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL); + bool ret_val = result ? PyObject_IsTrue(result) : false; + Py_XDECREF(result); + + return result; +} + void *lldb_private::python::SWIGBridge::LLDBSWIGPython_GetDynamicSetting( void *module, const char *setting, const lldb::TargetSP &target_sp) { if (!module || !setting) diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h index 821ff3cf7ce519..e0d15c3ecc5b1c 100644 --- a/lldb/include/lldb/API/SBFrame.h +++ b/lldb/include/lldb/API/SBFrame.h @@ -104,6 +104,10 @@ class LLDB_API SBFrame { bool IsArtificial() const; + /// Return whether a frame recognizer decided this frame should not + /// be displayes in backtraces etc. + bool IsHidden() const; + /// The version that doesn't supply a 'use_dynamic' value will use the /// target's default. lldb::SBValue EvaluateExpression(const char *expr); diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index 05f0d7f0955f3e..89a480a28880aa 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -252,6 +252,11 @@ class ScriptInterpreter : public PluginInterface { return lldb::ValueObjectListSP(); } + virtual bool ShouldHide(const StructuredData::ObjectSP &implementor, + lldb::StackFrameSP frame_sp) { + return false; + } + virtual StructuredData::GenericSP CreateScriptedBreakpointResolver(const char *class_name, const StructuredDataImpl &args_data, diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index 52f0a1ee662176..e4d17847763acf 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -407,6 +407,11 @@ class StackFrame : public ExecutionContextScope, /// may have limited support for inspecting variables. bool IsArtificial() const; + /// Query whether this frame should be hidden from backtraces. Frame + /// recognizers can customize this behavior and hide distracting + /// system implementation details this way. + bool IsHidden(); + /// Query this frame to find what frame it is in this Thread's /// StackFrameList. /// @@ -518,33 +523,36 @@ class StackFrame : public ExecutionContextScope, bool HasCachedData() const; private: - // For StackFrame only + /// For StackFrame only. + /// \{ lldb::ThreadWP m_thread_wp; uint32_t m_frame_index; uint32_t m_concrete_frame_index; lldb::RegisterContextSP m_reg_context_sp; StackID m_id; - Address m_frame_code_addr; // The frame code address (might not be the same as - // the actual PC for inlined frames) as a - // section/offset address + /// \} + + /// The frame code address (might not be the same as the actual PC + /// for inlined frames) as a section/offset address. + Address m_frame_code_addr; SymbolContext m_sc; Flags m_flags; Scalar m_frame_base; Status m_frame_base_error; - bool m_cfa_is_valid; // Does this frame have a CFA? Different from CFA == - // LLDB_INVALID_ADDRESS + uint16_t m_frame_recognizer_generation; + /// Does this frame have a CFA? Different from CFA == LLDB_INVALID_ADDRESS. + bool m_cfa_is_valid; Kind m_stack_frame_kind; - // Whether this frame behaves like the zeroth frame, in the sense - // that its pc value might not immediately follow a call (and thus might - // be the first address of its function). True for actual frame zero as - // well as any other frame with the same trait. + /// Whether this frame behaves like the zeroth frame, in the sense + /// that its pc value might not immediately follow a call (and thus might + /// be the first address of its function). True for actual frame zero as + /// well as any other frame with the same trait. bool m_behaves_like_zeroth_frame; lldb::VariableListSP m_variable_list_sp; - ValueObjectList m_variable_list_value_objects; // Value objects for each - // variable in - // m_variable_list_sp - lldb::RecognizedStackFrameSP m_recognized_frame_sp; + /// Value objects for each variable in m_variable_list_sp. + ValueObjectList m_variable_list_value_objects; + std::optional m_recognized_frame_sp; StreamString m_disassembly; std::recursive_mutex m_mutex; diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index 88e211ff692bd9..7d0e7a5b9a71b2 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -91,7 +91,7 @@ class StackFrameList { size_t GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, uint32_t num_frames_with_source, - bool show_unique = false, + bool show_unique = false, bool show_hidden = false, const char *frame_marker = nullptr); protected: diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h index e9ac2750192ef6..8acebc12c4b1dc 100644 --- a/lldb/include/lldb/Target/StackFrameRecognizer.h +++ b/lldb/include/lldb/Target/StackFrameRecognizer.h @@ -17,6 +17,7 @@ #include "lldb/lldb-private-forward.h" #include "lldb/lldb-public.h" +#include #include #include #include @@ -28,20 +29,23 @@ namespace lldb_private { /// 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 ~RecognizedStackFrame() = default; + virtual lldb::ValueObjectListSP GetRecognizedArguments() { return m_arguments; } virtual lldb::ValueObjectSP GetExceptionObject() { return lldb::ValueObjectSP(); } - virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; }; - virtual ~RecognizedStackFrame() = default; + virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; } std::string GetStopDescription() { return m_stop_desc; } + /// Controls whether this frame should be filtered out when + /// displaying backtraces, for example. + virtual bool ShouldHide() { return false; } protected: lldb::ValueObjectListSP m_arguments; @@ -53,7 +57,6 @@ class RecognizedStackFrame /// 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: @@ -73,10 +76,10 @@ class StackFrameRecognizer /// 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: @@ -123,8 +126,14 @@ class StackFrameRecognizerManager { lldb::StackFrameRecognizerSP GetRecognizerForFrame(lldb::StackFrameSP frame); lldb::RecognizedStackFrameSP RecognizeFrame(lldb::StackFrameSP frame); + /// Returns a number that changes whenever the list of recognizers + /// has been modified. + uint16_t GetGeneration() const { return m_generation; } private: + /// Increase the generation counter. + void BumpGeneration(); + struct RegisteredEntry { uint32_t recognizer_id; lldb::StackFrameRecognizerSP recognizer; @@ -137,6 +146,7 @@ class StackFrameRecognizerManager { }; std::deque m_recognizers; + uint16_t m_generation; }; /// \class ValueObjectRecognizerSynthesizedValue @@ -144,7 +154,6 @@ class StackFrameRecognizerManager { /// 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) { diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index aacc59c292ec79..38b65b2bc58490 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1128,11 +1128,11 @@ class Thread : public std::enable_shared_from_this, size_t GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames, uint32_t num_frames_with_source, bool stop_format, - bool only_stacks = false); + bool show_hidden, bool only_stacks = false); size_t GetStackFrameStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, - uint32_t num_frames_with_source); + uint32_t num_frames_with_source, bool show_hidden); // We need a way to verify that even though we have a thread in a shared // pointer that the object itself is still valid. Currently this won't be the diff --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp index 47fc88625e30c5..2689ecb2ab7bc7 100644 --- a/lldb/source/API/SBFrame.cpp +++ b/lldb/source/API/SBFrame.cpp @@ -1195,13 +1195,24 @@ bool SBFrame::IsArtificial() const { std::unique_lock lock; ExecutionContext exe_ctx(m_opaque_sp.get(), lock); - StackFrame *frame = exe_ctx.GetFramePtr(); - if (frame) + if (StackFrame *frame = exe_ctx.GetFramePtr()) return frame->IsArtificial(); return false; } +bool SBFrame::IsHidden() const { + LLDB_INSTRUMENT_VA(this); + + std::unique_lock lock; + ExecutionContext exe_ctx(m_opaque_sp.get(), lock); + + if (StackFrame *frame = exe_ctx.GetFramePtr()) + return frame->IsHidden(); + + return false; +} + const char *SBFrame::GetFunctionName() { LLDB_INSTRUMENT_VA(this); diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp index 786f62bd66d520..140a2920f05673 100644 --- a/lldb/source/API/SBThread.cpp +++ b/lldb/source/API/SBThread.cpp @@ -1208,7 +1208,8 @@ bool SBThread::GetStatus(SBStream &status) const { ExecutionContext exe_ctx(m_opaque_sp.get(), lock); if (exe_ctx.HasThreadScope()) { - exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true); + exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true, + /*show_hidden=*/true); } else strm.PutCString("No status"); diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp index 54f4b368166492..216aaf9abce6cf 100644 --- a/lldb/source/Commands/CommandCompletions.cpp +++ b/lldb/source/Commands/CommandCompletions.cpp @@ -791,7 +791,7 @@ void CommandCompletions::ThreadIndexes(CommandInterpreter &interpreter, lldb::ThreadSP thread_sp; for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) { StreamString strm; - thread_sp->GetStatus(strm, 0, 1, 1, true); + thread_sp->GetStatus(strm, 0, 1, 1, true, /*show_hidden*/ true); request.TryCompleteCurrentArg(std::to_string(thread_sp->GetIndexID()), strm.GetString()); } @@ -835,7 +835,7 @@ void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter, lldb::ThreadSP thread_sp; for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) { StreamString strm; - thread_sp->GetStatus(strm, 0, 1, 1, true); + thread_sp->GetStatus(strm, 0, 1, 1, true, /*show_hidden*/ true); request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()), strm.GetString()); } diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp index 29e460fe3885ff..46c75e3dd159c0 100644 --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -278,6 +278,30 @@ class CommandObjectFrameSelect : public CommandObjectParsed { if (frame_idx == UINT32_MAX) frame_idx = 0; + // If moving up/down by one, skip over hidden frames. + if (*m_options.relative_frame_offset == 1 || + *m_options.relative_frame_offset == -1) { + uint32_t candidate_idx = frame_idx; + const unsigned max_depth = 12; + for (unsigned num_try = 0; num_try < max_depth; ++num_try) { + if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) { + candidate_idx = UINT32_MAX; + break; + } + candidate_idx += *m_options.relative_frame_offset; + if (auto candidate_sp = thread->GetStackFrameAtIndex(candidate_idx)) { + if (candidate_sp->IsHidden()) + continue; + // Now candidate_idx is the first non-hidden frame. + break; + } + candidate_idx = UINT32_MAX; + break; + }; + if (candidate_idx != UINT32_MAX) + m_options.relative_frame_offset = candidate_idx - frame_idx; + } + if (*m_options.relative_frame_offset < 0) { if (static_cast(frame_idx) >= -*m_options.relative_frame_offset) diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp index 137b1ad981073c..baf5d9196e553e 100644 --- a/lldb/source/Commands/CommandObjectMemory.cpp +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -1570,7 +1570,8 @@ class CommandObjectMemoryHistory : public CommandObjectParsed { const bool stop_format = false; for (auto thread : thread_list) { - thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format); + thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format, + /*should_filter*/ false); } result.SetStatus(eReturnStatusSuccessFinishResult); diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp index 605f872a9f45e1..6a89c163f37d51 100644 --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -89,6 +89,9 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { "invalid boolean value for option '%c': %s", short_option, option_arg.data()); } break; + case 'u': + m_filtered_backtrace = false; + break; default: llvm_unreachable("Unimplemented option"); } @@ -99,6 +102,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { m_count = UINT32_MAX; m_start = 0; m_extended_backtrace = false; + m_filtered_backtrace = true; } llvm::ArrayRef GetDefinitions() override { @@ -109,6 +113,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { uint32_t m_count; uint32_t m_start; bool m_extended_backtrace; + bool m_filtered_backtrace; }; CommandObjectThreadBacktrace(CommandInterpreter &interpreter) @@ -121,7 +126,10 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { "call stacks.\n" "Use 'settings set frame-format' to customize the printing of " "frames in the backtrace and 'settings set thread-format' to " - "customize the thread header.", + "customize the thread header.\n" + "Customizable frame recognizers may filter out less interesting " + "frames, which results in gaps in the numbering. " + "Use '-u' to see all frames.", nullptr, eCommandRequiresProcess | eCommandRequiresThread | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | @@ -199,7 +207,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { strm.PutChar('\n'); if (ext_thread_sp->GetStatus(strm, m_options.m_start, m_options.m_count, - num_frames_with_source, stop_format)) { + num_frames_with_source, stop_format, + !m_options.m_filtered_backtrace)) { DoExtendedBacktrace(ext_thread_sp.get(), result); } } @@ -228,7 +237,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { const uint32_t num_frames_with_source = 0; const bool stop_format = true; if (!thread->GetStatus(strm, m_options.m_start, m_options.m_count, - num_frames_with_source, stop_format, only_stacks)) { + num_frames_with_source, stop_format, + !m_options.m_filtered_backtrace, only_stacks)) { result.AppendErrorWithFormat( "error displaying backtrace for thread: \"0x%4.4x\"\n", thread->GetIndexID()); @@ -1392,7 +1402,8 @@ class CommandObjectThreadException : public CommandObjectIterateOverThreads { const uint32_t num_frames_with_source = 0; const bool stop_format = false; exception_thread_sp->GetStatus(strm, 0, UINT32_MAX, - num_frames_with_source, stop_format); + num_frames_with_source, stop_format, + /*filtered*/ false); } return true; diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index f050cd2ebb5ae0..9c4dbed6939ba9 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1048,6 +1048,8 @@ let Command = "thread backtrace" in { 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">; + def thread_backtrace_unfiltered : Option<"unfiltered", "u">, Group<1>, + Desc<"Filter out frames according to installed frame recognizers">; } let Command = "thread step scope" in { diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 309e01e456580c..67f01707a2afee 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -1869,7 +1869,8 @@ void Debugger::HandleThreadEvent(const EventSP &event_sp) { ThreadSP thread_sp( Thread::ThreadEventData::GetThreadFromEvent(event_sp.get())); if (thread_sp) { - thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format); + thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format, + /*show_hidden*/ true); } } } diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index e45112530404b8..87298803e8415a 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -835,11 +835,12 @@ void CommandInterpreter::LoadCommandDictionary() { std::unique_ptr bt_regex_cmd_up( new CommandObjectRegexCommand( *this, "_regexp-bt", - "Show backtrace of the current thread's call stack. Any numeric " - "argument displays at most that many frames. The argument 'all' " - "displays all threads. Use 'settings set frame-format' to customize " + "Show backtrace of the current thread's call stack. Any numeric " + "argument displays at most that many frames. The argument 'all' " + "displays all threads. Use 'settings set frame-format' to customize " "the printing of individual frames and 'settings set thread-format' " - "to customize the thread header.", + "to customize the thread header. Frame recognizers may filter the" + "list. Use 'thread backtrace -u (--unfiltered)' to see them all.", "bt [ | all]", 0, false)); if (bt_regex_cmd_up) { // accept but don't document "bt -c " -- before bt was a regex diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp index c7202a47d0157e..c60200ab186d09 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp @@ -26,6 +26,7 @@ #include "lldb/Target/RegisterContext.h" #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/StackFrame.h" +#include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/ThreadPlanRunToAddress.h" #include "lldb/Target/ThreadPlanStepInRange.h" #include "lldb/Utility/Timer.h" @@ -40,8 +41,49 @@ static ConstString g_coro_frame = ConstString("__coro_frame"); char CPPLanguageRuntime::ID = 0; +/// A frame recognizer that is installed to hide libc++ implementation +/// details from the backtrace. +class LibCXXFrameRecognizer : public StackFrameRecognizer { + RegularExpression m_hidden_function_regex; + RecognizedStackFrameSP m_hidden_frame; + + struct LibCXXHiddenFrame : public RecognizedStackFrame { + bool ShouldHide() override { return true; } + }; + +public: + LibCXXFrameRecognizer() + : m_hidden_function_regex( + R"(^std::__1::(__function.*::operator\(\)|__invoke))" + R"((\[.*\])?)" // ABI tag. + R"(( const)?$)"), // const. + m_hidden_frame(new LibCXXHiddenFrame()) {} + + std::string GetName() override { return "libc++ frame recognizer"; } + + lldb::RecognizedStackFrameSP + RecognizeFrame(lldb::StackFrameSP frame_sp) override { + if (!frame_sp) + return {}; + const auto &sc = frame_sp->GetSymbolContext(lldb::eSymbolContextFunction); + if (!sc.function) + return {}; + + if (m_hidden_function_regex.Execute(sc.function->GetNameNoArguments())) + return m_hidden_frame; + + return {}; + } +}; + CPPLanguageRuntime::CPPLanguageRuntime(Process *process) - : LanguageRuntime(process) {} + : LanguageRuntime(process) { + if (process) + process->GetTarget().GetFrameRecognizerManager().AddRecognizer( + StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {}, + std::make_shared("^std::__1::"), + /*first_instruction_only*/ false); +} bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) { return name == g_this || name == g_promise || name == g_coro_frame; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 3026b6113ae8f3..5351c1a698b4a7 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -227,6 +227,9 @@ class SWIGBridge { LLDBSwigPython_GetRecognizedArguments(PyObject *implementor, const lldb::StackFrameSP &frame_sp); + static bool LLDBSwigPython_ShouldHide(PyObject *implementor, + const lldb::StackFrameSP &frame_sp); + static bool LLDBSWIGPythonRunScriptKeywordProcess( const char *python_function_name, const char *session_dictionary_name, const lldb::ProcessSP &process, std::string &output); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index 335c482f8495ad..2a94f110910400 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -1524,6 +1524,35 @@ lldb::ValueObjectListSP ScriptInterpreterPythonImpl::GetRecognizedArguments( return ValueObjectListSP(); } +bool ScriptInterpreterPythonImpl::ShouldHide( + const StructuredData::ObjectSP &os_plugin_object_sp, + lldb::StackFrameSP frame_sp) { + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + if (!os_plugin_object_sp) + return false; + + StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric(); + if (!generic) + return false; + + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)generic->GetValue()); + + if (!implementor.IsAllocated()) + return false; + + bool result = + SWIGBridge::LLDBSwigPython_ShouldHide(implementor.get(), frame_sp); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + } + return result; +} + ScriptedProcessInterfaceUP ScriptInterpreterPythonImpl::CreateScriptedProcessInterface() { return std::make_unique(*this); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index c2024efb395d70..85d79955e45efc 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -107,6 +107,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { GetRecognizedArguments(const StructuredData::ObjectSP &implementor, lldb::StackFrameSP frame_sp) override; + bool ShouldHide(const StructuredData::ObjectSP &implementor, + lldb::StackFrameSP frame_sp) override; + lldb::ScriptedProcessInterfaceUP CreateScriptedProcessInterface() override; lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 3c9247fdbbbc96..b2a0f13b9a1549 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -5545,7 +5545,8 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx, // Print a backtrace into the log so we can figure out where we are: StreamString s; s.PutCString("Thread state after unsuccessful completion: \n"); - thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX); + thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX, + /*show_hidden*/ true); log->PutString(s.GetString()); } // Restore the thread state if we are going to discard the plan execution. @@ -5819,8 +5820,8 @@ size_t Process::GetThreadStatus(Stream &strm, continue; } thread_sp->GetStatus(strm, start_frame, num_frames, - num_frames_with_source, - stop_format); + num_frames_with_source, stop_format, + /*show_hidden*/ num_frames <= 1); ++num_thread_infos_dumped; } else { Log *log = GetLog(LLDBLog::Process); diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index 3a2b4d05b28810..0ebaf555f86beb 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -1198,6 +1198,12 @@ bool StackFrame::IsArtificial() const { return m_stack_frame_kind == StackFrame::Kind::Artificial; } +bool StackFrame::IsHidden() { + if (auto recognized_frame_sp = GetRecognizedFrame()) + return recognized_frame_sp->ShouldHide(); + return false; +} + SourceLanguage StackFrame::GetLanguage() { CompileUnit *cu = GetSymbolContext(eSymbolContextCompUnit).comp_unit; if (cu) @@ -1971,12 +1977,16 @@ bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source, } RecognizedStackFrameSP StackFrame::GetRecognizedFrame() { - if (!m_recognized_frame_sp) { - m_recognized_frame_sp = GetThread() - ->GetProcess() - ->GetTarget() - .GetFrameRecognizerManager() - .RecognizeFrame(CalculateStackFrame()); - } - return m_recognized_frame_sp; + auto process = GetThread()->GetProcess(); + if (!process) + return {}; + // If recognizer list has been modified, discard cache. + auto &manager = process->GetTarget().GetFrameRecognizerManager(); + auto new_generation = manager.GetGeneration(); + if (m_frame_recognizer_generation != new_generation) + m_recognized_frame_sp.reset(); + m_frame_recognizer_generation = new_generation; + if (!m_recognized_frame_sp.has_value()) + m_recognized_frame_sp = manager.RecognizeFrame(CalculateStackFrame()); + return m_recognized_frame_sp.value(); } diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 0cf9ce1bf043f5..7808bd3674ab19 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -924,7 +924,7 @@ StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) { size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, uint32_t num_frames_with_source, - bool show_unique, + bool show_unique, bool show_hidden, const char *selected_frame_marker) { size_t num_frames_displayed = 0; @@ -951,7 +951,6 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, unselected_marker = buffer.c_str(); } const char *marker = nullptr; - for (frame_idx = first_frame; frame_idx < last_frame; ++frame_idx) { frame_sp = GetFrameAtIndex(frame_idx); if (!frame_sp) @@ -963,6 +962,11 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, else marker = unselected_marker; } + + // Hide uninteresting frames unless it's the selected frame. + if (!show_hidden && frame_sp != selected_frame_sp && frame_sp->IsHidden()) + continue; + // Check for interruption here. If we're fetching arguments, this loop // can go slowly: Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp index 0ccb1ae9c031e3..44411afc65dda9 100644 --- a/lldb/source/Target/StackFrameRecognizer.cpp +++ b/lldb/source/Target/StackFrameRecognizer.cpp @@ -17,10 +17,14 @@ using namespace lldb; using namespace lldb_private; class ScriptedRecognizedStackFrame : public RecognizedStackFrame { + bool m_hidden; + public: - ScriptedRecognizedStackFrame(ValueObjectListSP args) { - m_arguments = args; + ScriptedRecognizedStackFrame(ValueObjectListSP args, bool hidden) + : m_hidden(hidden) { + m_arguments = std::move(args); } + bool ShouldHide() override { return m_hidden; } }; ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer( @@ -38,13 +42,22 @@ ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) { 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)); + if (args) { + for (const auto &o : args->GetObjects()) + args_synthesized->Append(ValueObjectRecognizerSynthesizedValue::Create( + *o, eValueTypeVariableArgument)); } + bool hidden = m_interpreter->ShouldHide(m_python_object_sp, frame); + return RecognizedStackFrameSP( - new ScriptedRecognizedStackFrame(args_synthesized)); + new ScriptedRecognizedStackFrame(args_synthesized, hidden)); +} + +void StackFrameRecognizerManager::BumpGeneration() { + uint32_t n = m_generation; + n = (n + 1) & ((1 << 16) - 1); + m_generation = n; } void StackFrameRecognizerManager::AddRecognizer( @@ -53,6 +66,7 @@ void StackFrameRecognizerManager::AddRecognizer( m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, false, module, RegularExpressionSP(), symbols, RegularExpressionSP(), first_instruction_only}); + BumpGeneration(); } void StackFrameRecognizerManager::AddRecognizer( @@ -61,6 +75,7 @@ void StackFrameRecognizerManager::AddRecognizer( m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, true, ConstString(), module, std::vector(), symbol, first_instruction_only}); + BumpGeneration(); } void StackFrameRecognizerManager::ForEach( @@ -97,10 +112,12 @@ bool StackFrameRecognizerManager::RemoveRecognizerWithID( if (found == m_recognizers.end()) return false; m_recognizers.erase(found); + BumpGeneration(); return true; } void StackFrameRecognizerManager::RemoveAllRecognizers() { + BumpGeneration(); m_recognizers.clear(); } diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 74d1a268c6dffb..fcf0f4e2519085 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1748,7 +1748,7 @@ std::string Thread::RunModeAsString(lldb::RunMode mode) { size_t Thread::GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames, uint32_t num_frames_with_source, - bool stop_format, bool only_stacks) { + bool stop_format, bool show_hidden, bool only_stacks) { if (!only_stacks) { ExecutionContext exe_ctx(shared_from_this()); @@ -1795,7 +1795,7 @@ size_t Thread::GetStatus(Stream &strm, uint32_t start_frame, num_frames_shown = GetStackFrameList()->GetStatus( strm, start_frame, num_frames, show_frame_info, num_frames_with_source, - show_frame_unique, selected_frame_marker); + show_frame_unique, show_hidden, selected_frame_marker); if (num_frames == 1) strm.IndentLess(); strm.IndentLess(); @@ -1893,9 +1893,11 @@ bool Thread::GetDescription(Stream &strm, lldb::DescriptionLevel level, size_t Thread::GetStackFrameStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, - uint32_t num_frames_with_source) { - return GetStackFrameList()->GetStatus( - strm, first_frame, num_frames, show_frame_info, num_frames_with_source); + uint32_t num_frames_with_source, + bool show_hidden) { + return GetStackFrameList()->GetStatus(strm, first_frame, num_frames, + show_frame_info, num_frames_with_source, + /*show_unique*/ false, show_hidden); } Unwind &Thread::GetUnwinder() { diff --git a/lldb/source/Target/ThreadPlanStepOut.cpp b/lldb/source/Target/ThreadPlanStepOut.cpp index 0a1e2ae605efcf..8ca1dbc2fe4c46 100644 --- a/lldb/source/Target/ThreadPlanStepOut.cpp +++ b/lldb/source/Target/ThreadPlanStepOut.cpp @@ -58,7 +58,7 @@ ThreadPlanStepOut::ThreadPlanStepOut( return; // we can't do anything here. ValidatePlan() will return false. // While stepping out, behave as-if artificial frames are not present. - while (return_frame_sp->IsArtificial()) { + while (return_frame_sp->IsArtificial() || return_frame_sp->IsHidden()) { m_stepped_past_frames.push_back(return_frame_sp); ++return_frame_index; diff --git a/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py index eea0aafce6e25e..6174ac61a709dd 100644 --- a/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py +++ b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py @@ -162,6 +162,46 @@ def test_frame_recognizer_1(self): substrs=['*a = 78']) """ + @skipUnlessDarwin + def test_frame_recognizer_hiding(self): + self.build() + + target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "nested") + frame = thread.GetSelectedFrame() + + # Sanity check. + self.expect( + "thread backtrace", patterns=["frame.*nested", "frame.*baz", "frame.*main"] + ) + + self.expect("frame recognizer clear") + self.expect( + "command script import " + + os.path.join(self.getSourceDir(), "recognizer.py") + ) + + self.expect( + "frame recognizer add -l recognizer.BazFrameRecognizer -f false -s a.out -n baz" + ) + + self.expect( + "frame recognizer list", + substrs=["0: recognizer.BazFrameRecognizer"], + ) + + # Now main should be hidden. + self.expect("thread backtrace", matching=False, patterns=["frame.*baz"]) + self.assertFalse(frame.IsHidden()) + frame = thread.SetSelectedFrame(1) + self.assertIn("baz", frame.name) + self.assertTrue(frame.IsHidden()) + + # Test StepOut. + frame = thread.SetSelectedFrame(0) + thread.StepOut() + frame = thread.GetSelectedFrame() + self.assertIn("main", frame.name) + @skipUnlessDarwin def test_frame_recognizer_multi_symbol(self): self.build() diff --git a/lldb/test/API/commands/frame/recognizer/main.m b/lldb/test/API/commands/frame/recognizer/main.m index 6546692bba772e..74d219f1fff4c5 100644 --- a/lldb/test/API/commands/frame/recognizer/main.m +++ b/lldb/test/API/commands/frame/recognizer/main.m @@ -1,16 +1,17 @@ #import -void foo(int a, int b) -{ - printf("%d %d\n", a, b); -} +void foo(int a, int b) { printf("%d %d\n", a, b); } 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; +void nested(int *ptr) { bar(ptr); } + +void baz(int *ptr) { nested(ptr); } + +int main(int argc, const char *argv[]) { + foo(42, 56); + int i = 78; + bar(&i); + baz(&i); + return 0; } diff --git a/lldb/test/API/commands/frame/recognizer/recognizer.py b/lldb/test/API/commands/frame/recognizer/recognizer.py index 1a2a2d5c265070..98666b720b1e2b 100644 --- a/lldb/test/API/commands/frame/recognizer/recognizer.py +++ b/lldb/test/API/commands/frame/recognizer/recognizer.py @@ -36,3 +36,8 @@ def get_recognized_arguments(self, frame): class MyOtherFrameRecognizer(object): def get_recognized_arguments(self, frame): return [] + + +class BazFrameRecognizer(object): + def should_hide(self, frame): + return "baz" in frame.name diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/Makefile b/lldb/test/API/lang/cpp/std-function-recognizer/Makefile new file mode 100644 index 00000000000000..ab034edd121f9f --- /dev/null +++ b/lldb/test/API/lang/cpp/std-function-recognizer/Makefile @@ -0,0 +1,4 @@ +CXX_SOURCES := main.cpp +USE_LIBCPP := 1 + +include Makefile.rules diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py new file mode 100644 index 00000000000000..30fe3ecb1e4bf4 --- /dev/null +++ b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py @@ -0,0 +1,84 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class LibCxxStdFunctionRecognizerTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + @add_test_categories(["libc++"]) + def test_backtrace(self): + """Test that std::function implementation details are hidden in bt""" + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + # Filtered. + self.expect( + "thread backtrace", + ordered=True, + substrs=["frame", "foo", "frame", "main"], + ) + self.expect( + "thread backtrace", matching=False, patterns=["frame.*std::__1::__function"] + ) + # Unfiltered. + self.expect( + "thread backtrace -u", + ordered=True, + patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"], + ) + self.expect( + "thread backtrace --unfiltered", + ordered=True, + patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"], + ) + + @add_test_categories(["libc++"]) + def test_up_down(self): + """Test that std::function implementation details are skipped""" + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + frame = thread.GetSelectedFrame() + # up + self.assertIn("foo", frame.GetFunctionName()) + start_idx = frame.GetFrameID() + i = 0 + while i < thread.GetNumFrames(): + self.expect("up") + frame = thread.GetSelectedFrame() + if frame.GetFunctionName() == "main": + break + end_idx = frame.GetFrameID() + self.assertLess(i, end_idx - start_idx, "skipped frames") + + # Back down again. + start_idx = frame.GetFrameID() + for i in range(1, thread.GetNumFrames()): + self.expect("down") + frame = thread.GetSelectedFrame() + if "foo" in frame.GetFunctionName(): + break + end_idx = frame.GetFrameID() + self.assertLess(i, start_idx - end_idx, "skipped frames") + + @add_test_categories(["libc++"]) + def test_api(self): + """Test that std::function implementation details are skipped""" + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + frame = thread.GetSelectedFrame() + num_hidden = 0 + for i in range(1, thread.GetNumFrames()): + thread.SetSelectedFrame(i) + frame = thread.GetSelectedFrame() + if frame.IsHidden(): + num_hidden += 1 + + self.assertGreater(num_hidden, 0) + self.assertLess(num_hidden, thread.GetNumFrames()) diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/main.cpp b/lldb/test/API/lang/cpp/std-function-recognizer/main.cpp new file mode 100644 index 00000000000000..8cf4eaa2e51929 --- /dev/null +++ b/lldb/test/API/lang/cpp/std-function-recognizer/main.cpp @@ -0,0 +1,10 @@ +#include + +int foo(int x, int y) { + return x * y; // break here +} + +int main(int argc, char *argv[]) { + std::function fn = foo; + return fn(argc, 1); +}