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::makeArrayRef(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     CommandCompletions::InvokeCommonCompletionCallbacks(
100         GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion,
101         request, nullptr);
102   }
103 
104   ~CommandObjectTraceSave() override = default;
105 
106 protected:
107   bool DoExecute(Args &command, CommandReturnObject &result) override {
108     if (command.size() != 1) {
109       result.AppendError("a single path to a directory where the trace bundle "
110                          "will be created is required");
111       return false;
112     }
113 
114     FileSpec bundle_dir(command[0].ref());
115     FileSystem::Instance().Resolve(bundle_dir);
116 
117     ProcessSP process_sp = m_exe_ctx.GetProcessSP();
118 
119     TraceSP trace_sp = process_sp->GetTarget().GetTrace();
120 
121     if (llvm::Expected<FileSpec> desc_file =
122             trace_sp->SaveToDisk(bundle_dir, m_options.m_compact)) {
123       result.AppendMessageWithFormatv(
124           "Trace bundle description file written to: {0}", *desc_file);
125       result.SetStatus(eReturnStatusSuccessFinishResult);
126     } else {
127       result.AppendError(toString(desc_file.takeError()));
128     }
129 
130     return result.Succeeded();
131   }
132 
133   CommandOptions m_options;
134 };
135 
136 // CommandObjectTraceLoad
137 #define LLDB_OPTIONS_trace_load
138 #include "CommandOptions.inc"
139 
140 #pragma mark CommandObjectTraceLoad
141 
142 class CommandObjectTraceLoad : public CommandObjectParsed {
143 public:
144   class CommandOptions : public Options {
145   public:
146     CommandOptions() { OptionParsingStarting(nullptr); }
147 
148     ~CommandOptions() override = default;
149 
150     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
151                           ExecutionContext *execution_context) override {
152       Status error;
153       const int short_option = m_getopt_table[option_idx].val;
154 
155       switch (short_option) {
156       case 'v': {
157         m_verbose = true;
158         break;
159       }
160       default:
161         llvm_unreachable("Unimplemented option");
162       }
163       return error;
164     }
165 
166     void OptionParsingStarting(ExecutionContext *execution_context) override {
167       m_verbose = false;
168     }
169 
170     ArrayRef<OptionDefinition> GetDefinitions() override {
171       return makeArrayRef(g_trace_load_options);
172     }
173 
174     bool m_verbose; // Enable verbose logging for debugging purposes.
175   };
176 
177   CommandObjectTraceLoad(CommandInterpreter &interpreter)
178       : CommandObjectParsed(
179             interpreter, "trace load",
180             "Load a post-mortem processor trace session from a trace bundle.",
181             "trace load <trace_description_file>") {
182     CommandArgumentData session_file_arg{eArgTypeFilename, eArgRepeatPlain};
183     m_arguments.push_back({session_file_arg});
184   }
185 
186   void
187   HandleArgumentCompletion(CompletionRequest &request,
188                            OptionElementVector &opt_element_vector) override {
189     CommandCompletions::InvokeCommonCompletionCallbacks(
190         GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion,
191         request, nullptr);
192   }
193 
194   ~CommandObjectTraceLoad() override = default;
195 
196   Options *GetOptions() override { return &m_options; }
197 
198 protected:
199   bool DoExecute(Args &command, CommandReturnObject &result) override {
200     if (command.size() != 1) {
201       result.AppendError("a single path to a JSON file containing a the "
202                          "description of the trace bundle is required");
203       return false;
204     }
205 
206     const FileSpec trace_description_file(command[0].ref());
207 
208     llvm::Expected<lldb::TraceSP> trace_or_err =
209         Trace::LoadPostMortemTraceFromFile(GetDebugger(),
210                                            trace_description_file);
211 
212     if (!trace_or_err) {
213       result.AppendErrorWithFormat(
214           "%s\n", llvm::toString(trace_or_err.takeError()).c_str());
215       return false;
216     }
217 
218     if (m_options.m_verbose) {
219       result.AppendMessageWithFormatv("loading trace with plugin {0}\n",
220                                       trace_or_err.get()->GetPluginName());
221     }
222 
223     result.SetStatus(eReturnStatusSuccessFinishResult);
224     return true;
225   }
226 
227   CommandOptions m_options;
228 };
229 
230 // CommandObjectTraceDump
231 #define LLDB_OPTIONS_trace_dump
232 #include "CommandOptions.inc"
233 
234 #pragma mark CommandObjectTraceDump
235 
236 class CommandObjectTraceDump : public CommandObjectParsed {
237 public:
238   class CommandOptions : public Options {
239   public:
240     CommandOptions() { OptionParsingStarting(nullptr); }
241 
242     ~CommandOptions() override = default;
243 
244     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
245                           ExecutionContext *execution_context) override {
246       Status error;
247       const int short_option = m_getopt_table[option_idx].val;
248 
249       switch (short_option) {
250       case 'v': {
251         m_verbose = true;
252         break;
253       }
254       default:
255         llvm_unreachable("Unimplemented option");
256       }
257       return error;
258     }
259 
260     void OptionParsingStarting(ExecutionContext *execution_context) override {
261       m_verbose = false;
262     }
263 
264     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
265       return llvm::makeArrayRef(g_trace_dump_options);
266     }
267 
268     bool m_verbose; // Enable verbose logging for debugging purposes.
269   };
270 
271   CommandObjectTraceDump(CommandInterpreter &interpreter)
272       : CommandObjectParsed(interpreter, "trace dump",
273                             "Dump the loaded processor trace data.",
274                             "trace dump") {}
275 
276   ~CommandObjectTraceDump() override = default;
277 
278   Options *GetOptions() override { return &m_options; }
279 
280 protected:
281   bool DoExecute(Args &command, CommandReturnObject &result) override {
282     Status error;
283     // TODO: fill in the dumping code here!
284     if (error.Success()) {
285       result.SetStatus(eReturnStatusSuccessFinishResult);
286     } else {
287       result.AppendErrorWithFormat("%s\n", error.AsCString());
288     }
289     return result.Succeeded();
290   }
291 
292   CommandOptions m_options;
293 };
294 
295 // CommandObjectTraceSchema
296 #define LLDB_OPTIONS_trace_schema
297 #include "CommandOptions.inc"
298 
299 #pragma mark CommandObjectTraceSchema
300 
301 class CommandObjectTraceSchema : public CommandObjectParsed {
302 public:
303   class CommandOptions : public Options {
304   public:
305     CommandOptions() { OptionParsingStarting(nullptr); }
306 
307     ~CommandOptions() override = default;
308 
309     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
310                           ExecutionContext *execution_context) override {
311       Status error;
312       const int short_option = m_getopt_table[option_idx].val;
313 
314       switch (short_option) {
315       case 'v': {
316         m_verbose = true;
317         break;
318       }
319       default:
320         llvm_unreachable("Unimplemented option");
321       }
322       return error;
323     }
324 
325     void OptionParsingStarting(ExecutionContext *execution_context) override {
326       m_verbose = false;
327     }
328 
329     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
330       return llvm::makeArrayRef(g_trace_schema_options);
331     }
332 
333     bool m_verbose; // Enable verbose logging for debugging purposes.
334   };
335 
336   CommandObjectTraceSchema(CommandInterpreter &interpreter)
337       : CommandObjectParsed(interpreter, "trace schema",
338                             "Show the schema of the given trace plugin.",
339                             "trace schema <plug-in>. Use the plug-in name "
340                             "\"all\" to see all schemas.\n") {
341     CommandArgumentData plugin_arg{eArgTypeNone, eArgRepeatPlain};
342     m_arguments.push_back({plugin_arg});
343   }
344 
345   ~CommandObjectTraceSchema() override = default;
346 
347   Options *GetOptions() override { return &m_options; }
348 
349 protected:
350   bool DoExecute(Args &command, CommandReturnObject &result) override {
351     Status error;
352     if (command.empty()) {
353       result.AppendError(
354           "trace schema cannot be invoked without a plug-in as argument");
355       return false;
356     }
357 
358     StringRef plugin_name(command[0].c_str());
359     if (plugin_name == "all") {
360       size_t index = 0;
361       while (true) {
362         StringRef schema = PluginManager::GetTraceSchema(index++);
363         if (schema.empty())
364           break;
365 
366         result.AppendMessage(schema);
367       }
368     } else {
369       if (Expected<StringRef> schemaOrErr =
370               Trace::FindPluginSchema(plugin_name))
371         result.AppendMessage(*schemaOrErr);
372       else
373         error = schemaOrErr.takeError();
374     }
375 
376     if (error.Success()) {
377       result.SetStatus(eReturnStatusSuccessFinishResult);
378     } else {
379       result.AppendErrorWithFormat("%s\n", error.AsCString());
380     }
381     return result.Succeeded();
382   }
383 
384   CommandOptions m_options;
385 };
386 
387 // CommandObjectTrace
388 
389 CommandObjectTrace::CommandObjectTrace(CommandInterpreter &interpreter)
390     : CommandObjectMultiword(interpreter, "trace",
391                              "Commands for loading and using processor "
392                              "trace information.",
393                              "trace [<sub-command-options>]") {
394   LoadSubCommand("load",
395                  CommandObjectSP(new CommandObjectTraceLoad(interpreter)));
396   LoadSubCommand("dump",
397                  CommandObjectSP(new CommandObjectTraceDump(interpreter)));
398   LoadSubCommand("save",
399                  CommandObjectSP(new CommandObjectTraceSave(interpreter)));
400   LoadSubCommand("schema",
401                  CommandObjectSP(new CommandObjectTraceSchema(interpreter)));
402 }
403 
404 CommandObjectTrace::~CommandObjectTrace() = default;
405 
406 Expected<CommandObjectSP> CommandObjectTraceProxy::DoGetProxyCommandObject() {
407   ProcessSP process_sp = m_interpreter.GetExecutionContext().GetProcessSP();
408 
409   if (!process_sp)
410     return createStringError(inconvertibleErrorCode(),
411                              "Process not available.");
412   if (m_live_debug_session_only && !process_sp->IsLiveDebugSession())
413     return createStringError(inconvertibleErrorCode(),
414                              "Process must be alive.");
415 
416   if (Expected<TraceSP> trace_sp = process_sp->GetTarget().GetTraceOrCreate())
417     return GetDelegateCommand(**trace_sp);
418   else
419     return createStringError(inconvertibleErrorCode(),
420                              "Tracing is not supported. %s",
421                              toString(trace_sp.takeError()).c_str());
422 }
423 
424 CommandObject *CommandObjectTraceProxy::GetProxyCommandObject() {
425   if (Expected<CommandObjectSP> delegate = DoGetProxyCommandObject()) {
426     m_delegate_sp = *delegate;
427     m_delegate_error.clear();
428     return m_delegate_sp.get();
429   } else {
430     m_delegate_sp.reset();
431     m_delegate_error = toString(delegate.takeError());
432     return nullptr;
433   }
434 }
435