[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
Mon Aug 19 20:11:20 PDT 2024
https://github.com/adrian-prantl updated https://github.com/llvm/llvm-project/pull/104523
>From 8f6af48316f834ffe2e2ddd8d0a75df22955e9fb 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.
The functionality is user-extensible via Python recognizers and
exposed through the SBAPI via SBFrame::IsHidden().
rdar://126629381
---
lldb/bindings/python/python-wrapper.swig | 18 +++-
lldb/include/lldb/API/SBFrame.h | 4 +
.../lldb/Interpreter/ScriptInterpreter.h | 5 ++
lldb/include/lldb/Target/StackFrame.h | 33 ++++----
lldb/include/lldb/Target/StackFrameList.h | 2 +-
.../lldb/Target/StackFrameRecognizer.h | 19 +++--
lldb/include/lldb/Target/Thread.h | 5 +-
lldb/source/API/SBFrame.cpp | 13 +++
lldb/source/API/SBThread.cpp | 3 +-
lldb/source/Commands/CommandCompletions.cpp | 4 +-
lldb/source/Commands/CommandObjectFrame.cpp | 23 +++++
lldb/source/Commands/CommandObjectMemory.cpp | 3 +-
lldb/source/Commands/CommandObjectThread.cpp | 19 ++++-
lldb/source/Commands/Options.td | 2 +
lldb/source/Core/Debugger.cpp | 3 +-
.../source/Interpreter/CommandInterpreter.cpp | 9 +-
.../CPlusPlus/CPPLanguageRuntime.cpp | 44 +++++++++-
.../Python/SWIGPythonBridge.h | 3 +
.../Python/ScriptInterpreterPython.cpp | 29 +++++++
.../Python/ScriptInterpreterPythonImpl.h | 3 +
lldb/source/Target/Process.cpp | 7 +-
lldb/source/Target/StackFrame.cpp | 26 ++++--
lldb/source/Target/StackFrameList.cpp | 8 +-
lldb/source/Target/StackFrameRecognizer.cpp | 30 +++++--
lldb/source/Target/Thread.cpp | 13 +--
.../frame/recognizer/TestFrameRecognizer.py | 33 ++++++++
.../commands/frame/recognizer/recognizer.py | 5 ++
.../lang/cpp/std-function-recognizer/Makefile | 4 +
.../TestStdFunctionRecognizer.py | 84 +++++++++++++++++++
.../lang/cpp/std-function-recognizer/main.cpp | 10 +++
30 files changed, 401 insertions(+), 63 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/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 8f050643fa68b3..2ce42e3e017d5b 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -813,7 +813,7 @@ PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecogni
}
PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
- PyObject * implementor, const lldb::StackFrameSP &frame_sp) {
+ PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
static char callee_name[] = "get_recognized_arguments";
PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
@@ -824,6 +824,22 @@ PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArgument
return result;
}
+bool lldb_private::python::SWIGBridge::LLDBSwigPython_ShouldHide(
+ PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
+ static char callee_name[] = "should_hide";
+
+ PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
+
+ PythonString str(callee_name);
+
+ PyObject *result =
+ PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL);
+ bool ret_val = result ? PyObject_IsTrue(result) : false;
+ Py_XDECREF(result);
+
+ return result;
+}
+
void *lldb_private::python::SWIGBridge::LLDBSWIGPython_GetDynamicSetting(
void *module, const char *setting, const lldb::TargetSP &target_sp) {
if (!module || !setting)
diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h
index 821ff3cf7ce519..e0d15c3ecc5b1c 100644
--- a/lldb/include/lldb/API/SBFrame.h
+++ b/lldb/include/lldb/API/SBFrame.h
@@ -104,6 +104,10 @@ class LLDB_API SBFrame {
bool IsArtificial() const;
+ /// Return whether a frame recognizer decided this frame should not
+ /// be displayes in backtraces etc.
+ bool IsHidden() const;
+
/// The version that doesn't supply a 'use_dynamic' value will use the
/// target's default.
lldb::SBValue EvaluateExpression(const char *expr);
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 05f0d7f0955f3e..89a480a28880aa 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -252,6 +252,11 @@ class ScriptInterpreter : public PluginInterface {
return lldb::ValueObjectListSP();
}
+ virtual bool ShouldHide(const StructuredData::ObjectSP &implementor,
+ lldb::StackFrameSP frame_sp) {
+ return false;
+ }
+
virtual StructuredData::GenericSP
CreateScriptedBreakpointResolver(const char *class_name,
const StructuredDataImpl &args_data,
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index 52f0a1ee662176..1de582ac4e5fb7 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -407,6 +407,11 @@ class StackFrame : public ExecutionContextScope,
/// may have limited support for inspecting variables.
bool IsArtificial() const;
+ /// Query whether this frame should be hidden from backtraces. Frame
+ /// recognizers can customize this behavior and hide distracting
+ /// system implementation details this way.
+ bool IsHidden();
+
/// Query this frame to find what frame it is in this Thread's
/// StackFrameList.
///
@@ -518,33 +523,33 @@ class StackFrame : public ExecutionContextScope,
bool HasCachedData() const;
private:
- // For StackFrame only
+ /// For StackFrame only.
lldb::ThreadWP m_thread_wp;
uint32_t m_frame_index;
uint32_t m_concrete_frame_index;
lldb::RegisterContextSP m_reg_context_sp;
StackID m_id;
- Address m_frame_code_addr; // The frame code address (might not be the same as
- // the actual PC for inlined frames) as a
- // section/offset address
+ /// The frame code address (might not be the same as the actual PC
+ /// for inlined frames) as a section/offset address.
+ Address m_frame_code_addr;
SymbolContext m_sc;
Flags m_flags;
Scalar m_frame_base;
Status m_frame_base_error;
- bool m_cfa_is_valid; // Does this frame have a CFA? Different from CFA ==
- // LLDB_INVALID_ADDRESS
+ uint16_t m_frame_recognizer_generation;
+ /// Does this frame have a CFA? Different from CFA == LLDB_INVALID_ADDRESS.
+ bool m_cfa_is_valid;
Kind m_stack_frame_kind;
- // Whether this frame behaves like the zeroth frame, in the sense
- // that its pc value might not immediately follow a call (and thus might
- // be the first address of its function). True for actual frame zero as
- // well as any other frame with the same trait.
+ /// Whether this frame behaves like the zeroth frame, in the sense
+ /// that its pc value might not immediately follow a call (and thus might
+ /// be the first address of its function). True for actual frame zero as
+ /// well as any other frame with the same trait.
bool m_behaves_like_zeroth_frame;
lldb::VariableListSP m_variable_list_sp;
- ValueObjectList m_variable_list_value_objects; // Value objects for each
- // variable in
- // m_variable_list_sp
- lldb::RecognizedStackFrameSP m_recognized_frame_sp;
+ /// Value objects for each variable in m_variable_list_sp.
+ ValueObjectList m_variable_list_value_objects;
+ std::optional<lldb::RecognizedStackFrameSP> m_recognized_frame_sp;
StreamString m_disassembly;
std::recursive_mutex m_mutex;
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..b6af13ec32bae2 100644
--- a/lldb/include/lldb/Target/StackFrameRecognizer.h
+++ b/lldb/include/lldb/Target/StackFrameRecognizer.h
@@ -17,6 +17,7 @@
#include "lldb/lldb-private-forward.h"
#include "lldb/lldb-public.h"
+#include <_types/_uint16_t.h>
#include <deque>
#include <optional>
#include <vector>
@@ -28,20 +29,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 +57,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,10 +76,10 @@ 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;
+
std::string m_python_class;
public:
@@ -123,8 +126,12 @@ class StackFrameRecognizerManager {
lldb::StackFrameRecognizerSP GetRecognizerForFrame(lldb::StackFrameSP frame);
lldb::RecognizedStackFrameSP RecognizeFrame(lldb::StackFrameSP frame);
+ /// Quick way to determine whether the list of recognizers has been modified.
+ uint16_t GetGeneration() const { return m_generation; }
private:
+ void BumpGeneration();
+
struct RegisteredEntry {
uint32_t recognizer_id;
lldb::StackFrameRecognizerSP recognizer;
@@ -137,6 +144,7 @@ class StackFrameRecognizerManager {
};
std::deque<RegisteredEntry> m_recognizers;
+ uint16_t m_generation;
};
/// \class ValueObjectRecognizerSynthesizedValue
@@ -144,7 +152,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/SBFrame.cpp b/lldb/source/API/SBFrame.cpp
index 47fc88625e30c5..5e6a92bd2ed69a 100644
--- a/lldb/source/API/SBFrame.cpp
+++ b/lldb/source/API/SBFrame.cpp
@@ -1202,6 +1202,19 @@ bool SBFrame::IsArtificial() const {
return false;
}
+bool SBFrame::IsHidden() const {
+ LLDB_INSTRUMENT_VA(this);
+
+ std::unique_lock<std::recursive_mutex> lock;
+ ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
+
+ StackFrame *frame = exe_ctx.GetFramePtr();
+ if (frame)
+ return frame->IsHidden();
+
+ return false;
+}
+
const char *SBFrame::GetFunctionName() {
LLDB_INSTRUMENT_VA(this);
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index 786f62bd66d520..2691a24f746d00 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..e32337dd057dce 100644
--- a/lldb/source/Commands/CommandObjectFrame.cpp
+++ b/lldb/source/Commands/CommandObjectFrame.cpp
@@ -278,6 +278,29 @@ 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 (candidate_sp->IsHidden())
+ 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)
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..e0aef20214eec5 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -89,6 +89,9 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
"invalid boolean value for option '%c': %s", short_option,
option_arg.data());
} break;
+ case 'u':
+ m_filtered_backtrace = false;
+ break;
default:
llvm_unreachable("Unimplemented option");
}
@@ -99,6 +102,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 +113,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
uint32_t m_count;
uint32_t m_start;
bool m_extended_backtrace;
+ bool m_filtered_backtrace;
};
CommandObjectThreadBacktrace(CommandInterpreter &interpreter)
@@ -121,7 +126,10 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
"call stacks.\n"
"Use 'settings set frame-format' to customize the printing of "
"frames in the backtrace and 'settings set thread-format' to "
- "customize the thread header.",
+ "customize the thread header.\n"
+ "Customizable frame recognizers may filter out less interesting "
+ "frames, which results in gaps in the numbering. "
+ "Use '-u' to see all frames.",
nullptr,
eCommandRequiresProcess | eCommandRequiresThread |
eCommandTryTargetAPILock | eCommandProcessMustBeLaunched |
@@ -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..9c4dbed6939ba9 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1048,6 +1048,8 @@ 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_unfiltered : Option<"unfiltered", "u">, Group<1>,
+ 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/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index e45112530404b8..87298803e8415a 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -835,11 +835,12 @@ void CommandInterpreter::LoadCommandDictionary() {
std::unique_ptr<CommandObjectRegexCommand> bt_regex_cmd_up(
new CommandObjectRegexCommand(
*this, "_regexp-bt",
- "Show backtrace of the current thread's call stack. Any numeric "
- "argument displays at most that many frames. The argument 'all' "
- "displays all threads. Use 'settings set frame-format' to customize "
+ "Show backtrace of the current thread's call stack. Any numeric "
+ "argument displays at most that many frames. The argument 'all' "
+ "displays all threads. Use 'settings set frame-format' to customize "
"the printing of individual frames and 'settings set thread-format' "
- "to customize the thread header.",
+ "to customize the thread header. Frame recognizers may filter the"
+ "list. Use 'thread backtrace -u (--unfiltered)' to see them all.",
"bt [<digit> | all]", 0, false));
if (bt_regex_cmd_up) {
// accept but don't document "bt -c <number>" -- before bt was a regex
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<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/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 3026b6113ae8f3..5351c1a698b4a7 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -227,6 +227,9 @@ class SWIGBridge {
LLDBSwigPython_GetRecognizedArguments(PyObject *implementor,
const lldb::StackFrameSP &frame_sp);
+ static bool LLDBSwigPython_ShouldHide(PyObject *implementor,
+ const lldb::StackFrameSP &frame_sp);
+
static bool LLDBSWIGPythonRunScriptKeywordProcess(
const char *python_function_name, const char *session_dictionary_name,
const lldb::ProcessSP &process, std::string &output);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index a78c76b5f94ff7..3444e3b6d226be 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1527,6 +1527,35 @@ lldb::ValueObjectListSP ScriptInterpreterPythonImpl::GetRecognizedArguments(
return ValueObjectListSP();
}
+bool ScriptInterpreterPythonImpl::ShouldHide(
+ const StructuredData::ObjectSP &os_plugin_object_sp,
+ lldb::StackFrameSP frame_sp) {
+ Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock);
+
+ if (!os_plugin_object_sp)
+ return false;
+
+ StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric();
+ if (!generic)
+ return false;
+
+ PythonObject implementor(PyRefType::Borrowed,
+ (PyObject *)generic->GetValue());
+
+ if (!implementor.IsAllocated())
+ return false;
+
+ bool result =
+ SWIGBridge::LLDBSwigPython_ShouldHide(implementor.get(), frame_sp);
+
+ // if it fails, print the error but otherwise go on
+ if (PyErr_Occurred()) {
+ PyErr_Print();
+ PyErr_Clear();
+ }
+ return result;
+}
+
ScriptedProcessInterfaceUP
ScriptInterpreterPythonImpl::CreateScriptedProcessInterface() {
return std::make_unique<ScriptedProcessPythonInterface>(*this);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index c2024efb395d70..85d79955e45efc 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -107,6 +107,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
GetRecognizedArguments(const StructuredData::ObjectSP &implementor,
lldb::StackFrameSP frame_sp) override;
+ bool ShouldHide(const StructuredData::ObjectSP &implementor,
+ lldb::StackFrameSP frame_sp) override;
+
lldb::ScriptedProcessInterfaceUP CreateScriptedProcessInterface() override;
lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 3c9247fdbbbc96..cfc128ea44a469 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/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 3a2b4d05b28810..0ebaf555f86beb 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -1198,6 +1198,12 @@ bool StackFrame::IsArtificial() const {
return m_stack_frame_kind == StackFrame::Kind::Artificial;
}
+bool StackFrame::IsHidden() {
+ if (auto recognized_frame_sp = GetRecognizedFrame())
+ return recognized_frame_sp->ShouldHide();
+ return false;
+}
+
SourceLanguage StackFrame::GetLanguage() {
CompileUnit *cu = GetSymbolContext(eSymbolContextCompUnit).comp_unit;
if (cu)
@@ -1971,12 +1977,16 @@ bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source,
}
RecognizedStackFrameSP StackFrame::GetRecognizedFrame() {
- if (!m_recognized_frame_sp) {
- m_recognized_frame_sp = GetThread()
- ->GetProcess()
- ->GetTarget()
- .GetFrameRecognizerManager()
- .RecognizeFrame(CalculateStackFrame());
- }
- return m_recognized_frame_sp;
+ auto process = GetThread()->GetProcess();
+ if (!process)
+ return {};
+ // If recognizer list has been modified, discard cache.
+ auto &manager = process->GetTarget().GetFrameRecognizerManager();
+ auto new_generation = manager.GetGeneration();
+ if (m_frame_recognizer_generation != new_generation)
+ m_recognized_frame_sp.reset();
+ m_frame_recognizer_generation = new_generation;
+ if (!m_recognized_frame_sp.has_value())
+ m_recognized_frame_sp = manager.RecognizeFrame(CalculateStackFrame());
+ return m_recognized_frame_sp.value();
}
diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp
index 0cf9ce1bf043f5..87dbc5ebb7803d 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;
@@ -951,7 +951,6 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
unselected_marker = buffer.c_str();
}
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 +962,11 @@ 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 && frame_sp->IsHidden())
+ continue;
+
// Check for interruption here. If we're fetching arguments, this loop
// can go slowly:
Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger();
diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp
index 0ccb1ae9c031e3..df49178f71a3f2 100644
--- a/lldb/source/Target/StackFrameRecognizer.cpp
+++ b/lldb/source/Target/StackFrameRecognizer.cpp
@@ -17,10 +17,14 @@ using namespace lldb;
using namespace lldb_private;
class ScriptedRecognizedStackFrame : public RecognizedStackFrame {
+ bool m_hidden;
+
public:
- ScriptedRecognizedStackFrame(ValueObjectListSP args) {
- m_arguments = args;
+ ScriptedRecognizedStackFrame(ValueObjectListSP args, bool hidden)
+ : m_hidden(hidden) {
+ m_arguments = std::move(args);
}
+ bool ShouldHide() override { return m_hidden; }
};
ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer(
@@ -38,13 +42,21 @@ ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) {
ValueObjectListSP args =
m_interpreter->GetRecognizedArguments(m_python_object_sp, frame);
auto args_synthesized = ValueObjectListSP(new ValueObjectList());
- for (const auto &o : args->GetObjects()) {
- args_synthesized->Append(ValueObjectRecognizerSynthesizedValue::Create(
- *o, eValueTypeVariableArgument));
- }
+ if (args)
+ for (const auto &o : args->GetObjects())
+ args_synthesized->Append(ValueObjectRecognizerSynthesizedValue::Create(
+ *o, eValueTypeVariableArgument));
+
+ bool hidden = m_interpreter->ShouldHide(m_python_object_sp, frame);
return RecognizedStackFrameSP(
- new ScriptedRecognizedStackFrame(args_synthesized));
+ new ScriptedRecognizedStackFrame(args_synthesized, hidden));
+}
+
+void StackFrameRecognizerManager::BumpGeneration() {
+ uint32_t n = m_generation;
+ n = (n + 1) & ((1 << 16) - 1);
+ m_generation = n;
}
void StackFrameRecognizerManager::AddRecognizer(
@@ -53,6 +65,7 @@ void StackFrameRecognizerManager::AddRecognizer(
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, false,
module, RegularExpressionSP(), symbols,
RegularExpressionSP(), first_instruction_only});
+ BumpGeneration();
}
void StackFrameRecognizerManager::AddRecognizer(
@@ -61,6 +74,7 @@ void StackFrameRecognizerManager::AddRecognizer(
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, true,
ConstString(), module, std::vector<ConstString>(),
symbol, first_instruction_only});
+ BumpGeneration();
}
void StackFrameRecognizerManager::ForEach(
@@ -97,10 +111,12 @@ bool StackFrameRecognizerManager::RemoveRecognizerWithID(
if (found == m_recognizers.end())
return false;
m_recognizers.erase(found);
+ BumpGeneration();
return true;
}
void StackFrameRecognizerManager::RemoveAllRecognizers() {
+ BumpGeneration();
m_recognizers.clear();
}
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/commands/frame/recognizer/TestFrameRecognizer.py b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py
index eea0aafce6e25e..1f642e4fc6b128 100644
--- a/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py
+++ b/lldb/test/API/commands/frame/recognizer/TestFrameRecognizer.py
@@ -162,6 +162,39 @@ def test_frame_recognizer_1(self):
substrs=['*a = 78'])
"""
+ @skipUnlessDarwin
+ def test_frame_recognizer_hiding(self):
+ self.build()
+
+ target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "foo")
+ frame = thread.GetSelectedFrame()
+
+ # Sanity check.
+ self.expect("thread backtrace", patterns=["frame.*main"])
+
+ self.expect("frame recognizer clear")
+ self.expect(
+ "command script import "
+ + os.path.join(self.getSourceDir(), "recognizer.py")
+ )
+
+ self.expect(
+ "frame recognizer add -l recognizer.MainFrameRecognizer -f false -s a.out -n main"
+ )
+
+ self.expect(
+ "frame recognizer list",
+ substrs=["0: recognizer.MainFrameRecognizer"],
+ )
+
+ # Now main should be hidden.
+ self.expect("thread backtrace", substrs=["foo"])
+ self.expect("thread backtrace", matching=False, patterns=["frame.*main"])
+ self.assertFalse(frame.IsHidden())
+ frame = thread.SetSelectedFrame(1)
+ self.assertIn("main", frame.name)
+ self.assertTrue(frame.IsHidden())
+
@skipUnlessDarwin
def test_frame_recognizer_multi_symbol(self):
self.build()
diff --git a/lldb/test/API/commands/frame/recognizer/recognizer.py b/lldb/test/API/commands/frame/recognizer/recognizer.py
index 1a2a2d5c265070..bd3b7334b37a01 100644
--- a/lldb/test/API/commands/frame/recognizer/recognizer.py
+++ b/lldb/test/API/commands/frame/recognizer/recognizer.py
@@ -36,3 +36,8 @@ def get_recognized_arguments(self, frame):
class MyOtherFrameRecognizer(object):
def get_recognized_arguments(self, frame):
return []
+
+
+class MainFrameRecognizer(object):
+ def should_hide(self, frame):
+ return "main" in frame.name
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..30fe3ecb1e4bf4
--- /dev/null
+++ b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py
@@ -0,0 +1,84 @@
+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"],
+ )
+ self.expect(
+ "thread backtrace", matching=False, patterns=["frame.*std::__1::__function"]
+ )
+ # Unfiltered.
+ self.expect(
+ "thread backtrace -u",
+ ordered=True,
+ patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
+ )
+ self.expect(
+ "thread backtrace --unfiltered",
+ ordered=True,
+ patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
+ )
+
+ @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()
+ for i in range(1, 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")
+
+ @add_test_categories(["libc++"])
+ def test_api(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()
+ num_hidden = 0
+ for i in range(1, thread.GetNumFrames()):
+ thread.SetSelectedFrame(i)
+ frame = thread.GetSelectedFrame()
+ if frame.IsHidden():
+ num_hidden += 1
+
+ self.assertGreater(num_hidden, 0)
+ self.assertLess(num_hidden, thread.GetNumFrames())
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