| 1 | //===-- InstrumentationRuntimeTSan.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 "InstrumentationRuntimeTSan.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/Expression/UserExpression.h" |
| 18 | #include "lldb/Host/StreamFile.h" |
| 19 | #include "lldb/Interpreter/CommandReturnObject.h" |
| 20 | #include "lldb/Symbol/Symbol.h" |
| 21 | #include "lldb/Symbol/SymbolContext.h" |
| 22 | #include "lldb/Symbol/Variable.h" |
| 23 | #include "lldb/Symbol/VariableList.h" |
| 24 | #include "lldb/Target/InstrumentationRuntimeStopInfo.h" |
| 25 | #include "lldb/Target/SectionLoadList.h" |
| 26 | #include "lldb/Target/StopInfo.h" |
| 27 | #include "lldb/Target/Target.h" |
| 28 | #include "lldb/Target/Thread.h" |
| 29 | #include "lldb/Utility/LLDBLog.h" |
| 30 | #include "lldb/Utility/Log.h" |
| 31 | #include "lldb/Utility/RegularExpression.h" |
| 32 | #include "lldb/Utility/Stream.h" |
| 33 | #include "lldb/ValueObject/ValueObject.h" |
| 34 | |
| 35 | #include <memory> |
| 36 | |
| 37 | using namespace lldb; |
| 38 | using namespace lldb_private; |
| 39 | |
| 40 | LLDB_PLUGIN_DEFINE(InstrumentationRuntimeTSan) |
| 41 | |
| 42 | lldb::InstrumentationRuntimeSP |
| 43 | InstrumentationRuntimeTSan::CreateInstance(const lldb::ProcessSP &process_sp) { |
| 44 | return InstrumentationRuntimeSP(new InstrumentationRuntimeTSan(process_sp)); |
| 45 | } |
| 46 | |
| 47 | void InstrumentationRuntimeTSan::Initialize() { |
| 48 | PluginManager::RegisterPlugin( |
| 49 | name: GetPluginNameStatic(), description: "ThreadSanitizer instrumentation runtime plugin." , |
| 50 | create_callback: CreateInstance, get_type_callback: GetTypeStatic); |
| 51 | } |
| 52 | |
| 53 | void InstrumentationRuntimeTSan::Terminate() { |
| 54 | PluginManager::UnregisterPlugin(create_callback: CreateInstance); |
| 55 | } |
| 56 | |
| 57 | lldb::InstrumentationRuntimeType InstrumentationRuntimeTSan::GetTypeStatic() { |
| 58 | return eInstrumentationRuntimeTypeThreadSanitizer; |
| 59 | } |
| 60 | |
| 61 | InstrumentationRuntimeTSan::~InstrumentationRuntimeTSan() { Deactivate(); } |
| 62 | |
| 63 | const char *thread_sanitizer_retrieve_report_data_prefix = R"( |
| 64 | extern "C" |
| 65 | { |
| 66 | void *__tsan_get_current_report(); |
| 67 | int __tsan_get_report_data(void *report, const char **description, int *count, |
| 68 | int *stack_count, int *mop_count, int *loc_count, |
| 69 | int *mutex_count, int *thread_count, |
| 70 | int *unique_tid_count, void **sleep_trace, |
| 71 | unsigned long trace_size); |
| 72 | int __tsan_get_report_stack(void *report, unsigned long idx, void **trace, |
| 73 | unsigned long trace_size); |
| 74 | int __tsan_get_report_mop(void *report, unsigned long idx, int *tid, void **addr, |
| 75 | int *size, int *write, int *atomic, void **trace, |
| 76 | unsigned long trace_size); |
| 77 | int __tsan_get_report_loc(void *report, unsigned long idx, const char **type, |
| 78 | void **addr, unsigned long *start, unsigned long *size, int *tid, |
| 79 | int *fd, int *suppressable, void **trace, |
| 80 | unsigned long trace_size); |
| 81 | int __tsan_get_report_mutex(void *report, unsigned long idx, unsigned long *mutex_id, void **addr, |
| 82 | int *destroyed, void **trace, unsigned long trace_size); |
| 83 | int __tsan_get_report_thread(void *report, unsigned long idx, int *tid, unsigned long *os_id, |
| 84 | int *running, const char **name, int *parent_tid, |
| 85 | void **trace, unsigned long trace_size); |
| 86 | int __tsan_get_report_unique_tid(void *report, unsigned long idx, int *tid); |
| 87 | |
| 88 | // TODO: dlsym won't work on Windows. |
| 89 | void *dlsym(void* handle, const char* symbol); |
| 90 | int (*ptr__tsan_get_report_loc_object_type)(void *report, unsigned long idx, const char **object_type); |
| 91 | } |
| 92 | )" ; |
| 93 | |
| 94 | const char *thread_sanitizer_retrieve_report_data_command = R"( |
| 95 | |
| 96 | const int REPORT_TRACE_SIZE = 128; |
| 97 | const int REPORT_ARRAY_SIZE = 4; |
| 98 | |
| 99 | struct { |
| 100 | void *report; |
| 101 | const char *description; |
| 102 | int report_count; |
| 103 | |
| 104 | void *sleep_trace[REPORT_TRACE_SIZE]; |
| 105 | |
| 106 | int stack_count; |
| 107 | struct { |
| 108 | int idx; |
| 109 | void *trace[REPORT_TRACE_SIZE]; |
| 110 | } stacks[REPORT_ARRAY_SIZE]; |
| 111 | |
| 112 | int mop_count; |
| 113 | struct { |
| 114 | int idx; |
| 115 | int tid; |
| 116 | int size; |
| 117 | int write; |
| 118 | int atomic; |
| 119 | void *addr; |
| 120 | void *trace[REPORT_TRACE_SIZE]; |
| 121 | } mops[REPORT_ARRAY_SIZE]; |
| 122 | |
| 123 | int loc_count; |
| 124 | struct { |
| 125 | int idx; |
| 126 | const char *type; |
| 127 | void *addr; |
| 128 | unsigned long start; |
| 129 | unsigned long size; |
| 130 | int tid; |
| 131 | int fd; |
| 132 | int suppressable; |
| 133 | void *trace[REPORT_TRACE_SIZE]; |
| 134 | const char *object_type; |
| 135 | } locs[REPORT_ARRAY_SIZE]; |
| 136 | |
| 137 | int mutex_count; |
| 138 | struct { |
| 139 | int idx; |
| 140 | unsigned long mutex_id; |
| 141 | void *addr; |
| 142 | int destroyed; |
| 143 | void *trace[REPORT_TRACE_SIZE]; |
| 144 | } mutexes[REPORT_ARRAY_SIZE]; |
| 145 | |
| 146 | int thread_count; |
| 147 | struct { |
| 148 | int idx; |
| 149 | int tid; |
| 150 | unsigned long os_id; |
| 151 | int running; |
| 152 | const char *name; |
| 153 | int parent_tid; |
| 154 | void *trace[REPORT_TRACE_SIZE]; |
| 155 | } threads[REPORT_ARRAY_SIZE]; |
| 156 | |
| 157 | int unique_tid_count; |
| 158 | struct { |
| 159 | int idx; |
| 160 | int tid; |
| 161 | } unique_tids[REPORT_ARRAY_SIZE]; |
| 162 | } t = {0}; |
| 163 | |
| 164 | ptr__tsan_get_report_loc_object_type = (typeof(ptr__tsan_get_report_loc_object_type))(void *)dlsym((void*)-2 /*RTLD_DEFAULT*/, "__tsan_get_report_loc_object_type"); |
| 165 | |
| 166 | t.report = __tsan_get_current_report(); |
| 167 | __tsan_get_report_data(t.report, &t.description, &t.report_count, &t.stack_count, &t.mop_count, &t.loc_count, &t.mutex_count, &t.thread_count, &t.unique_tid_count, t.sleep_trace, REPORT_TRACE_SIZE); |
| 168 | |
| 169 | if (t.stack_count > REPORT_ARRAY_SIZE) t.stack_count = REPORT_ARRAY_SIZE; |
| 170 | for (int i = 0; i < t.stack_count; i++) { |
| 171 | t.stacks[i].idx = i; |
| 172 | __tsan_get_report_stack(t.report, i, t.stacks[i].trace, REPORT_TRACE_SIZE); |
| 173 | } |
| 174 | |
| 175 | if (t.mop_count > REPORT_ARRAY_SIZE) t.mop_count = REPORT_ARRAY_SIZE; |
| 176 | for (int i = 0; i < t.mop_count; i++) { |
| 177 | t.mops[i].idx = i; |
| 178 | __tsan_get_report_mop(t.report, i, &t.mops[i].tid, &t.mops[i].addr, &t.mops[i].size, &t.mops[i].write, &t.mops[i].atomic, t.mops[i].trace, REPORT_TRACE_SIZE); |
| 179 | } |
| 180 | |
| 181 | if (t.loc_count > REPORT_ARRAY_SIZE) t.loc_count = REPORT_ARRAY_SIZE; |
| 182 | for (int i = 0; i < t.loc_count; i++) { |
| 183 | t.locs[i].idx = i; |
| 184 | __tsan_get_report_loc(t.report, i, &t.locs[i].type, &t.locs[i].addr, &t.locs[i].start, &t.locs[i].size, &t.locs[i].tid, &t.locs[i].fd, &t.locs[i].suppressable, t.locs[i].trace, REPORT_TRACE_SIZE); |
| 185 | if (ptr__tsan_get_report_loc_object_type) |
| 186 | ptr__tsan_get_report_loc_object_type(t.report, i, &t.locs[i].object_type); |
| 187 | } |
| 188 | |
| 189 | if (t.mutex_count > REPORT_ARRAY_SIZE) t.mutex_count = REPORT_ARRAY_SIZE; |
| 190 | for (int i = 0; i < t.mutex_count; i++) { |
| 191 | t.mutexes[i].idx = i; |
| 192 | __tsan_get_report_mutex(t.report, i, &t.mutexes[i].mutex_id, &t.mutexes[i].addr, &t.mutexes[i].destroyed, t.mutexes[i].trace, REPORT_TRACE_SIZE); |
| 193 | } |
| 194 | |
| 195 | if (t.thread_count > REPORT_ARRAY_SIZE) t.thread_count = REPORT_ARRAY_SIZE; |
| 196 | for (int i = 0; i < t.thread_count; i++) { |
| 197 | t.threads[i].idx = i; |
| 198 | __tsan_get_report_thread(t.report, i, &t.threads[i].tid, &t.threads[i].os_id, &t.threads[i].running, &t.threads[i].name, &t.threads[i].parent_tid, t.threads[i].trace, REPORT_TRACE_SIZE); |
| 199 | } |
| 200 | |
| 201 | if (t.unique_tid_count > REPORT_ARRAY_SIZE) t.unique_tid_count = REPORT_ARRAY_SIZE; |
| 202 | for (int i = 0; i < t.unique_tid_count; i++) { |
| 203 | t.unique_tids[i].idx = i; |
| 204 | __tsan_get_report_unique_tid(t.report, i, &t.unique_tids[i].tid); |
| 205 | } |
| 206 | |
| 207 | t; |
| 208 | )" ; |
| 209 | |
| 210 | static StructuredData::ArraySP |
| 211 | CreateStackTrace(ValueObjectSP o, |
| 212 | const std::string &trace_item_name = ".trace" ) { |
| 213 | auto trace_sp = std::make_shared<StructuredData::Array>(); |
| 214 | ValueObjectSP trace_value_object = |
| 215 | o->GetValueForExpressionPath(expression: trace_item_name.c_str()); |
| 216 | size_t count = trace_value_object->GetNumChildrenIgnoringErrors(); |
| 217 | for (size_t j = 0; j < count; j++) { |
| 218 | addr_t trace_addr = |
| 219 | trace_value_object->GetChildAtIndex(idx: j)->GetValueAsUnsigned(fail_value: 0); |
| 220 | if (trace_addr == 0) |
| 221 | break; |
| 222 | trace_sp->AddIntegerItem(value: trace_addr); |
| 223 | } |
| 224 | return trace_sp; |
| 225 | } |
| 226 | |
| 227 | static StructuredData::ArraySP ConvertToStructuredArray( |
| 228 | ValueObjectSP return_value_sp, const std::string &items_name, |
| 229 | const std::string &count_name, |
| 230 | std::function<void(const ValueObjectSP &o, |
| 231 | const StructuredData::DictionarySP &dict)> const |
| 232 | &callback) { |
| 233 | auto array_sp = std::make_shared<StructuredData::Array>(); |
| 234 | unsigned int count = |
| 235 | return_value_sp->GetValueForExpressionPath(expression: count_name.c_str()) |
| 236 | ->GetValueAsUnsigned(fail_value: 0); |
| 237 | ValueObjectSP objects = |
| 238 | return_value_sp->GetValueForExpressionPath(expression: items_name.c_str()); |
| 239 | for (unsigned int i = 0; i < count; i++) { |
| 240 | ValueObjectSP o = objects->GetChildAtIndex(idx: i); |
| 241 | auto dict_sp = std::make_shared<StructuredData::Dictionary>(); |
| 242 | |
| 243 | callback(o, dict_sp); |
| 244 | |
| 245 | array_sp->AddItem(item: dict_sp); |
| 246 | } |
| 247 | return array_sp; |
| 248 | } |
| 249 | |
| 250 | static std::string RetrieveString(ValueObjectSP return_value_sp, |
| 251 | ProcessSP process_sp, |
| 252 | const std::string &expression_path) { |
| 253 | addr_t ptr = |
| 254 | return_value_sp->GetValueForExpressionPath(expression: expression_path.c_str()) |
| 255 | ->GetValueAsUnsigned(fail_value: 0); |
| 256 | std::string str; |
| 257 | Status error; |
| 258 | process_sp->ReadCStringFromMemory(vm_addr: ptr, out_str&: str, error); |
| 259 | return str; |
| 260 | } |
| 261 | |
| 262 | static void |
| 263 | GetRenumberedThreadIds(ProcessSP process_sp, ValueObjectSP data, |
| 264 | std::map<uint64_t, user_id_t> &thread_id_map) { |
| 265 | ConvertToStructuredArray( |
| 266 | return_value_sp: data, items_name: ".threads" , count_name: ".thread_count" , |
| 267 | callback: [process_sp, &thread_id_map](const ValueObjectSP &o, |
| 268 | const StructuredData::DictionarySP &dict) { |
| 269 | uint64_t thread_id = |
| 270 | o->GetValueForExpressionPath(expression: ".tid" )->GetValueAsUnsigned(fail_value: 0); |
| 271 | uint64_t thread_os_id = |
| 272 | o->GetValueForExpressionPath(expression: ".os_id" )->GetValueAsUnsigned(fail_value: 0); |
| 273 | user_id_t lldb_user_id = 0; |
| 274 | |
| 275 | bool can_update = true; |
| 276 | ThreadSP lldb_thread = process_sp->GetThreadList().FindThreadByID( |
| 277 | tid: thread_os_id, can_update); |
| 278 | if (lldb_thread) { |
| 279 | lldb_user_id = lldb_thread->GetIndexID(); |
| 280 | } else { |
| 281 | // This isn't a live thread anymore. Ask process to assign a new |
| 282 | // Index ID (or return an old one if we've already seen this |
| 283 | // thread_os_id). It will also make sure that no new threads are |
| 284 | // assigned this Index ID. |
| 285 | lldb_user_id = process_sp->AssignIndexIDToThread(thread_id: thread_os_id); |
| 286 | } |
| 287 | |
| 288 | thread_id_map[thread_id] = lldb_user_id; |
| 289 | }); |
| 290 | } |
| 291 | |
| 292 | static user_id_t Renumber(uint64_t id, |
| 293 | std::map<uint64_t, user_id_t> &thread_id_map) { |
| 294 | auto IT = thread_id_map.find(x: id); |
| 295 | if (IT == thread_id_map.end()) |
| 296 | return 0; |
| 297 | |
| 298 | return IT->second; |
| 299 | } |
| 300 | |
| 301 | StructuredData::ObjectSP InstrumentationRuntimeTSan::RetrieveReportData( |
| 302 | ExecutionContextRef exe_ctx_ref) { |
| 303 | ProcessSP process_sp = GetProcessSP(); |
| 304 | if (!process_sp) |
| 305 | return StructuredData::ObjectSP(); |
| 306 | |
| 307 | ThreadSP thread_sp = exe_ctx_ref.GetThreadSP(); |
| 308 | StackFrameSP frame_sp = |
| 309 | thread_sp->GetSelectedFrame(select_most_relevant: DoNoSelectMostRelevantFrame); |
| 310 | |
| 311 | if (!frame_sp) |
| 312 | return StructuredData::ObjectSP(); |
| 313 | |
| 314 | EvaluateExpressionOptions options; |
| 315 | options.SetUnwindOnError(true); |
| 316 | options.SetTryAllThreads(true); |
| 317 | options.SetStopOthers(true); |
| 318 | options.SetIgnoreBreakpoints(true); |
| 319 | options.SetTimeout(process_sp->GetUtilityExpressionTimeout()); |
| 320 | options.SetPrefix(thread_sanitizer_retrieve_report_data_prefix); |
| 321 | options.SetAutoApplyFixIts(false); |
| 322 | options.SetLanguage(eLanguageTypeObjC_plus_plus); |
| 323 | |
| 324 | ValueObjectSP main_value; |
| 325 | ExecutionContext exe_ctx; |
| 326 | frame_sp->CalculateExecutionContext(exe_ctx); |
| 327 | ExpressionResults result = UserExpression::Evaluate( |
| 328 | exe_ctx, options, expr_cstr: thread_sanitizer_retrieve_report_data_command, expr_prefix: "" , |
| 329 | result_valobj_sp&: main_value); |
| 330 | if (result != eExpressionCompleted) { |
| 331 | StreamString ss; |
| 332 | ss << "cannot evaluate ThreadSanitizer expression:\n" ; |
| 333 | if (main_value) |
| 334 | ss << main_value->GetError().AsCString(); |
| 335 | Debugger::ReportWarning(message: ss.GetString().str(), |
| 336 | debugger_id: process_sp->GetTarget().GetDebugger().GetID()); |
| 337 | return StructuredData::ObjectSP(); |
| 338 | } |
| 339 | |
| 340 | std::map<uint64_t, user_id_t> thread_id_map; |
| 341 | GetRenumberedThreadIds(process_sp, data: main_value, thread_id_map); |
| 342 | |
| 343 | auto dict = std::make_shared<StructuredData::Dictionary>(); |
| 344 | dict->AddStringItem(key: "instrumentation_class" , value: "ThreadSanitizer" ); |
| 345 | dict->AddStringItem(key: "issue_type" , |
| 346 | value: RetrieveString(return_value_sp: main_value, process_sp, expression_path: ".description" )); |
| 347 | dict->AddIntegerItem(key: "report_count" , |
| 348 | value: main_value->GetValueForExpressionPath(expression: ".report_count" ) |
| 349 | ->GetValueAsUnsigned(fail_value: 0)); |
| 350 | dict->AddItem(key: "sleep_trace" , value_sp: CreateStackTrace( |
| 351 | o: main_value, trace_item_name: ".sleep_trace" )); |
| 352 | |
| 353 | StructuredData::ArraySP stacks = ConvertToStructuredArray( |
| 354 | return_value_sp: main_value, items_name: ".stacks" , count_name: ".stack_count" , |
| 355 | callback: [thread_sp](const ValueObjectSP &o, |
| 356 | const StructuredData::DictionarySP &dict) { |
| 357 | dict->AddIntegerItem( |
| 358 | key: "index" , |
| 359 | value: o->GetValueForExpressionPath(expression: ".idx" )->GetValueAsUnsigned(fail_value: 0)); |
| 360 | dict->AddItem(key: "trace" , value_sp: CreateStackTrace(o)); |
| 361 | // "stacks" happen on the current thread |
| 362 | dict->AddIntegerItem(key: "thread_id" , value: thread_sp->GetIndexID()); |
| 363 | }); |
| 364 | dict->AddItem(key: "stacks" , value_sp: stacks); |
| 365 | |
| 366 | StructuredData::ArraySP mops = ConvertToStructuredArray( |
| 367 | return_value_sp: main_value, items_name: ".mops" , count_name: ".mop_count" , |
| 368 | callback: [&thread_id_map](const ValueObjectSP &o, |
| 369 | const StructuredData::DictionarySP &dict) { |
| 370 | dict->AddIntegerItem( |
| 371 | key: "index" , |
| 372 | value: o->GetValueForExpressionPath(expression: ".idx" )->GetValueAsUnsigned(fail_value: 0)); |
| 373 | dict->AddIntegerItem( |
| 374 | key: "thread_id" , |
| 375 | value: Renumber( |
| 376 | id: o->GetValueForExpressionPath(expression: ".tid" )->GetValueAsUnsigned(fail_value: 0), |
| 377 | thread_id_map)); |
| 378 | dict->AddIntegerItem( |
| 379 | key: "size" , |
| 380 | value: o->GetValueForExpressionPath(expression: ".size" )->GetValueAsUnsigned(fail_value: 0)); |
| 381 | dict->AddBooleanItem( |
| 382 | key: "is_write" , |
| 383 | value: o->GetValueForExpressionPath(expression: ".write" )->GetValueAsUnsigned(fail_value: 0)); |
| 384 | dict->AddBooleanItem( |
| 385 | key: "is_atomic" , |
| 386 | value: o->GetValueForExpressionPath(expression: ".atomic" )->GetValueAsUnsigned(fail_value: 0)); |
| 387 | dict->AddIntegerItem( |
| 388 | key: "address" , |
| 389 | value: o->GetValueForExpressionPath(expression: ".addr" )->GetValueAsUnsigned(fail_value: 0)); |
| 390 | dict->AddItem(key: "trace" , value_sp: CreateStackTrace(o)); |
| 391 | }); |
| 392 | dict->AddItem(key: "mops" , value_sp: mops); |
| 393 | |
| 394 | StructuredData::ArraySP locs = ConvertToStructuredArray( |
| 395 | return_value_sp: main_value, items_name: ".locs" , count_name: ".loc_count" , |
| 396 | callback: [process_sp, &thread_id_map](const ValueObjectSP &o, |
| 397 | const StructuredData::DictionarySP &dict) { |
| 398 | dict->AddIntegerItem( |
| 399 | key: "index" , |
| 400 | value: o->GetValueForExpressionPath(expression: ".idx" )->GetValueAsUnsigned(fail_value: 0)); |
| 401 | dict->AddStringItem(key: "type" , value: RetrieveString(return_value_sp: o, process_sp, expression_path: ".type" )); |
| 402 | dict->AddIntegerItem( |
| 403 | key: "address" , |
| 404 | value: o->GetValueForExpressionPath(expression: ".addr" )->GetValueAsUnsigned(fail_value: 0)); |
| 405 | dict->AddIntegerItem( |
| 406 | key: "start" , |
| 407 | value: o->GetValueForExpressionPath(expression: ".start" )->GetValueAsUnsigned(fail_value: 0)); |
| 408 | dict->AddIntegerItem( |
| 409 | key: "size" , |
| 410 | value: o->GetValueForExpressionPath(expression: ".size" )->GetValueAsUnsigned(fail_value: 0)); |
| 411 | dict->AddIntegerItem( |
| 412 | key: "thread_id" , |
| 413 | value: Renumber( |
| 414 | id: o->GetValueForExpressionPath(expression: ".tid" )->GetValueAsUnsigned(fail_value: 0), |
| 415 | thread_id_map)); |
| 416 | dict->AddIntegerItem( |
| 417 | key: "file_descriptor" , |
| 418 | value: o->GetValueForExpressionPath(expression: ".fd" )->GetValueAsUnsigned(fail_value: 0)); |
| 419 | dict->AddIntegerItem(key: "suppressable" , |
| 420 | value: o->GetValueForExpressionPath(expression: ".suppressable" ) |
| 421 | ->GetValueAsUnsigned(fail_value: 0)); |
| 422 | dict->AddItem(key: "trace" , value_sp: CreateStackTrace(o)); |
| 423 | dict->AddStringItem(key: "object_type" , |
| 424 | value: RetrieveString(return_value_sp: o, process_sp, expression_path: ".object_type" )); |
| 425 | }); |
| 426 | dict->AddItem(key: "locs" , value_sp: locs); |
| 427 | |
| 428 | StructuredData::ArraySP mutexes = ConvertToStructuredArray( |
| 429 | return_value_sp: main_value, items_name: ".mutexes" , count_name: ".mutex_count" , |
| 430 | callback: [](const ValueObjectSP &o, const StructuredData::DictionarySP &dict) { |
| 431 | dict->AddIntegerItem( |
| 432 | key: "index" , |
| 433 | value: o->GetValueForExpressionPath(expression: ".idx" )->GetValueAsUnsigned(fail_value: 0)); |
| 434 | dict->AddIntegerItem( |
| 435 | key: "mutex_id" , |
| 436 | value: o->GetValueForExpressionPath(expression: ".mutex_id" )->GetValueAsUnsigned(fail_value: 0)); |
| 437 | dict->AddIntegerItem( |
| 438 | key: "address" , |
| 439 | value: o->GetValueForExpressionPath(expression: ".addr" )->GetValueAsUnsigned(fail_value: 0)); |
| 440 | dict->AddIntegerItem( |
| 441 | key: "destroyed" , |
| 442 | value: o->GetValueForExpressionPath(expression: ".destroyed" )->GetValueAsUnsigned(fail_value: 0)); |
| 443 | dict->AddItem(key: "trace" , value_sp: CreateStackTrace(o)); |
| 444 | }); |
| 445 | dict->AddItem(key: "mutexes" , value_sp: mutexes); |
| 446 | |
| 447 | StructuredData::ArraySP threads = ConvertToStructuredArray( |
| 448 | return_value_sp: main_value, items_name: ".threads" , count_name: ".thread_count" , |
| 449 | callback: [process_sp, &thread_id_map](const ValueObjectSP &o, |
| 450 | const StructuredData::DictionarySP &dict) { |
| 451 | dict->AddIntegerItem( |
| 452 | key: "index" , |
| 453 | value: o->GetValueForExpressionPath(expression: ".idx" )->GetValueAsUnsigned(fail_value: 0)); |
| 454 | dict->AddIntegerItem( |
| 455 | key: "thread_id" , |
| 456 | value: Renumber( |
| 457 | id: o->GetValueForExpressionPath(expression: ".tid" )->GetValueAsUnsigned(fail_value: 0), |
| 458 | thread_id_map)); |
| 459 | dict->AddIntegerItem( |
| 460 | key: "thread_os_id" , |
| 461 | value: o->GetValueForExpressionPath(expression: ".os_id" )->GetValueAsUnsigned(fail_value: 0)); |
| 462 | dict->AddIntegerItem( |
| 463 | key: "running" , |
| 464 | value: o->GetValueForExpressionPath(expression: ".running" )->GetValueAsUnsigned(fail_value: 0)); |
| 465 | dict->AddStringItem(key: "name" , value: RetrieveString(return_value_sp: o, process_sp, expression_path: ".name" )); |
| 466 | dict->AddIntegerItem( |
| 467 | key: "parent_thread_id" , |
| 468 | value: Renumber(id: o->GetValueForExpressionPath(expression: ".parent_tid" ) |
| 469 | ->GetValueAsUnsigned(fail_value: 0), |
| 470 | thread_id_map)); |
| 471 | dict->AddItem(key: "trace" , value_sp: CreateStackTrace(o)); |
| 472 | }); |
| 473 | dict->AddItem(key: "threads" , value_sp: threads); |
| 474 | |
| 475 | StructuredData::ArraySP unique_tids = ConvertToStructuredArray( |
| 476 | return_value_sp: main_value, items_name: ".unique_tids" , count_name: ".unique_tid_count" , |
| 477 | callback: [&thread_id_map](const ValueObjectSP &o, |
| 478 | const StructuredData::DictionarySP &dict) { |
| 479 | dict->AddIntegerItem( |
| 480 | key: "index" , |
| 481 | value: o->GetValueForExpressionPath(expression: ".idx" )->GetValueAsUnsigned(fail_value: 0)); |
| 482 | dict->AddIntegerItem( |
| 483 | key: "tid" , |
| 484 | value: Renumber( |
| 485 | id: o->GetValueForExpressionPath(expression: ".tid" )->GetValueAsUnsigned(fail_value: 0), |
| 486 | thread_id_map)); |
| 487 | }); |
| 488 | dict->AddItem(key: "unique_tids" , value_sp: unique_tids); |
| 489 | |
| 490 | return dict; |
| 491 | } |
| 492 | |
| 493 | std::string |
| 494 | InstrumentationRuntimeTSan::FormatDescription(StructuredData::ObjectSP report) { |
| 495 | std::string description = std::string(report->GetAsDictionary() |
| 496 | ->GetValueForKey(key: "issue_type" ) |
| 497 | ->GetAsString() |
| 498 | ->GetValue()); |
| 499 | |
| 500 | if (description == "data-race" ) { |
| 501 | return "Data race" ; |
| 502 | } else if (description == "data-race-vptr" ) { |
| 503 | return "Data race on C++ virtual pointer" ; |
| 504 | } else if (description == "heap-use-after-free" ) { |
| 505 | return "Use of deallocated memory" ; |
| 506 | } else if (description == "heap-use-after-free-vptr" ) { |
| 507 | return "Use of deallocated C++ virtual pointer" ; |
| 508 | } else if (description == "thread-leak" ) { |
| 509 | return "Thread leak" ; |
| 510 | } else if (description == "locked-mutex-destroy" ) { |
| 511 | return "Destruction of a locked mutex" ; |
| 512 | } else if (description == "mutex-double-lock" ) { |
| 513 | return "Double lock of a mutex" ; |
| 514 | } else if (description == "mutex-invalid-access" ) { |
| 515 | return "Use of an uninitialized or destroyed mutex" ; |
| 516 | } else if (description == "mutex-bad-unlock" ) { |
| 517 | return "Unlock of an unlocked mutex (or by a wrong thread)" ; |
| 518 | } else if (description == "mutex-bad-read-lock" ) { |
| 519 | return "Read lock of a write locked mutex" ; |
| 520 | } else if (description == "mutex-bad-read-unlock" ) { |
| 521 | return "Read unlock of a write locked mutex" ; |
| 522 | } else if (description == "signal-unsafe-call" ) { |
| 523 | return "Signal-unsafe call inside a signal handler" ; |
| 524 | } else if (description == "errno-in-signal-handler" ) { |
| 525 | return "Overwrite of errno in a signal handler" ; |
| 526 | } else if (description == "lock-order-inversion" ) { |
| 527 | return "Lock order inversion (potential deadlock)" ; |
| 528 | } else if (description == "external-race" ) { |
| 529 | return "Race on a library object" ; |
| 530 | } else if (description == "swift-access-race" ) { |
| 531 | return "Swift access race" ; |
| 532 | } |
| 533 | |
| 534 | // for unknown report codes just show the code |
| 535 | return description; |
| 536 | } |
| 537 | |
| 538 | static std::string Sprintf(const char *format, ...) { |
| 539 | StreamString s; |
| 540 | va_list args; |
| 541 | va_start(args, format); |
| 542 | s.PrintfVarArg(format, args); |
| 543 | va_end(args); |
| 544 | return std::string(s.GetString()); |
| 545 | } |
| 546 | |
| 547 | static std::string GetSymbolNameFromAddress(ProcessSP process_sp, addr_t addr) { |
| 548 | lldb_private::Address so_addr; |
| 549 | if (!process_sp->GetTarget().ResolveLoadAddress(load_addr: addr, so_addr)) |
| 550 | return "" ; |
| 551 | |
| 552 | lldb_private::Symbol *symbol = so_addr.CalculateSymbolContextSymbol(); |
| 553 | if (!symbol) |
| 554 | return "" ; |
| 555 | |
| 556 | std::string sym_name = symbol->GetName().GetCString(); |
| 557 | return sym_name; |
| 558 | } |
| 559 | |
| 560 | static void GetSymbolDeclarationFromAddress(ProcessSP process_sp, addr_t addr, |
| 561 | Declaration &decl) { |
| 562 | lldb_private::Address so_addr; |
| 563 | if (!process_sp->GetTarget().ResolveLoadAddress(load_addr: addr, so_addr)) |
| 564 | return; |
| 565 | |
| 566 | lldb_private::Symbol *symbol = so_addr.CalculateSymbolContextSymbol(); |
| 567 | if (!symbol) |
| 568 | return; |
| 569 | |
| 570 | ConstString sym_name = symbol->GetMangled().GetName(preference: Mangled::ePreferMangled); |
| 571 | |
| 572 | ModuleSP module = symbol->CalculateSymbolContextModule(); |
| 573 | if (!module) |
| 574 | return; |
| 575 | |
| 576 | VariableList var_list; |
| 577 | module->FindGlobalVariables(name: sym_name, parent_decl_ctx: CompilerDeclContext(), max_matches: 1U, variable_list&: var_list); |
| 578 | if (var_list.GetSize() < 1) |
| 579 | return; |
| 580 | |
| 581 | VariableSP var = var_list.GetVariableAtIndex(idx: 0); |
| 582 | decl = var->GetDeclaration(); |
| 583 | } |
| 584 | |
| 585 | addr_t InstrumentationRuntimeTSan::GetFirstNonInternalFramePc( |
| 586 | StructuredData::ObjectSP trace, bool skip_one_frame) { |
| 587 | ProcessSP process_sp = GetProcessSP(); |
| 588 | ModuleSP runtime_module_sp = GetRuntimeModuleSP(); |
| 589 | |
| 590 | StructuredData::Array *trace_array = trace->GetAsArray(); |
| 591 | for (size_t i = 0; i < trace_array->GetSize(); i++) { |
| 592 | if (skip_one_frame && i == 0) |
| 593 | continue; |
| 594 | |
| 595 | auto maybe_addr = trace_array->GetItemAtIndexAsInteger<addr_t>(idx: i); |
| 596 | if (!maybe_addr) |
| 597 | continue; |
| 598 | addr_t addr = *maybe_addr; |
| 599 | |
| 600 | lldb_private::Address so_addr; |
| 601 | if (!process_sp->GetTarget().ResolveLoadAddress(load_addr: addr, so_addr)) |
| 602 | continue; |
| 603 | |
| 604 | if (so_addr.GetModule() == runtime_module_sp) |
| 605 | continue; |
| 606 | |
| 607 | return addr; |
| 608 | } |
| 609 | |
| 610 | return 0; |
| 611 | } |
| 612 | |
| 613 | std::string |
| 614 | InstrumentationRuntimeTSan::GenerateSummary(StructuredData::ObjectSP report) { |
| 615 | ProcessSP process_sp = GetProcessSP(); |
| 616 | |
| 617 | std::string summary = std::string(report->GetAsDictionary() |
| 618 | ->GetValueForKey(key: "description" ) |
| 619 | ->GetAsString() |
| 620 | ->GetValue()); |
| 621 | bool skip_one_frame = |
| 622 | report->GetObjectForDotSeparatedPath(path: "issue_type" )->GetStringValue() == |
| 623 | "external-race" ; |
| 624 | |
| 625 | addr_t pc = 0; |
| 626 | if (report->GetAsDictionary() |
| 627 | ->GetValueForKey(key: "mops" ) |
| 628 | ->GetAsArray() |
| 629 | ->GetSize() > 0) |
| 630 | pc = GetFirstNonInternalFramePc(trace: report->GetAsDictionary() |
| 631 | ->GetValueForKey(key: "mops" ) |
| 632 | ->GetAsArray() |
| 633 | ->GetItemAtIndex(idx: 0) |
| 634 | ->GetAsDictionary() |
| 635 | ->GetValueForKey(key: "trace" ), |
| 636 | skip_one_frame); |
| 637 | |
| 638 | if (report->GetAsDictionary() |
| 639 | ->GetValueForKey(key: "stacks" ) |
| 640 | ->GetAsArray() |
| 641 | ->GetSize() > 0) |
| 642 | pc = GetFirstNonInternalFramePc(trace: report->GetAsDictionary() |
| 643 | ->GetValueForKey(key: "stacks" ) |
| 644 | ->GetAsArray() |
| 645 | ->GetItemAtIndex(idx: 0) |
| 646 | ->GetAsDictionary() |
| 647 | ->GetValueForKey(key: "trace" ), |
| 648 | skip_one_frame); |
| 649 | |
| 650 | if (pc != 0) { |
| 651 | summary = summary + " in " + GetSymbolNameFromAddress(process_sp, addr: pc); |
| 652 | } |
| 653 | |
| 654 | if (report->GetAsDictionary() |
| 655 | ->GetValueForKey(key: "locs" ) |
| 656 | ->GetAsArray() |
| 657 | ->GetSize() > 0) { |
| 658 | StructuredData::ObjectSP loc = report->GetAsDictionary() |
| 659 | ->GetValueForKey(key: "locs" ) |
| 660 | ->GetAsArray() |
| 661 | ->GetItemAtIndex(idx: 0); |
| 662 | std::string object_type = std::string(loc->GetAsDictionary() |
| 663 | ->GetValueForKey(key: "object_type" ) |
| 664 | ->GetAsString() |
| 665 | ->GetValue()); |
| 666 | if (!object_type.empty()) { |
| 667 | summary = "Race on " + object_type + " object" ; |
| 668 | } |
| 669 | addr_t addr = loc->GetAsDictionary() |
| 670 | ->GetValueForKey(key: "address" ) |
| 671 | ->GetUnsignedIntegerValue(); |
| 672 | if (addr == 0) |
| 673 | addr = loc->GetAsDictionary() |
| 674 | ->GetValueForKey(key: "start" ) |
| 675 | ->GetUnsignedIntegerValue(); |
| 676 | |
| 677 | if (addr != 0) { |
| 678 | std::string global_name = GetSymbolNameFromAddress(process_sp, addr); |
| 679 | if (!global_name.empty()) { |
| 680 | summary = summary + " at " + global_name; |
| 681 | } else { |
| 682 | summary = summary + " at " + Sprintf(format: "0x%llx" , addr); |
| 683 | } |
| 684 | } else { |
| 685 | int fd = loc->GetAsDictionary() |
| 686 | ->GetValueForKey(key: "file_descriptor" ) |
| 687 | ->GetSignedIntegerValue(); |
| 688 | if (fd != 0) { |
| 689 | summary = summary + " on file descriptor " + Sprintf(format: "%d" , fd); |
| 690 | } |
| 691 | } |
| 692 | } |
| 693 | |
| 694 | return summary; |
| 695 | } |
| 696 | |
| 697 | addr_t InstrumentationRuntimeTSan::GetMainRacyAddress( |
| 698 | StructuredData::ObjectSP report) { |
| 699 | addr_t result = (addr_t)-1; |
| 700 | |
| 701 | report->GetObjectForDotSeparatedPath(path: "mops" )->GetAsArray()->ForEach( |
| 702 | foreach_callback: [&result](StructuredData::Object *o) -> bool { |
| 703 | addr_t addr = o->GetObjectForDotSeparatedPath(path: "address" ) |
| 704 | ->GetUnsignedIntegerValue(); |
| 705 | if (addr < result) |
| 706 | result = addr; |
| 707 | return true; |
| 708 | }); |
| 709 | |
| 710 | return (result == (addr_t)-1) ? 0 : result; |
| 711 | } |
| 712 | |
| 713 | std::string InstrumentationRuntimeTSan::GetLocationDescription( |
| 714 | StructuredData::ObjectSP report, addr_t &global_addr, |
| 715 | std::string &global_name, std::string &filename, uint32_t &line) { |
| 716 | std::string result; |
| 717 | |
| 718 | ProcessSP process_sp = GetProcessSP(); |
| 719 | |
| 720 | if (report->GetAsDictionary() |
| 721 | ->GetValueForKey(key: "locs" ) |
| 722 | ->GetAsArray() |
| 723 | ->GetSize() > 0) { |
| 724 | StructuredData::ObjectSP loc = report->GetAsDictionary() |
| 725 | ->GetValueForKey(key: "locs" ) |
| 726 | ->GetAsArray() |
| 727 | ->GetItemAtIndex(idx: 0); |
| 728 | std::string type = std::string( |
| 729 | loc->GetAsDictionary()->GetValueForKey(key: "type" )->GetStringValue()); |
| 730 | if (type == "global" ) { |
| 731 | global_addr = loc->GetAsDictionary() |
| 732 | ->GetValueForKey(key: "address" ) |
| 733 | ->GetUnsignedIntegerValue(); |
| 734 | |
| 735 | global_name = GetSymbolNameFromAddress(process_sp, addr: global_addr); |
| 736 | if (!global_name.empty()) { |
| 737 | result = Sprintf(format: "'%s' is a global variable (0x%llx)" , |
| 738 | global_name.c_str(), global_addr); |
| 739 | } else { |
| 740 | result = Sprintf(format: "0x%llx is a global variable" , global_addr); |
| 741 | } |
| 742 | |
| 743 | Declaration decl; |
| 744 | GetSymbolDeclarationFromAddress(process_sp, addr: global_addr, decl); |
| 745 | if (decl.GetFile()) { |
| 746 | filename = decl.GetFile().GetPath(); |
| 747 | line = decl.GetLine(); |
| 748 | } |
| 749 | } else if (type == "heap" ) { |
| 750 | addr_t addr = loc->GetAsDictionary() |
| 751 | ->GetValueForKey(key: "start" ) |
| 752 | ->GetUnsignedIntegerValue(); |
| 753 | |
| 754 | size_t size = loc->GetAsDictionary() |
| 755 | ->GetValueForKey(key: "size" ) |
| 756 | ->GetUnsignedIntegerValue(); |
| 757 | |
| 758 | std::string object_type = std::string(loc->GetAsDictionary() |
| 759 | ->GetValueForKey(key: "object_type" ) |
| 760 | ->GetAsString() |
| 761 | ->GetValue()); |
| 762 | if (!object_type.empty()) { |
| 763 | result = Sprintf(format: "Location is a %ld-byte %s object at 0x%llx" , size, |
| 764 | object_type.c_str(), addr); |
| 765 | } else { |
| 766 | result = |
| 767 | Sprintf(format: "Location is a %ld-byte heap object at 0x%llx" , size, addr); |
| 768 | } |
| 769 | } else if (type == "stack" ) { |
| 770 | lldb::tid_t tid = loc->GetAsDictionary() |
| 771 | ->GetValueForKey(key: "thread_id" ) |
| 772 | ->GetUnsignedIntegerValue(); |
| 773 | |
| 774 | result = Sprintf(format: "Location is stack of thread %d" , tid); |
| 775 | } else if (type == "tls" ) { |
| 776 | lldb::tid_t tid = loc->GetAsDictionary() |
| 777 | ->GetValueForKey(key: "thread_id" ) |
| 778 | ->GetUnsignedIntegerValue(); |
| 779 | |
| 780 | result = Sprintf(format: "Location is TLS of thread %d" , tid); |
| 781 | } else if (type == "fd" ) { |
| 782 | int fd = loc->GetAsDictionary() |
| 783 | ->GetValueForKey(key: "file_descriptor" ) |
| 784 | ->GetSignedIntegerValue(); |
| 785 | |
| 786 | result = Sprintf(format: "Location is file descriptor %d" , fd); |
| 787 | } |
| 788 | } |
| 789 | |
| 790 | return result; |
| 791 | } |
| 792 | |
| 793 | bool InstrumentationRuntimeTSan::NotifyBreakpointHit( |
| 794 | void *baton, StoppointCallbackContext *context, user_id_t break_id, |
| 795 | user_id_t break_loc_id) { |
| 796 | assert(baton && "null baton" ); |
| 797 | if (!baton) |
| 798 | return false; |
| 799 | |
| 800 | InstrumentationRuntimeTSan *const instance = |
| 801 | static_cast<InstrumentationRuntimeTSan *>(baton); |
| 802 | |
| 803 | ProcessSP process_sp = instance->GetProcessSP(); |
| 804 | |
| 805 | if (process_sp->GetModIDRef().IsLastResumeForUserExpression()) |
| 806 | return false; |
| 807 | |
| 808 | StructuredData::ObjectSP report = |
| 809 | instance->RetrieveReportData(exe_ctx_ref: context->exe_ctx_ref); |
| 810 | std::string stop_reason_description = |
| 811 | "unknown thread sanitizer fault (unable to extract thread sanitizer " |
| 812 | "report)" ; |
| 813 | if (report) { |
| 814 | std::string issue_description = instance->FormatDescription(report); |
| 815 | report->GetAsDictionary()->AddStringItem(key: "description" , value: issue_description); |
| 816 | stop_reason_description = issue_description + " detected" ; |
| 817 | report->GetAsDictionary()->AddStringItem(key: "stop_description" , |
| 818 | value: stop_reason_description); |
| 819 | std::string summary = instance->GenerateSummary(report); |
| 820 | report->GetAsDictionary()->AddStringItem(key: "summary" , value: summary); |
| 821 | addr_t main_address = instance->GetMainRacyAddress(report); |
| 822 | report->GetAsDictionary()->AddIntegerItem(key: "memory_address" , value: main_address); |
| 823 | |
| 824 | addr_t global_addr = 0; |
| 825 | std::string global_name; |
| 826 | std::string location_filename; |
| 827 | uint32_t location_line = 0; |
| 828 | std::string location_description = instance->GetLocationDescription( |
| 829 | report, global_addr, global_name, filename&: location_filename, line&: location_line); |
| 830 | report->GetAsDictionary()->AddStringItem(key: "location_description" , |
| 831 | value: location_description); |
| 832 | if (global_addr != 0) { |
| 833 | report->GetAsDictionary()->AddIntegerItem(key: "global_address" , value: global_addr); |
| 834 | } |
| 835 | if (!global_name.empty()) { |
| 836 | report->GetAsDictionary()->AddStringItem(key: "global_name" , value: global_name); |
| 837 | } |
| 838 | if (location_filename != "" ) { |
| 839 | report->GetAsDictionary()->AddStringItem(key: "location_filename" , |
| 840 | value: location_filename); |
| 841 | report->GetAsDictionary()->AddIntegerItem(key: "location_line" , value: location_line); |
| 842 | } |
| 843 | |
| 844 | bool all_addresses_are_same = true; |
| 845 | report->GetObjectForDotSeparatedPath(path: "mops" )->GetAsArray()->ForEach( |
| 846 | foreach_callback: [&all_addresses_are_same, |
| 847 | main_address](StructuredData::Object *o) -> bool { |
| 848 | addr_t addr = o->GetObjectForDotSeparatedPath(path: "address" ) |
| 849 | ->GetUnsignedIntegerValue(); |
| 850 | if (main_address != addr) |
| 851 | all_addresses_are_same = false; |
| 852 | return true; |
| 853 | }); |
| 854 | report->GetAsDictionary()->AddBooleanItem(key: "all_addresses_are_same" , |
| 855 | value: all_addresses_are_same); |
| 856 | } |
| 857 | |
| 858 | // Make sure this is the right process |
| 859 | if (process_sp && process_sp == context->exe_ctx_ref.GetProcessSP()) { |
| 860 | ThreadSP thread_sp = context->exe_ctx_ref.GetThreadSP(); |
| 861 | if (thread_sp) |
| 862 | thread_sp->SetStopInfo( |
| 863 | InstrumentationRuntimeStopInfo:: |
| 864 | CreateStopReasonWithInstrumentationData( |
| 865 | thread&: *thread_sp, description: stop_reason_description, additional_data: report)); |
| 866 | |
| 867 | lldb::StreamSP s = |
| 868 | process_sp->GetTarget().GetDebugger().GetAsyncOutputStream(); |
| 869 | s->Printf(format: "ThreadSanitizer report breakpoint hit. Use 'thread " |
| 870 | "info -s' to get extended information about the " |
| 871 | "report.\n" ); |
| 872 | |
| 873 | return true; // Return true to stop the target |
| 874 | } |
| 875 | return false; // Let target run |
| 876 | } |
| 877 | |
| 878 | const RegularExpression & |
| 879 | InstrumentationRuntimeTSan::GetPatternForRuntimeLibrary() { |
| 880 | static RegularExpression regex(llvm::StringRef("libclang_rt.tsan_" )); |
| 881 | return regex; |
| 882 | } |
| 883 | |
| 884 | bool InstrumentationRuntimeTSan::CheckIfRuntimeIsValid( |
| 885 | const lldb::ModuleSP module_sp) { |
| 886 | static ConstString g_tsan_get_current_report("__tsan_get_current_report" ); |
| 887 | const Symbol *symbol = module_sp->FindFirstSymbolWithNameAndType( |
| 888 | name: g_tsan_get_current_report, symbol_type: lldb::eSymbolTypeAny); |
| 889 | return symbol != nullptr; |
| 890 | } |
| 891 | |
| 892 | void InstrumentationRuntimeTSan::Activate() { |
| 893 | if (IsActive()) |
| 894 | return; |
| 895 | |
| 896 | ProcessSP process_sp = GetProcessSP(); |
| 897 | if (!process_sp) |
| 898 | return; |
| 899 | |
| 900 | ConstString symbol_name("__tsan_on_report" ); |
| 901 | const Symbol *symbol = GetRuntimeModuleSP()->FindFirstSymbolWithNameAndType( |
| 902 | name: symbol_name, symbol_type: eSymbolTypeCode); |
| 903 | |
| 904 | if (symbol == nullptr) |
| 905 | return; |
| 906 | |
| 907 | if (!symbol->ValueIsAddress() || !symbol->GetAddressRef().IsValid()) |
| 908 | return; |
| 909 | |
| 910 | Target &target = process_sp->GetTarget(); |
| 911 | addr_t symbol_address = symbol->GetAddressRef().GetOpcodeLoadAddress(target: &target); |
| 912 | |
| 913 | if (symbol_address == LLDB_INVALID_ADDRESS) |
| 914 | return; |
| 915 | |
| 916 | const bool internal = true; |
| 917 | const bool hardware = false; |
| 918 | const bool sync = false; |
| 919 | Breakpoint *breakpoint = |
| 920 | process_sp->GetTarget() |
| 921 | .CreateBreakpoint(load_addr: symbol_address, internal, request_hardware: hardware) |
| 922 | .get(); |
| 923 | breakpoint->SetCallback(callback: InstrumentationRuntimeTSan::NotifyBreakpointHit, baton: this, |
| 924 | is_synchronous: sync); |
| 925 | breakpoint->SetBreakpointKind("thread-sanitizer-report" ); |
| 926 | SetBreakpointID(breakpoint->GetID()); |
| 927 | |
| 928 | SetActive(true); |
| 929 | } |
| 930 | |
| 931 | void InstrumentationRuntimeTSan::Deactivate() { |
| 932 | if (GetBreakpointID() != LLDB_INVALID_BREAK_ID) { |
| 933 | ProcessSP process_sp = GetProcessSP(); |
| 934 | if (process_sp) { |
| 935 | process_sp->GetTarget().RemoveBreakpointByID(break_id: GetBreakpointID()); |
| 936 | SetBreakpointID(LLDB_INVALID_BREAK_ID); |
| 937 | } |
| 938 | } |
| 939 | SetActive(false); |
| 940 | } |
| 941 | static std::string GenerateThreadName(const std::string &path, |
| 942 | StructuredData::Object *o, |
| 943 | StructuredData::ObjectSP main_info) { |
| 944 | std::string result = "additional information" ; |
| 945 | |
| 946 | if (path == "mops" ) { |
| 947 | size_t size = |
| 948 | o->GetObjectForDotSeparatedPath(path: "size" )->GetUnsignedIntegerValue(); |
| 949 | lldb::tid_t thread_id = |
| 950 | o->GetObjectForDotSeparatedPath(path: "thread_id" )->GetUnsignedIntegerValue(); |
| 951 | bool is_write = |
| 952 | o->GetObjectForDotSeparatedPath(path: "is_write" )->GetBooleanValue(); |
| 953 | bool is_atomic = |
| 954 | o->GetObjectForDotSeparatedPath(path: "is_atomic" )->GetBooleanValue(); |
| 955 | addr_t addr = |
| 956 | o->GetObjectForDotSeparatedPath(path: "address" )->GetUnsignedIntegerValue(); |
| 957 | |
| 958 | std::string addr_string = Sprintf(format: " at 0x%llx" , addr); |
| 959 | |
| 960 | if (main_info->GetObjectForDotSeparatedPath(path: "all_addresses_are_same" ) |
| 961 | ->GetBooleanValue()) { |
| 962 | addr_string = "" ; |
| 963 | } |
| 964 | |
| 965 | if (main_info->GetObjectForDotSeparatedPath(path: "issue_type" ) |
| 966 | ->GetStringValue() == "external-race" ) { |
| 967 | result = Sprintf(format: "%s access by thread %d" , |
| 968 | is_write ? "mutating" : "read-only" , thread_id); |
| 969 | } else if (main_info->GetObjectForDotSeparatedPath(path: "issue_type" ) |
| 970 | ->GetStringValue() == "swift-access-race" ) { |
| 971 | result = Sprintf(format: "modifying access by thread %d" , thread_id); |
| 972 | } else { |
| 973 | result = Sprintf(format: "%s%s of size %zu%s by thread %" PRIu64, |
| 974 | is_atomic ? "atomic " : "" , is_write ? "write" : "read" , |
| 975 | size, addr_string.c_str(), thread_id); |
| 976 | } |
| 977 | } |
| 978 | |
| 979 | if (path == "threads" ) { |
| 980 | lldb::tid_t thread_id = |
| 981 | o->GetObjectForDotSeparatedPath(path: "thread_id" )->GetUnsignedIntegerValue(); |
| 982 | result = Sprintf(format: "Thread %zu created" , thread_id); |
| 983 | } |
| 984 | |
| 985 | if (path == "locs" ) { |
| 986 | std::string type = std::string( |
| 987 | o->GetAsDictionary()->GetValueForKey(key: "type" )->GetStringValue()); |
| 988 | lldb::tid_t thread_id = |
| 989 | o->GetObjectForDotSeparatedPath(path: "thread_id" )->GetUnsignedIntegerValue(); |
| 990 | int fd = o->GetObjectForDotSeparatedPath(path: "file_descriptor" ) |
| 991 | ->GetSignedIntegerValue(); |
| 992 | if (type == "heap" ) { |
| 993 | result = Sprintf(format: "Heap block allocated by thread %" PRIu64, thread_id); |
| 994 | } else if (type == "fd" ) { |
| 995 | result = Sprintf(format: "File descriptor %d created by thread %" PRIu64, fd, |
| 996 | thread_id); |
| 997 | } |
| 998 | } |
| 999 | |
| 1000 | if (path == "mutexes" ) { |
| 1001 | int mutex_id = |
| 1002 | o->GetObjectForDotSeparatedPath(path: "mutex_id" )->GetSignedIntegerValue(); |
| 1003 | |
| 1004 | result = Sprintf(format: "Mutex M%d created" , mutex_id); |
| 1005 | } |
| 1006 | |
| 1007 | if (path == "stacks" ) { |
| 1008 | lldb::tid_t thread_id = |
| 1009 | o->GetObjectForDotSeparatedPath(path: "thread_id" )->GetUnsignedIntegerValue(); |
| 1010 | result = Sprintf(format: "Thread %" PRIu64, thread_id); |
| 1011 | } |
| 1012 | |
| 1013 | result[0] = toupper(c: result[0]); |
| 1014 | |
| 1015 | return result; |
| 1016 | } |
| 1017 | |
| 1018 | static void AddThreadsForPath(const std::string &path, |
| 1019 | ThreadCollectionSP threads, ProcessSP process_sp, |
| 1020 | StructuredData::ObjectSP info) { |
| 1021 | info->GetObjectForDotSeparatedPath(path)->GetAsArray()->ForEach( |
| 1022 | foreach_callback: [process_sp, threads, path, info](StructuredData::Object *o) -> bool { |
| 1023 | std::vector<lldb::addr_t> pcs; |
| 1024 | o->GetObjectForDotSeparatedPath(path: "trace" )->GetAsArray()->ForEach( |
| 1025 | foreach_callback: [&pcs](StructuredData::Object *pc) -> bool { |
| 1026 | pcs.push_back(x: pc->GetUnsignedIntegerValue()); |
| 1027 | return true; |
| 1028 | }); |
| 1029 | |
| 1030 | if (pcs.size() == 0) |
| 1031 | return true; |
| 1032 | |
| 1033 | StructuredData::ObjectSP thread_id_obj = |
| 1034 | o->GetObjectForDotSeparatedPath(path: "thread_os_id" ); |
| 1035 | lldb::tid_t tid = |
| 1036 | thread_id_obj ? thread_id_obj->GetUnsignedIntegerValue() : 0; |
| 1037 | |
| 1038 | ThreadSP new_thread_sp = |
| 1039 | std::make_shared<HistoryThread>(args&: *process_sp, args&: tid, args&: pcs); |
| 1040 | new_thread_sp->SetName(GenerateThreadName(path, o, main_info: info).c_str()); |
| 1041 | |
| 1042 | // Save this in the Process' ExtendedThreadList so a strong pointer |
| 1043 | // retains the object |
| 1044 | process_sp->GetExtendedThreadList().AddThread(thread_sp: new_thread_sp); |
| 1045 | threads->AddThread(thread_sp: new_thread_sp); |
| 1046 | |
| 1047 | return true; |
| 1048 | }); |
| 1049 | } |
| 1050 | |
| 1051 | lldb::ThreadCollectionSP |
| 1052 | InstrumentationRuntimeTSan::GetBacktracesFromExtendedStopInfo( |
| 1053 | StructuredData::ObjectSP info) { |
| 1054 | |
| 1055 | ThreadCollectionSP threads = std::make_shared<ThreadCollection>(); |
| 1056 | |
| 1057 | if (info->GetObjectForDotSeparatedPath(path: "instrumentation_class" ) |
| 1058 | ->GetStringValue() != "ThreadSanitizer" ) |
| 1059 | return threads; |
| 1060 | |
| 1061 | ProcessSP process_sp = GetProcessSP(); |
| 1062 | |
| 1063 | AddThreadsForPath(path: "stacks" , threads, process_sp, info); |
| 1064 | AddThreadsForPath(path: "mops" , threads, process_sp, info); |
| 1065 | AddThreadsForPath(path: "locs" , threads, process_sp, info); |
| 1066 | AddThreadsForPath(path: "mutexes" , threads, process_sp, info); |
| 1067 | AddThreadsForPath(path: "threads" , threads, process_sp, info); |
| 1068 | |
| 1069 | return threads; |
| 1070 | } |
| 1071 | |