diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Commands/CommandObjectReproducer.cpp')
| -rw-r--r-- | contrib/llvm-project/lldb/source/Commands/CommandObjectReproducer.cpp | 413 | 
1 files changed, 401 insertions, 12 deletions
diff --git a/contrib/llvm-project/lldb/source/Commands/CommandObjectReproducer.cpp b/contrib/llvm-project/lldb/source/Commands/CommandObjectReproducer.cpp index 4b0e9e36d202..d15f622314d9 100644 --- a/contrib/llvm-project/lldb/source/Commands/CommandObjectReproducer.cpp +++ b/contrib/llvm-project/lldb/source/Commands/CommandObjectReproducer.cpp @@ -8,15 +8,94 @@  #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" + +#include <csignal>  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_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"  class CommandObjectReproducerGenerate : public CommandObjectParsed {  public: @@ -25,7 +104,7 @@ public:              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.", +            "disk and quit. In replay mode this command in a no-op.",              nullptr) {}    ~CommandObjectReproducerGenerate() override = default; @@ -38,11 +117,11 @@ protected:        return false;      } -    auto &r = repro::Reproducer::Instance(); +    auto &r = Reproducer::Instance();      if (auto generator = r.GetGenerator()) {        generator->Keep(); -    } else if (r.GetLoader()) { -      // Make this operation a NOP in replay mode. +    } else if (r.IsReplaying()) { +      // Make this operation a NO-OP in replay mode.        result.SetStatus(eReturnStatusSuccessFinishNoResult);        return result.Succeeded();      } else { @@ -57,9 +136,94 @@ protected:          << "Please have a look at the directory to assess if you're willing to "             "share the contained information.\n"; -    result.SetStatus(eReturnStatusSuccessFinishResult); +    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 { @@ -67,7 +231,8 @@ public:    CommandObjectReproducerStatus(CommandInterpreter &interpreter)        : CommandObjectParsed(              interpreter, "reproducer status", -            "Show the current reproducer status. In capture mode the debugger " +            "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 " @@ -84,10 +249,10 @@ protected:        return false;      } -    auto &r = repro::Reproducer::Instance(); -    if (r.GetGenerator()) { +    auto &r = Reproducer::Instance(); +    if (r.IsCapturing()) {        result.GetOutputStream() << "Reproducer is in capture mode.\n"; -    } else if (r.GetLoader()) { +    } else if (r.IsReplaying()) {        result.GetOutputStream() << "Reproducer is in replay mode.\n";      } else {        result.GetOutputStream() << "Reproducer is off.\n"; @@ -98,17 +263,241 @@ 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. " +                            "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>(llvm::inconvertibleErrorCode(), +                                         "Unable to create command loader.")); +        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); +      llvm::Optional<std::string> gdb_file; +      while ((gdb_file = multi_loader->GetNextFile())) { +        auto error_or_file = MemoryBuffer::getFile(*gdb_file); +        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 to inspect and manipulate the reproducer functionality.", -          "log <subcommand> [<command-options>]") { +          "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;  | 
