Skip to content

Commit 66292a2

Browse files
committed
[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
1 parent ae059a1 commit 66292a2

17 files changed

+217
-32
lines changed

lldb/include/lldb/Target/StackFrameList.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class StackFrameList {
9191

9292
size_t GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames,
9393
bool show_frame_info, uint32_t num_frames_with_source,
94-
bool show_unique = false,
94+
bool show_unique = false, bool should_filter = true,
9595
const char *frame_marker = nullptr);
9696

9797
protected:

lldb/include/lldb/Target/StackFrameRecognizer.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,23 @@ namespace lldb_private {
2828
/// This class provides extra information about a stack frame that was
2929
/// provided by a specific stack frame recognizer. Right now, this class only
3030
/// holds recognized arguments (via GetRecognizedArguments).
31-
3231
class RecognizedStackFrame
3332
: public std::enable_shared_from_this<RecognizedStackFrame> {
3433
public:
34+
virtual ~RecognizedStackFrame() = default;
35+
3536
virtual lldb::ValueObjectListSP GetRecognizedArguments() {
3637
return m_arguments;
3738
}
3839
virtual lldb::ValueObjectSP GetExceptionObject() {
3940
return lldb::ValueObjectSP();
4041
}
41-
virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; };
42-
virtual ~RecognizedStackFrame() = default;
42+
virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; }
4343

4444
std::string GetStopDescription() { return m_stop_desc; }
45+
/// Controls whether this frame should be filtered out when
46+
/// displaying backtraces, for example.
47+
virtual bool ShouldHide() { return false; }
4548

4649
protected:
4750
lldb::ValueObjectListSP m_arguments;
@@ -53,7 +56,6 @@ class RecognizedStackFrame
5356
/// A base class for frame recognizers. Subclasses (actual frame recognizers)
5457
/// should implement RecognizeFrame to provide a RecognizedStackFrame for a
5558
/// given stack frame.
56-
5759
class StackFrameRecognizer
5860
: public std::enable_shared_from_this<StackFrameRecognizer> {
5961
public:
@@ -73,7 +75,6 @@ class StackFrameRecognizer
7375
/// Python implementation for frame recognizers. An instance of this class
7476
/// tracks a particular Python classobject, which will be asked to recognize
7577
/// stack frames.
76-
7778
class ScriptedStackFrameRecognizer : public StackFrameRecognizer {
7879
lldb_private::ScriptInterpreter *m_interpreter;
7980
lldb_private::StructuredData::ObjectSP m_python_object_sp;
@@ -144,7 +145,6 @@ class StackFrameRecognizerManager {
144145
/// ValueObject subclass that presents the passed ValueObject as a recognized
145146
/// value with the specified ValueType. Frame recognizers should return
146147
/// instances of this class as the returned objects in GetRecognizedArguments().
147-
148148
class ValueObjectRecognizerSynthesizedValue : public ValueObject {
149149
public:
150150
static lldb::ValueObjectSP Create(ValueObject &parent, lldb::ValueType type) {

lldb/include/lldb/Target/Thread.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -1128,11 +1128,12 @@ class Thread : public std::enable_shared_from_this<Thread>,
11281128

11291129
size_t GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames,
11301130
uint32_t num_frames_with_source, bool stop_format,
1131-
bool only_stacks = false);
1131+
bool should_filter, bool only_stacks = false);
11321132

11331133
size_t GetStackFrameStatus(Stream &strm, uint32_t first_frame,
11341134
uint32_t num_frames, bool show_frame_info,
1135-
uint32_t num_frames_with_source);
1135+
uint32_t num_frames_with_source,
1136+
bool should_filter);
11361137

11371138
// We need a way to verify that even though we have a thread in a shared
11381139
// pointer that the object itself is still valid. Currently this won't be the

lldb/source/API/SBThread.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,8 @@ bool SBThread::GetStatus(SBStream &status) const {
12081208
ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
12091209

12101210
if (exe_ctx.HasThreadScope()) {
1211-
exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true);
1211+
exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true,
1212+
/*should_filter*/ false);
12121213
} else
12131214
strm.PutCString("No status");
12141215

lldb/source/Commands/CommandCompletions.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ void CommandCompletions::ThreadIndexes(CommandInterpreter &interpreter,
791791
lldb::ThreadSP thread_sp;
792792
for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
793793
StreamString strm;
794-
thread_sp->GetStatus(strm, 0, 1, 1, true);
794+
thread_sp->GetStatus(strm, 0, 1, 1, true, /*should_filter*/ false);
795795
request.TryCompleteCurrentArg(std::to_string(thread_sp->GetIndexID()),
796796
strm.GetString());
797797
}
@@ -835,7 +835,7 @@ void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter,
835835
lldb::ThreadSP thread_sp;
836836
for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
837837
StreamString strm;
838-
thread_sp->GetStatus(strm, 0, 1, 1, true);
838+
thread_sp->GetStatus(strm, 0, 1, 1, true, /*should_filter*/ false);
839839
request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()),
840840
strm.GetString());
841841
}

