Index: lldb/include/lldb/Utility/Diagnostics.h =================================================================== --- lldb/include/lldb/Utility/Diagnostics.h +++ lldb/include/lldb/Utility/Diagnostics.h @@ -44,6 +44,9 @@ static void Initialize(); static void Terminate(); + /// Create a unique diagnostic directory. + static llvm::Expected CreateUniqueDirectory(); + private: static llvm::Optional &InstanceImpl(); static const size_t g_log_messages; Index: lldb/source/Commands/CMakeLists.txt =================================================================== --- lldb/source/Commands/CMakeLists.txt +++ lldb/source/Commands/CMakeLists.txt @@ -8,6 +8,7 @@ CommandObjectBreakpoint.cpp CommandObjectBreakpointCommand.cpp CommandObjectCommands.cpp + CommandObjectDiagnostics.cpp CommandObjectDisassemble.cpp CommandObjectExpression.cpp CommandObjectFrame.cpp Index: lldb/source/Commands/CommandObjectDiagnostics.h =================================================================== --- /dev/null +++ lldb/source/Commands/CommandObjectDiagnostics.h @@ -0,0 +1,29 @@ +//===-- CommandObjectDiagnostics.h ------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTDIAGNOSTICS_H +#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTDIAGNOSTICS_H + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +class CommandObjectDiagnostics : public CommandObjectMultiword { +public: + CommandObjectDiagnostics(CommandInterpreter &interpreter); + ~CommandObjectDiagnostics() override; + +private: + CommandObjectDiagnostics(const CommandObjectDiagnostics &) = delete; + const CommandObjectDiagnostics & + operator=(const CommandObjectDiagnostics &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTDIAGNOSTICS_H Index: lldb/source/Commands/CommandObjectDiagnostics.cpp =================================================================== --- /dev/null +++ lldb/source/Commands/CommandObjectDiagnostics.cpp @@ -0,0 +1,114 @@ +//===-- 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/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 "lldb/Utility/Diagnostics.h" + +using namespace lldb; +using namespace lldb_private; + +#define LLDB_OPTIONS_diagnostics_dump +#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 GetDefinitions() override { + return llvm::makeArrayRef(g_diagnostics_dump_options); + } + + FileSpec directory; + }; + + Options *GetOptions() override { return &m_options; } + +protected: + llvm::Expected 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(); + } + + bool DoExecute(Args &args, CommandReturnObject &result) override { + llvm::Expected directory = GetDirectory(); + + if (!directory) { + result.AppendError(llvm::toString(directory.takeError())); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + llvm::Error error = Diagnostics::Instance().Create(*directory); + if (error) { + result.AppendError(llvm::toString(std::move(error))); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + result.GetOutputStream() << "diagnostics written to " << *directory << '\n'; + + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +CommandObjectDiagnostics::CommandObjectDiagnostics( + CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "log", + "Commands controlling LLDB diagnostics.", + "diagnostics []") { + LoadSubCommand( + "dump", CommandObjectSP(new CommandObjectDiagnosticsDump(interpreter))); +} + +CommandObjectDiagnostics::~CommandObjectDiagnostics() = default; Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -343,6 +343,11 @@ Desc<"Force disassembly of large functions.">; } +let Command = "diagnostics dump" in { + def diagnostics_dump_directory : Option<"directory", "d">, Group<1>, + Arg<"Path">, Desc<"Dump the diagnostics to the given directory.">; +} + let Command = "expression" in { def expression_options_all_threads : Option<"all-threads", "a">, Groups<[1,2]>, Arg<"Boolean">, Desc<"Should we run all threads if the " Index: lldb/source/Interpreter/CommandInterpreter.cpp =================================================================== --- lldb/source/Interpreter/CommandInterpreter.cpp +++ lldb/source/Interpreter/CommandInterpreter.cpp @@ -15,6 +15,7 @@ #include "Commands/CommandObjectApropos.h" #include "Commands/CommandObjectBreakpoint.h" #include "Commands/CommandObjectCommands.h" +#include "Commands/CommandObjectDiagnostics.h" #include "Commands/CommandObjectDisassemble.h" #include "Commands/CommandObjectExpression.h" #include "Commands/CommandObjectFrame.h" @@ -518,6 +519,7 @@ REGISTER_COMMAND_OBJECT("apropos", CommandObjectApropos); REGISTER_COMMAND_OBJECT("breakpoint", CommandObjectMultiwordBreakpoint); REGISTER_COMMAND_OBJECT("command", CommandObjectMultiwordCommands); + REGISTER_COMMAND_OBJECT("diagnostics", CommandObjectDiagnostics); REGISTER_COMMAND_OBJECT("disassemble", CommandObjectDisassemble); REGISTER_COMMAND_OBJECT("expression", CommandObjectExpression); REGISTER_COMMAND_OBJECT("frame", CommandObjectMultiwordFrame); Index: lldb/source/Utility/Diagnostics.cpp =================================================================== --- lldb/source/Utility/Diagnostics.cpp +++ lldb/source/Utility/Diagnostics.cpp @@ -56,19 +56,18 @@ } bool Diagnostics::Dump(raw_ostream &stream) { - SmallString<128> diagnostics_dir; - std::error_code ec = - sys::fs::createUniqueDirectory("diagnostics", diagnostics_dir); - if (ec) { + Expected diagnostics_dir = CreateUniqueDirectory(); + if (!diagnostics_dir) { stream << "unable to create diagnostic dir: " - << toString(errorCodeToError(ec)) << '\n'; + << toString(diagnostics_dir.takeError()) << '\n'; return false; } - stream << "LLDB diagnostics written to " << diagnostics_dir << "\n"; + stream << "LLDB diagnostics written to " << diagnostics_dir->GetPath() + << "\n"; stream << "Please include the directory content when filing a bug report\n"; - Error error = Create(FileSpec(diagnostics_dir.str())); + Error error = Create(*diagnostics_dir); if (error) { stream << toString(std::move(error)) << '\n'; return false; @@ -77,6 +76,15 @@ return true; } +llvm::Expected Diagnostics::CreateUniqueDirectory() { + SmallString<128> diagnostics_dir; + std::error_code ec = + sys::fs::createUniqueDirectory("diagnostics", diagnostics_dir); + if (ec) + return errorCodeToError(ec); + return FileSpec(diagnostics_dir.str()); +} + Error Diagnostics::Create(const FileSpec &dir) { LLDB_LOG(GetLog(LLDBLog::AlwaysOn), "Dumping {0} diagnostics to '{1}'", m_callbacks.size(), dir.GetPath()); Index: lldb/test/Shell/Diagnostics/TestAlwaysOnLog.test =================================================================== --- /dev/null +++ lldb/test/Shell/Diagnostics/TestAlwaysOnLog.test @@ -0,0 +1,6 @@ +# RUN: rm -rf %t +# RUN: mkdir -p %t +# RUN: %lldb -o 'diagnostics dump -d %t' +# RUN: cat %t/always-on.log | FileCheck %s + +# CHECK: Dumping {{.*}} diagnostics to '{{.*}}' Index: lldb/test/Shell/Diagnostics/TestDump.test =================================================================== --- /dev/null +++ lldb/test/Shell/Diagnostics/TestDump.test @@ -0,0 +1,12 @@ +# Check that the diagnostics dump command uses the correct directory. + +# Dump to an existing directory. +# RUN: rm -rf %t.existing +# RUN: mkdir -p %t.existing +# RUN: %lldb -o 'diagnostics dump -d %t.existing' +# RUN: cat %t.existing/always-on.log + +# Dump to a non-existing directory. +# RUN: rm -rf %t.nonexisting +# RUN: %lldb -o 'diagnostics dump -d %t.nonexisting' +# RUN: cat %t.nonexisting/always-on.log Index: lldb/test/Shell/Diagnostics/TestStats.test =================================================================== --- /dev/null +++ lldb/test/Shell/Diagnostics/TestStats.test @@ -0,0 +1,6 @@ +# RUN: rm -rf %t +# RUN: mkdir -p %t +# RUN: %lldb -o 'diagnostics dump -d %t' +# RUN: cat %t/debugger-0-stats.json | FileCheck %s + +# CHECK: "totalModuleCount":0