1 //===-- CommandObjectTrace.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 "CommandObjectTrace.h"
10 
11 #include "llvm/Support/JSON.h"
12 #include "llvm/Support/MemoryBuffer.h"
13 
14 #include "lldb/Core/Debugger.h"
15 #include "lldb/Core/PluginManager.h"
16 #include "lldb/Host/OptionParser.h"
17 #include "lldb/Interpreter/CommandInterpreter.h"
18 #include "lldb/Interpreter/CommandObject.h"
19 #include "lldb/Interpreter/CommandOptionArgumentTable.h"
20 #include "lldb/Interpreter/CommandReturnObject.h"
21 #include "lldb/Interpreter/OptionArgParser.h"
22 #include "lldb/Interpreter/OptionGroupFormat.h"
23 #include "lldb/Interpreter/OptionValueBoolean.h"
24 #include "lldb/Interpreter/OptionValueLanguage.h"
25 #include "lldb/Interpreter/OptionValueString.h"
26 #include "lldb/Interpreter/Options.h"
27 #include "lldb/Target/Process.h"
28 #include "lldb/Target/Trace.h"
29 
30 using namespace lldb;
31 using namespace lldb_private;
32 using namespace llvm;
33 
34 // CommandObjectTraceSave
35 #define LLDB_OPTIONS_trace_save
36 #include "CommandOptions.inc"
37 
38 #pragma mark CommandObjectTraceSave
39 
40 class CommandObjectTraceSave : public CommandObjectParsed {
41 public:
42   class CommandOptions : public Options {
43   public:
44     CommandOptions() { OptionParsingStarting(nullptr); }
45 
46     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
47                           ExecutionContext *execution_context) override {
48       Status error;
49       const int short_option = m_getopt_table[option_idx].val;
50 
51       switch (short_option) {
52       case 'c': {
53         m_compact = true;
54         break;
55       }
56       default:
57         llvm_unreachable("Unimplemented option");
58       }
59       return error;
60     }
61 
62     void OptionParsingStarting(ExecutionContext *execution_context) override {
63       m_compact = false;
64     };
65 
66     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
67       return llvm::ArrayRef(g_trace_save_options);
68     };
69 
70     bool m_compact;
71   };
72 
73   Options *GetOptions() override { return &m_options; }
74 
75   CommandObjectTraceSave(CommandInterpreter &interpreter)
76       : CommandObjectParsed(
77             interpreter, "trace save",
78             "Save the trace of the current target in the specified directory, "
79             "which will be created if needed. "
80             "This directory will contain a trace bundle, with all the "
81             "necessary files the reconstruct the trace session even on a "
82             "different computer. "
83             "Part of this bundle is the bundle description file with the name "
84             "trace.json. This file can be used by the \"trace load\" command "
85             "to load this trace in LLDB."
86             "Note: if the current target contains information of multiple "
87             "processes or targets, they all will be included in the bundle.",
88             "trace save [<cmd-options>] <bundle_directory>",
89             eCommandRequiresProcess | eCommandTryTargetAPILock |
90                 eCommandProcessMustBeLaunched | eCommandProcessMustBePaused |
91                 eCommandProcessMustBeTraced) {
92     CommandArgumentData bundle_dir{eArgTypeDirectoryName, eArgRepeatPlain};
93     m_arguments.push_back({bundle_dir});
94   }
95 
96   void
97   HandleArgumentCompletion(CompletionRequest &request,
98                            OptionElementVector &opt_element_vector) override {
99     lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
100         GetCommandInterpreter(), lldb::eDiskFileCompletion, request, nullptr);
101   }
102 
103   ~CommandObjectTraceSave() override = default;
104 
105 protected:
106   bool DoExecute(Args &command, CommandReturnObject &result) override {
107     if (command.size() != 1) {
108       result.AppendError("a single path to a directory where the trace bundle "
109                          "will be created is required");
110       return false;
111     }
112 
113     FileSpec bundle_dir(command[0].ref());
114     FileSystem::Instance().Resolve(bundle_dir);
115 
116     ProcessSP process_sp = m_exe_ctx.GetProcessSP();
117 
118     TraceSP trace_sp = process_sp->GetTarget().GetTrace();
119 
120     if (llvm::Expected<FileSpec> desc_file =
121             trace_sp->SaveToDisk(bundle_dir, m_options.m_compact)) {
122       result.AppendMessageWithFormatv(
123           "Trace bundle description file written to: {0}", *desc_file);
124       result.SetStatus(eReturnStatusSuccessFinishResult);
125     } else {
126       result.AppendError(toString(desc_file.takeError()));
127     }
128 
129     return result.Succeeded();
130   }
131 
132   CommandOptions m_options;
133 };
134 
135 // CommandObjectTraceLoad
136 #define LLDB_OPTIONS_trace_load
137 #include "CommandOptions.inc"
138 
139 #pragma mark CommandObjectTraceLoad
140 
141 class CommandObjectTraceLoad : public CommandObjectParsed {
142 public:
143   class CommandOptions : public Options {
144   public:
145     CommandOptions() { OptionParsingStarting(nullptr); }
146 
147     ~CommandOptions() override = default;
148 
149     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
150                           ExecutionContext *execution_context) override {
151       Status error;
152       const int short_option = m_getopt_table[option_idx].val;
153 
154       switch (short_option) {
155       case 'v': {
156         m_verbose = true;
157         break;
158       }
159       default:
160         llvm_unreachable("Unimplemented option");
161       }
162       return error;
163     }
164 
165     void OptionParsingStarting(ExecutionContext *execution_context) override {
166       m_verbose = false;
167     }
168 
169     ArrayRef<OptionDefinition> GetDefinitions() override {
170       return ArrayRef(g_trace_load_options);
171     }
172 
173     bool m_verbose; // Enable verbose logging for debugging purposes.
174   };
175 
176   CommandObjectTraceLoad(CommandInterpreter &interpreter)
177       : CommandObjectParsed(
178             interpreter, "trace load",
179             "Load a post-mortem processor trace session from a trace bundle.",
180             "trace load <trace_description_file>") {
181     CommandArgumentData session_file_arg{eArgTypeFilename, eArgRepeatPlain};
182     m_arguments.push_back({session_file_arg});
183   }
184 
185   void
186   HandleArgumentCompletion(CompletionRequest &request,
187                            OptionElementVector &opt_element_vector) override {
188     lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
189         GetCommandInterpreter(), lldb::eDiskFileCompletion, request, nullptr);
190   }
191 
192   ~CommandObjectTraceLoad() override = default;
193 
194   Options *GetOptions() override { return &m_options; }
195 
196 protected:
197   bool DoExecute(Args &command, CommandReturnObject &result) override {
198     if (command.size() != 1) {
199       result.AppendError("a single path to a JSON file containing a the "
200                          "description of the trace bundle is required");
201       return false;
202     }
203 
204     const FileSpec trace_description_file(command[0].ref());
205 
206     llvm::Expected<lldb::TraceSP> trace_or_err =
207         Trace::LoadPostMortemTraceFromFile(GetDebugger(),
208                                            trace_description_file);
209 
210     if (!trace_or_err) {
211       result.AppendErrorWithFormat(
212           "%s\n", llvm::toString(trace_or_err.takeError()).c_str());
213       return false;
214     }
215 
216     if (m_options.m_verbose) {
217       result.AppendMessageWithFormatv("loading trace with plugin {0}\n",
218                                       trace_or_err.get()->GetPluginName());
219     }
220 
221     result.SetStatus(eReturnStatusSuccessFinishResult);
222     return true;
223   }
224 
225   CommandOptions m_options;
226 };
227 
228 // CommandObjectTraceDump
229 #define LLDB_OPTIONS_trace_dump
230 #include "CommandOptions.inc"
231 
232 #pragma mark CommandObjectTraceDump
233 
234 class CommandObjectTraceDump : public CommandObjectParsed {
235 public:
236   class CommandOptions : public Options {
237   public:
238     CommandOptions() { OptionParsingStarting(nullptr); }
239 
240     ~CommandOptions() override = default;
241 
242     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
243                           ExecutionContext *execution_context) override {
244       Status error;
245       const int short_option = m_getopt_table[option_idx].val;
246 
247       switch (short_option) {
248       case 'v': {
249         m_verbose = true;
250         break;
251       }
252       default:
253         llvm_unreachable("Unimplemented option");
254       }
255       return error;
256     }
257 
258     void OptionParsingStarting(ExecutionContext *execution_context) override {
259       m_verbose = false;
260     }
261 
262     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
263       return llvm::ArrayRef(g_trace_dump_options);
264     }
265 
266     bool m_verbose; // Enable verbose logging for debugging purposes.
267   };
268 
269   CommandObjectTraceDump(CommandInterpreter &interpreter)
270       : CommandObjectParsed(interpreter, "trace dump",
271                             "Dump the loaded processor trace data.",
272                             "trace dump") {}
273 
274   ~CommandObjectTraceDump() override = default;
275 
276   Options *GetOptions() override { return &m_options; }
277 
278 protected:
279   bool DoExecute(Args &command, CommandReturnObject &result) override {
280     Status error;
281     // TODO: fill in the dumping code here!
282     if (error.Success()) {
283       result.SetStatus(eReturnStatusSuccessFinishResult);
284     } else {
285       result.AppendErrorWithFormat("%s\n", error.AsCString());
286     }
287     return result.Succeeded();
288   }
289 
290   CommandOptions m_options;
291 };
292 
293 // CommandObjectTraceSchema
294 #define LLDB_OPTIONS_trace_schema
295 #include "CommandOptions.inc"
296 
297 #pragma mark CommandObjectTraceSchema
298 
299 class CommandObjectTraceSchema : public CommandObjectParsed {
300 public:
301   class CommandOptions : public Options {
302   public:
303     CommandOptions() { OptionParsingStarting(nullptr); }
304 
305     ~CommandOptions() override = default;
306 
307     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
308                           ExecutionContext *execution_context) override {
309       Status error;
310       const int short_option = m_getopt_table[option_idx].val;
311 
312       switch (short_option) {
313       case 'v': {
314         m_verbose = true;
315         break;
316       }
317       default:
318         llvm_unreachable("Unimplemented option");
319       }
320       return error;
321     }
322 
323     void OptionParsingStarting(ExecutionContext *execution_context) override {
324       m_verbose = false;
325     }
326 
327     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
328       return llvm::ArrayRef(g_trace_schema_options);
329     }
330 
331     bool m_verbose; // Enable verbose logging for debugging purposes.
332   };
333 
334   CommandObjectTraceSchema(CommandInterpreter &interpreter)
335       : CommandObjectParsed(interpreter, "trace schema",
336                             "Show the schema of the given trace plugin.",
337                             "trace schema <plug-in>. Use the plug-in name "
338                             "\"all\" to see all schemas.\n") {
339     CommandArgumentData plugin_arg{eArgTypeNone, eArgRepeatPlain};
340     m_arguments.push_back({plugin_arg});
341   }
342 
343   ~CommandObjectTraceSchema() override = default;
344 
345   Options *GetOptions() override { return &m_options; }
346 
347 protected:
348   bool DoExecute(Args &command, CommandReturnObject &result) override {
349     Status error;
350     if (command.empty()) {
351       result.AppendError(
352           "trace schema cannot be invoked without a plug-in as argument");
353       return false;
354     }
355 
356     StringRef plugin_name(command[0].c_str());
357     if (plugin_name == "all") {
358       size_t index = 0;
359       while (true) {
360         StringRef schema = PluginManager::GetTraceSchema(index++);
361         if (schema.empty())
362           break;
363 
364         result.AppendMessage(schema);
365       }
366     } else {
367       if (Expected<StringRef> schemaOrErr =
368               Trace::FindPluginSchema(plugin_name))
369         result.AppendMessage(*schemaOrErr);
370       else
371         error = schemaOrErr.takeError();
372     }
373 
374     if (error.Success()) {
375       result.SetStatus(eReturnStatusSuccessFinishResult);
376     } else {
377       result.AppendErrorWithFormat("%s\n", error.AsCString());
378     }
379     return result.Succeeded();
380   }
381 
382   CommandOptions m_options;
383 };
384 
385 // CommandObjectTrace
386 
387 CommandObjectTrace::CommandObjectTrace(CommandInterpreter &interpreter)
388     : CommandObjectMultiword(interpreter, "trace",
389                              "Commands for loading and using processor "
390                              "trace information.",
391                              "trace [<sub-command-options>]") {
392   LoadSubCommand("load",
393                  CommandObjectSP(new CommandObjectTraceLoad(interpreter)));
394   LoadSubCommand("dump",
395                  CommandObjectSP(new CommandObjectTraceDump(interpreter)));
396   LoadSubCommand("save",
397                  CommandObjectSP(new CommandObjectTraceSave(interpreter)));
398   LoadSubCommand("schema",
399                  CommandObjectSP(new CommandObjectTraceSchema(interpreter)));
400 }
401 
402 CommandObjectTrace::~CommandObjectTrace() = default;
403 
404 Expected<CommandObjectSP> CommandObjectTraceProxy::DoGetProxyCommandObject() {
405   ProcessSP process_sp = m_interpreter.GetExecutionContext().GetProcessSP();
406 
407   if (!process_sp)
408     return createStringError(inconvertibleErrorCode(),
409                              "Process not available.");
410   if (m_live_debug_session_only && !process_sp->IsLiveDebugSession())
411     return createStringError(inconvertibleErrorCode(),
412                              "Process must be alive.");
413 
414   if (Expected<TraceSP> trace_sp = process_sp->GetTarget().GetTraceOrCreate())
415     return GetDelegateCommand(**trace_sp);
416   else
417     return createStringError(inconvertibleErrorCode(),
418                              "Tracing is not supported. %s",
419                              toString(trace_sp.takeError()).c_str());
420 }
421 
422 CommandObject *CommandObjectTraceProxy::GetProxyCommandObject() {
423   if (Expected<CommandObjectSP> delegate = DoGetProxyCommandObject()) {
424     m_delegate_sp = *delegate;
425     m_delegate_error.clear();
426     return m_delegate_sp.get();
427   } else {
428     m_delegate_sp.reset();
429     m_delegate_error = toString(delegate.takeError());
430     return nullptr;
431   }
432 }
433