lldb/source/Commands/CommandObjectFrame.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,30 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
278278
if (frame_idx == UINT32_MAX)
279279
frame_idx = 0;
280280

281+
// If moving up/down by one, skip over hidden frames.
282+
if (*m_options.relative_frame_offset == 1 ||
283+
*m_options.relative_frame_offset == -1) {
284+
uint32_t candidate_idx = frame_idx;
285+
for (unsigned num_try = 0; num_try < 12; ++num_try) {
286+
if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) {
287+
candidate_idx = UINT32_MAX;
288+
break;
289+
}
290+
candidate_idx += *m_options.relative_frame_offset;
291+
if (auto candidate_sp = thread->GetStackFrameAtIndex(candidate_idx)) {
292+
if (auto recognized_frame_sp = candidate_sp->GetRecognizedFrame())
293+
if (recognized_frame_sp->ShouldHide())
294+
continue;
295+
// Now candidate_idx is the first non-hidden frame.
296+
break;
297+
}
298+
candidate_idx = UINT32_MAX;
299+
break;
300+
};
301+
if (candidate_idx != UINT32_MAX)
302+
m_options.relative_frame_offset = candidate_idx - frame_idx;
303+
}
304+
281305
if (*m_options.relative_frame_offset < 0) {
282306
if (static_cast<int32_t>(frame_idx) >=
283307
-*m_options.relative_frame_offset)

lldb/source/Commands/CommandObjectMemory.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -1570,7 +1570,8 @@ class CommandObjectMemoryHistory : public CommandObjectParsed {
15701570

15711571
const bool stop_format = false;
15721572
for (auto thread : thread_list) {
1573-
thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format);
1573+
thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format,
1574+
/*should_filter*/ false);
15741575
}
15751576

15761577
result.SetStatus(eReturnStatusSuccessFinishResult);

lldb/source/Commands/CommandObjectThread.cpp

+18-7
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,20 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
8080
"invalid integer value for option '%c': %s", short_option,
8181
option_arg.data());
8282
break;
83-
case 'e': {
83+
case 'e':
84+
case 'f': {
8485
bool success;
85-
m_extended_backtrace =
86-
OptionArgParser::ToBoolean(option_arg, false, &success);
87-
if (!success)
86+
bool value = OptionArgParser::ToBoolean(option_arg, false, &success);
87+
if (!success) {
8888
error.SetErrorStringWithFormat(
8989
"invalid boolean value for option '%c': %s", short_option,
9090
option_arg.data());
91+
break;
92+
}
93+
if (short_option == 'e')
94+
m_extended_backtrace = value;
95+
else
96+
m_filtered_backtrace = value;
9197
} break;
9298
default:
9399
llvm_unreachable("Unimplemented option");
@@ -99,6 +105,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
99105
m_count = UINT32_MAX;
100106
m_start = 0;
101107
m_extended_backtrace = false;
108+
m_filtered_backtrace = true;
102109
}
103110

104111
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -109,6 +116,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
109116
uint32_t m_count;
110117
uint32_t m_start;
111118
bool m_extended_backtrace;
119+
bool m_filtered_backtrace;
112120
};
113121

