forked from OSchip/llvm-project
567 lines
18 KiB
C++
567 lines
18 KiB
C++
//===-- CommandObjectReproducer.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 "CommandObjectReproducer.h"
|
|
|
|
#include "lldb/Host/HostInfo.h"
|
|
#include "lldb/Host/OptionParser.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/CommandReturnObject.h"
|
|
#include "lldb/Interpreter/OptionArgParser.h"
|
|
#include "lldb/Utility/GDBRemote.h"
|
|
#include "lldb/Utility/ProcessInfo.h"
|
|
#include "lldb/Utility/Reproducer.h"
|
|
|
|
#include <csignal>
|
|
|
|
using namespace lldb;
|
|
using namespace llvm;
|
|
using namespace lldb_private;
|
|
using namespace lldb_private::repro;
|
|
|
|
enum ReproducerProvider {
|
|
eReproducerProviderCommands,
|
|
eReproducerProviderFiles,
|
|
eReproducerProviderGDB,
|
|
eReproducerProviderProcessInfo,
|
|
eReproducerProviderVersion,
|
|
eReproducerProviderWorkingDirectory,
|
|
eReproducerProviderNone
|
|
};
|
|
|
|
static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
|
|
{
|
|
eReproducerProviderCommands,
|
|
"commands",
|
|
"Command Interpreter Commands",
|
|
},
|
|
{
|
|
eReproducerProviderFiles,
|
|
"files",
|
|
"Files",
|
|
},
|
|
{
|
|
eReproducerProviderGDB,
|
|
"gdb",
|
|
"GDB Remote Packets",
|
|
},
|
|
{
|
|
eReproducerProviderProcessInfo,
|
|
"processes",
|
|
"Process Info",
|
|
},
|
|
{
|
|
eReproducerProviderVersion,
|
|
"version",
|
|
"Version",
|
|
},
|
|
{
|
|
eReproducerProviderWorkingDirectory,
|
|
"cwd",
|
|
"Working Directory",
|
|
},
|
|
{
|
|
eReproducerProviderNone,
|
|
"none",
|
|
"None",
|
|
},
|
|
};
|
|
|
|
static constexpr OptionEnumValues ReproducerProviderType() {
|
|
return OptionEnumValues(g_reproducer_provider_type);
|
|
}
|
|
|
|
#define LLDB_OPTIONS_reproducer_dump
|
|
#include "CommandOptions.inc"
|
|
|
|
enum ReproducerCrashSignal {
|
|
eReproducerCrashSigill,
|
|
eReproducerCrashSigsegv,
|
|
};
|
|
|
|
static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
|
|
{
|
|
eReproducerCrashSigill,
|
|
"SIGILL",
|
|
"Illegal instruction",
|
|
},
|
|
{
|
|
eReproducerCrashSigsegv,
|
|
"SIGSEGV",
|
|
"Segmentation fault",
|
|
},
|
|
};
|
|
|
|
static constexpr OptionEnumValues ReproducerSignalType() {
|
|
return OptionEnumValues(g_reproducer_signaltype);
|
|
}
|
|
|
|
#define LLDB_OPTIONS_reproducer_xcrash
|
|
#include "CommandOptions.inc"
|
|
|
|
template <typename T>
|
|
llvm::Expected<T> static ReadFromYAML(StringRef filename) {
|
|
auto error_or_file = MemoryBuffer::getFile(filename);
|
|
if (auto err = error_or_file.getError()) {
|
|
return errorCodeToError(err);
|
|
}
|
|
|
|
T t;
|
|
yaml::Input yin((*error_or_file)->getBuffer());
|
|
yin >> t;
|
|
|
|
if (auto err = yin.error()) {
|
|
return errorCodeToError(err);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
class CommandObjectReproducerGenerate : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "reproducer generate",
|
|
"Generate reproducer on disk. When the debugger is in capture "
|
|
"mode, this command will output the reproducer to a directory on "
|
|
"disk and quit. In replay mode this command in a no-op.",
|
|
nullptr) {}
|
|
|
|
~CommandObjectReproducerGenerate() override = default;
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (!command.empty()) {
|
|
result.AppendErrorWithFormat("'%s' takes no arguments",
|
|
m_cmd_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
auto &r = Reproducer::Instance();
|
|
if (auto generator = r.GetGenerator()) {
|
|
generator->Keep();
|
|
} else if (r.IsReplaying()) {
|
|
// Make this operation a NO-OP in replay mode.
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
return result.Succeeded();
|
|
} else {
|
|
result.AppendErrorWithFormat("Unable to get the reproducer generator");
|
|
result.SetStatus(eReturnStatusFailed);
|
|
return false;
|
|
}
|
|
|
|
result.GetOutputStream()
|
|
<< "Reproducer written to '" << r.GetReproducerPath() << "'\n";
|
|
result.GetOutputStream()
|
|
<< "Please have a look at the directory to assess if you're willing to "
|
|
"share the contained information.\n";
|
|
|
|
m_interpreter.BroadcastEvent(
|
|
CommandInterpreter::eBroadcastBitQuitCommandReceived);
|
|
result.SetStatus(eReturnStatusQuit);
|
|
return result.Succeeded();
|
|
}
|
|
};
|
|
|
|
class CommandObjectReproducerXCrash : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "reproducer xcrash",
|
|
"Intentionally force the debugger to crash in "
|
|
"order to trigger and test reproducer generation.",
|
|
nullptr) {}
|
|
|
|
~CommandObjectReproducerXCrash() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() : Options() {}
|
|
|
|
~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 's':
|
|
signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
|
|
option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
|
|
if (!error.Success())
|
|
error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
|
|
option_arg.str().c_str());
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
signal = eReproducerCrashSigsegv;
|
|
}
|
|
|
|
ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return makeArrayRef(g_reproducer_xcrash_options);
|
|
}
|
|
|
|
ReproducerCrashSignal signal = eReproducerCrashSigsegv;
|
|
};
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (!command.empty()) {
|
|
result.AppendErrorWithFormat("'%s' takes no arguments",
|
|
m_cmd_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
auto &r = Reproducer::Instance();
|
|
|
|
if (!r.IsCapturing() && !r.IsReplaying()) {
|
|
result.SetError(
|
|
"forcing a crash is only supported when capturing a reproducer.");
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
return false;
|
|
}
|
|
|
|
switch (m_options.signal) {
|
|
case eReproducerCrashSigill:
|
|
std::raise(SIGILL);
|
|
break;
|
|
case eReproducerCrashSigsegv:
|
|
std::raise(SIGSEGV);
|
|
break;
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusQuit);
|
|
return result.Succeeded();
|
|
}
|
|
|
|
private:
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
class CommandObjectReproducerStatus : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectReproducerStatus(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "reproducer status",
|
|
"Show the current reproducer status. In capture mode the "
|
|
"debugger "
|
|
"is collecting all the information it needs to create a "
|
|
"reproducer. In replay mode the reproducer is replaying a "
|
|
"reproducer. When the reproducers are off, no data is collected "
|
|
"and no reproducer can be generated.",
|
|
nullptr) {}
|
|
|
|
~CommandObjectReproducerStatus() override = default;
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (!command.empty()) {
|
|
result.AppendErrorWithFormat("'%s' takes no arguments",
|
|
m_cmd_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
auto &r = Reproducer::Instance();
|
|
if (r.IsCapturing()) {
|
|
result.GetOutputStream() << "Reproducer is in capture mode.\n";
|
|
} else if (r.IsReplaying()) {
|
|
result.GetOutputStream() << "Reproducer is in replay mode.\n";
|
|
} else {
|
|
result.GetOutputStream() << "Reproducer is off.\n";
|
|
}
|
|
|
|
if (r.IsCapturing() || r.IsReplaying()) {
|
|
result.GetOutputStream()
|
|
<< "Path: " << r.GetReproducerPath().GetPath() << '\n';
|
|
}
|
|
|
|
// Auto generate is hidden unless enabled because this is mostly for
|
|
// development and testing.
|
|
if (Generator *g = r.GetGenerator()) {
|
|
if (g->IsAutoGenerate())
|
|
result.GetOutputStream() << "Auto generate: on\n";
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return result.Succeeded();
|
|
}
|
|
};
|
|
|
|
static void SetError(CommandReturnObject &result, Error err) {
|
|
result.GetErrorStream().Printf("error: %s\n",
|
|
toString(std::move(err)).c_str());
|
|
result.SetStatus(eReturnStatusFailed);
|
|
}
|
|
|
|
class CommandObjectReproducerDump : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectReproducerDump(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "reproducer dump",
|
|
"Dump the information contained in a reproducer. "
|
|
"If no reproducer is specified during replay, it "
|
|
"dumps the content of the current reproducer.",
|
|
nullptr) {}
|
|
|
|
~CommandObjectReproducerDump() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() : Options(), file() {}
|
|
|
|
~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 'f':
|
|
file.SetFile(option_arg, FileSpec::Style::native);
|
|
FileSystem::Instance().Resolve(file);
|
|
break;
|
|
case 'p':
|
|
provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
|
|
option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
|
|
if (!error.Success())
|
|
error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
|
|
option_arg.str().c_str());
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
file.Clear();
|
|
provider = eReproducerProviderNone;
|
|
}
|
|
|
|
ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return makeArrayRef(g_reproducer_dump_options);
|
|
}
|
|
|
|
FileSpec file;
|
|
ReproducerProvider provider = eReproducerProviderNone;
|
|
};
|
|
|
|
protected:
|
|
bool DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (!command.empty()) {
|
|
result.AppendErrorWithFormat("'%s' takes no arguments",
|
|
m_cmd_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
// If no reproducer path is specified, use the loader currently used for
|
|
// replay. Otherwise create a new loader just for dumping.
|
|
llvm::Optional<Loader> loader_storage;
|
|
Loader *loader = nullptr;
|
|
if (!m_options.file) {
|
|
loader = Reproducer::Instance().GetLoader();
|
|
if (loader == nullptr) {
|
|
result.SetError(
|
|
"Not specifying a reproducer is only support during replay.");
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
return false;
|
|
}
|
|
} else {
|
|
loader_storage.emplace(m_options.file);
|
|
loader = &(*loader_storage);
|
|
if (Error err = loader->LoadIndex()) {
|
|
SetError(result, std::move(err));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If we get here we should have a valid loader.
|
|
assert(loader);
|
|
|
|
switch (m_options.provider) {
|
|
case eReproducerProviderFiles: {
|
|
FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
|
|
|
|
// Read the VFS mapping.
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
|
|
vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
|
|
if (!buffer) {
|
|
SetError(result, errorCodeToError(buffer.getError()));
|
|
return false;
|
|
}
|
|
|
|
// Initialize a VFS from the given mapping.
|
|
IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
|
|
std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
|
|
|
|
// Dump the VFS to a buffer.
|
|
std::string str;
|
|
raw_string_ostream os(str);
|
|
static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
|
|
os.flush();
|
|
|
|
// Return the string.
|
|
result.AppendMessage(str);
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return true;
|
|
}
|
|
case eReproducerProviderVersion: {
|
|
Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
|
|
if (!version) {
|
|
SetError(result, version.takeError());
|
|
return false;
|
|
}
|
|
result.AppendMessage(*version);
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return true;
|
|
}
|
|
case eReproducerProviderWorkingDirectory: {
|
|
Expected<std::string> cwd =
|
|
loader->LoadBuffer<WorkingDirectoryProvider>();
|
|
if (!cwd) {
|
|
SetError(result, cwd.takeError());
|
|
return false;
|
|
}
|
|
result.AppendMessage(*cwd);
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return true;
|
|
}
|
|
case eReproducerProviderCommands: {
|
|
std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader =
|
|
repro::MultiLoader<repro::CommandProvider>::Create(loader);
|
|
if (!multi_loader) {
|
|
SetError(result,
|
|
make_error<StringError>("Unable to create command loader.",
|
|
llvm::inconvertibleErrorCode()));
|
|
return false;
|
|
}
|
|
|
|
// Iterate over the command files and dump them.
|
|
llvm::Optional<std::string> command_file;
|
|
while ((command_file = multi_loader->GetNextFile())) {
|
|
if (!command_file)
|
|
break;
|
|
|
|
auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
|
|
if (auto err = command_buffer.getError()) {
|
|
SetError(result, errorCodeToError(err));
|
|
return false;
|
|
}
|
|
result.AppendMessage((*command_buffer)->getBuffer());
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return true;
|
|
}
|
|
case eReproducerProviderGDB: {
|
|
std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
|
|
multi_loader =
|
|
repro::MultiLoader<repro::GDBRemoteProvider>::Create(loader);
|
|
|
|
if (!multi_loader) {
|
|
SetError(result,
|
|
make_error<StringError>("Unable to create GDB loader.",
|
|
llvm::inconvertibleErrorCode()));
|
|
return false;
|
|
}
|
|
|
|
llvm::Optional<std::string> gdb_file;
|
|
while ((gdb_file = multi_loader->GetNextFile())) {
|
|
if (llvm::Expected<std::vector<GDBRemotePacket>> packets =
|
|
ReadFromYAML<std::vector<GDBRemotePacket>>(*gdb_file)) {
|
|
for (GDBRemotePacket &packet : *packets) {
|
|
packet.Dump(result.GetOutputStream());
|
|
}
|
|
} else {
|
|
SetError(result, packets.takeError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return true;
|
|
}
|
|
case eReproducerProviderProcessInfo: {
|
|
std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>>
|
|
multi_loader =
|
|
repro::MultiLoader<repro::ProcessInfoProvider>::Create(loader);
|
|
|
|
if (!multi_loader) {
|
|
SetError(result, make_error<StringError>(
|
|
llvm::inconvertibleErrorCode(),
|
|
"Unable to create process info loader."));
|
|
return false;
|
|
}
|
|
|
|
llvm::Optional<std::string> process_file;
|
|
while ((process_file = multi_loader->GetNextFile())) {
|
|
if (llvm::Expected<ProcessInstanceInfoList> infos =
|
|
ReadFromYAML<ProcessInstanceInfoList>(*process_file)) {
|
|
for (ProcessInstanceInfo info : *infos)
|
|
info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver());
|
|
} else {
|
|
SetError(result, infos.takeError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return true;
|
|
}
|
|
case eReproducerProviderNone:
|
|
result.SetError("No valid provider specified.");
|
|
return false;
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
return result.Succeeded();
|
|
}
|
|
|
|
private:
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
CommandObjectReproducer::CommandObjectReproducer(
|
|
CommandInterpreter &interpreter)
|
|
: CommandObjectMultiword(
|
|
interpreter, "reproducer",
|
|
"Commands for manipulating reproducers. Reproducers make it "
|
|
"possible "
|
|
"to capture full debug sessions with all its dependencies. The "
|
|
"resulting reproducer is used to replay the debug session while "
|
|
"debugging the debugger.\n"
|
|
"Because reproducers need the whole the debug session from "
|
|
"beginning to end, you need to launch the debugger in capture or "
|
|
"replay mode, commonly though the command line driver.\n"
|
|
"Reproducers are unrelated record-replay debugging, as you cannot "
|
|
"interact with the debugger during replay.\n",
|
|
"reproducer <subcommand> [<subcommand-options>]") {
|
|
LoadSubCommand(
|
|
"generate",
|
|
CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
|
|
LoadSubCommand("status", CommandObjectSP(
|
|
new CommandObjectReproducerStatus(interpreter)));
|
|
LoadSubCommand("dump",
|
|
CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
|
|
LoadSubCommand("xcrash", CommandObjectSP(
|
|
new CommandObjectReproducerXCrash(interpreter)));
|
|
}
|
|
|
|
CommandObjectReproducer::~CommandObjectReproducer() = default;
|