[Lldb-commits] [lldb] [lldb] Extend frame recognizers to hide frames from backtraces (PR #104523)

Adrian Prantl via lldb-commits lldb-commits at lists.llvm.org
Thu Aug 15 16:38:29 PDT 2024


https://github.com/adrian-prantl created https://github.com/llvm/llvm-project/pull/104523

Compilers and language runtimes often use helper functions that are fundamentally uninteresting when debugging anything but the compiler/runtime itself. This patch introduces a user-extensible mechanism that allows for these frames to be hidden from backtraces and automatically skipped over when navigating the stack with `up` and `down`.

This does not affect the numbering of frames, so `f <N>` will still provide access to the hidden frames. The `bt` output will also print a hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift programming language, but I'm including an example recognizer for `std::function::operator()` that I wished for myself many times while debugging LLDB.

rdar://126629381


Example output. (Yes, my proof-of-concept recognizer could hide even more frames if we had a method that returned the function name without the return type or I used something that isn't based off regex, but it's really only meant as an example).

before:
```
(lldb) thread backtrace --filtered=false
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame #4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame #5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
(lldb) 
```

after

```
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers
```


>From 6289b797e6699c356c03caf32a4b74a5e2ee3278 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 15 Aug 2024 16:18:33 -0700
Subject: [PATCH] [lldb] Extend frame recognizers to hide frames from
 backtraces

Compilers and language runtimes often use helper functions that are
fundamentally uninteresting when debugging anything but the
compiler/runtime itself. This patch introduces a user-extensible
mechanism that allows for these frames to be hidden from backtraces
and automatically skipped over when navigating the stack with `up` and
`down`.

This does not affect the numbering of frames, so `f <N>` will still
provide access to the hidden frames. The `bt` output will also print a
hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift
programming language, but I'm including an example recognizer for
`std::function::operator()` that I wished for myself many times while
debugging LLDB.

rdar://126629381
---
 lldb/include/lldb/Target/StackFrameList.h     |  2 +-
 .../lldb/Target/StackFrameRecognizer.h        | 12 ++--
 lldb/include/lldb/Target/Thread.h             |  5 +-
 lldb/source/API/SBThread.cpp                  |  3 +-
 lldb/source/Commands/CommandCompletions.cpp   |  4 +-
 lldb/source/Commands/CommandObjectFrame.cpp   | 28 +++++++-
 lldb/source/Commands/CommandObjectMemory.cpp  |  3 +-
 lldb/source/Commands/CommandObjectThread.cpp  | 25 +++++--
 lldb/source/Commands/Options.td               |  3 +
 lldb/source/Core/Debugger.cpp                 |  3 +-
 .../CPlusPlus/CPPLanguageRuntime.cpp          | 47 +++++++++++-
 lldb/source/Target/Process.cpp                |  7 +-
 lldb/source/Target/StackFrameList.cpp         | 15 +++-
 lldb/source/Target/Thread.cpp                 | 13 ++--
 .../lang/cpp/std-function-recognizer/Makefile |  4 ++
 .../TestStdFunctionRecognizer.py              | 72 +++++++++++++++++++
 .../lang/cpp/std-function-recognizer/main.cpp | 10 +++
 17 files changed, 222 insertions(+), 34 deletions(-)
 create mode 100644 lldb/test/API/lang/cpp/std-function-recognizer/Makefile
 create mode 100644 lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py
 create mode 100644 lldb/test/API/lang/cpp/std-function-recognizer/main.cpp

diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h
index 88e211ff692bd9..5cdca97e910613 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -91,7 +91,7 @@ class StackFrameList {
 
   size_t GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames,
                    bool show_frame_info, uint32_t num_frames_with_source,
-                   bool show_unique = false,
+                   bool show_unique = false, bool should_filter = true,
                    const char *frame_marker = nullptr);
 
 protected:
diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h
index e9ac2750192ef6..dae1c6f4415718 100644
--- a/lldb/include/lldb/Target/StackFrameRecognizer.h
+++ b/lldb/include/lldb/Target/StackFrameRecognizer.h
@@ -28,20 +28,23 @@ namespace lldb_private {
 /// This class provides extra information about a stack frame that was
 /// provided by a specific stack frame recognizer. Right now, this class only
 /// holds recognized arguments (via GetRecognizedArguments).
-
 class RecognizedStackFrame
     : public std::enable_shared_from_this<RecognizedStackFrame> {
 public:
+  virtual ~RecognizedStackFrame() = default;
+
   virtual lldb::ValueObjectListSP GetRecognizedArguments() {
     return m_arguments;
   }
   virtual lldb::ValueObjectSP GetExceptionObject() {
     return lldb::ValueObjectSP();
   }
-  virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; };
-  virtual ~RecognizedStackFrame() = default;
+  virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; }
 
   std::string GetStopDescription() { return m_stop_desc; }
+  /// Controls whether this frame should be filtered out when
+  /// displaying backtraces, for example.
+  virtual bool ShouldHide() { return false; }
 
 protected:
   lldb::ValueObjectListSP m_arguments;
@@ -53,7 +56,6 @@ class RecognizedStackFrame
 /// A base class for frame recognizers. Subclasses (actual frame recognizers)
 /// should implement RecognizeFrame to provide a RecognizedStackFrame for a
 /// given stack frame.
-
 class StackFrameRecognizer
     : public std::enable_shared_from_this<StackFrameRecognizer> {
 public:
@@ -73,7 +75,6 @@ class StackFrameRecognizer
 /// Python implementation for frame recognizers. An instance of this class
 /// tracks a particular Python classobject, which will be asked to recognize
 /// stack frames.
-
 class ScriptedStackFrameRecognizer : public StackFrameRecognizer {
   lldb_private::ScriptInterpreter *m_interpreter;
   lldb_private::StructuredData::ObjectSP m_python_object_sp;
@@ -144,7 +145,6 @@ class StackFrameRecognizerManager {
 /// ValueObject subclass that presents the passed ValueObject as a recognized
 /// value with the specified ValueType. Frame recognizers should return
 /// instances of this class as the returned objects in GetRecognizedArguments().
-
 class ValueObjectRecognizerSynthesizedValue : public ValueObject {
  public:
   static lldb::ValueObjectSP Create(ValueObject &parent, lldb::ValueType type) {
diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h
index aacc59c292ec79..9de58513b160b4 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -1128,11 +1128,12 @@ class Thread : public std::enable_shared_from_this<Thread>,
 
   size_t GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames,
                    uint32_t num_frames_with_source, bool stop_format,
-                   bool only_stacks = false);
+                   bool should_filter, bool only_stacks = false);
 
   size_t GetStackFrameStatus(Stream &strm, uint32_t first_frame,
                              uint32_t num_frames, bool show_frame_info,
-                             uint32_t num_frames_with_source);
+                             uint32_t num_frames_with_source,
+                             bool should_filter);
 
   // We need a way to verify that even though we have a thread in a shared
   // pointer that the object itself is still valid. Currently this won't be the
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index bda981041064ff..7a3d6ff9336159 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -1208,7 +1208,8 @@ bool SBThread::GetStatus(SBStream &status) const {
   ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
 
   if (exe_ctx.HasThreadScope()) {
-    exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true);
+    exe_ctx.GetThreadPtr()->GetStatus(strm, 0, 1, 1, true,
+                                      /*should_filter*/ false);
   } else
     strm.PutCString("No status");
 
diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp
index 54f4b368166492..ea647bbfe4c026 100644
--- a/lldb/source/Commands/CommandCompletions.cpp
+++ b/lldb/source/Commands/CommandCompletions.cpp
@@ -791,7 +791,7 @@ void CommandCompletions::ThreadIndexes(CommandInterpreter &interpreter,
   lldb::ThreadSP thread_sp;
   for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
     StreamString strm;
-    thread_sp->GetStatus(strm, 0, 1, 1, true);
+    thread_sp->GetStatus(strm, 0, 1, 1, true, /*should_filter*/ false);
     request.TryCompleteCurrentArg(std::to_string(thread_sp->GetIndexID()),
                                   strm.GetString());
   }
@@ -835,7 +835,7 @@ void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter,
   lldb::ThreadSP thread_sp;
   for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
     StreamString strm;
-    thread_sp->GetStatus(strm, 0, 1, 1, true);
+    thread_sp->GetStatus(strm, 0, 1, 1, true, /*should_filter*/ false);
     request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()),
                                   strm.GetString());
   }
diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp
index 29e460fe3885ff..b93ffe27bb2f29 100644
--- a/lldb/source/Commands/CommandObjectFrame.cpp
+++ b/lldb/source/Commands/CommandObjectFrame.cpp
@@ -278,6 +278,30 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
       if (frame_idx == UINT32_MAX)
         frame_idx = 0;
 
+      // If moving up/down by one, skip over hidden frames.
+      if (*m_options.relative_frame_offset == 1 ||
+          *m_options.relative_frame_offset == -1) {
+        uint32_t candidate_idx = frame_idx;
+        for (unsigned num_try = 0; num_try < 12; ++num_try) {
+          if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) {
+            candidate_idx = UINT32_MAX;
+            break;
+          }
+          candidate_idx += *m_options.relative_frame_offset;
+          if (auto candidate_sp = thread->GetStackFrameAtIndex(candidate_idx)) {
+            if (auto recognized_frame_sp = candidate_sp->GetRecognizedFrame())
+              if (recognized_frame_sp->ShouldHide())
+                continue;
+            // Now candidate_idx is the first non-hidden frame.
+            break;
+          }
+          candidate_idx = UINT32_MAX;
+          break;
+        };
+        if (candidate_idx != UINT32_MAX)
+          m_options.relative_frame_offset = candidate_idx - frame_idx;
+      }
+
       if (*m_options.relative_frame_offset < 0) {
         if (static_cast<int32_t>(frame_idx) >=
             -*m_options.relative_frame_offset)
@@ -318,7 +342,7 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
           }
         }
       }
