Index: lldb/include/lldb/API/SBDebugger.h =================================================================== --- lldb/include/lldb/API/SBDebugger.h +++ lldb/include/lldb/API/SBDebugger.h @@ -94,6 +94,8 @@ static void PrintStackTraceOnError(); + static void PrintDiagnosticsOnError(); + static void Terminate(); // Deprecated, use the one that takes a source_init_files bool. Index: lldb/include/lldb/Target/Statistics.h =================================================================== --- lldb/include/lldb/Target/Statistics.h +++ lldb/include/lldb/Target/Statistics.h @@ -12,6 +12,7 @@ #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Stream.h" #include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include #include @@ -154,6 +155,9 @@ class DebuggerStats { public: + static void Initialize(); + static void Terminate(); + static void SetCollectingStats(bool enable) { g_collecting_stats = enable; } static bool GetCollectingStats() { return g_collecting_stats; } @@ -171,6 +175,8 @@ /// Returns a JSON value that contains all target metrics. static llvm::json::Value ReportStatistics(Debugger &debugger, Target *target); + static llvm::Error Dump(const FileSpec &dir); + protected: // Collecting stats can be set to true to collect stats that are expensive // to collect. By default all stats that are cheap to collect are enabled. Index: lldb/include/lldb/Utility/Diagnostics.h =================================================================== --- /dev/null +++ lldb/include/lldb/Utility/Diagnostics.h @@ -0,0 +1,57 @@ +//===-- Diagnostics.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_UTILITY_DIAGNOSTICS_H +#define LLDB_UTILITY_DIAGNOSTICS_H + +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Log.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Error.h" + +#include +#include +#include + +namespace lldb_private { + +/// Diagnostics are a collection of files to help investigate bugs and +/// troubleshoot issues. Any part of the debugger can register itself with the +/// help of a callback to emit one or more files into the diagnostic directory. +class Diagnostics { +public: + Diagnostics(); + ~Diagnostics(); + + /// Gather diagnostics in the given directory. + llvm::Error Create(const FileSpec &dir); + + /// Gather diagnostics and print a message to the given output stream. + bool Dump(llvm::raw_ostream &stream); + + using Callback = llvm::Error (*)(const FileSpec &); + + void AddCallback(Callback callback); + void RemoveCallback(Callback callback); + + static Diagnostics &Instance(); + static void Initialize(); + static void Terminate(); + +private: + static llvm::Optional &InstanceImpl(); + + llvm::SmallVector m_callbacks; + std::mutex m_callbacks_mutex; +}; + +} // namespace lldb_private + +#endif Index: lldb/source/API/SBDebugger.cpp =================================================================== --- lldb/source/API/SBDebugger.cpp +++ lldb/source/API/SBDebugger.cpp @@ -51,6 +51,7 @@ #include "lldb/Target/Process.h" #include "lldb/Target/TargetList.h" #include "lldb/Utility/Args.h" +#include "lldb/Utility/Diagnostics.h" #include "lldb/Utility/State.h" #include "lldb/Version/Version.h" @@ -218,6 +219,16 @@ llvm::sys::PrintStackTraceOnErrorSignal(executable); } +static void DumpDiagnostics(void *cookie) { + Diagnostics::Instance().Dump(llvm::errs()); +} + +void SBDebugger::PrintDiagnosticsOnError() { + LLDB_INSTRUMENT(); + + llvm::sys::AddSignalHandler(&DumpDiagnostics, nullptr); +} + void SBDebugger::Terminate() { LLDB_INSTRUMENT(); Index: lldb/source/Core/Debugger.cpp =================================================================== --- lldb/source/Core/Debugger.cpp +++ lldb/source/Core/Debugger.cpp @@ -44,6 +44,7 @@ #include "lldb/Target/Thread.h" #include "lldb/Target/ThreadList.h" #include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/Diagnostics.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Listener.h" Index: lldb/source/Initialization/SystemInitializerCommon.cpp =================================================================== --- lldb/source/Initialization/SystemInitializerCommon.cpp +++ lldb/source/Initialization/SystemInitializerCommon.cpp @@ -12,6 +12,8 @@ #include "lldb/Host/FileSystem.h" #include "lldb/Host/Host.h" #include "lldb/Host/Socket.h" +#include "lldb/Target/Statistics.h" +#include "lldb/Utility/Diagnostics.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Timer.h" #include "lldb/Version/Version.h" @@ -63,6 +65,8 @@ InitializeLldbChannel(); + Diagnostics::Initialize(); + DebuggerStats::Initialize(); FileSystem::Initialize(); HostInfo::Initialize(m_shlib_dir_helper); @@ -95,4 +99,6 @@ HostInfo::Terminate(); Log::DisableAllLogChannels(); FileSystem::Terminate(); + DebuggerStats::Terminate(); + Diagnostics::Terminate(); } Index: lldb/source/Target/Statistics.cpp =================================================================== --- lldb/source/Target/Statistics.cpp +++ lldb/source/Target/Statistics.cpp @@ -14,6 +14,7 @@ #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/Diagnostics.h" using namespace lldb; using namespace lldb_private; @@ -284,3 +285,31 @@ }; return std::move(global_stats); } + +void DebuggerStats::Initialize() { Diagnostics::Instance().AddCallback(Dump); } + +void DebuggerStats::Terminate() { + Diagnostics::Instance().RemoveCallback(Dump); +} + +llvm::Error DebuggerStats::Dump(const FileSpec &dir) { + for (size_t debugger_idx = 0; debugger_idx < Debugger::GetNumDebuggers(); + debugger_idx++) { + DebuggerSP debugger_sp(Debugger::GetDebuggerAtIndex(debugger_idx)); + if (!debugger_sp) + continue; + + std::string filename = + (Twine("debugger-") + Twine(debugger_idx) + Twine("-stats.json")).str(); + FileSpec stat_file = dir.CopyByAppendingPathComponent(filename); + + std::error_code ec; + raw_fd_ostream stats_stream(stat_file.GetPath(), ec, sys::fs::OF_None); + if (ec) + return errorCodeToError(ec); + + stats_stream << ReportStatistics(*debugger_sp, nullptr); + } + + return llvm::Error::success(); +} Index: lldb/source/Utility/CMakeLists.txt =================================================================== --- lldb/source/Utility/CMakeLists.txt +++ lldb/source/Utility/CMakeLists.txt @@ -35,6 +35,7 @@ DataBufferLLVM.cpp DataEncoder.cpp DataExtractor.cpp + Diagnostics.cpp Environment.cpp Event.cpp FileSpec.cpp Index: lldb/source/Utility/Diagnostics.cpp =================================================================== --- /dev/null +++ lldb/source/Utility/Diagnostics.cpp @@ -0,0 +1,87 @@ +//===-- Diagnostics.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 "lldb/Utility/Diagnostics.h" +#include "lldb/Utility/LLDBAssert.h" + +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; +using namespace lldb; +using namespace llvm; + +void Diagnostics::Initialize() { + lldbassert(!InstanceImpl() && "Already initialized."); + InstanceImpl().emplace(); +} + +void Diagnostics::Terminate() { + lldbassert(InstanceImpl() && "Already terminated."); + InstanceImpl().reset(); +} + +Optional &Diagnostics::InstanceImpl() { + static Optional g_diagnostics; + return g_diagnostics; +} + +Diagnostics &Diagnostics::Instance() { return *InstanceImpl(); } + +Diagnostics::Diagnostics() {} + +Diagnostics::~Diagnostics() {} + +void Diagnostics::AddCallback(Callback callback) { + std::lock_guard guard(m_callbacks_mutex); + assert(std::find(m_callbacks.begin(), m_callbacks.end(), callback) == + m_callbacks.end() && + "callback already added"); + m_callbacks.push_back(callback); +} + +void Diagnostics::RemoveCallback(Callback callback) { + std::lock_guard guard(m_callbacks_mutex); + assert(std::find(m_callbacks.begin(), m_callbacks.end(), callback) != + m_callbacks.end() && + "callback already removed"); + m_callbacks.erase( + std::remove(m_callbacks.begin(), m_callbacks.end(), callback), + m_callbacks.end()); +} + +bool Diagnostics::Dump(raw_ostream &stream) { + SmallString<128> diagnostics_dir; + std::error_code ec = + sys::fs::createUniqueDirectory("diagnostics", diagnostics_dir); + if (ec) { + stream << "unable to create diagnostic dir: " + << toString(errorCodeToError(ec)) << '\n'; + return false; + } + + stream << "LLDB diagnostics written to " << diagnostics_dir << "\n"; + stream << "Please include the directory content when filing a bug report\n"; + + Error error = Create(FileSpec(diagnostics_dir.str())); + if (error) { + stream << toString(std::move(error)) << '\n'; + return false; + } + + return true; +} + +Error Diagnostics::Create(const FileSpec &dir) { + for (Callback c : m_callbacks) { + if (Error err = c(dir)) + return err; + } + return Error::success(); +} Index: lldb/tools/driver/Driver.cpp =================================================================== --- lldb/tools/driver/Driver.cpp +++ lldb/tools/driver/Driver.cpp @@ -788,6 +788,10 @@ << '\n'; return 1; } + + // Setup LLDB signal handlers once the debugger has been initialized. + SBDebugger::PrintDiagnosticsOnError(); + SBHostOS::ThreadCreated(""); signal(SIGINT, sigint_handler);