114122
CommandObjectThreadBacktrace(CommandInterpreter &interpreter)
@@ -199,7 +207,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
199207
strm.PutChar('\n');
200208
if (ext_thread_sp->GetStatus(strm, m_options.m_start,
201209
m_options.m_count,
202-
num_frames_with_source, stop_format)) {
210+
num_frames_with_source, stop_format,
211+
m_options.m_filtered_backtrace)) {
203212
DoExtendedBacktrace(ext_thread_sp.get(), result);
204213
}
205214
}
@@ -228,7 +237,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
228237
const uint32_t num_frames_with_source = 0;
229238
const bool stop_format = true;
230239
if (!thread->GetStatus(strm, m_options.m_start, m_options.m_count,
231-
num_frames_with_source, stop_format, only_stacks)) {
240+
num_frames_with_source, stop_format,
241+
m_options.m_filtered_backtrace, only_stacks)) {
232242
result.AppendErrorWithFormat(
233243
"error displaying backtrace for thread: \"0x%4.4x\"\n",
234244
thread->GetIndexID());
@@ -1392,7 +1402,8 @@ class CommandObjectThreadException : public CommandObjectIterateOverThreads {
13921402
const uint32_t num_frames_with_source = 0;
13931403
const bool stop_format = false;
13941404
exception_thread_sp->GetStatus(strm, 0, UINT32_MAX,
1395-
num_frames_with_source, stop_format);
1405+
num_frames_with_source, stop_format,
1406+
/*filtered*/ false);
13961407
}
13971408

13981409
return true;

lldb/source/Commands/Options.td

+3
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,9 @@ let Command = "thread backtrace" in {
10481048
Arg<"FrameIndex">, Desc<"Frame in which to start the backtrace">;
10491049
def thread_backtrace_extended : Option<"extended", "e">, Group<1>,
10501050
Arg<"Boolean">, Desc<"Show the extended backtrace, if available">;
1051+
def thread_backtrace_full : Option<"filtered", "f">, Group<1>,
1052+
Arg<"Boolean">,
1053+
Desc<"Filter out frames according to installed frame recognizers">;
10511054
}
10521055

10531056
let Command = "thread step scope" in {

lldb/source/Core/Debugger.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -1869,7 +1869,8 @@ void Debugger::HandleThreadEvent(const EventSP &event_sp) {
18691869
ThreadSP thread_sp(
18701870
Thread::ThreadEventData::GetThreadFromEvent(event_sp.get()));
18711871
if (thread_sp) {
1872-
thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format);
1872+
thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format,
1873+
/*should_filter*/ false);
18731874
}
18741875
}
18751876
}

lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp

+43-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "lldb/Target/RegisterContext.h"
2727
#include "lldb/Target/SectionLoadList.h"
2828
#include "lldb/Target/StackFrame.h"
29+
#include "lldb/Target/StackFrameRecognizer.h"
2930
#include "lldb/Target/ThreadPlanRunToAddress.h"
3031
#include "lldb/Target/ThreadPlanStepInRange.h"
3132
#include "lldb/Utility/Timer.h"
@@ -40,8 +41,49 @@ static ConstString g_coro_frame = ConstString("__coro_frame");
4041

4142
char CPPLanguageRuntime::ID = 0;
4243

44+
/// A frame recognizer that is isntalled to hide libc++ implementation
45+
/// details from the backtrace.
46+
class LibCXXFrameRecognizer : public StackFrameRecognizer {
47+
RegularExpression m_hidden_function_regex;
48+
RecognizedStackFrameSP m_hidden_frame;
49+
50+
struct LibCXXHiddenFrame : public RecognizedStackFrame {
51+
bool ShouldHide() override { return true; }
52+
};
53+
54+
public:
55+
LibCXXFrameRecognizer()
56+
: m_hidden_function_regex(
57+
R"(^std::__1::(__function.*::operator\(\)|__invoke))"
58+
R"((\[.*\])?)" // ABI tag.
59+
R"(( const)?$)"), // const.
60+
m_hidden_frame(new LibCXXHiddenFrame()) {}
61+
62+
std::string GetName() override { return "libc++ frame recognizer"; }
63+
64+
lldb::RecognizedStackFrameSP
65+
RecognizeFrame(lldb::StackFrameSP frame_sp) override {
66+
if (!frame_sp)
67+
return {};
68+
auto &sc = frame_sp->GetSymbolContext(lldb::eSymbolContextFunction);
69+
if (!sc.function)
70+
return {};
71+
72+
if (m_hidden_function_regex.Execute(sc.function->GetNameNoArguments()))
73+
return m_hidden_frame;
74+
75+
return {};
76+
}
77+
};
78+
4379
CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
44-
: LanguageRuntime(process) {}
80+
: LanguageRuntime(process) {
81+
if (process)
82+
process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
83+
StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {},
84+
std::make_shared<RegularExpression>("^std::__1::"),
85+
/*first_instruction_only*/ false);
86+
}
4587