-    } else {
+      } else {
       if (command.GetArgumentCount() > 1) {
         result.AppendErrorWithFormat(
             "too many arguments; expected frame-index, saw '%s'.\n",
@@ -341,7 +365,7 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
           frame_idx = 0;
         }
       }
-    }
+      }
 
     bool success = thread->SetSelectedFrameByIndexNoisily(
         frame_idx, result.GetOutputStream());
diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp
index 137b1ad981073c..baf5d9196e553e 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1570,7 +1570,8 @@ class CommandObjectMemoryHistory : public CommandObjectParsed {
 
     const bool stop_format = false;
     for (auto thread : thread_list) {
-      thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format);
+      thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format,
+                        /*should_filter*/ false);
     }
 
     result.SetStatus(eReturnStatusSuccessFinishResult);
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index 605f872a9f45e1..d58c8533f90f7b 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -80,14 +80,20 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
               "invalid integer value for option '%c': %s", short_option,
               option_arg.data());
         break;
-      case 'e': {
+      case 'e':
+      case 'f': {
         bool success;
-        m_extended_backtrace =
-            OptionArgParser::ToBoolean(option_arg, false, &success);
-        if (!success)
+        bool value = OptionArgParser::ToBoolean(option_arg, false, &success);
+        if (!success) {
           error.SetErrorStringWithFormat(
               "invalid boolean value for option '%c': %s", short_option,
               option_arg.data());
+          break;
+        }
+        if (short_option == 'e')
+          m_extended_backtrace = value;
+        else
+          m_filtered_backtrace = value;
       } break;
       default:
         llvm_unreachable("Unimplemented option");
@@ -99,6 +105,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
       m_count = UINT32_MAX;
       m_start = 0;
       m_extended_backtrace = false;
+      m_filtered_backtrace = true;
     }
 
     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -109,6 +116,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
     uint32_t m_count;
     uint32_t m_start;
     bool m_extended_backtrace;
