[Lldb-commits] [lldb] [lldb] Extend frame recognizers to hide frames from backtraces (PR #104523)
Adrian Prantl via lldb-commits
lldb-commits at lists.llvm.org
Thu Aug 15 16:38:29 PDT 2024
https://github.com/adrian-prantl created https://github.com/llvm/llvm-project/pull/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 <N>` 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<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
frame #3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, 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), std::__1::allocator<int (*)(int, int)>, 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<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::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<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::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
```
>From 6289b797e6699c356c03caf32a4b74a5e2ee3278 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
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 <N>` 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 | 28 +++++++-
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 | 47 +++++++++++-
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, 222 insertions(+), 34 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<RecognizedStackFrame> {
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<StackFrameRecognizer> {
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<Thread>,
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..b93ffe27bb2f29 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<int32_t>(frame_idx) >=
-*m_options.relative_frame_offset)
@@ -318,7 +342,7 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
}
}
}
- } else {
+ } else {
if (command.GetArgumentCount() > 1) {
result.AppendErrorWithFormat(
"too many arguments; expected frame-index, saw '%s'.\n",
@@ -341,7 +365,7 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
frame_idx = 0;
}
}
- }
+ }
bool success = thread->SetSelectedFrameByIndexNoisily(
frame_idx, result.GetOutputStream());
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<OptionDefinition> 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..209b971b2f2ade 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,52 @@ 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<RegularExpression>("^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..0ff244c605eb26
--- /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 <functional>
+
+int foo(int x, int y) {
+ return x * y; // break here
+}
+
+int main(int argc, char *argv[]) {
+ std::function<int(int, int)> fn = foo;
+ return fn(argc, 1);
+}
More information about the lldb-commits
mailing list