1 //===-- TraceIntelPT.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 "TraceIntelPT.h" 10 11 #include "../common/ThreadPostMortemTrace.h" 12 #include "CommandObjectTraceStartIntelPT.h" 13 #include "DecodedThread.h" 14 #include "TraceIntelPTConstants.h" 15 #include "TraceIntelPTSessionFileParser.h" 16 #include "TraceIntelPTSessionSaver.h" 17 #include "lldb/Core/PluginManager.h" 18 #include "lldb/Target/Process.h" 19 #include "lldb/Target/Target.h" 20 #include "llvm/ADT/None.h" 21 22 using namespace lldb; 23 using namespace lldb_private; 24 using namespace lldb_private::trace_intel_pt; 25 using namespace llvm; 26 27 LLDB_PLUGIN_DEFINE(TraceIntelPT) 28 29 lldb::CommandObjectSP 30 TraceIntelPT::GetProcessTraceStartCommand(CommandInterpreter &interpreter) { 31 return CommandObjectSP( 32 new CommandObjectProcessTraceStartIntelPT(*this, interpreter)); 33 } 34 35 lldb::CommandObjectSP 36 TraceIntelPT::GetThreadTraceStartCommand(CommandInterpreter &interpreter) { 37 return CommandObjectSP( 38 new CommandObjectThreadTraceStartIntelPT(*this, interpreter)); 39 } 40 41 void TraceIntelPT::Initialize() { 42 PluginManager::RegisterPlugin(GetPluginNameStatic(), "Intel Processor Trace", 43 CreateInstanceForSessionFile, 44 CreateInstanceForLiveProcess, 45 TraceIntelPTSessionFileParser::GetSchema()); 46 } 47 48 void TraceIntelPT::Terminate() { 49 PluginManager::UnregisterPlugin(CreateInstanceForSessionFile); 50 } 51 52 StringRef TraceIntelPT::GetSchema() { 53 return TraceIntelPTSessionFileParser::GetSchema(); 54 } 55 56 void TraceIntelPT::Dump(Stream *s) const {} 57 58 llvm::Error TraceIntelPT::SaveLiveTraceToDisk(FileSpec directory) { 59 RefreshLiveProcessState(); 60 return TraceIntelPTSessionSaver().SaveToDisk(*this, directory); 61 } 62 63 Expected<TraceSP> TraceIntelPT::CreateInstanceForSessionFile( 64 const json::Value &trace_session_file, StringRef session_file_dir, 65 Debugger &debugger) { 66 return TraceIntelPTSessionFileParser(debugger, trace_session_file, 67 session_file_dir) 68 .Parse(); 69 } 70 71 Expected<TraceSP> TraceIntelPT::CreateInstanceForLiveProcess(Process &process) { 72 TraceSP instance(new TraceIntelPT(process)); 73 process.GetTarget().SetTrace(instance); 74 return instance; 75 } 76 77 TraceIntelPT::TraceIntelPT(JSONTraceSession &session, 78 ArrayRef<ProcessSP> traced_processes, 79 ArrayRef<ThreadPostMortemTraceSP> traced_threads) 80 : Trace(traced_processes, session.GetCoreIds()), 81 m_cpu_info(session.cpu_info), 82 m_tsc_conversion(session.tsc_perf_zero_conversion) { 83 for (const ThreadPostMortemTraceSP &thread : traced_threads) { 84 m_thread_decoders.emplace(thread->GetID(), 85 std::make_unique<ThreadDecoder>(thread, *this)); 86 if (const Optional<FileSpec> &trace_file = thread->GetTraceFile()) { 87 SetPostMortemThreadDataFile(thread->GetID(), 88 IntelPTDataKinds::kTraceBuffer, *trace_file); 89 } 90 } 91 if (session.cores) { 92 for (const JSONCore &core : *session.cores) { 93 SetPostMortemCoreDataFile(core.core_id, IntelPTDataKinds::kTraceBuffer, 94 FileSpec(core.trace_buffer)); 95 SetPostMortemCoreDataFile(core.core_id, 96 IntelPTDataKinds::kPerfContextSwitchTrace, 97 FileSpec(core.context_switch_trace)); 98 } 99 } 100 } 101 102 DecodedThreadSP TraceIntelPT::Decode(Thread &thread) { 103 if (const char *error = RefreshLiveProcessState()) 104 return std::make_shared<DecodedThread>( 105 thread.shared_from_this(), 106 createStringError(inconvertibleErrorCode(), error)); 107 108 auto it = m_thread_decoders.find(thread.GetID()); 109 if (it == m_thread_decoders.end()) 110 return std::make_shared<DecodedThread>( 111 thread.shared_from_this(), 112 createStringError(inconvertibleErrorCode(), "thread not traced")); 113 return it->second->Decode(); 114 } 115 116 lldb::TraceCursorUP TraceIntelPT::GetCursor(Thread &thread) { 117 return Decode(thread)->GetCursor(); 118 } 119 120 void TraceIntelPT::DumpTraceInfo(Thread &thread, Stream &s, bool verbose) { 121 lldb::tid_t tid = thread.GetID(); 122 s.Format("\nthread #{0}: tid = {1}", thread.GetIndexID(), thread.GetID()); 123 if (!IsTraced(tid)) { 124 s << ", not traced\n"; 125 return; 126 } 127 s << "\n"; 128 129 Expected<size_t> raw_size = GetRawTraceSize(thread); 130 if (!raw_size) { 131 s.Format(" {0}\n", toString(raw_size.takeError())); 132 return; 133 } 134 135 DecodedThreadSP decoded_trace_sp = Decode(thread); 136 size_t insn_len = decoded_trace_sp->GetInstructionsCount(); 137 size_t mem_used = decoded_trace_sp->CalculateApproximateMemoryUsage(); 138 139 s.Format(" Total number of instructions: {0}\n", insn_len); 140 141 s << "\n Memory usage:\n"; 142 s.Format(" Raw trace size: {0} KiB\n", *raw_size / 1024); 143 s.Format( 144 " Total approximate memory usage (excluding raw trace): {0:2} KiB\n", 145 (double)mem_used / 1024); 146 if (insn_len != 0) 147 s.Format(" Average memory usage per instruction (excluding raw trace): " 148 "{0:2} bytes\n", 149 (double)mem_used / insn_len); 150 151 s << "\n Timing:\n"; 152 GetTimer().ForThread(tid).ForEachTimedTask( 153 [&](const std::string &name, std::chrono::milliseconds duration) { 154 s.Format(" {0}: {1:2}s\n", name, duration.count() / 1000.0); 155 }); 156 157 const DecodedThread::EventsStats &events_stats = 158 decoded_trace_sp->GetEventsStats(); 159 s << "\n Events:\n"; 160 s.Format(" Number of instructions with events: {0}\n", 161 events_stats.total_instructions_with_events); 162 s.Format(" Number of individual events: {0}\n", events_stats.total_count); 163 for (const auto &event_to_count : events_stats.events_counts) { 164 s.Format(" {0}: {1}\n", 165 trace_event_utils::EventToDisplayString(event_to_count.first), 166 event_to_count.second); 167 } 168 169 s << "\n Errors:\n"; 170 const DecodedThread::LibiptErrorsStats &tsc_errors_stats = 171 decoded_trace_sp->GetTscErrorsStats(); 172 s.Format(" Number of TSC decoding errors: {0}\n", 173 tsc_errors_stats.total_count); 174 for (const auto &error_message_to_count : 175 tsc_errors_stats.libipt_errors_counts) { 176 s.Format(" {0}: {1}\n", error_message_to_count.first, 177 error_message_to_count.second); 178 } 179 } 180 181 llvm::Expected<size_t> TraceIntelPT::GetRawTraceSize(Thread &thread) { 182 size_t size; 183 auto callback = [&](llvm::ArrayRef<uint8_t> data) { 184 size = data.size(); 185 return Error::success(); 186 }; 187 if (Error err = OnThreadBufferRead(thread.GetID(), callback)) 188 return std::move(err); 189 190 return size; 191 } 192 193 Expected<pt_cpu> TraceIntelPT::GetCPUInfoForLiveProcess() { 194 Expected<std::vector<uint8_t>> cpu_info = 195 GetLiveProcessBinaryData(IntelPTDataKinds::kProcFsCpuInfo); 196 if (!cpu_info) 197 return cpu_info.takeError(); 198 199 int64_t cpu_family = -1; 200 int64_t model = -1; 201 int64_t stepping = -1; 202 std::string vendor_id; 203 204 StringRef rest(reinterpret_cast<const char *>(cpu_info->data()), 205 cpu_info->size()); 206 while (!rest.empty()) { 207 StringRef line; 208 std::tie(line, rest) = rest.split('\n'); 209 210 SmallVector<StringRef, 2> columns; 211 line.split(columns, StringRef(":"), -1, false); 212 213 if (columns.size() < 2) 214 continue; // continue searching 215 216 columns[1] = columns[1].trim(" "); 217 if (columns[0].contains("cpu family") && 218 columns[1].getAsInteger(10, cpu_family)) 219 continue; 220 221 else if (columns[0].contains("model") && columns[1].getAsInteger(10, model)) 222 continue; 223 224 else if (columns[0].contains("stepping") && 225 columns[1].getAsInteger(10, stepping)) 226 continue; 227 228 else if (columns[0].contains("vendor_id")) { 229 vendor_id = columns[1].str(); 230 if (!vendor_id.empty()) 231 continue; 232 } 233 234 if ((cpu_family != -1) && (model != -1) && (stepping != -1) && 235 (!vendor_id.empty())) { 236 return pt_cpu{vendor_id == "GenuineIntel" ? pcv_intel : pcv_unknown, 237 static_cast<uint16_t>(cpu_family), 238 static_cast<uint8_t>(model), 239 static_cast<uint8_t>(stepping)}; 240 } 241 } 242 return createStringError(inconvertibleErrorCode(), 243 "Failed parsing the target's /proc/cpuinfo file"); 244 } 245 246 Expected<pt_cpu> TraceIntelPT::GetCPUInfo() { 247 if (!m_cpu_info) { 248 if (llvm::Expected<pt_cpu> cpu_info = GetCPUInfoForLiveProcess()) 249 m_cpu_info = *cpu_info; 250 else 251 return cpu_info.takeError(); 252 } 253 return *m_cpu_info; 254 } 255 256 llvm::Optional<LinuxPerfZeroTscConversion> 257 TraceIntelPT::GetPerfZeroTscConversion() { 258 RefreshLiveProcessState(); 259 return m_tsc_conversion; 260 } 261 262 Error TraceIntelPT::DoRefreshLiveProcessState(TraceGetStateResponse state, 263 StringRef json_response) { 264 m_thread_decoders.clear(); 265 266 for (const TraceThreadState &thread_state : state.traced_threads) { 267 ThreadSP thread_sp = 268 GetLiveProcess()->GetThreadList().FindThreadByID(thread_state.tid); 269 m_thread_decoders.emplace( 270 thread_state.tid, std::make_unique<ThreadDecoder>(thread_sp, *this)); 271 } 272 273 Expected<TraceIntelPTGetStateResponse> intelpt_state = 274 json::parse<TraceIntelPTGetStateResponse>(json_response, 275 "TraceIntelPTGetStateResponse"); 276 if (!intelpt_state) 277 return intelpt_state.takeError(); 278 279 m_tsc_conversion = intelpt_state->tsc_perf_zero_conversion; 280 if (m_tsc_conversion) { 281 Log *log = GetLog(LLDBLog::Target); 282 LLDB_LOG(log, "TraceIntelPT found TSC conversion information"); 283 } 284 return Error::success(); 285 } 286 287 bool TraceIntelPT::IsTraced(lldb::tid_t tid) { 288 RefreshLiveProcessState(); 289 return m_thread_decoders.count(tid); 290 } 291 292 // The information here should match the description of the intel-pt section 293 // of the jLLDBTraceStart packet in the lldb/docs/lldb-gdb-remote.txt 294 // documentation file. Similarly, it should match the CLI help messages of the 295 // TraceIntelPTOptions.td file. 296 const char *TraceIntelPT::GetStartConfigurationHelp() { 297 static Optional<std::string> message; 298 if (!message) { 299 message.emplace(formatv(R"(Parameters: 300 301 See the jLLDBTraceStart section in lldb/docs/lldb-gdb-remote.txt for a 302 description of each parameter below. 303 304 - int traceBufferSize (defaults to {0} bytes): 305 [process and thread tracing] 306 307 - boolean enableTsc (default to {1}): 308 [process and thread tracing] 309 310 - int psbPeriod (defaults to {2}): 311 [process and thread tracing] 312 313 - boolean perCoreTracing (default to {3}): 314 [process tracing only] 315 316 - int processBufferSizeLimit (defaults to {4} MiB): 317 [process tracing only])", 318 kDefaultTraceBufferSize, kDefaultEnableTscValue, 319 kDefaultPsbPeriod, kDefaultPerCoreTracing, 320 kDefaultProcessBufferSizeLimit / 1024 / 1024)); 321 } 322 return message->c_str(); 323 } 324 325 Error TraceIntelPT::Start(size_t trace_buffer_size, 326 size_t total_buffer_size_limit, bool enable_tsc, 327 Optional<size_t> psb_period, bool per_core_tracing) { 328 TraceIntelPTStartRequest request; 329 request.trace_buffer_size = trace_buffer_size; 330 request.process_buffer_size_limit = total_buffer_size_limit; 331 request.enable_tsc = enable_tsc; 332 request.psb_period = 333 psb_period.map([](size_t val) { return static_cast<uint64_t>(val); }); 334 request.type = GetPluginName().str(); 335 request.per_core_tracing = per_core_tracing; 336 return Trace::Start(toJSON(request)); 337 } 338 339 Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) { 340 size_t trace_buffer_size = kDefaultTraceBufferSize; 341 size_t process_buffer_size_limit = kDefaultProcessBufferSizeLimit; 342 bool enable_tsc = kDefaultEnableTscValue; 343 Optional<size_t> psb_period = kDefaultPsbPeriod; 344 bool per_core_tracing = kDefaultPerCoreTracing; 345 346 if (configuration) { 347 if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { 348 dict->GetValueForKeyAsInteger("traceBufferSize", trace_buffer_size); 349 dict->GetValueForKeyAsInteger("processBufferSizeLimit", 350 process_buffer_size_limit); 351 dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); 352 dict->GetValueForKeyAsInteger("psbPeriod", psb_period); 353 dict->GetValueForKeyAsBoolean("perCoreTracing", per_core_tracing); 354 } else { 355 return createStringError(inconvertibleErrorCode(), 356 "configuration object is not a dictionary"); 357 } 358 } 359 360 return Start(trace_buffer_size, process_buffer_size_limit, enable_tsc, 361 psb_period, per_core_tracing); 362 } 363 364 llvm::Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids, 365 size_t trace_buffer_size, bool enable_tsc, 366 Optional<size_t> psb_period) { 367 TraceIntelPTStartRequest request; 368 request.trace_buffer_size = trace_buffer_size; 369 request.enable_tsc = enable_tsc; 370 request.psb_period = 371 psb_period.map([](size_t val) { return static_cast<uint64_t>(val); }); 372 request.type = GetPluginName().str(); 373 request.tids.emplace(); 374 for (lldb::tid_t tid : tids) 375 request.tids->push_back(tid); 376 return Trace::Start(toJSON(request)); 377 } 378 379 Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids, 380 StructuredData::ObjectSP configuration) { 381 uint64_t trace_buffer_size = kDefaultTraceBufferSize; 382 bool enable_tsc = kDefaultEnableTscValue; 383 Optional<uint64_t> psb_period = kDefaultPsbPeriod; 384 385 if (configuration) { 386 if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { 387 dict->GetValueForKeyAsInteger("traceBufferSize", trace_buffer_size); 388 dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); 389 dict->GetValueForKeyAsInteger("psbPeriod", psb_period); 390 } else { 391 return createStringError(inconvertibleErrorCode(), 392 "configuration object is not a dictionary"); 393 } 394 } 395 396 return Start(tids, trace_buffer_size, enable_tsc, psb_period); 397 } 398 399 Error TraceIntelPT::OnThreadBufferRead(lldb::tid_t tid, 400 OnBinaryDataReadCallback callback) { 401 return OnThreadBinaryDataRead(tid, IntelPTDataKinds::kTraceBuffer, callback); 402 } 403 404 TaskTimer &TraceIntelPT::GetTimer() { return m_task_timer; } 405