From 66292a2279cb66522932a4909a8382790da80060 Mon Sep 17 00:00:00 2001 From: Adrian Prantl Date: Thu, 15 Aug 2024 16:18:33 -0700 Subject: [PATCH] [lldb] Extend frame recognizers to hide frames from backtraces 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 --- lldb/include/lldb/Target/StackFrameList.h | 2 +- .../lldb/Target/StackFrameRecognizer.h | 12 ++-- lldb/include/lldb/Target/Thread.h | 5 +- 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 | 25 +++++-- lldb/source/Commands/Options.td | 3 + lldb/source/Core/Debugger.cpp | 3 +- .../CPlusPlus/CPPLanguageRuntime.cpp | 44 +++++++++++- lldb/source/Target/Process.cpp | 7 +- lldb/source/Target/StackFrameList.cpp | 15 +++- lldb/source/Target/Thread.cpp | 13 ++-- .../lang/cpp/std-function-recognizer/Makefile | 4 ++ .../TestStdFunctionRecognizer.py | 72 +++++++++++++++++++ .../lang/cpp/std-function-recognizer/main.cpp | 10 +++ 17 files changed, 217 insertions(+), 32 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/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index 88e211ff692bd9..5cdca97e910613 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 should_filter = true, const char *frame_marker = nullptr); protected: diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h index e9ac2750192ef6..dae1c6f4415718 100644 --- a/lldb/include/lldb/Target/StackFrameRecognizer.h +++ b/lldb/include/lldb/Target/StackFrameRecognizer.h @@ -28,20 +28,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 +56,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,7 +75,6 @@ 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; @@ -144,7 +145,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..9de58513b160b4 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1128,11 +1128,12 @@ 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 should_filter, 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 should_filter); // 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/SBThread.cpp b/lldb/source/API/SBThread.cpp index bda981041064ff..7a3d6ff9336159 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, + /*should_filter*/ false); } else strm.PutCString("No status"); diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp index 54f4b368166492..ea647bbfe4c026 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, /*should_filter*/ false); 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, /*should_filter*/ false); 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..aae7306291b0a7 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; + for (unsigned num_try = 0; num_try < 12; ++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 (auto recognized_frame_sp = candidate_sp->GetRecognizedFrame()) + if (recognized_frame_sp->ShouldHide()) + 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..d58c8533f90f7b 100644 --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -80,14 +80,20 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { "invalid integer value for option '%c': %s", short_option, option_arg.data()); break; - case 'e': { + case 'e': + case 'f': { bool success; - m_extended_backtrace = - OptionArgParser::ToBoolean(option_arg, false, &success); - if (!success) + bool value = OptionArgParser::ToBoolean(option_arg, false, &success); + if (!success) { error.SetErrorStringWithFormat( "invalid boolean value for option '%c': %s", short_option, option_arg.data()); + break; + } + if (short_option == 'e') + m_extended_backtrace = value; + else + m_filtered_backtrace = value; } break; default: llvm_unreachable("Unimplemented option"); @@ -99,6 +105,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 +116,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { uint32_t m_count; uint32_t m_start; bool m_extended_backtrace; + bool m_filtered_backtrace; }; CommandObjectThreadBacktrace(CommandInterpreter &interpreter) @@ -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..a59c3c18918115 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1048,6 +1048,9 @@ 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_full : Option<"filtered", "f">, Group<1>, + Arg<"Boolean">, + 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..d9f1fb882fb105 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, + /*should_filter*/ false); } } } diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp index c7202a47d0157e..768e519f33f2e3 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 isntalled 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 {}; + 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/Target/Process.cpp b/lldb/source/Target/Process.cpp index e3c4f2ee398cc4..d7efa8267436fd 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, + /*should_filter*/ false); 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, + /*should_filter*/ num_frames > 1); ++num_thread_infos_dumped; } else { Log *log = GetLog(LLDBLog::Process); diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 0cf9ce1bf043f5..2f284d32ce77ac 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 should_filter, const char *selected_frame_marker) { size_t num_frames_displayed = 0; @@ -950,8 +950,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, buffer.insert(buffer.begin(), len, ' '); unselected_marker = buffer.c_str(); } + bool filtered = false; 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 +963,15 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, else marker = unselected_marker; } + + // Hide uninteresting frames unless it's the selected frame. + if (should_filter && frame_sp != selected_frame_sp) + if (auto recognized_frame_sp = frame_sp->GetRecognizedFrame()) + if (recognized_frame_sp->ShouldHide()) { + filtered = true; + continue; + } + // Check for interruption here. If we're fetching arguments, this loop // can go slowly: Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); @@ -979,6 +988,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, ++num_frames_displayed; } + if (filtered) + strm << "Note: Some frames were hidden by frame recognizers\n"; strm.IndentLess(); return num_frames_displayed; } diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 74d1a268c6dffb..bedb89c72dd433 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1748,7 +1748,8 @@ 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 should_filter, + bool only_stacks) { if (!only_stacks) { ExecutionContext exe_ctx(shared_from_this()); @@ -1795,7 +1796,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, should_filter, selected_frame_marker); if (num_frames == 1) strm.IndentLess(); strm.IndentLess(); @@ -1893,9 +1894,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 should_filter) { + return GetStackFrameList()->GetStatus(strm, first_frame, num_frames, + show_frame_info, num_frames_with_source, + /*show_unique*/ false, should_filter); } Unwind &Thread::GetUnwinder() { 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..b2b1d87ea6429b --- /dev/null +++ b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py @@ -0,0 +1,72 @@ +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", "frames", "hidden"], + ) + self.expect( + "thread backtrace", matching=False, patterns=["frame.*std::__1::__function"] + ) + # Unfiltered. + self.expect( + "thread backtrace -f false", + ordered=True, + patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"], + ) + self.expect( + "thread backtrace --filtered false", + ordered=True, + patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"], + ) + self.expect( + "thread backtrace --filtered false", + matching=False, + patterns=["frames.*hidden"], + ) + + @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() + i = 0 + while i < 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") 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); +}