+    bool m_filtered_backtrace;
   };
 
   CommandObjectThreadBacktrace(CommandInterpreter &interpreter)
@@ -199,7 +207,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
           strm.PutChar('\n');
           if (ext_thread_sp->GetStatus(strm, m_options.m_start,
                                        m_options.m_count,
-                                       num_frames_with_source, stop_format)) {
+                                       num_frames_with_source, stop_format,
+                                       m_options.m_filtered_backtrace)) {
             DoExtendedBacktrace(ext_thread_sp.get(), result);
           }
         }
@@ -228,7 +237,8 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
     const uint32_t num_frames_with_source = 0;
     const bool stop_format = true;
     if (!thread->GetStatus(strm, m_options.m_start, m_options.m_count,
-                           num_frames_with_source, stop_format, only_stacks)) {
+                           num_frames_with_source, stop_format,
+                           m_options.m_filtered_backtrace, only_stacks)) {
       result.AppendErrorWithFormat(
           "error displaying backtrace for thread: \"0x%4.4x\"\n",
           thread->GetIndexID());
@@ -1392,7 +1402,8 @@ class CommandObjectThreadException : public CommandObjectIterateOverThreads {
       const uint32_t num_frames_with_source = 0;
       const bool stop_format = false;
       exception_thread_sp->GetStatus(strm, 0, UINT32_MAX,
-                                     num_frames_with_source, stop_format);
+                                     num_frames_with_source, stop_format,
+                                     /*filtered*/ false);
     }
 
     return true;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index f050cd2ebb5ae0..a59c3c18918115 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1048,6 +1048,9 @@ let Command = "thread backtrace" in {
   Arg<"FrameIndex">, Desc<"Frame in which to start the backtrace">;
   def thread_backtrace_extended : Option<"extended", "e">, Group<1>,
   Arg<"Boolean">, Desc<"Show the extended backtrace, if available">;
+  def thread_backtrace_full : Option<"filtered", "f">, Group<1>,
+  Arg<"Boolean">,
+  Desc<"Filter out frames according to installed frame recognizers">;
 }
 
 let Command = "thread step scope" in {
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 309e01e456580c..d9f1fb882fb105 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -1869,7 +1869,8 @@ void Debugger::HandleThreadEvent(const EventSP &event_sp) {
     ThreadSP thread_sp(
         Thread::ThreadEventData::GetThreadFromEvent(event_sp.get()));
     if (thread_sp) {
-      thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format);
+      thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format,
+                           /*should_filter*/ false);
     }
   }
 }
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index c7202a47d0157e..209b971b2f2ade 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -26,6 +26,7 @@
 #include "lldb/Target/RegisterContext.h"
 #include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/StackFrame.h"
+#include "lldb/Target/StackFrameRecognizer.h"
 #include "lldb/Target/ThreadPlanRunToAddress.h"
 #include "lldb/Target/ThreadPlanStepInRange.h"
 #include "lldb/Utility/Timer.h"
@@ -40,8 +41,52 @@ static ConstString g_coro_frame = ConstString("__coro_frame");
 
 char CPPLanguageRuntime::ID = 0;
 
+/// A frame recognizer that is isntalled to hide libc++ implementation
+/// details from the backtrace.
+class LibCXXFrameRecognizer : public StackFrameRecognizer {
+  RegularExpression m_hidden_function_regex;
+  RecognizedStackFrameSP m_hidden_frame;
+
+  struct LibCXXHiddenFrame : public RecognizedStackFrame {
+    bool ShouldHide() override { return true; }
+  };
+
+public:
+  LibCXXFrameRecognizer()
+      : m_hidden_function_regex(
+            R"(^std::__1::(__function.*::operator\(\)|__invoke))"
+            R"((\[.*\])?)"    // ABI tag.
+            R"(( const)?$)"), // const.
+        m_hidden_frame(new LibCXXHiddenFrame()) {}
+
+  std::string GetName() override {
+    return "libc++ frame recognizer";
+  }
+
+  lldb::RecognizedStackFrameSP
+  RecognizeFrame(lldb::StackFrameSP frame_sp) override {
+    if (!frame_sp)
+      return {};
+    auto &sc =
+        frame_sp->GetSymbolContext(lldb::eSymbolContextFunction);
+    if (!sc.function)
+      return {};
+
+    if (m_hidden_function_regex.Execute(sc.function->GetNameNoArguments()))
+      return m_hidden_frame;
+
+    return {};
+  }
+};
+
 CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
