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