
This patch should allow the user to set specific auto-completion type for their custom commands. To do so, we had to hoist the `CompletionType` enum so the user can access it and add a new completion type flag to the CommandScriptAdd Command Object. So now, the user can specify which completion type will be used with their custom command, when they register it. This also makes the `crashlog` custom commands use disk-file completion type, to browse through the user file system and load the report. Differential Revision: https://reviews.llvm.org/D152011 Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
433 lines
14 KiB
C++
433 lines
14 KiB
C++
//===-- CommandObjectTrace.cpp --------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "CommandObjectTrace.h"
|
|
|
|
#include "llvm/Support/JSON.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Host/OptionParser.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/CommandObject.h"
|
|
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
|
|
#include "lldb/Interpreter/CommandReturnObject.h"
|
|
#include "lldb/Interpreter/OptionArgParser.h"
|
|
#include "lldb/Interpreter/OptionGroupFormat.h"
|
|
#include "lldb/Interpreter/OptionValueBoolean.h"
|
|
#include "lldb/Interpreter/OptionValueLanguage.h"
|
|
#include "lldb/Interpreter/OptionValueString.h"
|
|
#include "lldb/Interpreter/Options.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/Trace.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace llvm;
|
|
|
|
// CommandObjectTraceSave
|
|
#define LLDB_OPTIONS_trace_save
|
|
#include "CommandOptions.inc"
|
|
|
|
#pragma mark CommandObjectTraceSave
|
|
|
|
class CommandObjectTraceSave : public CommandObjectParsed {
|
|
public:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() { OptionParsingStarting(nullptr); }
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'c': {
|
|
m_compact = true;
|
|
break;
|
|
}
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_compact = false;
|
|
};
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_trace_save_options);
|
|
};
|
|
|
|
bool m_compact;
|
|
};
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
CommandObjectTraceSave(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "trace save",
|
|
"Save the trace of the current target in the specified directory, "
|
|
"which will be created if needed. "
|
|
"This directory will contain a trace bundle, with all the "
|
|
"necessary files the reconstruct the trace session even on a "
|
|
"different computer. "
|
|
"Part of this bundle is the bundle description file with the name "
|
|
"trace.json. This file can be used by the \"trace load\" command "
|
|
"to load this trace in LLDB."
|
|
"Note: if the current target contains information of multiple "
|
|
"processes or targets, they all will be included in the bundle.",
|
|
"trace save [<cmd-options>] <bundle_directory>",
|
|
eCommandRequiresProcess | eCommandTryTargetAPILock |
|
|
eCommandProcessMustBeLaunched | eCommandProcessMustBePaused |
|
|
eCommandProcessMustBeTraced) {
|
|
CommandArgumentData bundle_dir{eArgTypeDirectoryName, eArgRepeatPlain};
|
|
m_arguments.push_back({bundle_dir});
|
|
}
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
|
|
GetCommandInterpreter(), lldb::eDiskFileCompletion, request, nullptr);
|
|
}
|
|
|
|
~CommandObjectTraceSave() override = default;
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (command.size() != 1) {
|
|
result.AppendError("a single path to a directory where the trace bundle "
|
|
"will be created is required");
|
|
return false;
|
|
}
|
|
|
|
FileSpec bundle_dir(command[0].ref());
|
|
FileSystem::Instance().Resolve(bundle_dir);
|
|
|
|
ProcessSP process_sp = m_exe_ctx.GetProcessSP();
|
|
|
|
TraceSP trace_sp = process_sp->GetTarget().GetTrace();
|
|
|
|
if (llvm::Expected<FileSpec> desc_file =
|
|
trace_sp->SaveToDisk(bundle_dir, m_options.m_compact)) {
|
|
result.AppendMessageWithFormatv(
|
|
"Trace bundle description file written to: {0}", *desc_file);
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
} else {
|
|
result.AppendError(toString(desc_file.takeError()));
|
|
}
|
|
|
|
return result.Succeeded();
|
|
}
|
|
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
// CommandObjectTraceLoad
|
|
#define LLDB_OPTIONS_trace_load
|
|
#include "CommandOptions.inc"
|
|
|
|
#pragma mark CommandObjectTraceLoad
|
|
|
|
class CommandObjectTraceLoad : public CommandObjectParsed {
|
|
public:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() { OptionParsingStarting(nullptr); }
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'v': {
|
|
m_verbose = true;
|
|
break;
|
|
}
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_verbose = false;
|
|
}
|
|
|
|
ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return ArrayRef(g_trace_load_options);
|
|
}
|
|
|
|
bool m_verbose; // Enable verbose logging for debugging purposes.
|
|
};
|
|
|
|
CommandObjectTraceLoad(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "trace load",
|
|
"Load a post-mortem processor trace session from a trace bundle.",
|
|
"trace load <trace_description_file>") {
|
|
CommandArgumentData session_file_arg{eArgTypeFilename, eArgRepeatPlain};
|
|
m_arguments.push_back({session_file_arg});
|
|
}
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
|
|
GetCommandInterpreter(), lldb::eDiskFileCompletion, request, nullptr);
|
|
}
|
|
|
|
~CommandObjectTraceLoad() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (command.size() != 1) {
|
|
result.AppendError("a single path to a JSON file containing a the "
|
|
"description of the trace bundle is required");
|
|
return false;
|
|
}
|
|
|
|
const FileSpec trace_description_file(command[0].ref());
|
|
|
|
llvm::Expected<lldb::TraceSP> trace_or_err =
|
|
Trace::LoadPostMortemTraceFromFile(GetDebugger(),
|
|
trace_description_file);
|
|
|
|
if (!trace_or_err) {
|
|
result.AppendErrorWithFormat(
|
|
"%s\n", llvm::toString(trace_or_err.takeError()).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (m_options.m_verbose) {
|
|
result.AppendMessageWithFormatv("loading trace with plugin {0}\n",
|
|
trace_or_err.get()->GetPluginName());
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return true;
|
|
}
|
|
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
// CommandObjectTraceDump
|
|
#define LLDB_OPTIONS_trace_dump
|
|
#include "CommandOptions.inc"
|
|
|
|
#pragma mark CommandObjectTraceDump
|
|
|
|
class CommandObjectTraceDump : public CommandObjectParsed {
|
|
public:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() { OptionParsingStarting(nullptr); }
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'v': {
|
|
m_verbose = true;
|
|
break;
|
|
}
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_verbose = false;
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_trace_dump_options);
|
|
}
|
|
|
|
bool m_verbose; // Enable verbose logging for debugging purposes.
|
|
};
|
|
|
|
CommandObjectTraceDump(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "trace dump",
|
|
"Dump the loaded processor trace data.",
|
|
"trace dump") {}
|
|
|
|
~CommandObjectTraceDump() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
Status error;
|
|
// TODO: fill in the dumping code here!
|
|
if (error.Success()) {
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
} else {
|
|
result.AppendErrorWithFormat("%s\n", error.AsCString());
|
|
}
|
|
return result.Succeeded();
|
|
}
|
|
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
// CommandObjectTraceSchema
|
|
#define LLDB_OPTIONS_trace_schema
|
|
#include "CommandOptions.inc"
|
|
|
|
#pragma mark CommandObjectTraceSchema
|
|
|
|
class CommandObjectTraceSchema : public CommandObjectParsed {
|
|
public:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() { OptionParsingStarting(nullptr); }
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'v': {
|
|
m_verbose = true;
|
|
break;
|
|
}
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_verbose = false;
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_trace_schema_options);
|
|
}
|
|
|
|
bool m_verbose; // Enable verbose logging for debugging purposes.
|
|
};
|
|
|
|
CommandObjectTraceSchema(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "trace schema",
|
|
"Show the schema of the given trace plugin.",
|
|
"trace schema <plug-in>. Use the plug-in name "
|
|
"\"all\" to see all schemas.\n") {
|
|
CommandArgumentData plugin_arg{eArgTypeNone, eArgRepeatPlain};
|
|
m_arguments.push_back({plugin_arg});
|
|
}
|
|
|
|
~CommandObjectTraceSchema() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
Status error;
|
|
if (command.empty()) {
|
|
result.AppendError(
|
|
"trace schema cannot be invoked without a plug-in as argument");
|
|
return false;
|
|
}
|
|
|
|
StringRef plugin_name(command[0].c_str());
|
|
if (plugin_name == "all") {
|
|
size_t index = 0;
|
|
while (true) {
|
|
StringRef schema = PluginManager::GetTraceSchema(index++);
|
|
if (schema.empty())
|
|
break;
|
|
|
|
result.AppendMessage(schema);
|
|
}
|
|
} else {
|
|
if (Expected<StringRef> schemaOrErr =
|
|
Trace::FindPluginSchema(plugin_name))
|
|
result.AppendMessage(*schemaOrErr);
|
|
else
|
|
error = schemaOrErr.takeError();
|
|
}
|
|
|
|
if (error.Success()) {
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
} else {
|
|
result.AppendErrorWithFormat("%s\n", error.AsCString());
|
|
}
|
|
return result.Succeeded();
|
|
}
|
|
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
// CommandObjectTrace
|
|
|
|
CommandObjectTrace::CommandObjectTrace(CommandInterpreter &interpreter)
|
|
: CommandObjectMultiword(interpreter, "trace",
|
|
"Commands for loading and using processor "
|
|
"trace information.",
|
|
"trace [<sub-command-options>]") {
|
|
LoadSubCommand("load",
|
|
CommandObjectSP(new CommandObjectTraceLoad(interpreter)));
|
|
LoadSubCommand("dump",
|
|
CommandObjectSP(new CommandObjectTraceDump(interpreter)));
|
|
LoadSubCommand("save",
|
|
CommandObjectSP(new CommandObjectTraceSave(interpreter)));
|
|
LoadSubCommand("schema",
|
|
CommandObjectSP(new CommandObjectTraceSchema(interpreter)));
|
|
}
|
|
|
|
CommandObjectTrace::~CommandObjectTrace() = default;
|
|
|
|
Expected<CommandObjectSP> CommandObjectTraceProxy::DoGetProxyCommandObject() {
|
|
ProcessSP process_sp = m_interpreter.GetExecutionContext().GetProcessSP();
|
|
|
|
if (!process_sp)
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Process not available.");
|
|
if (m_live_debug_session_only && !process_sp->IsLiveDebugSession())
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Process must be alive.");
|
|
|
|
if (Expected<TraceSP> trace_sp = process_sp->GetTarget().GetTraceOrCreate())
|
|
return GetDelegateCommand(**trace_sp);
|
|
else
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Tracing is not supported. %s",
|
|
toString(trace_sp.takeError()).c_str());
|
|
}
|
|
|
|
CommandObject *CommandObjectTraceProxy::GetProxyCommandObject() {
|
|
if (Expected<CommandObjectSP> delegate = DoGetProxyCommandObject()) {
|
|
m_delegate_sp = *delegate;
|
|
m_delegate_error.clear();
|
|
return m_delegate_sp.get();
|
|
} else {
|
|
m_delegate_sp.reset();
|
|
m_delegate_error = toString(delegate.takeError());
|
|
return nullptr;
|
|
}
|
|
}
|