forked from OSchip/llvm-project
[Reproducer] Add reproducer dump command.
This adds a reproducer dump commands which makes it possible to inspect a reproducer from inside LLDB. Currently it supports the Files, Commands and Version providers. I'm planning to add support for the GDB Remote provider in a follow-up patch. Differential revision: https://reviews.llvm.org/D67474 llvm-svn: 371909
This commit is contained in:
parent
ff5225bfb6
commit
97fc8eb438
|
@ -1,3 +1,4 @@
|
|||
run
|
||||
reproducer status
|
||||
reproducer dump -p files
|
||||
reproducer generate
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# This tests the reproducer dump functionality.
|
||||
|
||||
# Generate a reproducer.
|
||||
# RUN: mkdir -p %t
|
||||
# RUN: rm -rf %t.repro
|
||||
# RUN: %clang %S/Inputs/simple.c -g -o %t/reproducer.out
|
||||
# RUN: %lldb -x -b -s %S/Inputs/FileCapture.in -o 'reproducer dump -p files' --capture --capture-path %t.repro %t/reproducer.out
|
||||
|
||||
# RUN: %lldb -b -o 'reproducer dump -p files -f %t.repro' | FileCheck %s --check-prefix FILES
|
||||
# FILES: 'reproducer.out'
|
||||
# FILES: 'FileCapture.in'
|
||||
|
||||
# RUN: %lldb -b -o 'reproducer dump -p version -f %t.repro' | FileCheck %s --check-prefix VERSION
|
||||
# VERSION: lldb version
|
||||
|
||||
# RUN: %lldb -b -o 'reproducer dump -p commands -f %t.repro' | FileCheck %s --check-prefix COMMANDS
|
||||
# COMMANDS: command source
|
||||
# COMMANDS: target create
|
||||
# COMMANDS: command source
|
||||
|
||||
# RUN: %lldb --replay %t.repro | FileCheck %s --check-prefix FILES
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "CommandObjectReproducer.h"
|
||||
|
||||
#include "lldb/Host/OptionParser.h"
|
||||
#include "lldb/Utility/Reproducer.h"
|
||||
|
||||
#include "lldb/Interpreter/CommandInterpreter.h"
|
||||
|
@ -16,7 +17,52 @@
|
|||
#include "lldb/Interpreter/OptionGroupBoolean.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace llvm;
|
||||
using namespace lldb_private;
|
||||
using namespace lldb_private::repro;
|
||||
|
||||
enum ReproducerProvider {
|
||||
eReproducerProviderCommands,
|
||||
eReproducerProviderFiles,
|
||||
eReproducerProviderGDB,
|
||||
eReproducerProviderVersion,
|
||||
eReproducerProviderNone
|
||||
};
|
||||
|
||||
static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
|
||||
{
|
||||
eReproducerProviderCommands,
|
||||
"commands",
|
||||
"Command Interpreter Commands",
|
||||
},
|
||||
{
|
||||
eReproducerProviderFiles,
|
||||
"files",
|
||||
"Files",
|
||||
},
|
||||
{
|
||||
eReproducerProviderGDB,
|
||||
"gdb",
|
||||
"GDB Remote Packets",
|
||||
},
|
||||
{
|
||||
eReproducerProviderVersion,
|
||||
"version",
|
||||
"Version",
|
||||
},
|
||||
{
|
||||
eReproducerProviderNone,
|
||||
"none",
|
||||
"None",
|
||||
},
|
||||
};
|
||||
|
||||
static constexpr OptionEnumValues ReproducerProviderType() {
|
||||
return OptionEnumValues(g_reproducer_provider_type);
|
||||
}
|
||||
|
||||
#define LLDB_OPTIONS_reproducer
|
||||
#include "CommandOptions.inc"
|
||||
|
||||
class CommandObjectReproducerGenerate : public CommandObjectParsed {
|
||||
public:
|
||||
|
@ -38,7 +84,7 @@ protected:
|
|||
return false;
|
||||
}
|
||||
|
||||
auto &r = repro::Reproducer::Instance();
|
||||
auto &r = Reproducer::Instance();
|
||||
if (auto generator = r.GetGenerator()) {
|
||||
generator->Keep();
|
||||
} else if (r.GetLoader()) {
|
||||
|
@ -84,7 +130,7 @@ protected:
|
|||
return false;
|
||||
}
|
||||
|
||||
auto &r = repro::Reproducer::Instance();
|
||||
auto &r = Reproducer::Instance();
|
||||
if (r.GetGenerator()) {
|
||||
result.GetOutputStream() << "Reproducer is in capture mode.\n";
|
||||
} else if (r.GetLoader()) {
|
||||
|
@ -98,6 +144,191 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
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.",
|
||||
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_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: {
|
||||
FileSpec version_file = loader->GetFile<VersionProvider::Info>();
|
||||
|
||||
// Load the version info into a buffer.
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
|
||||
vfs::getRealFileSystem()->getBufferForFile(version_file.GetPath());
|
||||
if (!buffer) {
|
||||
SetError(result, errorCodeToError(buffer.getError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the version string.
|
||||
StringRef version = (*buffer)->getBuffer();
|
||||
result.AppendMessage(version.str());
|
||||
result.SetStatus(eReturnStatusSuccessFinishResult);
|
||||
return true;
|
||||
}
|
||||
case eReproducerProviderCommands: {
|
||||
// Create a new command loader.
|
||||
std::unique_ptr<repro::CommandLoader> command_loader =
|
||||
repro::CommandLoader::Create(loader);
|
||||
if (!command_loader) {
|
||||
SetError(result,
|
||||
make_error<StringError>(llvm::inconvertibleErrorCode(),
|
||||
"Unable to create command loader."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iterate over the command files and dump them.
|
||||
while (true) {
|
||||
llvm::Optional<std::string> command_file =
|
||||
command_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: {
|
||||
// FIXME: Dumping the GDB remote packets means moving the
|
||||
// (de)serialization code out of the GDB-remote plugin.
|
||||
result.AppendMessage("Dumping GDB remote packets isn't implemented yet.");
|
||||
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(
|
||||
|
@ -109,6 +340,8 @@ CommandObjectReproducer::CommandObjectReproducer(
|
|||
CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
|
||||
LoadSubCommand("status", CommandObjectSP(
|
||||
new CommandObjectReproducerStatus(interpreter)));
|
||||
LoadSubCommand("dump",
|
||||
CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
|
||||
}
|
||||
|
||||
CommandObjectReproducer::~CommandObjectReproducer() = default;
|
||||
|
|
|
@ -442,6 +442,15 @@ let Command = "log" in {
|
|||
Desc<"Prepend the names of files and function that generate the logs.">;
|
||||
}
|
||||
|
||||
let Command = "reproducer" in {
|
||||
def reproducer_provider : Option<"provider", "p">, Group<1>,
|
||||
EnumArg<"None", "ReproducerProviderType()">,
|
||||
Required, Desc<"The reproducer provider to dump.">;
|
||||
def reproducer_file : Option<"file", "f">, Group<1>, Arg<"Filename">,
|
||||
Desc<"The reproducer path. If a reproducer is replayed and no path is "
|
||||
"provided, that reproducer is dumped.">;
|
||||
}
|
||||
|
||||
let Command = "memory read" in {
|
||||
def memory_read_num_per_line : Option<"num-per-line", "l">, Group<1>,
|
||||
Arg<"NumberPerLine">, Desc<"The number of items per line to display.">;
|
||||
|
|
|
@ -730,9 +730,10 @@ public:
|
|||
|
||||
StringRef getExternalContentsPrefixDir() const;
|
||||
|
||||
void dump(raw_ostream &OS) const;
|
||||
void dumpEntry(raw_ostream &OS, Entry *E, int NumSpaces = 0) const;
|
||||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
||||
LLVM_DUMP_METHOD void dump() const;
|
||||
LLVM_DUMP_METHOD void dumpEntry(Entry *E, int NumSpaces = 0) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -1082,20 +1082,19 @@ StringRef RedirectingFileSystem::getExternalContentsPrefixDir() const {
|
|||
return ExternalContentsPrefixDir;
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
||||
LLVM_DUMP_METHOD void RedirectingFileSystem::dump() const {
|
||||
void RedirectingFileSystem::dump(raw_ostream &OS) const {
|
||||
for (const auto &Root : Roots)
|
||||
dumpEntry(Root.get());
|
||||
dumpEntry(OS, Root.get());
|
||||
}
|
||||
|
||||
LLVM_DUMP_METHOD void
|
||||
RedirectingFileSystem::dumpEntry(RedirectingFileSystem::Entry *E,
|
||||
int NumSpaces) const {
|
||||
void RedirectingFileSystem::dumpEntry(raw_ostream &OS,
|
||||
RedirectingFileSystem::Entry *E,
|
||||
int NumSpaces) const {
|
||||
StringRef Name = E->getName();
|
||||
for (int i = 0, e = NumSpaces; i < e; ++i)
|
||||
dbgs() << " ";
|
||||
dbgs() << "'" << Name.str().c_str() << "'"
|
||||
<< "\n";
|
||||
OS << " ";
|
||||
OS << "'" << Name.str().c_str() << "'"
|
||||
<< "\n";
|
||||
|
||||
if (E->getKind() == RedirectingFileSystem::EK_Directory) {
|
||||
auto *DE = dyn_cast<RedirectingFileSystem::RedirectingDirectoryEntry>(E);
|
||||
|
@ -1103,9 +1102,12 @@ RedirectingFileSystem::dumpEntry(RedirectingFileSystem::Entry *E,
|
|||
|
||||
for (std::unique_ptr<Entry> &SubEntry :
|
||||
llvm::make_range(DE->contents_begin(), DE->contents_end()))
|
||||
dumpEntry(SubEntry.get(), NumSpaces + 2);
|
||||
dumpEntry(OS, SubEntry.get(), NumSpaces + 2);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
||||
LLVM_DUMP_METHOD void RedirectingFileSystem::dump() const { dump(dbgs()); }
|
||||
#endif
|
||||
|
||||
/// A helper class to hold the common YAML parsing state.
|
||||
|
|
Loading…
Reference in New Issue