4688
bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) {
4789
return name == g_this || name == g_promise || name == g_coro_frame;

lldb/source/Target/Process.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -5545,7 +5545,8 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
55455545
// Print a backtrace into the log so we can figure out where we are:
55465546
StreamString s;
55475547
s.PutCString("Thread state after unsuccessful completion: \n");
5548-
thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX);
5548+
thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX,
5549+
/*should_filter*/ false);
55495550
log->PutString(s.GetString());
55505551
}
55515552
// Restore the thread state if we are going to discard the plan execution.
@@ -5819,8 +5820,8 @@ size_t Process::GetThreadStatus(Stream &strm,
58195820
continue;
58205821
}
58215822
thread_sp->GetStatus(strm, start_frame, num_frames,
5822-
num_frames_with_source,
5823-
stop_format);
5823+
num_frames_with_source, stop_format,
5824+
/*should_filter*/ num_frames > 1);
58245825
++num_thread_infos_dumped;
58255826
} else {
58265827
Log *log = GetLog(LLDBLog::Process);

lldb/source/Target/StackFrameList.cpp

+13-2
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) {
924924
size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
925925
uint32_t num_frames, bool show_frame_info,
926926
uint32_t num_frames_with_source,
927-
bool show_unique,
927+
bool show_unique, bool should_filter,
928928
const char *selected_frame_marker) {
929929
size_t num_frames_displayed = 0;
930930

@@ -950,8 +950,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
950950
buffer.insert(buffer.begin(), len, ' ');
951951
unselected_marker = buffer.c_str();
952952
}
953+
bool filtered = false;
953954
const char *marker = nullptr;
954-
955955
for (frame_idx = first_frame; frame_idx < last_frame; ++frame_idx) {
956956
frame_sp = GetFrameAtIndex(frame_idx);
957957
if (!frame_sp)
@@ -963,6 +963,15 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
963963
else
964964
marker = unselected_marker;
965965
}
966+
967+
// Hide uninteresting frames unless it's the selected frame.
968+
if (should_filter && frame_sp != selected_frame_sp)
969+
if (auto recognized_frame_sp = frame_sp->GetRecognizedFrame())
970+
if (recognized_frame_sp->ShouldHide()) {
971+
filtered = true;
972+
continue;
973+
}
974+
966975
// Check for interruption here. If we're fetching arguments, this loop
967976
// can go slowly:
968977
Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger();
@@ -979,6 +988,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
979988
++num_frames_displayed;
980989
}
981990

991+
if (filtered)
992+
strm << "Note: Some frames were hidden by frame recognizers\n";
982993
strm.IndentLess();
983994
return num_frames_displayed;
984995
}

lldb/source/Target/Thread.cpp

+8-5
Original file line numberDiff line numberDiff line change
@@ -1748,7 +1748,8 @@ std::string Thread::RunModeAsString(lldb::RunMode mode) {
17481748

17491749
size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
17501750
uint32_t num_frames, uint32_t num_frames_with_source,
1751-
bool stop_format, bool only_stacks) {
1751+
bool stop_format, bool should_filter,
1752+
bool only_stacks) {
17521753

17531754
if (!only_stacks) {
17541755
ExecutionContext exe_ctx(shared_from_this());
@@ -1795,7 +1796,7 @@ size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
17951796

17961797
num_frames_shown = GetStackFrameList()->GetStatus(
17971798
strm, start_frame, num_frames, show_frame_info, num_frames_with_source,
1798-
show_frame_unique, selected_frame_marker);
1799+
show_frame_unique, should_filter, selected_frame_marker);
17991800
if (num_frames == 1)
18001801
strm.IndentLess();
18011802
strm.IndentLess();
@@ -1893,9 +1894,11 @@ bool Thread::GetDescription(Stream &strm, lldb::DescriptionLevel level,
18931894

18941895
size_t Thread::GetStackFrameStatus(Stream &strm, uint32_t first_frame,
18951896
uint32_t num_frames, bool show_frame_info,
1896-
uint32_t num_frames_with_source) {
1897-
return GetStackFrameList()->GetStatus(
1898-
strm, first_frame, num_frames, show_frame_info, num_frames_with_source);
1897+
uint32_t num_frames_with_source,
1898+
bool should_filter) {
1899+
return GetStackFrameList()->GetStatus(strm, first_frame, num_frames,
1900+
show_frame_info, num_frames_with_source,
1901+
/*show_unique*/ false, should_filter);
18991902
}
19001903

19011904
Unwind &Thread::GetUnwinder() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CXX_SOURCES := main.cpp
2+
USE_LIBCPP := 1
3+
4+
include Makefile.rules

0 commit comments

Comments
 (0)