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] Add frame recognizer for __builtin_verbose_trap #80368

Merged
merged 7 commits into from
Jul 16, 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
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
Copy link
Member

Choose a reason for hiding this comment

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

Missing license header.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

#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 @@ -524,7 +525,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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there anyway for there to not be a stack frame at index 1? If that's possible could we bail out early so we don't crash/have bad behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

While I don't see it documented explicitly, GetStackFrameAtIndex handles cases where we asked for a frame index that's out-of-bounds. And according to the docs GetStackFrameCount can be expensive to check, so we probably don't want to do that here


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
Loading