blob: 55f34c05d11da1af7eabe5a1d5ead4890da3bc23 [file] [log] [blame]
//===-- 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,
eReproducerProviderSymbolFiles,
eReproducerProviderGDB,
eReproducerProviderProcessInfo,
eReproducerProviderVersion,
eReproducerProviderWorkingDirectory,
eReproducerProviderHomeDirectory,
eReproducerProviderNone
};
static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
{
eReproducerProviderCommands,
"commands",
"Command Interpreter Commands",
},
{
eReproducerProviderFiles,
"files",
"Files",
},
{
eReproducerProviderSymbolFiles,
"symbol-files",
"Symbol Files",
},
{
eReproducerProviderGDB,
"gdb",
"GDB Remote Packets",
},
{
eReproducerProviderProcessInfo,
"processes",
"Process Info",
},
{
eReproducerProviderVersion,
"version",
"Version",
},
{
eReproducerProviderWorkingDirectory,
"cwd",
"Working Directory",
},
{
eReproducerProviderHomeDirectory,
"home",
"Home 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"
#define LLDB_OPTIONS_reproducer_verify
#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;
}
static void SetError(CommandReturnObject &result, Error err) {
result.GetErrorStream().Printf("error: %s\n",
toString(std::move(err)).c_str());
result.SetStatus(eReturnStatusFailed);
}
/// Create a loader from the given path if specified. Otherwise use the current
/// loader used for replay.
static Loader *
GetLoaderFromPathOrCurrent(llvm::Optional<Loader> &loader_storage,
CommandReturnObject &result,
FileSpec reproducer_path) {
if (reproducer_path) {
loader_storage.emplace(reproducer_path);
Loader *loader = &(*loader_storage);
if (Error err = loader->LoadIndex()) {
// This is a hard error and will set the result to eReturnStatusFailed.
SetError(result, std::move(err));
return nullptr;
}
return loader;
}
if (Loader *loader = Reproducer::Instance().GetLoader())
return loader;
// This is a soft error because this is expected to fail during capture.
result.SetError("Not specifying a reproducer is only support during replay.");
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return nullptr;
}
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();
if (llvm::Error e = repro::Finalize(r.GetReproducerPath())) {
SetError(result, std::move(e));
return result.Succeeded();
}
} 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();
}
};
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;
}
llvm::Optional<Loader> loader_storage;
Loader *loader =
GetLoaderFromPathOrCurrent(loader_storage, result, m_options.file);
if (!loader)
return false;
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 eReproducerProviderSymbolFiles: {
Expected<std::string> symbol_files =
loader->LoadBuffer<SymbolFileProvider>();
if (!symbol_files) {
SetError(result, symbol_files.takeError());
return false;
}
std::vector<SymbolFileProvider::Entry> entries;
llvm::yaml::Input yin(*symbol_files);
yin >> entries;
for (const auto &entry : entries) {
result.AppendMessageWithFormat("- uuid: %s\n",
entry.uuid.c_str());
result.AppendMessageWithFormat(" module path: %s\n",
entry.module_path.c_str());
result.AppendMessageWithFormat(" symbol path: %s\n",
entry.symbol_path.c_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 =
repro::GetDirectoryFrom<WorkingDirectoryProvider>(loader);
if (!cwd) {
SetError(result, cwd.takeError());
return false;
}
result.AppendMessage(*cwd);
result.SetStatus(eReturnStatusSuccessFinishResult);
return true;
}
case eReproducerProviderHomeDirectory: {
Expected<std::string> home =
repro::GetDirectoryFrom<HomeDirectoryProvider>(loader);
if (!home) {
SetError(result, home.takeError());
return false;
}
result.AppendMessage(*home);
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;
};
class CommandObjectReproducerVerify : public CommandObjectParsed {
public:
CommandObjectReproducerVerify(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "reproducer verify",
"Verify the contents of a reproducer. "
"If no reproducer is specified during replay, it "
"verifies the content of the current reproducer.",
nullptr) {}
~CommandObjectReproducerVerify() 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;
default:
llvm_unreachable("Unimplemented option");
}
return error;
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
file.Clear();
}
ArrayRef<OptionDefinition> GetDefinitions() override {
return makeArrayRef(g_reproducer_verify_options);
}
FileSpec file;
};
protected:
bool DoExecute(Args &command, CommandReturnObject &result) override {
if (!command.empty()) {
result.AppendErrorWithFormat("'%s' takes no arguments",
m_cmd_name.c_str());
return false;
}
llvm::Optional<Loader> loader_storage;
Loader *loader =
GetLoaderFromPathOrCurrent(loader_storage, result, m_options.file);
if (!loader)
return false;
bool errors = false;
auto error_callback = [&](llvm::StringRef error) {
errors = true;
result.AppendError(error);
};
bool warnings = false;
auto warning_callback = [&](llvm::StringRef warning) {
warnings = true;
result.AppendWarning(warning);
};
auto note_callback = [&](llvm::StringRef warning) {
result.AppendMessage(warning);
};
Verifier verifier(loader);
verifier.Verify(error_callback, warning_callback, note_callback);
if (warnings || errors) {
result.AppendMessage("reproducer verification failed");
result.SetStatus(eReturnStatusFailed);
} else {
result.AppendMessage("reproducer verification succeeded");
result.SetStatus(eReturnStatusSuccessFinishResult);
}
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("verify", CommandObjectSP(
new CommandObjectReproducerVerify(interpreter)));
LoadSubCommand("xcrash", CommandObjectSP(
new CommandObjectReproducerXCrash(interpreter)));
}
CommandObjectReproducer::~CommandObjectReproducer() = default;