Skip to content

Commit

Permalink
[lldb] Add frame recognizer for __builtin_verbose_trap (llvm#80368)
Browse files Browse the repository at this point in the history
This patch adds a frame recognizer for Clang's
`__builtin_verbose_trap`, which behaves like a
`__builtin_trap`, but emits a failure-reason string into debug-info in
order for debuggers to display
it to a user.

The frame recognizer triggers when we encounter
a frame with a function name that begins with
`__clang_trap_msg`, which is the magic prefix
Clang emits into debug-info for verbose traps.
Once such frame is encountered we display the
frame function name as the `Stop Reason` and display that frame to the
user.

Example output:
```
(lldb) run
warning: a.out was compiled with optimization - stepping may behave oddly; variables may not be available.
Process 35942 launched: 'a.out' (arm64)
Process 35942 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = Misc.: Function is not implemented
    frame #1: 0x0000000100003fa4 a.out`main [inlined] Dummy::func(this=<unavailable>) at verbose_trap.cpp:3:5 [opt]
   1    struct Dummy {
   2      void func() {
-> 3        __builtin_verbose_trap("Misc.", "Function is not implemented");
   4      }
   5    };
   6
   7    int main() {
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = Misc.: Function is not implemented
    frame #0: 0x0000000100003fa4 a.out`main [inlined] __clang_trap_msg$Misc.$Function is not implemented$ at verbose_trap.cpp:0 [opt]
  * frame #1: 0x0000000100003fa4 a.out`main [inlined] Dummy::func(this=<unavailable>) at verbose_trap.cpp:3:5 [opt]
    frame #2: 0x0000000100003fa4 a.out`main at verbose_trap.cpp:8:13 [opt]
    frame llvm#3: 0x0000000189d518b4 dyld`start + 1988
```
  • Loading branch information
Michael137 authored Jul 16, 2024
1 parent 94ed08d commit 8a27ef6
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 0 deletions.
39 changes: 39 additions & 0 deletions lldb/include/lldb/Target/VerboseTrapFrameRecognizer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef LLDB_TARGET_VERBOSETRAPFRAMERECOGNIZER_H
#define LLDB_TARGET_VERBOSETRAPFRAMERECOGNIZER_H

#include "lldb/Target/StackFrameRecognizer.h"

namespace lldb_private {

void RegisterVerboseTrapFrameRecognizer(Process &process);

/// Holds the stack frame that caused the Verbose trap and the inlined stop
/// reason message.
class VerboseTrapRecognizedStackFrame : public RecognizedStackFrame {
public:
VerboseTrapRecognizedStackFrame(lldb::StackFrameSP most_relevant_frame_sp,
std::string stop_desc);

lldb::StackFrameSP GetMostRelevantFrame() override;

private:
lldb::StackFrameSP m_most_relevant_frame;
};

/// When a thread stops, it checks the current frame contains a
/// Verbose Trap diagnostic. If so, it returns a \a
/// VerboseTrapRecognizedStackFrame holding the diagnostic a stop reason
/// description with and the parent frame as the most relavant frame.
class VerboseTrapFrameRecognizer : public StackFrameRecognizer {
public:
std::string GetName() override {
return "Verbose Trap StackFrame Recognizer";
}

lldb::RecognizedStackFrameSP
RecognizeFrame(lldb::StackFrameSP frame) override;
};

} // namespace lldb_private

#endif // LLDB_TARGET_VERBOSETRAPFRAMERECOGNIZER_H
1 change: 1 addition & 0 deletions lldb/source/Target/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ add_lldb_library(lldbTarget
UnixSignals.cpp
UnwindAssembly.cpp
UnwindLLDB.cpp
VerboseTrapFrameRecognizer.cpp

LINK_LIBS
lldbBreakpoint
Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include "lldb/Target/ThreadPlanCallFunction.h"
#include "lldb/Target/ThreadPlanStack.h"
#include "lldb/Target/UnixSignals.h"
#include "lldb/Target/VerboseTrapFrameRecognizer.h"
#include "lldb/Utility/AddressableBits.h"
#include "lldb/Utility/Event.h"
#include "lldb/Utility/LLDBLog.h"
Expand Down Expand Up @@ -522,7 +523,11 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
if (!value_sp->OptionWasSet() && platform_cache_line_size != 0)
value_sp->SetValueAs(platform_cache_line_size);

// FIXME: Frame recognizer registration should not be done in Target.
// We should have a plugin do the registration instead, for example, a
// common C LanguageRuntime plugin.
RegisterAssertFrameRecognizer(this);
RegisterVerboseTrapFrameRecognizer(*this);
}

Process::~Process() {
Expand Down
122 changes: 122 additions & 0 deletions lldb/source/Target/VerboseTrapFrameRecognizer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#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;

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 = thread_sp->GetStackFrameAtIndex(1);

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, false);
}

} // namespace lldb_private
12 changes: 12 additions & 0 deletions lldb/test/Shell/Recognizer/Inputs/verbose_trap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#if !defined(VERBOSE_TRAP_TEST_CATEGORY) || !defined(VERBOSE_TRAP_TEST_MESSAGE)
#error Please define required macros
#endif

struct Dummy {
void func() { __builtin_verbose_trap(VERBOSE_TRAP_TEST_CATEGORY, VERBOSE_TRAP_TEST_MESSAGE); }
};

int main() {
Dummy{}.func();
return 0;
}
22 changes: 22 additions & 0 deletions lldb/test/Shell/Recognizer/verbose_trap.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap.cpp -o %t.out -DVERBOSE_TRAP_TEST_CATEGORY=\"Foo\" -DVERBOSE_TRAP_TEST_MESSAGE=\"Bar\"
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK,CHECK-BOTH
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap.cpp -o %t.out -DVERBOSE_TRAP_TEST_CATEGORY=\"\" -DVERBOSE_TRAP_TEST_MESSAGE=\"Bar\"
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK,CHECK-MESSAGE_ONLY
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap.cpp -o %t.out -DVERBOSE_TRAP_TEST_CATEGORY=\"Foo\" -DVERBOSE_TRAP_TEST_MESSAGE=\"\"
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK,CHECK-CATEGORY_ONLY
#
# RUN: %clang_host -g -O0 %S/Inputs/verbose_trap.cpp -o %t.out -DVERBOSE_TRAP_TEST_CATEGORY=\"\" -DVERBOSE_TRAP_TEST_MESSAGE=\"\"
# RUN: %lldb -b -s %s %t.out | FileCheck %s --check-prefixes=CHECK,CHECK-NONE

run
# CHECK-BOTH: thread #{{.*}}stop reason = Foo: Bar
# CHECK-MESSAGE_ONLY: thread #{{.*}}stop reason = <empty category>: Bar
# CHECK-CATEGORY_ONLY: thread #{{.*}}stop reason = Foo
# CHECK-NONE: thread #{{.*}}stop reason = <empty category>
frame info
# CHECK: frame #{{.*}}`Dummy::func(this={{.*}}) at verbose_trap.cpp
frame recognizer info 0
# CHECK: frame 0 is recognized by Verbose Trap StackFrame Recognizer
q

0 comments on commit 8a27ef6

Please sign in to comment.