| //===-- CommandObjectReproducer.cpp -----------------------------*- C++ -*-===// | |
| // | |
| // 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/OptionParser.h" | |
| #include "lldb/Utility/GDBRemote.h" | |
| #include "lldb/Utility/Reproducer.h" | |
| #include "lldb/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Interpreter/OptionArgParser.h" | |
| #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, | |
| eReproducerProviderWorkingDirectory, | |
| eReproducerProviderNone | |
| }; | |
| static constexpr OptionEnumValueElement g_reproducer_provider_type[] = { | |
| { | |
| eReproducerProviderCommands, | |
| "commands", | |
| "Command Interpreter Commands", | |
| }, | |
| { | |
| eReproducerProviderFiles, | |
| "files", | |
| "Files", | |
| }, | |
| { | |
| eReproducerProviderGDB, | |
| "gdb", | |
| "GDB Remote Packets", | |
| }, | |
| { | |
| eReproducerProviderVersion, | |
| "version", | |
| "Version", | |
| }, | |
| { | |
| eReproducerProviderWorkingDirectory, | |
| "cwd", | |
| "Working Directory", | |
| }, | |
| { | |
| 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: | |
| 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. 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 NOP 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"; | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return result.Succeeded(); | |
| } | |
| }; | |
| 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"; | |
| } | |
| 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_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: { | |
| // 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: { | |
| FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>(); | |
| auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath()); | |
| if (auto err = error_or_file.getError()) { | |
| SetError(result, errorCodeToError(err)); | |
| return false; | |
| } | |
| std::vector<GDBRemotePacket> packets; | |
| yaml::Input yin((*error_or_file)->getBuffer()); | |
| yin >> packets; | |
| if (auto err = yin.error()) { | |
| SetError(result, errorCodeToError(err)); | |
| return false; | |
| } | |
| for (GDBRemotePacket &packet : packets) { | |
| packet.Dump(result.GetOutputStream()); | |
| } | |
| 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))); | |
| } | |
| CommandObjectReproducer::~CommandObjectReproducer() = default; |