Skip to content

Commit

Permalink
[lldb][FrameRecognizer] Display the first non-std frame on verbose_tr…
Browse files Browse the repository at this point in the history
…ap (llvm#108825)

This attempts to improve user-experience when LLDB stops on a
verbose_trap. Currently if a `__builtin_verbose_trap` triggers, we
display the first frame above the call to the verbose_trap. So in the
newly added test case, we would've previously stopped here:
```
(lldb) run
Process 28095 launched: '/Users/michaelbuch/a.out' (arm64)
Process 28095 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = Bounds error: out-of-bounds access
    frame #1: 0x0000000100003f5c a.out`std::__1::vector<int>::operator[](this=0x000000016fdfebef size=0, (null)=10) at verbose_trap.cpp:6:9
   3    template <typename T>
   4    struct vector {
   5        void operator[](unsigned) {
-> 6            __builtin_verbose_trap("Bounds error", "out-of-bounds access");
   7        }
   8    };
```

After this patch, we would stop in the first non-`std` frame:
```
(lldb) run
Process 27843 launched: '/Users/michaelbuch/a.out' (arm64)
Process 27843 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = Bounds error: out-of-bounds access
    frame #2: 0x0000000100003f44 a.out`g() at verbose_trap.cpp:14:5
   11  
   12   void g() {
   13       std::vector<int> v;
-> 14       v[10];
   15   }
   16  
```

rdar://134490328
  • Loading branch information
Michael137 committed Sep 19, 2024
1 parent 57777a5 commit bca5073
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 1 deletion.
35 changes: 34 additions & 1 deletion lldb/source/Target/VerboseTrapFrameRecognizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,39 @@ using namespace llvm;
using namespace lldb;
using namespace lldb_private;

/// The 0th frame is the artificial inline frame generated to store
/// the verbose_trap message. So, starting with the current parent frame,
/// find the first frame that's not inside of the STL.
static StackFrameSP FindMostRelevantFrame(Thread &selected_thread) {
// Defensive upper-bound of when we stop walking up the frames in
// case we somehow ended up looking at an infinite recursion.
const size_t max_stack_depth = 128;

// Start at parent frame.
size_t stack_idx = 1;
StackFrameSP most_relevant_frame_sp =
selected_thread.GetStackFrameAtIndex(stack_idx);

while (most_relevant_frame_sp && stack_idx <= max_stack_depth) {
auto const &sc =
most_relevant_frame_sp->GetSymbolContext(eSymbolContextEverything);
ConstString frame_name = sc.GetFunctionName();
if (!frame_name)
return nullptr;

// Found a frame outside of the `std` namespace. That's the
// first frame in user-code that ended up triggering the
// verbose_trap. Hence that's the one we want to display.
if (!frame_name.GetStringRef().starts_with("std::"))
return most_relevant_frame_sp;

++stack_idx;
most_relevant_frame_sp = selected_thread.GetStackFrameAtIndex(stack_idx);
}

return nullptr;
}

VerboseTrapRecognizedStackFrame::VerboseTrapRecognizedStackFrame(
StackFrameSP most_relevant_frame_sp, std::string stop_desc)
: m_most_relevant_frame(most_relevant_frame_sp) {
Expand All @@ -30,7 +63,7 @@ VerboseTrapFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) {
ThreadSP thread_sp = frame_sp->GetThread();
ProcessSP process_sp = thread_sp->GetProcess();

StackFrameSP most_relevant_frame_sp = thread_sp->GetStackFrameAtIndex(1);
StackFrameSP most_relevant_frame_sp = FindMostRelevantFrame(*thread_sp);

if (!most_relevant_frame_sp) {
Log *log = GetLog(LLDBLog::Unwind);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
void definitely_aborts() { __builtin_verbose_trap("User", "Invariant violated"); }

namespace std {
void aborts_soon() { definitely_aborts(); }
} // namespace std

void g() { std::aborts_soon(); }

namespace std {
namespace detail {
void eventually_aborts() { g(); }
} // namespace detail

inline namespace __1 {
void eventually_aborts() { detail::eventually_aborts(); }
} // namespace __1
} // namespace std

int main() {
std::eventually_aborts();
return 0;
}
22 changes: 22 additions & 0 deletions lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace std {
void definitely_aborts() { __builtin_verbose_trap("Failed", "Invariant violated"); }

void aborts_soon() { definitely_aborts(); }
} // namespace std

void g() { std::aborts_soon(); }

namespace std {
namespace detail {
void eventually_aborts() { g(); }
} // namespace detail

inline namespace __1 {
void eventually_aborts() { detail::eventually_aborts(); }
} // namespace __1
} // namespace std

int main() {
std::eventually_aborts();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace std {
void recursively_aborts(int depth) {
if (depth == 0)
__builtin_verbose_trap("Error", "max depth");

recursively_aborts(--depth);
}
} // namespace std

int main() {
std::recursively_aborts(256);
return 0;
}
21 changes: 21 additions & 0 deletions lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-nested.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace std {
namespace detail {
void function_that_aborts() { __builtin_verbose_trap("Bounds error", "out-of-bounds access"); }
} // namespace detail

inline namespace __1 {
template <typename T> struct vector {
void operator[](unsigned) { detail::function_that_aborts(); }
};
} // namespace __1
} // namespace std

void g() {
std::vector<int> v;
v[10];
}

int main() {
g();
return 0;
}
17 changes: 17 additions & 0 deletions lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace std {
inline namespace __1 {
template <typename T> struct vector {
void operator[](unsigned) { __builtin_verbose_trap("Bounds error", "out-of-bounds access"); }
};
} // namespace __1
} // namespace std

void g() {
std::vector<int> v;
v[10];
}

int main() {
g();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Tests that we show the first non-STL frame when
# a verbose_trap triggers from within the STL.
#
# Specifically tests that we correctly handle backtraces
# of the form:
# #0 __builtin_verbose_trap
# #1 user-code
# #2 STL
# #3 user-code
# #4 STL
# #5 user-code

# UNSUPPORTED: system-windows
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap-in-stl-callback-user-leaf.cpp -o %t.out
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK

run
# CHECK: thread #{{.*}}stop reason = User: Invariant violated
frame info
# CHECK: frame #{{.*}}`definitely_aborts() at verbose_trap-in-stl-callback-user-leaf.cpp
q
21 changes: 21 additions & 0 deletions lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Tests that we show the first non-STL frame when
# a verbose_trap triggers from within the STL.
#
# Specifically tests that we correctly handle backtraces
# of the form:
# #0 __builtin_verbose_trap
# #1 STL
# #2 user-code
# #3 STL
# #4 user-code

# UNSUPPORTED: system-windows
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap-in-stl-callback.cpp -o %t.out
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK

run
# CHECK: thread #{{.*}}stop reason = Failed: Invariant violated
frame info
# CHECK: frame #{{.*}}`g() at verbose_trap-in-stl-callback.cpp
q
16 changes: 16 additions & 0 deletions lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Tests that the VerboseTrapFrameRecognizer stops
# walking the stack once a certain implementation-defined
# threshold is reached.

# UNSUPPORTED: system-windows
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap-in-stl-max-depth.cpp -o %t.out
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK

run
# CHECK: thread #{{.*}}stop reason =
frame recognizer info 0
# CHECK: frame 0 is recognized by Verbose Trap StackFrame Recognizer
frame info
# CHECK: frame #0: {{.*}}`std::recursively_aborts(int) {{.*}} at verbose_trap-in-stl-max-depth.cpp
q
13 changes: 13 additions & 0 deletions lldb/test/Shell/Recognizer/verbose_trap-in-stl-nested.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Tests that we show the first non-STL frame when
# a verbose_trap triggers from within the STL.

# UNSUPPORTED: system-windows
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap-in-stl-nested.cpp -o %t.out
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK

run
# CHECK: thread #{{.*}}stop reason = Bounds error: out-of-bounds access
frame info
# CHECK: frame #{{.*}}`g() at verbose_trap-in-stl-nested.cpp
q
13 changes: 13 additions & 0 deletions lldb/test/Shell/Recognizer/verbose_trap-in-stl.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Tests that we show the first non-STL frame when
# a verbose_trap triggers from within the STL.

# UNSUPPORTED: system-windows
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap-in-stl.cpp -o %t.out
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK

run
# CHECK: thread #{{.*}}stop reason = Bounds error: out-of-bounds access
frame info
# CHECK: frame #{{.*}}`g() at verbose_trap-in-stl.cpp
q

0 comments on commit bca5073

Please sign in to comment.