llvm-project/lldb/source/Target/VerboseTrapFrameRecognizer.cpp
Michael Buch bca507387a
[lldb][FrameRecognizer] Display the first non-std frame on verbose_trap (#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
2024-09-19 10:06:28 +01:00

157 lines
4.7 KiB
C++

#include "lldb/Target/VerboseTrapFrameRecognizer.h"
#include "lldb/Core/Module.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/StackFrameRecognizer.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "clang/CodeGen/ModuleBuilder.h"
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) {
m_stop_desc = std::move(stop_desc);
}
lldb::RecognizedStackFrameSP
VerboseTrapFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) {
if (frame_sp->GetFrameIndex())
return {};
ThreadSP thread_sp = frame_sp->GetThread();
ProcessSP process_sp = thread_sp->GetProcess();
StackFrameSP most_relevant_frame_sp = FindMostRelevantFrame(*thread_sp);
if (!most_relevant_frame_sp) {
Log *log = GetLog(LLDBLog::Unwind);
LLDB_LOG(
log,
"Failed to find most relevant frame: Hit unwinding bound (1 frame)!");
return {};
}
SymbolContext sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
if (!sc.block)
return {};
// The runtime error is set as the function name in the inlined function info
// of frame #0 by the compiler
const InlineFunctionInfo *inline_info = nullptr;
Block *inline_block = sc.block->GetContainingInlinedBlock();
if (!inline_block)
return {};
inline_info = sc.block->GetInlinedFunctionInfo();
if (!inline_info)
return {};
auto func_name = inline_info->GetName().GetStringRef();
if (func_name.empty())
return {};
static auto trap_regex =
llvm::Regex(llvm::formatv("^{0}\\$(.*)\\$(.*)$", ClangTrapPrefix).str());
SmallVector<llvm::StringRef, 3> matches;
std::string regex_err_msg;
if (!trap_regex.match(func_name, &matches, &regex_err_msg)) {
LLDB_LOGF(GetLog(LLDBLog::Unwind),
"Failed to parse match trap regex for '%s': %s", func_name.data(),
regex_err_msg.c_str());
return {};
}
// For `__clang_trap_msg$category$message$` we expect 3 matches:
// 1. entire string
// 2. category
// 3. message
if (matches.size() != 3) {
LLDB_LOGF(GetLog(LLDBLog::Unwind),
"Unexpected function name format. Expected '<trap prefix>$<trap "
"category>$<trap message>'$ but got: '%s'.",
func_name.data());
return {};
}
auto category = matches[1];
auto message = matches[2];
std::string stop_reason =
category.empty() ? "<empty category>" : category.str();
if (!message.empty()) {
stop_reason += ": ";
stop_reason += message.str();
}
return std::make_shared<VerboseTrapRecognizedStackFrame>(
most_relevant_frame_sp, std::move(stop_reason));
}
lldb::StackFrameSP VerboseTrapRecognizedStackFrame::GetMostRelevantFrame() {
return m_most_relevant_frame;
}
namespace lldb_private {
void RegisterVerboseTrapFrameRecognizer(Process &process) {
RegularExpressionSP module_regex_sp = nullptr;
auto symbol_regex_sp = std::make_shared<RegularExpression>(
llvm::formatv("^{0}", ClangTrapPrefix).str());
StackFrameRecognizerSP srf_recognizer_sp =
std::make_shared<VerboseTrapFrameRecognizer>();
process.GetTarget().GetFrameRecognizerManager().AddRecognizer(
srf_recognizer_sp, module_regex_sp, symbol_regex_sp,
Mangled::ePreferDemangled, false);
}
} // namespace lldb_private