1 | //===-- InstrumentationRuntimeUBSan.cpp -----------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "InstrumentationRuntimeUBSan.h" |
10 | |
11 | #include "Plugins/Process/Utility/HistoryThread.h" |
12 | #include "lldb/Breakpoint/StoppointCallbackContext.h" |
13 | #include "lldb/Core/Debugger.h" |
14 | #include "lldb/Core/Module.h" |
15 | #include "lldb/Core/PluginInterface.h" |
16 | #include "lldb/Core/PluginManager.h" |
17 | #include "lldb/Core/ValueObject.h" |
18 | #include "lldb/Expression/UserExpression.h" |
19 | #include "lldb/Host/StreamFile.h" |
20 | #include "lldb/Interpreter/CommandReturnObject.h" |
21 | #include "lldb/Symbol/Symbol.h" |
22 | #include "lldb/Symbol/SymbolContext.h" |
23 | #include "lldb/Symbol/Variable.h" |
24 | #include "lldb/Symbol/VariableList.h" |
25 | #include "lldb/Target/InstrumentationRuntimeStopInfo.h" |
26 | #include "lldb/Target/SectionLoadList.h" |
27 | #include "lldb/Target/StopInfo.h" |
28 | #include "lldb/Target/Target.h" |
29 | #include "lldb/Target/Thread.h" |
30 | #include "lldb/Utility/RegularExpression.h" |
31 | #include "lldb/Utility/Stream.h" |
32 | #include <cctype> |
33 | |
34 | #include <memory> |
35 | |
36 | using namespace lldb; |
37 | using namespace lldb_private; |
38 | |
39 | LLDB_PLUGIN_DEFINE(InstrumentationRuntimeUBSan) |
40 | |
41 | InstrumentationRuntimeUBSan::~InstrumentationRuntimeUBSan() { Deactivate(); } |
42 | |
43 | lldb::InstrumentationRuntimeSP |
44 | InstrumentationRuntimeUBSan::CreateInstance(const lldb::ProcessSP &process_sp) { |
45 | return InstrumentationRuntimeSP(new InstrumentationRuntimeUBSan(process_sp)); |
46 | } |
47 | |
48 | void InstrumentationRuntimeUBSan::Initialize() { |
49 | PluginManager::RegisterPlugin( |
50 | name: GetPluginNameStatic(), |
51 | description: "UndefinedBehaviorSanitizer instrumentation runtime plugin." , |
52 | create_callback: CreateInstance, get_type_callback: GetTypeStatic); |
53 | } |
54 | |
55 | void InstrumentationRuntimeUBSan::Terminate() { |
56 | PluginManager::UnregisterPlugin(create_callback: CreateInstance); |
57 | } |
58 | |
59 | lldb::InstrumentationRuntimeType InstrumentationRuntimeUBSan::GetTypeStatic() { |
60 | return eInstrumentationRuntimeTypeUndefinedBehaviorSanitizer; |
61 | } |
62 | |
63 | static const char *ub_sanitizer_retrieve_report_data_prefix = R"( |
64 | extern "C" { |
65 | void |
66 | __ubsan_get_current_report_data(const char **OutIssueKind, |
67 | const char **OutMessage, const char **OutFilename, unsigned *OutLine, |
68 | unsigned *OutCol, char **OutMemoryAddr); |
69 | } |
70 | )" ; |
71 | |
72 | static const char *ub_sanitizer_retrieve_report_data_command = R"( |
73 | struct { |
74 | const char *issue_kind; |
75 | const char *message; |
76 | const char *filename; |
77 | unsigned line; |
78 | unsigned col; |
79 | char *memory_addr; |
80 | } t; |
81 | |
82 | __ubsan_get_current_report_data(&t.issue_kind, &t.message, &t.filename, &t.line, |
83 | &t.col, &t.memory_addr); |
84 | t; |
85 | )" ; |
86 | |
87 | static addr_t RetrieveUnsigned(ValueObjectSP return_value_sp, |
88 | ProcessSP process_sp, |
89 | const std::string &expression_path) { |
90 | return return_value_sp->GetValueForExpressionPath(expression: expression_path.c_str()) |
91 | ->GetValueAsUnsigned(fail_value: 0); |
92 | } |
93 | |
94 | static std::string RetrieveString(ValueObjectSP return_value_sp, |
95 | ProcessSP process_sp, |
96 | const std::string &expression_path) { |
97 | addr_t ptr = RetrieveUnsigned(return_value_sp, process_sp, expression_path); |
98 | std::string str; |
99 | Status error; |
100 | process_sp->ReadCStringFromMemory(vm_addr: ptr, out_str&: str, error); |
101 | return str; |
102 | } |
103 | |
104 | StructuredData::ObjectSP InstrumentationRuntimeUBSan::RetrieveReportData( |
105 | ExecutionContextRef exe_ctx_ref) { |
106 | ProcessSP process_sp = GetProcessSP(); |
107 | if (!process_sp) |
108 | return StructuredData::ObjectSP(); |
109 | |
110 | ThreadSP thread_sp = exe_ctx_ref.GetThreadSP(); |
111 | StackFrameSP frame_sp = |
112 | thread_sp->GetSelectedFrame(select_most_relevant: DoNoSelectMostRelevantFrame); |
113 | ModuleSP runtime_module_sp = GetRuntimeModuleSP(); |
114 | Target &target = process_sp->GetTarget(); |
115 | |
116 | if (!frame_sp) |
117 | return StructuredData::ObjectSP(); |
118 | |
119 | StreamFileSP Stream = target.GetDebugger().GetOutputStreamSP(); |
120 | |
121 | EvaluateExpressionOptions options; |
122 | options.SetUnwindOnError(true); |
123 | options.SetTryAllThreads(true); |
124 | options.SetStopOthers(true); |
125 | options.SetIgnoreBreakpoints(true); |
126 | options.SetTimeout(process_sp->GetUtilityExpressionTimeout()); |
127 | options.SetPrefix(ub_sanitizer_retrieve_report_data_prefix); |
128 | options.SetAutoApplyFixIts(false); |
129 | options.SetLanguage(eLanguageTypeObjC_plus_plus); |
130 | |
131 | ValueObjectSP main_value; |
132 | ExecutionContext exe_ctx; |
133 | Status eval_error; |
134 | frame_sp->CalculateExecutionContext(exe_ctx); |
135 | ExpressionResults result = UserExpression::Evaluate( |
136 | exe_ctx, options, expr_cstr: ub_sanitizer_retrieve_report_data_command, expr_prefix: "" , |
137 | result_valobj_sp&: main_value, error&: eval_error); |
138 | if (result != eExpressionCompleted) { |
139 | StreamString ss; |
140 | ss << "cannot evaluate UndefinedBehaviorSanitizer expression:\n" ; |
141 | ss << eval_error.AsCString(); |
142 | Debugger::ReportWarning(message: ss.GetString().str(), |
143 | debugger_id: process_sp->GetTarget().GetDebugger().GetID()); |
144 | return StructuredData::ObjectSP(); |
145 | } |
146 | |
147 | // Gather the PCs of the user frames in the backtrace. |
148 | StructuredData::Array *trace = new StructuredData::Array(); |
149 | auto trace_sp = StructuredData::ObjectSP(trace); |
150 | for (unsigned I = 0; I < thread_sp->GetStackFrameCount(); ++I) { |
151 | const Address FCA = thread_sp->GetStackFrameAtIndex(idx: I) |
152 | ->GetFrameCodeAddressForSymbolication(); |
153 | if (FCA.GetModule() == runtime_module_sp) // Skip PCs from the runtime. |
154 | continue; |
155 | |
156 | lldb::addr_t PC = FCA.GetLoadAddress(target: &target); |
157 | trace->AddIntegerItem(value: PC); |
158 | } |
159 | |
160 | std::string IssueKind = RetrieveString(return_value_sp: main_value, process_sp, expression_path: ".issue_kind" ); |
161 | std::string ErrMessage = RetrieveString(return_value_sp: main_value, process_sp, expression_path: ".message" ); |
162 | std::string Filename = RetrieveString(return_value_sp: main_value, process_sp, expression_path: ".filename" ); |
163 | unsigned Line = RetrieveUnsigned(return_value_sp: main_value, process_sp, expression_path: ".line" ); |
164 | unsigned Col = RetrieveUnsigned(return_value_sp: main_value, process_sp, expression_path: ".col" ); |
165 | uintptr_t MemoryAddr = |
166 | RetrieveUnsigned(return_value_sp: main_value, process_sp, expression_path: ".memory_addr" ); |
167 | |
168 | auto *d = new StructuredData::Dictionary(); |
169 | auto dict_sp = StructuredData::ObjectSP(d); |
170 | d->AddStringItem(key: "instrumentation_class" , value: "UndefinedBehaviorSanitizer" ); |
171 | d->AddStringItem(key: "description" , value: IssueKind); |
172 | d->AddStringItem(key: "summary" , value: ErrMessage); |
173 | d->AddStringItem(key: "filename" , value: Filename); |
174 | d->AddIntegerItem(key: "line" , value: Line); |
175 | d->AddIntegerItem(key: "col" , value: Col); |
176 | d->AddIntegerItem(key: "memory_address" , value: MemoryAddr); |
177 | d->AddIntegerItem(key: "tid" , value: thread_sp->GetID()); |
178 | d->AddItem(key: "trace" , value_sp: trace_sp); |
179 | return dict_sp; |
180 | } |
181 | |
182 | static std::string GetStopReasonDescription(StructuredData::ObjectSP report) { |
183 | llvm::StringRef stop_reason_description_ref; |
184 | report->GetAsDictionary()->GetValueForKeyAsString( |
185 | key: "description" , result&: stop_reason_description_ref); |
186 | std::string stop_reason_description = |
187 | std::string(stop_reason_description_ref); |
188 | |
189 | if (!stop_reason_description.size()) { |
190 | stop_reason_description = "Undefined behavior detected" ; |
191 | } else { |
192 | stop_reason_description[0] = toupper(c: stop_reason_description[0]); |
193 | for (unsigned I = 1; I < stop_reason_description.size(); ++I) |
194 | if (stop_reason_description[I] == '-') |
195 | stop_reason_description[I] = ' '; |
196 | } |
197 | return stop_reason_description; |
198 | } |
199 | |
200 | bool InstrumentationRuntimeUBSan::NotifyBreakpointHit( |
201 | void *baton, StoppointCallbackContext *context, user_id_t break_id, |
202 | user_id_t break_loc_id) { |
203 | assert(baton && "null baton" ); |
204 | if (!baton) |
205 | return false; ///< false => resume execution. |
206 | |
207 | InstrumentationRuntimeUBSan *const instance = |
208 | static_cast<InstrumentationRuntimeUBSan *>(baton); |
209 | |
210 | ProcessSP process_sp = instance->GetProcessSP(); |
211 | ThreadSP thread_sp = context->exe_ctx_ref.GetThreadSP(); |
212 | if (!process_sp || !thread_sp || |
213 | process_sp != context->exe_ctx_ref.GetProcessSP()) |
214 | return false; |
215 | |
216 | if (process_sp->GetModIDRef().IsLastResumeForUserExpression()) |
217 | return false; |
218 | |
219 | StructuredData::ObjectSP report = |
220 | instance->RetrieveReportData(exe_ctx_ref: context->exe_ctx_ref); |
221 | |
222 | if (report) { |
223 | thread_sp->SetStopInfo( |
224 | InstrumentationRuntimeStopInfo::CreateStopReasonWithInstrumentationData( |
225 | thread&: *thread_sp, description: GetStopReasonDescription(report), additional_data: report)); |
226 | return true; |
227 | } |
228 | |
229 | return false; |
230 | } |
231 | |
232 | const RegularExpression & |
233 | InstrumentationRuntimeUBSan::GetPatternForRuntimeLibrary() { |
234 | static RegularExpression regex(llvm::StringRef("libclang_rt\\.(a|t|ub)san_" )); |
235 | return regex; |
236 | } |
237 | |
238 | bool InstrumentationRuntimeUBSan::CheckIfRuntimeIsValid( |
239 | const lldb::ModuleSP module_sp) { |
240 | static ConstString ubsan_test_sym("__ubsan_on_report" ); |
241 | const Symbol *symbol = module_sp->FindFirstSymbolWithNameAndType( |
242 | name: ubsan_test_sym, symbol_type: lldb::eSymbolTypeAny); |
243 | return symbol != nullptr; |
244 | } |
245 | |
246 | // FIXME: Factor out all the logic we have in common with the {a,t}san plugins. |
247 | void InstrumentationRuntimeUBSan::Activate() { |
248 | if (IsActive()) |
249 | return; |
250 | |
251 | ProcessSP process_sp = GetProcessSP(); |
252 | if (!process_sp) |
253 | return; |
254 | |
255 | ModuleSP runtime_module_sp = GetRuntimeModuleSP(); |
256 | |
257 | ConstString symbol_name("__ubsan_on_report" ); |
258 | const Symbol *symbol = runtime_module_sp->FindFirstSymbolWithNameAndType( |
259 | name: symbol_name, symbol_type: eSymbolTypeCode); |
260 | |
261 | if (symbol == nullptr) |
262 | return; |
263 | |
264 | if (!symbol->ValueIsAddress() || !symbol->GetAddressRef().IsValid()) |
265 | return; |
266 | |
267 | Target &target = process_sp->GetTarget(); |
268 | addr_t symbol_address = symbol->GetAddressRef().GetOpcodeLoadAddress(target: &target); |
269 | |
270 | if (symbol_address == LLDB_INVALID_ADDRESS) |
271 | return; |
272 | |
273 | Breakpoint *breakpoint = |
274 | process_sp->GetTarget() |
275 | .CreateBreakpoint(load_addr: symbol_address, /*internal=*/true, |
276 | /*hardware=*/request_hardware: false) |
277 | .get(); |
278 | const bool sync = false; |
279 | breakpoint->SetCallback(callback: InstrumentationRuntimeUBSan::NotifyBreakpointHit, |
280 | baton: this, is_synchronous: sync); |
281 | breakpoint->SetBreakpointKind("undefined-behavior-sanitizer-report" ); |
282 | SetBreakpointID(breakpoint->GetID()); |
283 | |
284 | SetActive(true); |
285 | } |
286 | |
287 | void InstrumentationRuntimeUBSan::Deactivate() { |
288 | SetActive(false); |
289 | |
290 | auto BID = GetBreakpointID(); |
291 | if (BID == LLDB_INVALID_BREAK_ID) |
292 | return; |
293 | |
294 | if (ProcessSP process_sp = GetProcessSP()) { |
295 | process_sp->GetTarget().RemoveBreakpointByID(break_id: BID); |
296 | SetBreakpointID(LLDB_INVALID_BREAK_ID); |
297 | } |
298 | } |
299 | |
300 | lldb::ThreadCollectionSP |
301 | InstrumentationRuntimeUBSan::GetBacktracesFromExtendedStopInfo( |
302 | StructuredData::ObjectSP info) { |
303 | ThreadCollectionSP threads; |
304 | threads = std::make_shared<ThreadCollection>(); |
305 | |
306 | ProcessSP process_sp = GetProcessSP(); |
307 | |
308 | if (info->GetObjectForDotSeparatedPath(path: "instrumentation_class" ) |
309 | ->GetStringValue() != "UndefinedBehaviorSanitizer" ) |
310 | return threads; |
311 | |
312 | std::vector<lldb::addr_t> PCs; |
313 | auto trace = info->GetObjectForDotSeparatedPath(path: "trace" )->GetAsArray(); |
314 | trace->ForEach(foreach_callback: [&PCs](StructuredData::Object *PC) -> bool { |
315 | PCs.push_back(x: PC->GetUnsignedIntegerValue()); |
316 | return true; |
317 | }); |
318 | |
319 | if (PCs.empty()) |
320 | return threads; |
321 | |
322 | StructuredData::ObjectSP thread_id_obj = |
323 | info->GetObjectForDotSeparatedPath(path: "tid" ); |
324 | tid_t tid = thread_id_obj ? thread_id_obj->GetUnsignedIntegerValue() : 0; |
325 | |
326 | // We gather symbolication addresses above, so no need for HistoryThread to |
327 | // try to infer the call addresses. |
328 | bool pcs_are_call_addresses = true; |
329 | ThreadSP new_thread_sp = std::make_shared<HistoryThread>( |
330 | args&: *process_sp, args&: tid, args&: PCs, args&: pcs_are_call_addresses); |
331 | std::string stop_reason_description = GetStopReasonDescription(report: info); |
332 | new_thread_sp->SetName(stop_reason_description.c_str()); |
333 | |
334 | // Save this in the Process' ExtendedThreadList so a strong pointer retains |
335 | // the object |
336 | process_sp->GetExtendedThreadList().AddThread(thread_sp: new_thread_sp); |
337 | threads->AddThread(thread_sp: new_thread_sp); |
338 | |
339 | return threads; |
340 | } |
341 | |