Skip to content

Commit 81af101

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`, and when stepping out of the current frame. 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. The functionality is user-extensible via Python recognizers and exposed through the SBAPI via SBFrame::IsHidden(). rdar://126629381
1 parent 5fcd059 commit 81af101

32 files changed

+419
-74
lines changed

lldb/bindings/python/python-wrapper.swig

+17-1
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecogni
813813
}
814814

815815
PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
816-
PyObject * implementor, const lldb::StackFrameSP &frame_sp) {
816+
PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
817817
static char callee_name[] = "get_recognized_arguments";
818818

819819
PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
@@ -824,6 +824,22 @@ PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArgument
824824
return result;
825825
}
826826

827+
bool lldb_private::python::SWIGBridge::LLDBSwigPython_ShouldHide(
828+
PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
829+
static char callee_name[] = "should_hide";
830+
831+
PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
832+
833+
PythonString str(callee_name);
834+
835+
PyObject *result =
836+
PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL);
837+
bool ret_val = result ? PyObject_IsTrue(result) : false;
838+
Py_XDECREF(result);
839+
840+
return result;
841+
}
842+
827843
void *lldb_private::python::SWIGBridge::LLDBSWIGPython_GetDynamicSetting(
828844
void *module, const char *setting, const lldb::TargetSP &target_sp) {
829845
if (!module || !setting)

lldb/include/lldb/API/SBFrame.h

+4
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ class LLDB_API SBFrame {
104104

105105
bool IsArtificial() const;
106106

107+
/// Return whether a frame recognizer decided this frame should not
108+
/// be displayes in backtraces etc.
109+
bool IsHidden() const;
110+
107111
/// The version that doesn't supply a 'use_dynamic' value will use the
108112
/// target's default.
109113
lldb::SBValue EvaluateExpression(const char *expr);

lldb/include/lldb/Interpreter/ScriptInterpreter.h

+5
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@ class ScriptInterpreter : public PluginInterface {
252252
return lldb::ValueObjectListSP();
253253
}
254254

255+
virtual bool ShouldHide(const StructuredData::ObjectSP &implementor,
256+
lldb::StackFrameSP frame_sp) {
257+
return false;
258+
}
259+
255260
virtual StructuredData::GenericSP
256261
CreateScriptedBreakpointResolver(const char *class_name,
257262
const StructuredDataImpl &args_data,

lldb/include/lldb/Target/StackFrame.h

+19-14
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ class StackFrame : public ExecutionContextScope,
407407
/// may have limited support for inspecting variables.
408408
bool IsArtificial() const;
409409

410+
/// Query whether this frame should be hidden from backtraces. Frame
411+
/// recognizers can customize this behavior and hide distracting
412+
/// system implementation details this way.
413+
bool IsHidden();
414+
410415
/// Query this frame to find what frame it is in this Thread's
411416
/// StackFrameList.
412417
///
@@ -518,33 +523,33 @@ class StackFrame : public ExecutionContextScope,
518523
bool HasCachedData() const;
519524

520525
private:
521-
// For StackFrame only
526+
/// For StackFrame only.
522527
lldb::ThreadWP m_thread_wp;
523528
uint32_t m_frame_index;
524529
uint32_t m_concrete_frame_index;
525530
lldb::RegisterContextSP m_reg_context_sp;
526531
StackID m_id;
527-
Address m_frame_code_addr; // The frame code address (might not be the same as
528-
// the actual PC for inlined frames) as a
529-
// section/offset address
532+
/// The frame code address (might not be the same as the actual PC
533+
/// for inlined frames) as a section/offset address.
534+
Address m_frame_code_addr;
530535
SymbolContext m_sc;
531536
Flags m_flags;
532537
Scalar m_frame_base;
533538
Status m_frame_base_error;
534-
bool m_cfa_is_valid; // Does this frame have a CFA? Different from CFA ==
535-
// LLDB_INVALID_ADDRESS
539+
uint16_t m_frame_recognizer_generation;
540+
/// Does this frame have a CFA? Different from CFA == LLDB_INVALID_ADDRESS.
541+
bool m_cfa_is_valid;
536542
Kind m_stack_frame_kind;
537543

538-
// Whether this frame behaves like the zeroth frame, in the sense
539-
// that its pc value might not immediately follow a call (and thus might
540-
// be the first address of its function). True for actual frame zero as
541-
// well as any other frame with the same trait.
544+
/// Whether this frame behaves like the zeroth frame, in the sense
545+
/// that its pc value might not immediately follow a call (and thus might
546+
/// be the first address of its function). True for actual frame zero as
547+
/// well as any other frame with the same trait.
542548
bool m_behaves_like_zeroth_frame;
543549
lldb::VariableListSP m_variable_list_sp;
544-
ValueObjectList m_variable_list_value_objects; // Value objects for each
545-
// variable in
546-
// m_variable_list_sp
547-
lldb::RecognizedStackFrameSP m_recognized_frame_sp;
550+
/// Value objects for each variable in m_variable_list_sp.
551+
ValueObjectList m_variable_list_value_objects;
552+
std::optional<lldb::RecognizedStackFrameSP> m_recognized_frame_sp;
548553
StreamString m_disassembly;
549554
std::recursive_mutex m_mutex;
550555

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 show_hidden = false,
9595
const char *frame_marker = nullptr);
9696

9797
protected:

lldb/include/lldb/Target/StackFrameRecognizer.h

+14-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "lldb/lldb-private-forward.h"
1818
#include "lldb/lldb-public.h"
1919

20+
#include <_types/_uint16_t.h>
2021
#include <deque>
2122
#include <optional>
2223
#include <vector>
@@ -28,20 +29,23 @@ namespace lldb_private {
2829
/// This class provides extra information about a stack frame that was
2930
/// provided by a specific stack frame recognizer. Right now, this class only
3031
/// holds recognized arguments (via GetRecognizedArguments).
31-
3232
class RecognizedStackFrame
3333
: public std::enable_shared_from_this<RecognizedStackFrame> {
3434
public:
35+
virtual ~RecognizedStackFrame() = default;
36+
3537
virtual lldb::ValueObjectListSP GetRecognizedArguments() {
3638
return m_arguments;
3739
}
3840
virtual lldb::ValueObjectSP GetExceptionObject() {
3941
return lldb::ValueObjectSP();
4042
}
41-
virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; };
42-
virtual ~RecognizedStackFrame() = default;
43+
virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; }
4344

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

4650
protected:
4751
lldb::ValueObjectListSP m_arguments;
@@ -53,7 +57,6 @@ class RecognizedStackFrame
5357
/// A base class for frame recognizers. Subclasses (actual frame recognizers)
5458
/// should implement RecognizeFrame to provide a RecognizedStackFrame for a
5559
/// given stack frame.
56-
5760
class StackFrameRecognizer
5861
: public std::enable_shared_from_this<StackFrameRecognizer> {
5962
public:
@@ -73,10 +76,10 @@ class StackFrameRecognizer
7376
/// Python implementation for frame recognizers. An instance of this class
7477
/// tracks a particular Python classobject, which will be asked to recognize
7578
/// stack frames.
76-
7779
class ScriptedStackFrameRecognizer : public StackFrameRecognizer {
7880
lldb_private::ScriptInterpreter *m_interpreter;
7981
lldb_private::StructuredData::ObjectSP m_python_object_sp;
82+
8083
std::string m_python_class;
8184

8285
public:
@@ -123,8 +126,13 @@ class StackFrameRecognizerManager {
123126
lldb::StackFrameRecognizerSP GetRecognizerForFrame(lldb::StackFrameSP frame);
124127

125128
lldb::RecognizedStackFrameSP RecognizeFrame(lldb::StackFrameSP frame);
129+
/// This number changes whenever the list of recognizers has been modified.
130+
uint16_t GetGeneration() const { return m_generation; }
126131

127132
private:
133+
/// Increase the generation counter.
134+
void BumpGeneration();
135+
128136
struct RegisteredEntry {
129137
uint32_t recognizer_id;
130138
lldb::StackFrameRecognizerSP recognizer;
@@ -137,14 +145,14 @@ class StackFrameRecognizerManager {
137145
};
138146

139147
std::deque<RegisteredEntry> m_recognizers;
148+
uint16_t m_generation;
140149
};
141150

142151
/// \class ValueObjectRecognizerSynthesizedValue
143152
///
144153
/// ValueObject subclass that presents the passed ValueObject as a recognized
145154
/// value with the specified ValueType. Frame recognizers should return
146155
/// instances of this class as the returned objects in GetRecognizedArguments().
147-
148156
class ValueObjectRecognizerSynthesizedValue : public ValueObject {
149157
public:
150158
static lldb::ValueObjectSP Create(ValueObject &parent, lldb::ValueType type) {

lldb/include/lldb/Target/Thread.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -1128,11 +1128,11 @@ 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 show_hidden, 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, bool show_hidden);
11361136

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

lldb/source/API/SBFrame.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,19 @@ bool SBFrame::IsArtificial() const {
12021202
return false;
12031203
}
12041204

1205+
bool SBFrame::IsHidden() const {
1206+
LLDB_INSTRUMENT_VA(this);
1207+
1208+
std::unique_lock<std::recursive_mutex> lock;
1209+
ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
1210+
1211+
StackFrame *frame = exe_ctx.GetFramePtr();
1212+
if (frame)
1213+
return frame->IsHidden();
1214+
1215+
return false;
1216+
}
1217+
12051218
const char *SBFrame::GetFunctionName() {
12061219
LLDB_INSTRUMENT_VA(this);
12071220

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+
/*show_hidden*/ true);
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, /*show_hidden*/ true);
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, /*show_hidden*/ true);
839839
request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()),
840840
strm.GetString());
841841
}

lldb/source/Commands/CommandObjectFrame.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,29 @@ 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 (candidate_sp->IsHidden())
293+
continue;
294+
// Now candidate_idx is the first non-hidden frame.
295+
break;
296+
}
297+
candidate_idx = UINT32_MAX;
298+
break;
299+
};
300+
if (candidate_idx != UINT32_MAX)
301+
m_options.relative_frame_offset = candidate_idx - frame_idx;
302+
}
303+
281304
if (*m_options.relative_frame_offset < 0) {
282305
if (static_cast<int32_t>(frame_idx) >=
283306
-*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

+15-4
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
8989
"invalid boolean value for option '%c': %s", short_option,
9090
option_arg.data());
9191
} break;
92+
case 'u':
93+
m_filtered_backtrace = false;
94+
break;
9295
default:
9396
llvm_unreachable("Unimplemented option");
9497
}
@@ -99,6 +102,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
99102
m_count = UINT32_MAX;
100103
m_start = 0;
101104
m_extended_backtrace = false;
105+
m_filtered_backtrace = true;
102106
}
103107

104108
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -109,6 +113,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
109113
uint32_t m_count;
110114
uint32_t m_start;
111115
bool m_extended_backtrace;
116+
bool m_filtered_backtrace;
112117
};
113118

114119
CommandObjectThreadBacktrace(CommandInterpreter &interpreter)
@@ -121,7 +126,10 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
121126
"call stacks.\n"
122127
"Use 'settings set frame-format' to customize the printing of "
123128
"frames in the backtrace and 'settings set thread-format' to "
124-
"customize the thread header.",
129+
"customize the thread header.\n"
130+
"Customizable frame recognizers may filter out less interesting "
131+
"frames, which results in gaps in the numbering. "
132+
"Use '-u' to see all frames.",
125133
nullptr,
126134
eCommandRequiresProcess | eCommandRequiresThread |
127135
eCommandTryTargetAPILock | eCommandProcessMustBeLaunched |
@@ -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

+2
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,8 @@ 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_unfiltered : Option<"unfiltered", "u">, Group<1>,
1052+
Desc<"Filter out frames according to installed frame recognizers">;
10511053
}
10521054

10531055
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+
/*show_hidden*/ true);
18731874
}
18741875
}
18751876
}

0 commit comments

Comments
 (0)