1 //===-- IntelPTCollector.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 "IntelPTCollector.h" 10 11 #include "Perf.h" 12 #include "Procfs.h" 13 14 #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" 15 #include "lldb/Host/linux/Support.h" 16 #include "lldb/Utility/StreamString.h" 17 18 #include "llvm/ADT/StringRef.h" 19 #include "llvm/Support/Error.h" 20 #include "llvm/Support/MathExtras.h" 21 22 #include <algorithm> 23 #include <cstddef> 24 #include <fstream> 25 #include <linux/perf_event.h> 26 #include <sstream> 27 #include <sys/ioctl.h> 28 #include <sys/syscall.h> 29 30 using namespace lldb; 31 using namespace lldb_private; 32 using namespace process_linux; 33 using namespace llvm; 34 35 const char *kOSEventIntelPTTypeFile = 36 "/sys/bus/event_source/devices/intel_pt/type"; 37 38 const char *kPSBPeriodCapFile = 39 "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc"; 40 41 const char *kPSBPeriodValidValuesFile = 42 "/sys/bus/event_source/devices/intel_pt/caps/psb_periods"; 43 44 const char *kTSCBitOffsetFile = 45 "/sys/bus/event_source/devices/intel_pt/format/tsc"; 46 47 const char *kPSBPeriodBitOffsetFile = 48 "/sys/bus/event_source/devices/intel_pt/format/psb_period"; 49 50 enum IntelPTConfigFileType { 51 Hex = 0, 52 // 0 or 1 53 ZeroOne, 54 Decimal, 55 // a bit index file always starts with the prefix config: following by an int, 56 // which represents the offset of the perf_event_attr.config value where to 57 // store a given configuration. 58 BitOffset 59 }; 60 61 static Expected<uint32_t> ReadIntelPTConfigFile(const char *file, 62 IntelPTConfigFileType type) { 63 ErrorOr<std::unique_ptr<MemoryBuffer>> stream = 64 MemoryBuffer::getFileAsStream(file); 65 66 if (!stream) 67 return createStringError(inconvertibleErrorCode(), 68 "Can't open the file '%s'", file); 69 70 uint32_t value = 0; 71 StringRef text_buffer = stream.get()->getBuffer(); 72 73 if (type == BitOffset) { 74 const char *prefix = "config:"; 75 if (!text_buffer.startswith(prefix)) 76 return createStringError(inconvertibleErrorCode(), 77 "The file '%s' contents doesn't start with '%s'", 78 file, prefix); 79 text_buffer = text_buffer.substr(strlen(prefix)); 80 } 81 82 auto getRadix = [&]() { 83 switch (type) { 84 case Hex: 85 return 16; 86 case ZeroOne: 87 case Decimal: 88 case BitOffset: 89 return 10; 90 } 91 llvm_unreachable("Fully covered switch above!"); 92 }; 93 94 auto createError = [&](const char *expected_value_message) { 95 return createStringError( 96 inconvertibleErrorCode(), 97 "The file '%s' has an invalid value. It should be %s.", file, 98 expected_value_message); 99 }; 100 101 if (text_buffer.trim().consumeInteger(getRadix(), value) || 102 (type == ZeroOne && value != 0 && value != 1)) { 103 switch (type) { 104 case Hex: 105 return createError("an unsigned hexadecimal int"); 106 case ZeroOne: 107 return createError("0 or 1"); 108 case Decimal: 109 case BitOffset: 110 return createError("an unsigned decimal int"); 111 } 112 } 113 return value; 114 } 115 116 /// Return the Linux perf event type for Intel PT. 117 static Expected<uint32_t> GetOSEventType() { 118 return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, 119 IntelPTConfigFileType::Decimal); 120 } 121 122 static Error CheckPsbPeriod(size_t psb_period) { 123 Expected<uint32_t> cap = 124 ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne); 125 if (!cap) 126 return cap.takeError(); 127 if (*cap == 0) 128 return createStringError(inconvertibleErrorCode(), 129 "psb_period is unsupported in the system."); 130 131 Expected<uint32_t> valid_values = ReadIntelPTConfigFile( 132 kPSBPeriodValidValuesFile, IntelPTConfigFileType::Hex); 133 if (!valid_values) 134 return valid_values.takeError(); 135 136 if (valid_values.get() & (1 << psb_period)) 137 return Error::success(); 138 139 std::ostringstream error; 140 // 0 is always a valid value 141 error << "Invalid psb_period. Valid values are: 0"; 142 uint32_t mask = valid_values.get(); 143 while (mask) { 144 int index = __builtin_ctz(mask); 145 if (index > 0) 146 error << ", " << index; 147 // clear the lowest bit 148 mask &= mask - 1; 149 } 150 error << "."; 151 return createStringError(inconvertibleErrorCode(), error.str().c_str()); 152 } 153 154 size_t IntelPTThreadTrace::GetTraceBufferSize() const { 155 #ifndef PERF_ATTR_SIZE_VER5 156 llvm_unreachable("Intel PT Linux perf event not supported"); 157 #else 158 return m_perf_event.GetAuxBuffer().size(); 159 #endif 160 } 161 162 static Expected<uint64_t> 163 GeneratePerfEventConfigValue(bool enable_tsc, Optional<size_t> psb_period) { 164 uint64_t config = 0; 165 // tsc is always supported 166 if (enable_tsc) { 167 if (Expected<uint32_t> offset = ReadIntelPTConfigFile( 168 kTSCBitOffsetFile, IntelPTConfigFileType::BitOffset)) 169 config |= 1 << *offset; 170 else 171 return offset.takeError(); 172 } 173 if (psb_period) { 174 if (Error error = CheckPsbPeriod(*psb_period)) 175 return std::move(error); 176 177 if (Expected<uint32_t> offset = ReadIntelPTConfigFile( 178 kPSBPeriodBitOffsetFile, IntelPTConfigFileType::BitOffset)) 179 config |= *psb_period << *offset; 180 else 181 return offset.takeError(); 182 } 183 return config; 184 } 185 186 llvm::Expected<perf_event_attr> 187 IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration( 188 bool enable_tsc, Optional<size_t> psb_period) { 189 perf_event_attr attr; 190 memset(&attr, 0, sizeof(attr)); 191 attr.size = sizeof(attr); 192 attr.exclude_kernel = 1; 193 attr.sample_type = PERF_SAMPLE_TIME; 194 attr.sample_id_all = 1; 195 attr.exclude_hv = 1; 196 attr.exclude_idle = 1; 197 attr.mmap = 1; 198 199 if (Expected<uint64_t> config_value = 200 GeneratePerfEventConfigValue(enable_tsc, psb_period)) { 201 attr.config = *config_value; 202 } else { 203 return config_value.takeError(); 204 } 205 206 if (Expected<uint32_t> intel_pt_type = GetOSEventType()) { 207 attr.type = *intel_pt_type; 208 } else { 209 return intel_pt_type.takeError(); 210 } 211 212 return attr; 213 } 214 215 llvm::Expected<IntelPTThreadTraceUP> 216 IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, 217 bool enable_tsc, Optional<size_t> psb_period) { 218 #ifndef PERF_ATTR_SIZE_VER5 219 llvm_unreachable("Intel PT Linux perf event not supported"); 220 #else 221 Log *log = GetLog(POSIXLog::Ptrace); 222 223 LLDB_LOG(log, "called thread id {0}", tid); 224 225 if (__builtin_popcount(buffer_size) != 1 || buffer_size < 4096) { 226 return createStringError( 227 inconvertibleErrorCode(), 228 "The trace buffer size must be a power of 2 greater than or equal to " 229 "4096 (2^12) bytes. It was %" PRIu64 ".", 230 buffer_size); 231 } 232 uint64_t page_size = getpagesize(); 233 uint64_t buffer_numpages = static_cast<uint64_t>( 234 llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); 235 236 Expected<perf_event_attr> attr = 237 IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(enable_tsc, 238 psb_period); 239 if (!attr) 240 return attr.takeError(); 241 242 LLDB_LOG(log, "buffer size {0} ", buffer_size); 243 244 if (Expected<PerfEvent> perf_event = PerfEvent::Init(*attr, tid)) { 245 if (Error mmap_err = perf_event->MmapMetadataAndBuffers(buffer_numpages, 246 buffer_numpages)) { 247 return std::move(mmap_err); 248 } 249 return IntelPTThreadTraceUP( 250 new IntelPTThreadTrace(std::move(*perf_event), tid)); 251 } else { 252 return perf_event.takeError(); 253 } 254 #endif 255 } 256 257 Expected<std::vector<uint8_t>> 258 IntelPTThreadTrace::GetIntelPTBuffer(size_t offset, size_t size) const { 259 std::vector<uint8_t> data(size, 0); 260 MutableArrayRef<uint8_t> buffer_ref(data); 261 Status error = ReadPerfTraceAux(buffer_ref, 0); 262 if (error.Fail()) 263 return error.ToError(); 264 return data; 265 } 266 267 Status 268 IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer, 269 size_t offset) const { 270 #ifndef PERF_ATTR_SIZE_VER5 271 llvm_unreachable("perf event not supported"); 272 #else 273 auto fd = m_perf_event.GetFd(); 274 perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage(); 275 // Disable the perf event to force a flush out of the CPU's internal buffer. 276 // Besides, we can guarantee that the CPU won't override any data as we are 277 // reading the buffer. 278 // 279 // The Intel documentation says: 280 // 281 // Packets are first buffered internally and then written out asynchronously. 282 // To collect packet output for postprocessing, a collector needs first to 283 // ensure that all packet data has been flushed from internal buffers. 284 // Software can ensure this by stopping packet generation by clearing 285 // IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in 286 // Section 35.2.7.2). 287 // 288 // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned 289 // in the man page of perf_event_open. 290 ioctl(fd, PERF_EVENT_IOC_DISABLE); 291 292 Log *log = GetLog(POSIXLog::Ptrace); 293 Status error; 294 uint64_t head = mmap_metadata.aux_head; 295 296 LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.aux_size, head); 297 298 /** 299 * When configured as ring buffer, the aux buffer keeps wrapping around 300 * the buffer and its not possible to detect how many times the buffer 301 * wrapped. Initially the buffer is filled with zeros,as shown below 302 * so in order to get complete buffer we first copy firstpartsize, followed 303 * by any left over part from beginning to aux_head 304 * 305 * aux_offset [d,d,d,d,d,d,d,d,0,0,0,0,0,0,0,0,0,0,0] aux_size 306 * aux_head->||<- firstpartsize ->| 307 * 308 * */ 309 310 ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(), 311 static_cast<size_t>(head), offset); 312 LLDB_LOG(log, "ReadCyclic Buffer Done"); 313 314 // Reenable tracing now we have read the buffer 315 ioctl(fd, PERF_EVENT_IOC_ENABLE); 316 return error; 317 #endif 318 } 319 320 Status 321 IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer, 322 size_t offset) const { 323 #ifndef PERF_ATTR_SIZE_VER5 324 llvm_unreachable("perf event not supported"); 325 #else 326 Log *log = GetLog(POSIXLog::Ptrace); 327 uint64_t bytes_remaining = buffer.size(); 328 Status error; 329 330 perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage(); 331 uint64_t head = mmap_metadata.data_head; 332 333 /* 334 * The data buffer and aux buffer have different implementations 335 * with respect to their definition of head pointer. In the case 336 * of Aux data buffer the head always wraps around the aux buffer 337 * and we don't need to care about it, whereas the data_head keeps 338 * increasing and needs to be wrapped by modulus operator 339 */ 340 341 LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining); 342 343 auto data_buffer = m_perf_event.GetDataBuffer(); 344 345 if (head > data_buffer.size()) { 346 head = head % data_buffer.size(); 347 LLDB_LOG(log, "Data size -{0} Head - {1}", mmap_metadata.data_size, head); 348 349 ReadCyclicBuffer(buffer, data_buffer, static_cast<size_t>(head), offset); 350 bytes_remaining -= buffer.size(); 351 } else { 352 LLDB_LOG(log, "Head - {0}", head); 353 if (offset >= head) { 354 LLDB_LOG(log, "Invalid Offset "); 355 error.SetErrorString("invalid offset"); 356 buffer = buffer.slice(buffer.size()); 357 return error; 358 } 359 360 auto data = data_buffer.slice(offset, (head - offset)); 361 auto remaining = std::copy(data.begin(), data.end(), buffer.begin()); 362 bytes_remaining -= (remaining - buffer.begin()); 363 } 364 buffer = buffer.drop_back(bytes_remaining); 365 return error; 366 #endif 367 } 368 369 void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst, 370 llvm::ArrayRef<uint8_t> src, 371 size_t src_cyc_index, size_t offset) { 372 373 Log *log = GetLog(POSIXLog::Ptrace); 374 375 if (dst.empty() || src.empty()) { 376 dst = dst.drop_back(dst.size()); 377 return; 378 } 379 380 if (dst.data() == nullptr || src.data() == nullptr) { 381 dst = dst.drop_back(dst.size()); 382 return; 383 } 384 385 if (src_cyc_index > src.size()) { 386 dst = dst.drop_back(dst.size()); 387 return; 388 } 389 390 if (offset >= src.size()) { 391 LLDB_LOG(log, "Too Big offset "); 392 dst = dst.drop_back(dst.size()); 393 return; 394 } 395 396 llvm::SmallVector<ArrayRef<uint8_t>, 2> parts = { 397 src.slice(src_cyc_index), src.take_front(src_cyc_index)}; 398 399 if (offset > parts[0].size()) { 400 parts[1] = parts[1].slice(offset - parts[0].size()); 401 parts[0] = parts[0].drop_back(parts[0].size()); 402 } else if (offset == parts[0].size()) { 403 parts[0] = parts[0].drop_back(parts[0].size()); 404 } else { 405 parts[0] = parts[0].slice(offset); 406 } 407 auto next = dst.begin(); 408 auto bytes_left = dst.size(); 409 for (auto part : parts) { 410 size_t chunk_size = std::min(part.size(), bytes_left); 411 next = std::copy_n(part.begin(), chunk_size, next); 412 bytes_left -= chunk_size; 413 } 414 dst = dst.drop_back(bytes_left); 415 } 416 417 TraceThreadState IntelPTThreadTrace::GetState() const { 418 return {static_cast<int64_t>(m_tid), 419 {TraceBinaryData{IntelPTDataKinds::kThreadTraceBuffer, 420 static_cast<int64_t>(GetTraceBufferSize())}}}; 421 } 422 423 /// IntelPTThreadTraceCollection 424 425 bool IntelPTThreadTraceCollection::TracesThread(lldb::tid_t tid) const { 426 return m_thread_traces.count(tid); 427 } 428 429 Error IntelPTThreadTraceCollection::TraceStop(lldb::tid_t tid) { 430 auto it = m_thread_traces.find(tid); 431 if (it == m_thread_traces.end()) 432 return createStringError(inconvertibleErrorCode(), 433 "Thread %" PRIu64 " not currently traced", tid); 434 m_total_buffer_size -= it->second->GetTraceBufferSize(); 435 m_thread_traces.erase(tid); 436 return Error::success(); 437 } 438 439 Error IntelPTThreadTraceCollection::TraceStart( 440 lldb::tid_t tid, const TraceIntelPTStartRequest &request) { 441 if (TracesThread(tid)) 442 return createStringError(inconvertibleErrorCode(), 443 "Thread %" PRIu64 " already traced", tid); 444 445 Expected<IntelPTThreadTraceUP> trace_up = IntelPTThreadTrace::Create( 446 m_pid, tid, request.threadBufferSize, request.enableTsc, 447 request.psbPeriod.map([](int64_t period) { return (size_t)period; })); 448 if (!trace_up) 449 return trace_up.takeError(); 450 451 m_total_buffer_size += (*trace_up)->GetTraceBufferSize(); 452 m_thread_traces.try_emplace(tid, std::move(*trace_up)); 453 return Error::success(); 454 } 455 456 size_t IntelPTThreadTraceCollection::GetTotalBufferSize() const { 457 return m_total_buffer_size; 458 } 459 460 std::vector<TraceThreadState> 461 IntelPTThreadTraceCollection::GetThreadStates() const { 462 std::vector<TraceThreadState> states; 463 for (const auto &it : m_thread_traces) 464 states.push_back(it.second->GetState()); 465 return states; 466 } 467 468 Expected<const IntelPTThreadTrace &> 469 IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) const { 470 auto it = m_thread_traces.find(tid); 471 if (it == m_thread_traces.end()) 472 return createStringError(inconvertibleErrorCode(), 473 "Thread %" PRIu64 " not currently traced", tid); 474 return *it->second.get(); 475 } 476 477 void IntelPTThreadTraceCollection::Clear() { 478 m_thread_traces.clear(); 479 m_total_buffer_size = 0; 480 } 481 482 /// IntelPTProcessTrace 483 484 bool IntelPTProcessTrace::TracesThread(lldb::tid_t tid) const { 485 return m_thread_traces.TracesThread(tid); 486 } 487 488 Error IntelPTProcessTrace::TraceStop(lldb::tid_t tid) { 489 return m_thread_traces.TraceStop(tid); 490 } 491 492 Error IntelPTProcessTrace::TraceStart(lldb::tid_t tid) { 493 if (m_thread_traces.GetTotalBufferSize() + m_tracing_params.threadBufferSize > 494 static_cast<size_t>(*m_tracing_params.processBufferSizeLimit)) 495 return createStringError( 496 inconvertibleErrorCode(), 497 "Thread %" PRIu64 " can't be traced as the process trace size limit " 498 "has been reached. Consider retracing with a higher " 499 "limit.", 500 tid); 501 502 return m_thread_traces.TraceStart(tid, m_tracing_params); 503 } 504 505 const IntelPTThreadTraceCollection & 506 IntelPTProcessTrace::GetThreadTraces() const { 507 return m_thread_traces; 508 } 509 510 /// IntelPTCollector 511 512 IntelPTCollector::IntelPTCollector(lldb::pid_t pid) 513 : m_pid(pid), m_thread_traces(pid) { 514 if (Expected<LinuxPerfZeroTscConversion> tsc_conversion = 515 LoadPerfTscConversionParameters()) 516 m_tsc_conversion = 517 std::make_unique<LinuxPerfZeroTscConversion>(*tsc_conversion); 518 else 519 LLDB_LOG_ERROR(GetLog(POSIXLog::Trace), tsc_conversion.takeError(), 520 "unable to load TSC to wall time conversion: {0}"); 521 } 522 523 Error IntelPTCollector::TraceStop(lldb::tid_t tid) { 524 if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) 525 return m_process_trace->TraceStop(tid); 526 return m_thread_traces.TraceStop(tid); 527 } 528 529 Error IntelPTCollector::TraceStop(const TraceStopRequest &request) { 530 if (request.IsProcessTracing()) { 531 Clear(); 532 return Error::success(); 533 } else { 534 Error error = Error::success(); 535 for (int64_t tid : *request.tids) 536 error = joinErrors(std::move(error), 537 TraceStop(static_cast<lldb::tid_t>(tid))); 538 return error; 539 } 540 } 541 542 Error IntelPTCollector::TraceStart( 543 const TraceIntelPTStartRequest &request, 544 const std::vector<lldb::tid_t> &process_threads) { 545 if (request.IsProcessTracing()) { 546 if (IsProcessTracingEnabled()) { 547 return createStringError( 548 inconvertibleErrorCode(), 549 "Process currently traced. Stop process tracing first"); 550 } 551 m_process_trace = IntelPTProcessTrace(m_pid, request); 552 553 Error error = Error::success(); 554 for (lldb::tid_t tid : process_threads) 555 error = joinErrors(std::move(error), m_process_trace->TraceStart(tid)); 556 return error; 557 } else { 558 Error error = Error::success(); 559 for (int64_t tid : *request.tids) 560 error = joinErrors(std::move(error), 561 m_thread_traces.TraceStart(tid, request)); 562 return error; 563 } 564 } 565 566 Error IntelPTCollector::OnThreadCreated(lldb::tid_t tid) { 567 if (!IsProcessTracingEnabled()) 568 return Error::success(); 569 return m_process_trace->TraceStart(tid); 570 } 571 572 Error IntelPTCollector::OnThreadDestroyed(lldb::tid_t tid) { 573 if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) 574 return m_process_trace->TraceStop(tid); 575 else if (m_thread_traces.TracesThread(tid)) 576 return m_thread_traces.TraceStop(tid); 577 return Error::success(); 578 } 579 580 Expected<json::Value> IntelPTCollector::GetState() const { 581 Expected<ArrayRef<uint8_t>> cpu_info = GetProcfsCpuInfo(); 582 if (!cpu_info) 583 return cpu_info.takeError(); 584 585 TraceGetStateResponse state; 586 state.processBinaryData.push_back({IntelPTDataKinds::kProcFsCpuInfo, 587 static_cast<int64_t>(cpu_info->size())}); 588 589 std::vector<TraceThreadState> thread_states = 590 m_thread_traces.GetThreadStates(); 591 state.tracedThreads.insert(state.tracedThreads.end(), thread_states.begin(), 592 thread_states.end()); 593 594 if (IsProcessTracingEnabled()) { 595 thread_states = m_process_trace->GetThreadTraces().GetThreadStates(); 596 state.tracedThreads.insert(state.tracedThreads.end(), thread_states.begin(), 597 thread_states.end()); 598 } 599 return toJSON(state); 600 } 601 602 Expected<const IntelPTThreadTrace &> 603 IntelPTCollector::GetTracedThread(lldb::tid_t tid) const { 604 if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) 605 return m_process_trace->GetThreadTraces().GetTracedThread(tid); 606 return m_thread_traces.GetTracedThread(tid); 607 } 608 609 Expected<std::vector<uint8_t>> 610 IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const { 611 if (request.kind == IntelPTDataKinds::kThreadTraceBuffer) { 612 if (Expected<const IntelPTThreadTrace &> trace = 613 GetTracedThread(*request.tid)) 614 return trace->GetIntelPTBuffer(request.offset, request.size); 615 else 616 return trace.takeError(); 617 } else if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) { 618 return GetProcfsCpuInfo(); 619 } 620 return createStringError(inconvertibleErrorCode(), 621 "Unsuported trace binary data kind: %s", 622 request.kind.c_str()); 623 } 624 625 void IntelPTCollector::ClearProcessTracing() { m_process_trace = None; } 626 627 bool IntelPTCollector::IsSupported() { 628 Expected<uint32_t> intel_pt_type = GetOSEventType(); 629 if (!intel_pt_type) { 630 llvm::consumeError(intel_pt_type.takeError()); 631 return false; 632 } 633 return true; 634 } 635 636 bool IntelPTCollector::IsProcessTracingEnabled() const { 637 return (bool)m_process_trace; 638 } 639 640 void IntelPTCollector::Clear() { 641 ClearProcessTracing(); 642 m_thread_traces.Clear(); 643 } 644