| 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 | |