From 729a0f69583c2c8ac597e68622231e4b719735b8 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Mon, 16 Sep 2024 14:10:46 +0100 Subject: [PATCH 1/3] [lldb][FrameRecognizer] Display the first non-std frame on verbose_trap --- .../Target/VerboseTrapFrameRecognizer.cpp | 27 ++++++++++++++++++- .../Inputs/verbose_trap-in-stl-callback.cpp | 22 +++++++++++++++ .../Inputs/verbose_trap-in-stl-nested.cpp | 21 +++++++++++++++ .../Recognizer/Inputs/verbose_trap-in-stl.cpp | 17 ++++++++++++ .../verbose_trap-in-stl-callback.test | 13 +++++++++ .../verbose_trap-in-stl-nested.test | 13 +++++++++ .../Shell/Recognizer/verbose_trap-in-stl.test | 13 +++++++++ 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback.cpp create mode 100644 lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-nested.cpp create mode 100644 lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl.cpp create mode 100644 lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test create mode 100644 lldb/test/Shell/Recognizer/verbose_trap-in-stl-nested.test create mode 100644 lldb/test/Shell/Recognizer/verbose_trap-in-stl.test diff --git a/lldb/source/Target/VerboseTrapFrameRecognizer.cpp b/lldb/source/Target/VerboseTrapFrameRecognizer.cpp index de710fcda54064..530a8941c8d504 100644 --- a/lldb/source/Target/VerboseTrapFrameRecognizer.cpp +++ b/lldb/source/Target/VerboseTrapFrameRecognizer.cpp @@ -16,6 +16,31 @@ 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) { + StackFrameSP most_relevant_frame_sp = selected_thread.GetStackFrameAtIndex(1); + while (most_relevant_frame_sp) { + 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; + + most_relevant_frame_sp = selected_thread.GetStackFrameAtIndex( + most_relevant_frame_sp->GetFrameIndex() + 1); + } + + return nullptr; +} + VerboseTrapRecognizedStackFrame::VerboseTrapRecognizedStackFrame( StackFrameSP most_relevant_frame_sp, std::string stop_desc) : m_most_relevant_frame(most_relevant_frame_sp) { @@ -30,7 +55,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); diff --git a/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback.cpp b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback.cpp new file mode 100644 index 00000000000000..23beed4c62c3b3 --- /dev/null +++ b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback.cpp @@ -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; +} diff --git a/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-nested.cpp b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-nested.cpp new file mode 100644 index 00000000000000..67fa65c9ceae22 --- /dev/null +++ b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-nested.cpp @@ -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 struct vector { + void operator[](unsigned) { detail::function_that_aborts(); } +}; +} // namespace __1 +} // namespace std + +void g() { + std::vector v; + v[10]; +} + +int main() { + g(); + return 0; +} diff --git a/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl.cpp b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl.cpp new file mode 100644 index 00000000000000..4f01827944e166 --- /dev/null +++ b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl.cpp @@ -0,0 +1,17 @@ +namespace std { +inline namespace __1 { +template struct vector { + void operator[](unsigned) { __builtin_verbose_trap("Bounds error", "out-of-bounds access"); } +}; +} // namespace __1 +} // namespace std + +void g() { + std::vector v; + v[10]; +} + +int main() { + g(); + return 0; +} diff --git a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test new file mode 100644 index 00000000000000..6ea397d3ee700b --- /dev/null +++ b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test @@ -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-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 diff --git a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-nested.test b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-nested.test new file mode 100644 index 00000000000000..81a492d1ed5791 --- /dev/null +++ b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-nested.test @@ -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 diff --git a/lldb/test/Shell/Recognizer/verbose_trap-in-stl.test b/lldb/test/Shell/Recognizer/verbose_trap-in-stl.test new file mode 100644 index 00000000000000..dd08290174e3af --- /dev/null +++ b/lldb/test/Shell/Recognizer/verbose_trap-in-stl.test @@ -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 From 05f3c3b45a64cbc1aa92bb3925ec16e071cbf6c5 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Tue, 17 Sep 2024 17:43:18 +0100 Subject: [PATCH 2/3] fixup! add defensive stack walking threshold; add more tests --- .../Target/VerboseTrapFrameRecognizer.cpp | 16 ++++++++++---- ...verbose_trap-in-stl-callback-user-leaf.cpp | 22 +++++++++++++++++++ .../Inputs/verbose_trap-in-stl-max-depth.cpp | 13 +++++++++++ ...erbose_trap-in-stl-callback-user-leaf.test | 22 +++++++++++++++++++ .../verbose_trap-in-stl-callback.test | 8 +++++++ .../verbose_trap-in-stl-max-depth.test | 16 ++++++++++++++ 6 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback-user-leaf.cpp create mode 100644 lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-max-depth.cpp create mode 100644 lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback-user-leaf.test create mode 100644 lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test diff --git a/lldb/source/Target/VerboseTrapFrameRecognizer.cpp b/lldb/source/Target/VerboseTrapFrameRecognizer.cpp index 530a8941c8d504..03ab58b8c59a9b 100644 --- a/lldb/source/Target/VerboseTrapFrameRecognizer.cpp +++ b/lldb/source/Target/VerboseTrapFrameRecognizer.cpp @@ -20,8 +20,16 @@ using namespace lldb_private; /// 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) { - StackFrameSP most_relevant_frame_sp = selected_thread.GetStackFrameAtIndex(1); - while (most_relevant_frame_sp) { + // 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(); @@ -34,8 +42,8 @@ static StackFrameSP FindMostRelevantFrame(Thread &selected_thread) { if (!frame_name.GetStringRef().starts_with("std::")) return most_relevant_frame_sp; - most_relevant_frame_sp = selected_thread.GetStackFrameAtIndex( - most_relevant_frame_sp->GetFrameIndex() + 1); + ++stack_idx; + most_relevant_frame_sp = selected_thread.GetStackFrameAtIndex(stack_idx); } return nullptr; diff --git a/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback-user-leaf.cpp b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback-user-leaf.cpp new file mode 100644 index 00000000000000..6c36682626a6ef --- /dev/null +++ b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-callback-user-leaf.cpp @@ -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; +} diff --git a/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-max-depth.cpp b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-max-depth.cpp new file mode 100644 index 00000000000000..48f564ce674e4d --- /dev/null +++ b/lldb/test/Shell/Recognizer/Inputs/verbose_trap-in-stl-max-depth.cpp @@ -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; +} diff --git a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback-user-leaf.test b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback-user-leaf.test new file mode 100644 index 00000000000000..5a84c163453ccd --- /dev/null +++ b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback-user-leaf.test @@ -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 diff --git a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test index 6ea397d3ee700b..b15bcb3a384f98 100644 --- a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test +++ b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-callback.test @@ -1,5 +1,13 @@ # 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 # diff --git a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test new file mode 100644 index 00000000000000..2a72b23297d2b4 --- /dev/null +++ b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test @@ -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 = EXC_BREAKPOINT +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 From 8a124e1436ef6a0d97c71dde8c64f18218eb40ef Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Thu, 19 Sep 2024 09:52:22 +0100 Subject: [PATCH 3/3] fixup! don't match stop reason --- lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test index 2a72b23297d2b4..0c3275c571b3d9 100644 --- a/lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test +++ b/lldb/test/Shell/Recognizer/verbose_trap-in-stl-max-depth.test @@ -8,7 +8,7 @@ # RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK run -# CHECK: thread #{{.*}}stop reason = EXC_BREAKPOINT +# CHECK: thread #{{.*}}stop reason = frame recognizer info 0 # CHECK: frame 0 is recognized by Verbose Trap StackFrame Recognizer frame info