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