1 | #include "lldb/Target/VerboseTrapFrameRecognizer.h" |
2 | |
3 | #include "lldb/Core/Module.h" |
4 | #include "lldb/Symbol/Function.h" |
5 | #include "lldb/Symbol/SymbolContext.h" |
6 | #include "lldb/Target/Process.h" |
7 | #include "lldb/Target/StackFrameRecognizer.h" |
8 | #include "lldb/Target/Target.h" |
9 | |
10 | #include "lldb/Utility/LLDBLog.h" |
11 | #include "lldb/Utility/Log.h" |
12 | |
13 | #include "clang/CodeGen/ModuleBuilder.h" |
14 | |
15 | using namespace llvm; |
16 | using namespace lldb; |
17 | using namespace lldb_private; |
18 | |
19 | /// The 0th frame is the artificial inline frame generated to store |
20 | /// the verbose_trap message. So, starting with the current parent frame, |
21 | /// find the first frame that's not inside of the STL. |
22 | static StackFrameSP FindMostRelevantFrame(Thread &selected_thread) { |
23 | // Defensive upper-bound of when we stop walking up the frames in |
24 | // case we somehow ended up looking at an infinite recursion. |
25 | const size_t max_stack_depth = 128; |
26 | |
27 | // Start at parent frame. |
28 | size_t stack_idx = 1; |
29 | StackFrameSP most_relevant_frame_sp = |
30 | selected_thread.GetStackFrameAtIndex(idx: stack_idx); |
31 | |
32 | while (most_relevant_frame_sp && stack_idx <= max_stack_depth) { |
33 | auto const &sc = |
34 | most_relevant_frame_sp->GetSymbolContext(resolve_scope: eSymbolContextEverything); |
35 | ConstString frame_name = sc.GetFunctionName(); |
36 | if (!frame_name) |
37 | return nullptr; |
38 | |
39 | // Found a frame outside of the `std` namespace. That's the |
40 | // first frame in user-code that ended up triggering the |
41 | // verbose_trap. Hence that's the one we want to display. |
42 | if (!frame_name.GetStringRef().starts_with(Prefix: "std::" )) |
43 | return most_relevant_frame_sp; |
44 | |
45 | ++stack_idx; |
46 | most_relevant_frame_sp = selected_thread.GetStackFrameAtIndex(idx: stack_idx); |
47 | } |
48 | |
49 | return nullptr; |
50 | } |
51 | |
52 | VerboseTrapRecognizedStackFrame::VerboseTrapRecognizedStackFrame( |
53 | StackFrameSP most_relevant_frame_sp, std::string stop_desc) |
54 | : m_most_relevant_frame(most_relevant_frame_sp) { |
55 | m_stop_desc = std::move(stop_desc); |
56 | } |
57 | |
58 | lldb::RecognizedStackFrameSP |
59 | VerboseTrapFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) { |
60 | if (frame_sp->GetFrameIndex()) |
61 | return {}; |
62 | |
63 | ThreadSP thread_sp = frame_sp->GetThread(); |
64 | ProcessSP process_sp = thread_sp->GetProcess(); |
65 | |
66 | StackFrameSP most_relevant_frame_sp = FindMostRelevantFrame(selected_thread&: *thread_sp); |
67 | |
68 | if (!most_relevant_frame_sp) { |
69 | Log *log = GetLog(mask: LLDBLog::Unwind); |
70 | LLDB_LOG( |
71 | log, |
72 | "Failed to find most relevant frame: Hit unwinding bound (1 frame)!" ); |
73 | return {}; |
74 | } |
75 | |
76 | SymbolContext sc = frame_sp->GetSymbolContext(resolve_scope: eSymbolContextEverything); |
77 | |
78 | if (!sc.block) |
79 | return {}; |
80 | |
81 | // The runtime error is set as the function name in the inlined function info |
82 | // of frame #0 by the compiler |
83 | const InlineFunctionInfo *inline_info = nullptr; |
84 | Block *inline_block = sc.block->GetContainingInlinedBlock(); |
85 | |
86 | if (!inline_block) |
87 | return {}; |
88 | |
89 | inline_info = sc.block->GetInlinedFunctionInfo(); |
90 | |
91 | if (!inline_info) |
92 | return {}; |
93 | |
94 | auto func_name = inline_info->GetName().GetStringRef(); |
95 | if (func_name.empty()) |
96 | return {}; |
97 | |
98 | static auto trap_regex = |
99 | llvm::Regex(llvm::formatv(Fmt: "^{0}\\$(.*)\\$(.*)$" , Vals: ClangTrapPrefix).str()); |
100 | SmallVector<llvm::StringRef, 3> matches; |
101 | std::string regex_err_msg; |
102 | if (!trap_regex.match(String: func_name, Matches: &matches, Error: ®ex_err_msg)) { |
103 | LLDB_LOGF(GetLog(LLDBLog::Unwind), |
104 | "Failed to parse match trap regex for '%s': %s" , func_name.data(), |
105 | regex_err_msg.c_str()); |
106 | |
107 | return {}; |
108 | } |
109 | |
110 | // For `__clang_trap_msg$category$message$` we expect 3 matches: |
111 | // 1. entire string |
112 | // 2. category |
113 | // 3. message |
114 | if (matches.size() != 3) { |
115 | LLDB_LOGF(GetLog(LLDBLog::Unwind), |
116 | "Unexpected function name format. Expected '<trap prefix>$<trap " |
117 | "category>$<trap message>'$ but got: '%s'." , |
118 | func_name.data()); |
119 | |
120 | return {}; |
121 | } |
122 | |
123 | auto category = matches[1]; |
124 | auto message = matches[2]; |
125 | |
126 | std::string stop_reason = |
127 | category.empty() ? "<empty category>" : category.str(); |
128 | if (!message.empty()) { |
129 | stop_reason += ": " ; |
130 | stop_reason += message.str(); |
131 | } |
132 | |
133 | return std::make_shared<VerboseTrapRecognizedStackFrame>( |
134 | args&: most_relevant_frame_sp, args: std::move(stop_reason)); |
135 | } |
136 | |
137 | lldb::StackFrameSP VerboseTrapRecognizedStackFrame::GetMostRelevantFrame() { |
138 | return m_most_relevant_frame; |
139 | } |
140 | |
141 | namespace lldb_private { |
142 | |
143 | void RegisterVerboseTrapFrameRecognizer(Process &process) { |
144 | RegularExpressionSP module_regex_sp = nullptr; |
145 | auto symbol_regex_sp = std::make_shared<RegularExpression>( |
146 | args: llvm::formatv(Fmt: "^{0}" , Vals: ClangTrapPrefix).str()); |
147 | |
148 | StackFrameRecognizerSP srf_recognizer_sp = |
149 | std::make_shared<VerboseTrapFrameRecognizer>(); |
150 | |
151 | process.GetTarget().GetFrameRecognizerManager().AddRecognizer( |
152 | recognizer: srf_recognizer_sp, module: module_regex_sp, symbol: symbol_regex_sp, |
153 | symbol_mangling: Mangled::ePreferDemangled, first_instruction_only: false); |
154 | } |
155 | |
156 | } // namespace lldb_private |
157 | |