| //===-- CommandObjectDiagnostics.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 "CommandObjectDiagnostics.h" | |
| #include "lldb/Core/BugReporter.h" | |
| #include "lldb/Core/Diagnostics.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Host/OptionParser.h" | |
| #include "lldb/Interpreter/CommandOptionArgumentTable.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Interpreter/OptionArgParser.h" | |
| #include "lldb/Interpreter/OptionValueEnumeration.h" | |
| #include "lldb/Interpreter/OptionValueUInt64.h" | |
| #include "lldb/Interpreter/Options.h" | |
| #include "llvm/Support/JSON.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| #define LLDB_OPTIONS_diagnostics_dump | |
| #include "CommandOptions.inc" | |
| #define LLDB_OPTIONS_diagnostics_report | |
| #include "CommandOptions.inc" | |
| class CommandObjectDiagnosticsDump : public CommandObjectParsed { | |
| public: | |
| // Constructors and Destructors | |
| CommandObjectDiagnosticsDump(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "diagnostics dump", | |
| "Dump diagnostics to disk", nullptr) {} | |
| ~CommandObjectDiagnosticsDump() override = default; | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~CommandOptions() override = default; | |
| Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, | |
| ExecutionContext *execution_context) override { | |
| Status error; | |
| const int short_option = m_getopt_table[option_idx].val; | |
| switch (short_option) { | |
| case 'd': | |
| directory.SetDirectory(option_arg); | |
| break; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| directory.Clear(); | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_diagnostics_dump_options); | |
| } | |
| FileSpec directory; | |
| }; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| llvm::Expected<FileSpec> GetDirectory() { | |
| if (m_options.directory) { | |
| auto ec = | |
| llvm::sys::fs::create_directories(m_options.directory.GetPath()); | |
| if (ec) | |
| return llvm::errorCodeToError(ec); | |
| return m_options.directory; | |
| } | |
| return Diagnostics::CreateUniqueDirectory(); | |
| } | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| llvm::Expected<FileSpec> directory = GetDirectory(); | |
| if (!directory) { | |
| result.AppendError(llvm::toString(directory.takeError())); | |
| return; | |
| } | |
| // Collect the diagnostics bundle into the directory. | |
| llvm::Expected<Diagnostics::Report> report = | |
| Diagnostics::Instance().Collect(GetDebugger(), m_exe_ctx, *directory); | |
| if (!report) { | |
| result.AppendErrorWithFormat("failed to write diagnostics to %s", | |
| directory->GetPath().c_str()); | |
| result.AppendError(llvm::toString(report.takeError())); | |
| return; | |
| } | |
| // Print the report as JSON so the user can review what a bug report would | |
| // carry. The bundle directory and its files are listed under "attachments". | |
| result.GetOutputStream().Format("{0:2}\n", toJSON(*report)); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| CommandOptions m_options; | |
| }; | |
| class CommandObjectDiagnosticsReport : public CommandObjectParsed { | |
| public: | |
| CommandObjectDiagnosticsReport(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "diagnostics report", | |
| "Assemble a diagnostics bundle and file it as a bug report.", | |
| nullptr) {} | |
| ~CommandObjectDiagnosticsReport() override = default; | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~CommandOptions() override = default; | |
| Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, | |
| ExecutionContext *execution_context) override { | |
| Status error; | |
| const int short_option = m_getopt_table[option_idx].val; | |
| switch (short_option) { | |
| case 'd': | |
| directory.SetDirectory(option_arg); | |
| break; | |
| case 'n': | |
| no_open = true; | |
| break; | |
| case 'P': | |
| plugin = option_arg.str(); | |
| break; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| directory.Clear(); | |
| no_open = false; | |
| plugin.clear(); | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_diagnostics_report_options); | |
| } | |
| FileSpec directory; | |
| bool no_open = false; | |
| std::string plugin; | |
| }; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| llvm::Expected<FileSpec> GetDirectory() { | |
| if (m_options.directory) { | |
| auto ec = | |
| llvm::sys::fs::create_directories(m_options.directory.GetPath()); | |
| if (ec) | |
| return llvm::errorCodeToError(ec); | |
| return m_options.directory; | |
| } | |
| return Diagnostics::CreateUniqueDirectory(); | |
| } | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| llvm::Expected<FileSpec> directory = GetDirectory(); | |
| if (!directory) { | |
| result.AppendError(llvm::toString(directory.takeError())); | |
| return; | |
| } | |
| llvm::Expected<Diagnostics::Report> report = | |
| Diagnostics::Instance().Collect(GetDebugger(), m_exe_ctx, *directory); | |
| if (!report) { | |
| result.AppendError(llvm::toString(report.takeError())); | |
| return; | |
| } | |
| Stream &out = result.GetOutputStream(); | |
| out << "Bug report written to " << directory->GetPath() << "\n"; | |
| if (!report->attachments.files.empty()) { | |
| out << "Attach the following files to the issue:\n"; | |
| for (const std::string &file : report->attachments.files) | |
| out << " [ ] " << file << "\n"; | |
| } | |
| out << "Complete the report before sharing it:\n"; | |
| out << " [ ] Give the report a descriptive title\n"; | |
| out << " [ ] Answer the questions\n"; | |
| result.AppendWarning("the report may contain file paths, command history " | |
| "and program data. Review it before attaching it to a " | |
| "public issue"); | |
| if (m_options.no_open) { | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| return; | |
| } | |
| // No-tracker handling lives in the fallback reporter's File(), not here. | |
| std::unique_ptr<BugReporter> reporter = | |
| PluginManager::CreateBugReporterInstance(m_options.plugin); | |
| if (!reporter) { | |
| if (!m_options.plugin.empty()) | |
| result.AppendErrorWithFormat("no bug reporter named '%s'", | |
| m_options.plugin.c_str()); | |
| else | |
| result.AppendError("no bug reporter is available"); | |
| return; | |
| } | |
| if (llvm::Error error = reporter->File(*report)) { | |
| result.AppendError(llvm::toString(std::move(error))); | |
| return; | |
| } | |
| out << "Opened a pre-filled " << reporter->GetPluginName() << " report.\n"; | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| CommandOptions m_options; | |
| }; | |
| CommandObjectDiagnostics::CommandObjectDiagnostics( | |
| CommandInterpreter &interpreter) | |
| : CommandObjectMultiword(interpreter, "diagnostics", | |
| "Commands controlling LLDB diagnostics.", | |
| "diagnostics <subcommand> [<command-options>]") { | |
| LoadSubCommand( | |
| "dump", CommandObjectSP(new CommandObjectDiagnosticsDump(interpreter))); | |
| LoadSubCommand("report", CommandObjectSP(new CommandObjectDiagnosticsReport( | |
| interpreter))); | |
| } | |
| CommandObjectDiagnostics::~CommandObjectDiagnostics() = default; |