Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb][FrameRecognizer] Display the first non-std frame on verbose_trap #108825

Merged
merged 3 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
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;
}
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a good reason why this isn't an API test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it much easier to understand what the test is trying to check when it's done as a Shell test. Especially for when we just want to inspect the format of the frame. (FWIW, all the other frame format tests are also Shell tests).

From the Testing FAQ:

API tests: Integration tests that interact with the debugger through the SB API. These are written in Python and use LLDB’s dotest.py testing framework on top of Python’s unittest.

And later:

A good rule of thumb is to prefer shell tests when what is being tested is relatively simple. Expressivity is limited compared to the API tests, which means that you have to have a well-defined test scenario that you can easily match with FileCheck.

IMO these tests fit into the latter category.

But I'm happy to change these to API tests if that is more appropriate.


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
Loading