-    : LanguageRuntime(process) {}
+    : LanguageRuntime(process) {
+  if (process)
+        process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
+            StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {},
+            std::make_shared<RegularExpression>("^std::__1::"),
+            /*first_instruction_only*/ false);
+}
 
 bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) {
   return name == g_this || name == g_promise || name == g_coro_frame;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index e3c4f2ee398cc4..d7efa8267436fd 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -5545,7 +5545,8 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
       // Print a backtrace into the log so we can figure out where we are:
       StreamString s;
       s.PutCString("Thread state after unsuccessful completion: \n");
-      thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX);
+      thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX,
+                                  /*should_filter*/ false);
       log->PutString(s.GetString());
     }
     // Restore the thread state if we are going to discard the plan execution.
@@ -5819,8 +5820,8 @@ size_t Process::GetThreadStatus(Stream &strm,
           continue;
       }
       thread_sp->GetStatus(strm, start_frame, num_frames,
-                           num_frames_with_source,
-                           stop_format);
+                           num_frames_with_source, stop_format,
+                           /*should_filter*/ num_frames > 1);
       ++num_thread_infos_dumped;
     } else {
       Log *log = GetLog(LLDBLog::Process);
diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp
index 0cf9ce1bf043f5..2f284d32ce77ac 100644
--- a/lldb/source/Target/StackFrameList.cpp
+++ b/lldb/source/Target/StackFrameList.cpp
@@ -924,7 +924,7 @@ StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) {
 size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
                                  uint32_t num_frames, bool show_frame_info,
                                  uint32_t num_frames_with_source,
-                                 bool show_unique,
+                                 bool show_unique, bool should_filter,
                                  const char *selected_frame_marker) {
   size_t num_frames_displayed = 0;
 
@@ -950,8 +950,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
     buffer.insert(buffer.begin(), len, ' ');
     unselected_marker = buffer.c_str();
   }
+  bool filtered = false;
   const char *marker = nullptr;
-
   for (frame_idx = first_frame; frame_idx < last_frame; ++frame_idx) {
     frame_sp = GetFrameAtIndex(frame_idx);
     if (!frame_sp)
@@ -963,6 +963,15 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
       else
         marker = unselected_marker;
     }
+
+    // Hide uninteresting frames unless it's the selected frame.
+    if (should_filter && frame_sp != selected_frame_sp)
+      if (auto recognized_frame_sp = frame_sp->GetRecognizedFrame())
+        if (recognized_frame_sp->ShouldHide()) {
+          filtered = true;
+          continue;
+        }
+
     // Check for interruption here.  If we're fetching arguments, this loop
     // can go slowly:
     Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger();
@@ -979,6 +988,8 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
     ++num_frames_displayed;
   }
 
