Skip to content

Commit f01f80c

Browse files
[lldb] Extend frame recognizers to hide frames from backtraces (llvm#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 ```
1 parent 1e9d002 commit f01f80c

32 files changed

+424
-75
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

+22-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,36 @@ class StackFrame : public ExecutionContextScope,
518523
bool HasCachedData() const;
519524

520525
private:
521-
// For StackFrame only
526+
/// For StackFrame only.
527+
/// \{
522528
lldb::ThreadWP m_thread_wp;
523529
uint32_t m_frame_index;
524530
uint32_t m_concrete_frame_index;
525531
lldb::RegisterContextSP m_reg_context_sp;
526532
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
533+
/// \}
534+
535+
/// The frame code address (might not be the same as the actual PC
536+
/// for inlined frames) as a section/offset address.
537+
Address m_frame_code_addr;
530538
SymbolContext m_sc;
531539
Flags m_flags;
532540
Scalar m_frame_base;
533541
Status m_frame_base_error;
534-
bool m_cfa_is_valid; // Does this frame have a CFA? Different from CFA ==
535-
// LLDB_INVALID_ADDRESS
542+
uint16_t m_frame_recognizer_generation;
543+
/// Does this frame have a CFA? Different from CFA == LLDB_INVALID_ADDRESS.
544+
bool m_cfa_is_valid;
536545
Kind m_stack_frame_kind;
537546

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.
547+
/// Whether this frame behaves like the zeroth frame, in the sense
548+
/// that its pc value might not immediately follow a call (and thus might
549+
/// be the first address of its function). True for actual frame zero as
550+
/// well as any other frame with the same trait.
542551
bool m_behaves_like_zeroth_frame;
543552
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;
553+
/// Value objects for each variable in m_variable_list_sp.
554+
ValueObjectList m_variable_list_value_objects;
555+
std::optional<lldb::RecognizedStackFrameSP> m_recognized_frame_sp;
548556
StreamString m_disassembly;
549557
std::recursive_mutex m_mutex;
550558

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

+15-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 <cstdint>
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,14 @@ class StackFrameRecognizerManager {
123126
lldb::StackFrameRecognizerSP GetRecognizerForFrame(lldb::StackFrameSP frame);
124127

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

127133
private:
134+
/// Increase the generation counter.
135+
void BumpGeneration();
136+
128137
struct RegisteredEntry {
129138
uint32_t recognizer_id;
130139
lldb::StackFrameRecognizerSP recognizer;
@@ -137,14 +146,14 @@ class StackFrameRecognizerManager {
137146
};
138147

139148
std::deque<RegisteredEntry> m_recognizers;
149+
uint16_t m_generation;
140150
};
141151

142152
/// \class ValueObjectRecognizerSynthesizedValue
143153
///
144154
/// ValueObject subclass that presents the passed ValueObject as a recognized
145155
/// value with the specified ValueType. Frame recognizers should return
146156
/// instances of this class as the returned objects in GetRecognizedArguments().
147-
148157
class ValueObjectRecognizerSynthesizedValue : public ValueObject {
149158
public:
150159
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-2
Original file line numberDiff line numberDiff line change
@@ -1195,13 +1195,24 @@ bool SBFrame::IsArtificial() const {
11951195
std::unique_lock<std::recursive_mutex> lock;
11961196
ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
11971197

1198-
StackFrame *frame = exe_ctx.GetFramePtr();
1199-
if (frame)
1198+
if (StackFrame *frame = exe_ctx.GetFramePtr())
12001199
return frame->IsArtificial();
12011200

12021201
return false;
12031202
}
12041203

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

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

+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+
const unsigned max_depth = 12;
286+
for (unsigned num_try = 0; num_try < max_depth; ++num_try) {
287+
if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) {
288+
candidate_idx = UINT32_MAX;
289+
break;
290+
}
291+
candidate_idx += *m_options.relative_frame_offset;
292+
if (auto candidate_sp = thread->GetStackFrameAtIndex(candidate_idx)) {
293+
if (candidate_sp->IsHidden())
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

+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 {

0 commit comments

Comments
 (0)