1 | //===-- Trace.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 "lldb/Target/Trace.h" |
10 | |
11 | #include "llvm/Support/Format.h" |
12 | |
13 | #include "lldb/Core/Module.h" |
14 | #include "lldb/Core/PluginManager.h" |
15 | #include "lldb/Symbol/Function.h" |
16 | #include "lldb/Target/ExecutionContext.h" |
17 | #include "lldb/Target/Process.h" |
18 | #include "lldb/Target/SectionLoadList.h" |
19 | #include "lldb/Target/Thread.h" |
20 | #include "lldb/Utility/LLDBLog.h" |
21 | #include "lldb/Utility/Stream.h" |
22 | #include <optional> |
23 | |
24 | using namespace lldb; |
25 | using namespace lldb_private; |
26 | using namespace llvm; |
27 | |
28 | // Helper structs used to extract the type of a JSON trace bundle description |
29 | // object without having to parse the entire object. |
30 | |
31 | struct JSONSimpleTraceBundleDescription { |
32 | std::string type; |
33 | }; |
34 | |
35 | namespace llvm { |
36 | namespace json { |
37 | |
38 | bool fromJSON(const Value &value, JSONSimpleTraceBundleDescription &bundle, |
39 | Path path) { |
40 | json::ObjectMapper o(value, path); |
41 | return o && o.map(Prop: "type" , Out&: bundle.type); |
42 | } |
43 | |
44 | } // namespace json |
45 | } // namespace llvm |
46 | |
47 | /// Helper functions for fetching data in maps and returning Optionals or |
48 | /// pointers instead of iterators for simplicity. It's worth mentioning that the |
49 | /// Optionals version can't return the inner data by reference because of |
50 | /// limitations in move constructors. |
51 | /// \{ |
52 | template <typename K, typename V> |
53 | static std::optional<V> Lookup(DenseMap<K, V> &map, K k) { |
54 | auto it = map.find(k); |
55 | if (it == map.end()) |
56 | return std::nullopt; |
57 | return it->second; |
58 | } |
59 | |
60 | template <typename K, typename V> |
61 | static V *LookupAsPtr(DenseMap<K, V> &map, K k) { |
62 | auto it = map.find(k); |
63 | if (it == map.end()) |
64 | return nullptr; |
65 | return &it->second; |
66 | } |
67 | |
68 | /// Similar to the methods above but it looks for an item in a map of maps. |
69 | template <typename K1, typename K2, typename V> |
70 | static std::optional<V> Lookup(DenseMap<K1, DenseMap<K2, V>> &map, K1 k1, |
71 | K2 k2) { |
72 | auto it = map.find(k1); |
73 | if (it == map.end()) |
74 | return std::nullopt; |
75 | return Lookup(it->second, k2); |
76 | } |
77 | |
78 | /// Similar to the methods above but it looks for an item in a map of maps. |
79 | template <typename K1, typename K2, typename V> |
80 | static V *LookupAsPtr(DenseMap<K1, DenseMap<K2, V>> &map, K1 k1, K2 k2) { |
81 | auto it = map.find(k1); |
82 | if (it == map.end()) |
83 | return nullptr; |
84 | return LookupAsPtr(it->second, k2); |
85 | } |
86 | /// \} |
87 | |
88 | static Error createInvalidPlugInError(StringRef plugin_name) { |
89 | return createStringError( |
90 | EC: std::errc::invalid_argument, |
91 | Fmt: "no trace plug-in matches the specified type: \"%s\"" , |
92 | Vals: plugin_name.data()); |
93 | } |
94 | |
95 | Expected<lldb::TraceSP> |
96 | Trace::LoadPostMortemTraceFromFile(Debugger &debugger, |
97 | const FileSpec &trace_description_file) { |
98 | |
99 | auto buffer_or_error = |
100 | MemoryBuffer::getFile(Filename: trace_description_file.GetPath()); |
101 | if (!buffer_or_error) { |
102 | return createStringError(EC: std::errc::invalid_argument, |
103 | Fmt: "could not open input file: %s - %s." , |
104 | Vals: trace_description_file.GetPath().c_str(), |
105 | Vals: buffer_or_error.getError().message().c_str()); |
106 | } |
107 | |
108 | Expected<json::Value> session_file = |
109 | json::parse(JSON: buffer_or_error.get()->getBuffer().str()); |
110 | if (!session_file) { |
111 | return session_file.takeError(); |
112 | } |
113 | |
114 | return Trace::FindPluginForPostMortemProcess( |
115 | debugger, bundle_description: *session_file, |
116 | session_file_dir: trace_description_file.GetDirectory().AsCString()); |
117 | } |
118 | |
119 | Expected<lldb::TraceSP> Trace::FindPluginForPostMortemProcess( |
120 | Debugger &debugger, const json::Value &trace_bundle_description, |
121 | StringRef bundle_dir) { |
122 | JSONSimpleTraceBundleDescription json_bundle; |
123 | json::Path::Root root("traceBundle" ); |
124 | if (!json::fromJSON(value: trace_bundle_description, bundle&: json_bundle, path: root)) |
125 | return root.getError(); |
126 | |
127 | if (auto create_callback = |
128 | PluginManager::GetTraceCreateCallback(plugin_name: json_bundle.type)) |
129 | return create_callback(trace_bundle_description, bundle_dir, debugger); |
130 | |
131 | return createInvalidPlugInError(plugin_name: json_bundle.type); |
132 | } |
133 | |
134 | Expected<lldb::TraceSP> Trace::FindPluginForLiveProcess(llvm::StringRef name, |
135 | Process &process) { |
136 | if (!process.IsLiveDebugSession()) |
137 | return createStringError(EC: inconvertibleErrorCode(), |
138 | Msg: "Can't trace non-live processes" ); |
139 | |
140 | if (auto create_callback = |
141 | PluginManager::GetTraceCreateCallbackForLiveProcess(plugin_name: name)) |
142 | return create_callback(process); |
143 | |
144 | return createInvalidPlugInError(plugin_name: name); |
145 | } |
146 | |
147 | Expected<StringRef> Trace::FindPluginSchema(StringRef name) { |
148 | StringRef schema = PluginManager::GetTraceSchema(plugin_name: name); |
149 | if (!schema.empty()) |
150 | return schema; |
151 | |
152 | return createInvalidPlugInError(plugin_name: name); |
153 | } |
154 | |
155 | Error Trace::Start(const llvm::json::Value &request) { |
156 | if (!m_live_process) |
157 | return createStringError( |
158 | EC: inconvertibleErrorCode(), |
159 | Msg: "Attempted to start tracing without a live process." ); |
160 | return m_live_process->TraceStart(request); |
161 | } |
162 | |
163 | Error Trace::Stop() { |
164 | if (!m_live_process) |
165 | return createStringError( |
166 | EC: inconvertibleErrorCode(), |
167 | Msg: "Attempted to stop tracing without a live process." ); |
168 | return m_live_process->TraceStop(request: TraceStopRequest(GetPluginName())); |
169 | } |
170 | |
171 | Error Trace::Stop(llvm::ArrayRef<lldb::tid_t> tids) { |
172 | if (!m_live_process) |
173 | return createStringError( |
174 | EC: inconvertibleErrorCode(), |
175 | Msg: "Attempted to stop tracing without a live process." ); |
176 | return m_live_process->TraceStop(request: TraceStopRequest(GetPluginName(), tids)); |
177 | } |
178 | |
179 | Expected<std::string> Trace::GetLiveProcessState() { |
180 | if (!m_live_process) |
181 | return createStringError( |
182 | EC: inconvertibleErrorCode(), |
183 | Msg: "Attempted to fetch live trace information without a live process." ); |
184 | return m_live_process->TraceGetState(type: GetPluginName()); |
185 | } |
186 | |
187 | std::optional<uint64_t> |
188 | Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, llvm::StringRef kind) { |
189 | Storage &storage = GetUpdatedStorage(); |
190 | return Lookup(map&: storage.live_thread_data, k1: tid, k2: ConstString(kind)); |
191 | } |
192 | |
193 | std::optional<uint64_t> Trace::GetLiveCpuBinaryDataSize(lldb::cpu_id_t cpu_id, |
194 | llvm::StringRef kind) { |
195 | Storage &storage = GetUpdatedStorage(); |
196 | return Lookup(map&: storage.live_cpu_data_sizes, k1: cpu_id, k2: ConstString(kind)); |
197 | } |
198 | |
199 | std::optional<uint64_t> |
200 | Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { |
201 | Storage &storage = GetUpdatedStorage(); |
202 | return Lookup(map&: storage.live_process_data, k: ConstString(kind)); |
203 | } |
204 | |
205 | Expected<std::vector<uint8_t>> |
206 | Trace::GetLiveTraceBinaryData(const TraceGetBinaryDataRequest &request, |
207 | uint64_t expected_size) { |
208 | if (!m_live_process) |
209 | return createStringError( |
210 | EC: inconvertibleErrorCode(), |
211 | S: formatv(Fmt: "Attempted to fetch live trace data without a live process. " |
212 | "Data kind = {0}, tid = {1}, cpu id = {2}." , |
213 | Vals: request.kind, Vals: request.tid, Vals: request.cpu_id)); |
214 | |
215 | Expected<std::vector<uint8_t>> data = |
216 | m_live_process->TraceGetBinaryData(request); |
217 | |
218 | if (!data) |
219 | return data.takeError(); |
220 | |
221 | if (data->size() != expected_size) |
222 | return createStringError( |
223 | EC: inconvertibleErrorCode(), |
224 | S: formatv(Fmt: "Got incomplete live trace data. Data kind = {0}, expected " |
225 | "size = {1}, actual size = {2}, tid = {3}, cpu id = {4}" , |
226 | Vals: request.kind, Vals&: expected_size, Vals: data->size(), Vals: request.tid, |
227 | Vals: request.cpu_id)); |
228 | |
229 | return data; |
230 | } |
231 | |
232 | Expected<std::vector<uint8_t>> |
233 | Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { |
234 | std::optional<uint64_t> size = GetLiveThreadBinaryDataSize(tid, kind); |
235 | if (!size) |
236 | return createStringError( |
237 | EC: inconvertibleErrorCode(), |
238 | Fmt: "Tracing data \"%s\" is not available for thread %" PRIu64 "." , |
239 | Vals: kind.data(), Vals: tid); |
240 | |
241 | TraceGetBinaryDataRequest request{.type: GetPluginName().str(), .kind: kind.str(), .tid: tid, |
242 | /*cpu_id=*/std::nullopt}; |
243 | return GetLiveTraceBinaryData(request, expected_size: *size); |
244 | } |
245 | |
246 | Expected<std::vector<uint8_t>> |
247 | Trace::GetLiveCpuBinaryData(lldb::cpu_id_t cpu_id, llvm::StringRef kind) { |
248 | if (!m_live_process) |
249 | return createStringError( |
250 | EC: inconvertibleErrorCode(), |
251 | Msg: "Attempted to fetch live cpu data without a live process." ); |
252 | std::optional<uint64_t> size = GetLiveCpuBinaryDataSize(cpu_id, kind); |
253 | if (!size) |
254 | return createStringError( |
255 | EC: inconvertibleErrorCode(), |
256 | Fmt: "Tracing data \"%s\" is not available for cpu_id %" PRIu64 "." , |
257 | Vals: kind.data(), Vals: cpu_id); |
258 | |
259 | TraceGetBinaryDataRequest request{.type: GetPluginName().str(), .kind: kind.str(), |
260 | /*tid=*/std::nullopt, .cpu_id: cpu_id}; |
261 | return m_live_process->TraceGetBinaryData(request); |
262 | } |
263 | |
264 | Expected<std::vector<uint8_t>> |
265 | Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { |
266 | std::optional<uint64_t> size = GetLiveProcessBinaryDataSize(kind); |
267 | if (!size) |
268 | return createStringError( |
269 | EC: inconvertibleErrorCode(), |
270 | Fmt: "Tracing data \"%s\" is not available for the process." , Vals: kind.data()); |
271 | |
272 | TraceGetBinaryDataRequest request{.type: GetPluginName().str(), .kind: kind.str(), |
273 | /*tid=*/std::nullopt, |
274 | /*cpu_id*/ std::nullopt}; |
275 | return GetLiveTraceBinaryData(request, expected_size: *size); |
276 | } |
277 | |
278 | Trace::Storage &Trace::GetUpdatedStorage() { |
279 | RefreshLiveProcessState(); |
280 | return m_storage; |
281 | } |
282 | |
283 | const char *Trace::RefreshLiveProcessState() { |
284 | if (!m_live_process) |
285 | return nullptr; |
286 | |
287 | uint32_t new_stop_id = m_live_process->GetStopID(); |
288 | if (new_stop_id == m_stop_id) |
289 | return nullptr; |
290 | |
291 | Log *log = GetLog(mask: LLDBLog::Target); |
292 | LLDB_LOG(log, "Trace::RefreshLiveProcessState invoked" ); |
293 | |
294 | m_stop_id = new_stop_id; |
295 | m_storage = Trace::Storage(); |
296 | |
297 | auto do_refresh = [&]() -> Error { |
298 | Expected<std::string> json_string = GetLiveProcessState(); |
299 | if (!json_string) |
300 | return json_string.takeError(); |
301 | |
302 | Expected<TraceGetStateResponse> live_process_state = |
303 | json::parse<TraceGetStateResponse>(JSON: *json_string, |
304 | RootName: "TraceGetStateResponse" ); |
305 | if (!live_process_state) |
306 | return live_process_state.takeError(); |
307 | |
308 | if (live_process_state->warnings) { |
309 | for (std::string &warning : *live_process_state->warnings) |
310 | LLDB_LOG(log, "== Warning when fetching the trace state: {0}" , warning); |
311 | } |
312 | |
313 | for (const TraceThreadState &thread_state : |
314 | live_process_state->traced_threads) { |
315 | for (const TraceBinaryData &item : thread_state.binary_data) |
316 | m_storage.live_thread_data[thread_state.tid].insert( |
317 | KV: {ConstString(item.kind), item.size}); |
318 | } |
319 | |
320 | LLDB_LOG(log, "== Found {0} threads being traced" , |
321 | live_process_state->traced_threads.size()); |
322 | |
323 | if (live_process_state->cpus) { |
324 | m_storage.cpus.emplace(); |
325 | for (const TraceCpuState &cpu_state : *live_process_state->cpus) { |
326 | m_storage.cpus->push_back(x: cpu_state.id); |
327 | for (const TraceBinaryData &item : cpu_state.binary_data) |
328 | m_storage.live_cpu_data_sizes[cpu_state.id].insert( |
329 | KV: {ConstString(item.kind), item.size}); |
330 | } |
331 | LLDB_LOG(log, "== Found {0} cpu cpus being traced" , |
332 | live_process_state->cpus->size()); |
333 | } |
334 | |
335 | for (const TraceBinaryData &item : live_process_state->process_binary_data) |
336 | m_storage.live_process_data.insert(KV: {ConstString(item.kind), item.size}); |
337 | |
338 | return DoRefreshLiveProcessState(state: std::move(*live_process_state), |
339 | json_response: *json_string); |
340 | }; |
341 | |
342 | if (Error err = do_refresh()) { |
343 | m_storage.live_refresh_error = toString(E: std::move(err)); |
344 | return m_storage.live_refresh_error->c_str(); |
345 | } |
346 | |
347 | return nullptr; |
348 | } |
349 | |
350 | Trace::Trace(ArrayRef<ProcessSP> postmortem_processes, |
351 | std::optional<std::vector<lldb::cpu_id_t>> postmortem_cpus) { |
352 | for (ProcessSP process_sp : postmortem_processes) |
353 | m_storage.postmortem_processes.push_back(x: process_sp.get()); |
354 | m_storage.cpus = postmortem_cpus; |
355 | } |
356 | |
357 | Process *Trace::GetLiveProcess() { return m_live_process; } |
358 | |
359 | ArrayRef<Process *> Trace::GetPostMortemProcesses() { |
360 | return m_storage.postmortem_processes; |
361 | } |
362 | |
363 | std::vector<Process *> Trace::GetAllProcesses() { |
364 | if (Process *proc = GetLiveProcess()) |
365 | return {proc}; |
366 | return GetPostMortemProcesses(); |
367 | } |
368 | |
369 | uint32_t Trace::GetStopID() { |
370 | RefreshLiveProcessState(); |
371 | return m_stop_id; |
372 | } |
373 | |
374 | llvm::Expected<FileSpec> |
375 | Trace::GetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind) { |
376 | Storage &storage = GetUpdatedStorage(); |
377 | if (std::optional<FileSpec> file = |
378 | Lookup(map&: storage.postmortem_thread_data, k1: tid, k2: ConstString(kind))) |
379 | return *file; |
380 | else |
381 | return createStringError( |
382 | EC: inconvertibleErrorCode(), |
383 | S: formatv(Fmt: "The thread with tid={0} doesn't have the tracing data {1}" , |
384 | Vals&: tid, Vals&: kind)); |
385 | } |
386 | |
387 | llvm::Expected<FileSpec> Trace::GetPostMortemCpuDataFile(lldb::cpu_id_t cpu_id, |
388 | llvm::StringRef kind) { |
389 | Storage &storage = GetUpdatedStorage(); |
390 | if (std::optional<FileSpec> file = |
391 | Lookup(map&: storage.postmortem_cpu_data, k1: cpu_id, k2: ConstString(kind))) |
392 | return *file; |
393 | else |
394 | return createStringError( |
395 | EC: inconvertibleErrorCode(), |
396 | S: formatv(Fmt: "The cpu with id={0} doesn't have the tracing data {1}" , Vals&: cpu_id, |
397 | Vals&: kind)); |
398 | } |
399 | |
400 | void Trace::SetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind, |
401 | FileSpec file_spec) { |
402 | Storage &storage = GetUpdatedStorage(); |
403 | storage.postmortem_thread_data[tid].insert(KV: {ConstString(kind), file_spec}); |
404 | } |
405 | |
406 | void Trace::SetPostMortemCpuDataFile(lldb::cpu_id_t cpu_id, |
407 | llvm::StringRef kind, FileSpec file_spec) { |
408 | Storage &storage = GetUpdatedStorage(); |
409 | storage.postmortem_cpu_data[cpu_id].insert(KV: {ConstString(kind), file_spec}); |
410 | } |
411 | |
412 | llvm::Error |
413 | Trace::OnLiveThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, |
414 | OnBinaryDataReadCallback callback) { |
415 | Expected<std::vector<uint8_t>> data = GetLiveThreadBinaryData(tid, kind); |
416 | if (!data) |
417 | return data.takeError(); |
418 | return callback(*data); |
419 | } |
420 | |
421 | llvm::Error Trace::OnLiveCpuBinaryDataRead(lldb::cpu_id_t cpu_id, |
422 | llvm::StringRef kind, |
423 | OnBinaryDataReadCallback callback) { |
424 | Storage &storage = GetUpdatedStorage(); |
425 | if (std::vector<uint8_t> *cpu_data = |
426 | LookupAsPtr(map&: storage.live_cpu_data, k1: cpu_id, k2: ConstString(kind))) |
427 | return callback(*cpu_data); |
428 | |
429 | Expected<std::vector<uint8_t>> data = GetLiveCpuBinaryData(cpu_id, kind); |
430 | if (!data) |
431 | return data.takeError(); |
432 | auto it = storage.live_cpu_data[cpu_id].insert( |
433 | KV: {ConstString(kind), std::move(*data)}); |
434 | return callback(it.first->second); |
435 | } |
436 | |
437 | llvm::Error Trace::OnDataFileRead(FileSpec file, |
438 | OnBinaryDataReadCallback callback) { |
439 | ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error = |
440 | MemoryBuffer::getFile(Filename: file.GetPath()); |
441 | if (std::error_code err = trace_or_error.getError()) |
442 | return createStringError( |
443 | EC: inconvertibleErrorCode(), Fmt: "Failed fetching trace-related file %s. %s" , |
444 | Vals: file.GetPath().c_str(), Vals: toString(E: errorCodeToError(EC: err)).c_str()); |
445 | |
446 | MemoryBuffer &data = **trace_or_error; |
447 | ArrayRef<uint8_t> array_ref( |
448 | reinterpret_cast<const uint8_t *>(data.getBufferStart()), |
449 | data.getBufferSize()); |
450 | return callback(array_ref); |
451 | } |
452 | |
453 | llvm::Error |
454 | Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, |
455 | OnBinaryDataReadCallback callback) { |
456 | if (Expected<FileSpec> file = GetPostMortemThreadDataFile(tid, kind)) |
457 | return OnDataFileRead(file: *file, callback); |
458 | else |
459 | return file.takeError(); |
460 | } |
461 | |
462 | llvm::Error |
463 | Trace::OnPostMortemCpuBinaryDataRead(lldb::cpu_id_t cpu_id, |
464 | llvm::StringRef kind, |
465 | OnBinaryDataReadCallback callback) { |
466 | if (Expected<FileSpec> file = GetPostMortemCpuDataFile(cpu_id, kind)) |
467 | return OnDataFileRead(file: *file, callback); |
468 | else |
469 | return file.takeError(); |
470 | } |
471 | |
472 | llvm::Error Trace::OnThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, |
473 | OnBinaryDataReadCallback callback) { |
474 | if (m_live_process) |
475 | return OnLiveThreadBinaryDataRead(tid, kind, callback); |
476 | else |
477 | return OnPostMortemThreadBinaryDataRead(tid, kind, callback); |
478 | } |
479 | |
480 | llvm::Error |
481 | Trace::OnAllCpusBinaryDataRead(llvm::StringRef kind, |
482 | OnCpusBinaryDataReadCallback callback) { |
483 | DenseMap<cpu_id_t, ArrayRef<uint8_t>> buffers; |
484 | Storage &storage = GetUpdatedStorage(); |
485 | if (!storage.cpus) |
486 | return Error::success(); |
487 | |
488 | std::function<Error(std::vector<cpu_id_t>::iterator)> process_cpu = |
489 | [&](std::vector<cpu_id_t>::iterator cpu_id) -> Error { |
490 | if (cpu_id == storage.cpus->end()) |
491 | return callback(buffers); |
492 | |
493 | return OnCpuBinaryDataRead(cpu_id: *cpu_id, kind, |
494 | callback: [&](ArrayRef<uint8_t> data) -> Error { |
495 | buffers.try_emplace(Key: *cpu_id, Args&: data); |
496 | auto next_id = cpu_id; |
497 | next_id++; |
498 | return process_cpu(next_id); |
499 | }); |
500 | }; |
501 | return process_cpu(storage.cpus->begin()); |
502 | } |
503 | |
504 | llvm::Error Trace::OnCpuBinaryDataRead(lldb::cpu_id_t cpu_id, |
505 | llvm::StringRef kind, |
506 | OnBinaryDataReadCallback callback) { |
507 | if (m_live_process) |
508 | return OnLiveCpuBinaryDataRead(cpu_id, kind, callback); |
509 | else |
510 | return OnPostMortemCpuBinaryDataRead(cpu_id, kind, callback); |
511 | } |
512 | |
513 | ArrayRef<lldb::cpu_id_t> Trace::GetTracedCpus() { |
514 | Storage &storage = GetUpdatedStorage(); |
515 | if (storage.cpus) |
516 | return *storage.cpus; |
517 | return {}; |
518 | } |
519 | |
520 | std::vector<Process *> Trace::GetTracedProcesses() { |
521 | std::vector<Process *> processes; |
522 | Storage &storage = GetUpdatedStorage(); |
523 | |
524 | for (Process *proc : storage.postmortem_processes) |
525 | processes.push_back(x: proc); |
526 | |
527 | if (m_live_process) |
528 | processes.push_back(x: m_live_process); |
529 | return processes; |
530 | } |
531 | |