+  if (filtered)
+    strm << "Note: Some frames were hidden by frame recognizers\n";
   strm.IndentLess();
   return num_frames_displayed;
 }
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index 74d1a268c6dffb..bedb89c72dd433 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -1748,7 +1748,8 @@ std::string Thread::RunModeAsString(lldb::RunMode mode) {
 
 size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
                          uint32_t num_frames, uint32_t num_frames_with_source,
-                         bool stop_format, bool only_stacks) {
+                         bool stop_format, bool should_filter,
+                         bool only_stacks) {
 
   if (!only_stacks) {
     ExecutionContext exe_ctx(shared_from_this());
@@ -1795,7 +1796,7 @@ size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
 
     num_frames_shown = GetStackFrameList()->GetStatus(
         strm, start_frame, num_frames, show_frame_info, num_frames_with_source,
-        show_frame_unique, selected_frame_marker);
+        show_frame_unique, should_filter, selected_frame_marker);
     if (num_frames == 1)
       strm.IndentLess();
     strm.IndentLess();
@@ -1893,9 +1894,11 @@ bool Thread::GetDescription(Stream &strm, lldb::DescriptionLevel level,
 
 size_t Thread::GetStackFrameStatus(Stream &strm, uint32_t first_frame,
                                    uint32_t num_frames, bool show_frame_info,
-                                   uint32_t num_frames_with_source) {
-  return GetStackFrameList()->GetStatus(
-      strm, first_frame, num_frames, show_frame_info, num_frames_with_source);
+                                   uint32_t num_frames_with_source,
+                                   bool should_filter) {
+  return GetStackFrameList()->GetStatus(strm, first_frame, num_frames,
+                                        show_frame_info, num_frames_with_source,
+                                        /*show_unique*/ false, should_filter);
 }
 
 Unwind &Thread::GetUnwinder() {
diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/Makefile b/lldb/test/API/lang/cpp/std-function-recognizer/Makefile
new file mode 100644
index 00000000000000..ab034edd121f9f
--- /dev/null
+++ b/lldb/test/API/lang/cpp/std-function-recognizer/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+USE_LIBCPP := 1
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py
new file mode 100644
index 00000000000000..0ff244c605eb26
--- /dev/null
+++ b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py
@@ -0,0 +1,72 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class LibCxxStdFunctionRecognizerTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @add_test_categories(["libc++"])
+    def test_backtrace(self):
+        """Test that std::function implementation details are hidden in bt"""
+        self.build()
+        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp")
+        )
+        # Filtered.
+        self.expect(
+            "thread backtrace",
+            ordered=True,
+            substrs=["frame", "foo", "frame", "main", "frames", "hidden"],
+        )
+        self.expect(
+            "thread backtrace", matching=False, patterns=["frame.*std::__1::__function"]
+        )
+        # Unfiltered.
+        self.expect(
+            "thread backtrace -f false",
+            ordered=True,
+            patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
+        )
+        self.expect(
+            "thread backtrace --filtered false",
+            ordered=True,
+            patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
+        )
+        self.expect(
+            "thread backtrace --filtered false",
+            matching=False,
+            patterns=["frames.*hidden"],
+        )
+        
+    @add_test_categories(["libc++"])
+    def test_up_down(self):
+        """Test that std::function implementation details are skipped"""
+        self.build()
+        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp")
+        )
+        frame = thread.GetSelectedFrame()
+        # up
+        self.assertIn("foo", frame.GetFunctionName())
+        start_idx = frame.GetFrameID()
+        i = 0
+        while i < thread.GetNumFrames():
+            self.expect("up")
+            frame = thread.GetSelectedFrame()
+            if frame.GetFunctionName() == "main":
+                break
+        end_idx = frame.GetFrameID()
+        self.assertLess(i, end_idx - start_idx, "skipped frames")
+
+        # Back down again.
+        start_idx = frame.GetFrameID()
+        i = 0
+        while i < thread.GetNumFrames():
+            self.expect("down")
+            frame = thread.GetSelectedFrame()
+            if "foo" in frame.GetFunctionName():
+                break
+        end_idx = frame.GetFrameID()
+        self.assertLess(i, start_idx - end_idx, "skipped frames")
diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/main.cpp b/lldb/test/API/lang/cpp/std-function-recognizer/main.cpp
new file mode 100644
index 00000000000000..8cf4eaa2e51929
--- /dev/null
+++ b/lldb/test/API/lang/cpp/std-function-recognizer/main.cpp
@@ -0,0 +1,10 @@
+#include <functional>
+
+int foo(int x, int y) {
+  return x * y; // break here
+}
+
+int main(int argc, char *argv[]) {
+  std::function<int(int, int)> fn = foo;
+  return fn(argc, 1);
+}



More information about the lldb-commits mailing list