diff --git a/lldb/include/lldb/API/SBBroadcaster.h b/lldb/include/lldb/API/SBBroadcaster.h --- a/lldb/include/lldb/API/SBBroadcaster.h +++ b/lldb/include/lldb/API/SBBroadcaster.h @@ -63,6 +63,7 @@ protected: friend class SBCommandInterpreter; friend class SBCommunication; + friend class SBDebugger; friend class SBEvent; friend class SBListener; friend class SBProcess; diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -33,6 +33,8 @@ class LLDB_API SBDebugger { public: + FLAGS_ANONYMOUS_ENUM(){eBroadcastBitProgress = (1 << 0)}; + SBDebugger(); SBDebugger(const lldb::SBDebugger &rhs); @@ -41,6 +43,42 @@ ~SBDebugger(); + static const char *GetBroadcasterClass(); + + lldb::SBBroadcaster GetBroadcaster(); + + /// Get progress data from a SBEvent whose type is eBroadcastBitProgress. + /// + /// \param [in] event + /// The event to extract the progress information from. + /// + /// \param [out] progress_id + /// The unique integer identifier for the progress to report. + /// + /// \param [out] completed + /// The amount of work completed. If \a completed is zero, then this event + /// is a progress started event. If \a completed is equal to \a total, then + /// this event is a progress end event. Otherwise completed indicates the + /// current progress update. + /// + /// \param [out] total + /// The total amount of work units that need to be completed. If this value + /// is UINT64_MAX, then an indeterminate progress indicator should be + /// displayed. + /// + /// \param [out] is_debugger_specific + /// Set to true if this progress is specific to this debugger only. Many + /// progress events are not specific to a debugger instance, like any + /// progress events for loading information in modules since LLDB has a + /// global module cache that all debuggers use. + /// + /// \return The message for the progress. If the returned value is NULL, then + /// \a event was not a eBroadcastBitProgress event. + static const char *GetProgressFromEvent(const lldb::SBEvent &event, + uint64_t &progress_id, + uint64_t &completed, uint64_t &total, + bool &is_debugger_specific); + lldb::SBDebugger &operator=(const lldb::SBDebugger &rhs); static void Initialize(); diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -73,6 +73,50 @@ friend class SourceManager; // For GetSourceFileCache. public: + /// Broadcaster event bits definitions. + enum { + eBroadcastBitProgress = (1 << 0), + }; + + static ConstString GetStaticBroadcasterClass(); + + /// Get the public broadcaster for this debugger. + Broadcaster &GetBroadcaster() { return m_broadcaster; } + const Broadcaster &GetBroadcaster() const { return m_broadcaster; } + + class ProgressEventData : public EventData { + + public: + ProgressEventData(uint64_t progress_id, const std::string &message, + uint64_t completed, uint64_t total, + bool debugger_specific) + : m_message(message), m_id(progress_id), m_completed(completed), + m_total(total), m_debugger_specific(debugger_specific) {} + + static ConstString GetFlavorString(); + + ConstString GetFlavor() const override; + + void Dump(Stream *s) const override; + + static const ProgressEventData * + GetEventDataFromEvent(const Event *event_ptr); + uint64_t GetID() const { return m_id; } + uint64_t GetCompleted() const { return m_completed; } + uint64_t GetTotal() const { return m_total; } + const std::string &GetMessage() const { return m_message; } + bool IsDebuggerSpecific() const { return m_debugger_specific; } + + private: + std::string m_message; + const uint64_t m_id; + uint64_t m_completed; + const uint64_t m_total; + const bool m_debugger_specific; + ProgressEventData(const ProgressEventData &) = delete; + const ProgressEventData &operator=(const ProgressEventData &) = delete; + }; + ~Debugger() override; static lldb::DebuggerSP @@ -346,6 +390,40 @@ protected: friend class CommandInterpreter; friend class REPL; + friend class Progress; + + /// Report progress events. + /// + /// Progress events will be delivered to any debuggers that have listeners + /// for the eBroadcastBitProgress. This function is called by the + /// lldb_private::Progress class to deliver the events to any debuggers that + /// qualify. + /// + /// \param [in] progress_id + /// The unique integer identifier for the progress to report. + /// + /// \param[in] message + /// The title of the progress dialog to display in the UI. + /// + /// \param [in] completed + /// The amount of work completed. If \a completed is zero, then this event + /// is a progress started event. If \a completed is equal to \a total, then + /// this event is a progress end event. Otherwise completed indicates the + /// current progress compare to the total value. + /// + /// \param [in] total + /// The total amount of work units that need to be completed. If this value + /// is UINT64_MAX, then an indeterminate progress indicator should be + /// displayed. + /// + /// \param [in] debugger_id + /// If this optional parameter has a value, it indicates the unique + /// debugger identifier that this progress should be delivered to. If this + /// optional parameter does not have a value, the the progress will be + /// delivered to all debuggers. + static void ReportProgress(uint64_t progress_id, const std::string &message, + uint64_t completed, uint64_t total, + llvm::Optional debugger_id); bool StartEventHandlerThread(); @@ -432,7 +510,8 @@ LoadedPluginsList m_loaded_plugins; HostThread m_event_handler_thread; HostThread m_io_handler_thread; - Broadcaster m_sync_broadcaster; + Broadcaster m_sync_broadcaster; ///< Private debugger synchronization. + Broadcaster m_broadcaster; ///< Public Debugger event broadcaster. lldb::ListenerSP m_forward_listener_sp; llvm::once_flag m_clear_once; lldb::TargetSP m_dummy_target_sp; diff --git a/lldb/include/lldb/Core/Progress.h b/lldb/include/lldb/Core/Progress.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Core/Progress.h @@ -0,0 +1,114 @@ +//===-- Progress.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_CORE_PROGRESS_H +#define LLDB_CORE_PROGRESS_H + +#include "lldb/Utility/ConstString.h" +#include "lldb/lldb-types.h" +#include +#include + +namespace lldb_private { + +/// A Progress indicator helper class. +/// +/// Any potentially long running sections of code in LLDB should report +/// progress so that clients are aware of delays that might appear during +/// debugging. Delays commonly include indexing debug information, parsing +/// symbol tables for object files, downloading symbols from remote +/// repositories, and many more things. +/// +/// The Progress class helps make sure that progress is correctly reported +/// and will always send an initial progress update, updates when +/// Progress::Increment() is called, and also will make sure that a progress +/// completed update is reported even if the user doesn't explicitly cause one +/// to be sent. +/// +/// The progress is reported via a callback whose type is ProgressCallback: +/// +/// typedef void (*ProgressCallback)(uint64_t progress_id, +/// const char *message, +/// uint64_t completed, +/// uint64_t total, +/// void *baton); +/// +/// This callback will always initially be called with "completed" set to zero +/// and "total" set to the total amount specified in the contructor. This is +/// considered the progress start event. As Progress::Increment() is called, +/// the callback will be called as long as the Progress::m_completed has not +/// yet exceeded the Progress::m_total. When the callback is called with +/// Progress::m_completed == Progress::m_total, that is considered a progress +/// completed event. If Progress::m_completed is non-zero and less than +/// Progress::m_total, then this is considered a progress update event. +/// +/// This callback will be called in the destructor if Progress::m_completed is +/// not equal to Progress::m_total with the "completed" set to +/// Progress::m_total. This ensures we always send a progress completed update +/// even if the user does not. + +class Progress { +public: + /// Construct a progress object that will report information. + /// + /// The constructor will create a unique progress reporting object and + /// immediately send out a progress update by calling the installed callback + /// with completed set to zero out of the specified total. + /// + /// @param [in] title The title of this progress activity. + /// + /// @param [in] total The total units of work to be done if specified, if + /// set to UINT64_MAX then an indeterminate progress indicator should be + /// displayed. + /// + /// @param [in] debugger An optional debugger pointer to specify that this + /// progress is to be reported only to specific debuggers. + Progress(std::string title, uint64_t total = UINT64_MAX, + lldb_private::Debugger *debugger = nullptr); + + /// Destroy the progress object. + /// + /// If the progress has not yet sent a completion update, the destructor + /// will send out a notification where the completed == m_total. This ensures + /// that we always send out a progress complete notification. + ~Progress(); + + /// Increment the progress and send a notification to the intalled callback. + /// + /// If incrementing ends up exceeding m_total, m_completed will be updated + /// to match m_total and no subsequent progress notifications will be sent. + /// If no total was specified in the constructor, this function will not do + /// anything nor send any progress updates. + /// + /// @param [in] amount The amount to increment m_completed by. + void Increment(uint64_t amount = 1); + +private: + void ReportProgress(); + static std::atomic g_id; + /// The title of the progress activity. + std::string m_title; + std::mutex m_mutex; + /// A unique integer identifier for progress reporting. + const uint64_t m_id; + /// How much work ([0...m_total]) that has been completed. + uint64_t m_completed; + /// Total amount of work, UINT64_MAX for non deterministic progress. + const uint64_t m_total; + /// The optional debugger ID to report progress to. If this has no value then + /// all debuggers will receive this event. + llvm::Optional m_debugger_id; + /// Set to true when progress has been reported where m_completed == m_total + /// to ensure that we don't send progress updates after progress has + /// completed. + bool m_complete = false; +}; + +} // namespace lldb_private + +#endif // LLDB_CORE_PROGRESS_H diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -113,6 +113,7 @@ self.initialize_body = None self.thread_stop_reasons = {} self.breakpoint_events = [] + self.progress_events = [] self.sequence = 1 self.threads = None self.recv_thread.start() @@ -225,6 +226,13 @@ self.breakpoint_events.append(packet) # no need to add 'breakpoint' event packets to our packets list return keepGoing + elif event.startswith('progress'): + # Progress events come in as 'progressStart', 'progressUpdate', + # and 'progressEnd' events. Keep these around in case test + # cases want to verify them. + self.progress_events.append(packet) + # No need to add 'progress' event packets to our packets list. + return keepGoing elif packet_type == 'response': if packet['command'] == 'disconnect': diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -38,6 +38,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Progress.h" #include "lldb/Core/StreamFile.h" #include "lldb/Core/StructuredDataImpl.h" #include "lldb/DataFormatters/DataVisualization.h" @@ -149,6 +150,41 @@ return LLDB_RECORD_RESULT(*this); } +const char *SBDebugger::GetBroadcasterClass() { + LLDB_RECORD_STATIC_METHOD_NO_ARGS(const char *, SBDebugger, + GetBroadcasterClass); + + return Debugger::GetStaticBroadcasterClass().AsCString(); +} + +const char *SBDebugger::GetProgressFromEvent(const lldb::SBEvent &event, + uint64_t &progress_id, + uint64_t &completed, + uint64_t &total, + bool &is_debugger_specific) { + const Debugger::ProgressEventData *progress_data = + Debugger::ProgressEventData::GetEventDataFromEvent(event.get()); + if (progress_data == nullptr) + return nullptr; + progress_id = progress_data->GetID(); + completed = progress_data->GetCompleted(); + total = progress_data->GetTotal(); + is_debugger_specific = progress_data->IsDebuggerSpecific(); + // We must record the static method _after_ the out paramters have been + // filled in. + LLDB_RECORD_STATIC_METHOD( + const char *, SBDebugger, GetProgressFromEvent, + (const lldb::SBEvent &, uint64_t &, uint64_t &, uint64_t &, bool &), + event, progress_id, completed, total, is_debugger_specific); + return LLDB_RECORD_RESULT(progress_data->GetMessage().c_str()) +} + +SBBroadcaster SBDebugger::GetBroadcaster() { + LLDB_RECORD_METHOD_NO_ARGS(lldb::SBBroadcaster, SBDebugger, GetBroadcaster); + SBBroadcaster broadcaster(&m_opaque_sp->GetBroadcaster(), false); + return LLDB_RECORD_RESULT(broadcaster); +} + void SBDebugger::Initialize() { LLDB_RECORD_STATIC_METHOD_NO_ARGS(void, SBDebugger, Initialize); SBError ignored = SBDebugger::InitializeWithErrorHandling(); @@ -824,7 +860,7 @@ if (error.Success()) sb_target.SetSP(target_sp); } - + LLDB_LOGF(log, "SBDebugger(%p)::CreateTargetWithFileAndArch (filename=\"%s\", " "arch=%s) => SBTarget(%p)", @@ -1711,6 +1747,12 @@ LLDB_REGISTER_METHOD(void, SBDebugger, Clear, ()); LLDB_REGISTER_STATIC_METHOD(lldb::SBDebugger, SBDebugger, Create, ()); LLDB_REGISTER_STATIC_METHOD(lldb::SBDebugger, SBDebugger, Create, (bool)); + LLDB_REGISTER_STATIC_METHOD( + const char *, SBDebugger, GetProgressFromEvent, + (const lldb::SBEvent &, uint64_t &, uint64_t &, uint64_t &, bool &)); + LLDB_REGISTER_STATIC_METHOD(const char *, SBDebugger, GetBroadcasterClass, + ()); + LLDB_REGISTER_METHOD(SBBroadcaster, SBDebugger, GetBroadcaster, ()); LLDB_REGISTER_STATIC_METHOD(void, SBDebugger, Destroy, (lldb::SBDebugger &)); LLDB_REGISTER_STATIC_METHOD(void, SBDebugger, MemoryPressureDetected, ()); LLDB_REGISTER_METHOD_CONST(bool, SBDebugger, IsValid, ()); diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -43,6 +43,7 @@ ModuleList.cpp Opcode.cpp PluginManager.cpp + Progress.cpp RichManglingContext.cpp SearchFilter.cpp Section.cpp diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -661,6 +661,11 @@ return target_sp; } +ConstString Debugger::GetStaticBroadcasterClass() { + static ConstString class_name("lldb.debugger"); + return class_name; +} + Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton) : UserID(g_unique_id++), Properties(std::make_shared()), @@ -677,6 +682,8 @@ m_io_handler_stack(), m_instance_name(), m_loaded_plugins(), m_event_handler_thread(), m_io_handler_thread(), m_sync_broadcaster(nullptr, "lldb.debugger.sync"), + m_broadcaster(m_broadcaster_manager_sp, + GetStaticBroadcasterClass().AsCString()), m_forward_listener_sp(), m_clear_once() { m_instance_name.SetString(llvm::formatv("debugger_{0}", GetID()).str()); if (log_callback) @@ -1137,6 +1144,74 @@ std::make_shared(log_callback, baton); } +ConstString Debugger::ProgressEventData::GetFlavorString() { + static ConstString g_flavor("Debugger::ProgressEventData"); + return g_flavor; +} + +ConstString Debugger::ProgressEventData::GetFlavor() const { + return Debugger::ProgressEventData::GetFlavorString(); +} + +void Debugger::ProgressEventData::Dump(Stream *s) const { + s->Printf(" id = %" PRIu64 ", message = \"%s\"", m_id, m_message.c_str()); + if (m_completed == 0 || m_completed == m_total) + s->Printf(", type = %s", m_completed == 0 ? "start" : "end"); + else + s->PutCString(", type = update"); + // If m_total is UINT64_MAX, there is no progress to report, just "start" + // and "end". If it isn't we will show the completed and total amounts. + if (m_total != UINT64_MAX) + s->Printf(", progress = %" PRIu64 " of %" PRIu64, m_completed, m_total); +} + +const Debugger::ProgressEventData * +Debugger::ProgressEventData::GetEventDataFromEvent(const Event *event_ptr) { + if (event_ptr) + if (const EventData *event_data = event_ptr->GetData()) + if (event_data->GetFlavor() == ProgressEventData::GetFlavorString()) + return static_cast(event_ptr->GetData()); + return nullptr; +} + +static void PrivateReportProgress(Debugger &debugger, uint64_t progress_id, + const std::string &message, + uint64_t completed, uint64_t total, + bool is_debugger_specific) { + // Only deliver progress events if we have any progress listeners. + const uint32_t event_type = Debugger::eBroadcastBitProgress; + if (!debugger.GetBroadcaster().EventTypeHasListeners(event_type)) + return; + EventSP event_sp(new Event(event_type, new Debugger::ProgressEventData( + progress_id, message, completed, + total, is_debugger_specific))); + debugger.GetBroadcaster().BroadcastEvent(event_sp); +} + +void Debugger::ReportProgress(uint64_t progress_id, const std::string &message, + uint64_t completed, uint64_t total, + llvm::Optional debugger_id) { + // Check if this progress is for a specific debugger. + if (debugger_id.hasValue()) { + // It is debugger specific, grab it and deliver the event if the debugger + // still exists. + DebuggerSP debugger_sp = FindDebuggerWithID(*debugger_id); + if (debugger_sp) + PrivateReportProgress(*debugger_sp, progress_id, message, completed, + total, /*is_debugger_specific*/ true); + return; + } + // The progress event is not debugger specific, iterate over all debuggers + // and deliver a progress event to each one. + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard guard(*g_debugger_list_mutex_ptr); + DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); + for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) + PrivateReportProgress(*(*pos), progress_id, message, completed, total, + /*is_debugger_specific*/ false); + } +} + bool Debugger::EnableLog(llvm::StringRef channel, llvm::ArrayRef categories, llvm::StringRef log_file, uint32_t log_options, diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -1072,8 +1072,6 @@ void Module::GetDescription(llvm::raw_ostream &s, lldb::DescriptionLevel level) { - std::lock_guard guard(m_mutex); - if (level >= eDescriptionLevelFull) { if (m_arch.IsValid()) s << llvm::formatv("({0}) ", m_arch.GetArchitectureName()); diff --git a/lldb/source/Core/Progress.cpp b/lldb/source/Core/Progress.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Core/Progress.cpp @@ -0,0 +1,60 @@ +//===-- Progress.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/Core/Progress.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Utility/StreamString.h" + +using namespace lldb; +using namespace lldb_private; + +std::atomic Progress::g_id(0); + +Progress::Progress(std::string title, uint64_t total, + lldb_private::Debugger *debugger) + : m_title(title), m_id(++g_id), m_completed(0), m_total(total) { + assert(total > 0); + if (debugger) + m_debugger_id = debugger->GetID(); + std::lock_guard guard(m_mutex); + ReportProgress(); +} + +Progress::~Progress() { + // Make sure to always report progress completed when this object is + // destructed so it indicates the progress dialog/activity should go away. + std::lock_guard guard(m_mutex); + if (!m_completed) { + m_completed = m_total; + ReportProgress(); + } +} + +void Progress::Increment(uint64_t amount) { + if (amount > 0) { + std::lock_guard guard(m_mutex); + // Watch out for unsigned overflow and make sure we don't increment too + // much and exceed m_total. + if (amount > (m_total - m_completed)) + m_completed = m_total; + else + m_completed += amount; + ReportProgress(); + } +} + +void Progress::ReportProgress() { + if (!m_complete) { + // Make sure we only send one notification that indicates the progress is + // complete. + m_complete = m_completed == m_total; + Debugger::ReportProgress(m_id, m_title, m_completed, m_total, + m_debugger_id); + } +} diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -16,6 +16,7 @@ #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Progress.h" #include "lldb/Core/Section.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/LZMA.h" @@ -37,6 +38,7 @@ #include "llvm/Object/Decompressor.h" #include "llvm/Support/ARMBuildAttributes.h" #include "llvm/Support/CRC.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/MipsABIFlags.h" @@ -1861,7 +1863,7 @@ // unified section list. if (GetType() != eTypeDebugInfo) unified_section_list = *m_sections_up; - + // If there's a .gnu_debugdata section, we'll try to read the .symtab that's // embedded in there and replace the one in the original object file (if any). // If there's none in the orignal object file, we add it to it. @@ -1923,7 +1925,7 @@ ArchSpec spec = m_gnu_debug_data_object_file->GetArchitecture(); if (spec && m_gnu_debug_data_object_file->SetModulesArchitecture(spec)) return m_gnu_debug_data_object_file; - + return nullptr; } @@ -2707,6 +2709,9 @@ if (!module_sp) return nullptr; + Progress progress(llvm::formatv("Parsing symbol table for {0}", + m_file.GetFilename().AsCString(""))); + // We always want to use the main object file so we (hopefully) only have one // cached copy of our symtab, dynamic sections, etc. ObjectFile *module_obj_file = module_sp->GetObjectFile(); diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -17,6 +17,7 @@ #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Progress.h" #include "lldb/Core/Section.h" #include "lldb/Core/StreamFile.h" #include "lldb/Host/Host.h" @@ -43,6 +44,7 @@ #include "lldb/Host/SafeMachO.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/MemoryBuffer.h" #include "ObjectFileMachO.h" @@ -2167,6 +2169,9 @@ if (!module_sp) return 0; + Progress progress(llvm::formatv("Parsing symbol table for {0}", + m_file.GetFilename().AsCString(""))); + struct symtab_command symtab_load_command = {0, 0, 0, 0, 0, 0}; struct linkedit_data_command function_starts_load_command = {0, 0, 0, 0}; struct dyld_info_command dyld_info = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp @@ -13,9 +13,11 @@ #include "Plugins/SymbolFile/DWARF/LogChannelDWARF.h" #include "Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.h" #include "lldb/Core/Module.h" +#include "lldb/Core/Progress.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/Timer.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/ThreadPool.h" using namespace lldb_private; @@ -56,6 +58,17 @@ if (units_to_index.empty()) return; + StreamString module_desc; + m_module.GetDescription(module_desc.AsRawOstream(), + lldb::eDescriptionLevelBrief); + + // Include 2 passes per unit to index for extracting DIEs from the unit and + // indexing the unit, and then 8 extra entries for finalizing each index set. + const uint64_t total_progress = units_to_index.size() * 2 + 8; + Progress progress( + llvm::formatv("Manually indexing DWARF for {0}", module_desc.GetData()), + total_progress); + std::vector sets(units_to_index.size()); // Keep memory down by clearing DIEs for any units if indexing @@ -64,10 +77,12 @@ units_to_index.size()); auto parser_fn = [&](size_t cu_idx) { IndexUnit(*units_to_index[cu_idx], dwp_dwarf, sets[cu_idx]); + progress.Increment(); }; - auto extract_fn = [&units_to_index, &clear_cu_dies](size_t cu_idx) { + auto extract_fn = [&](size_t cu_idx) { clear_cu_dies[cu_idx] = units_to_index[cu_idx]->ExtractDIEsScoped(); + progress.Increment(); }; // Share one thread pool across operations to avoid the overhead of @@ -92,11 +107,12 @@ pool.async(parser_fn, i); pool.wait(); - auto finalize_fn = [this, &sets](NameToDIE(IndexSet::*index)) { + auto finalize_fn = [this, &sets, &progress](NameToDIE(IndexSet::*index)) { NameToDIE &result = m_set.*index; for (auto &set : sets) result.Append(set.*index); result.Finalize(); + progress.Increment(); }; pool.async(finalize_fn, &IndexSet::function_basenames); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -16,6 +16,7 @@ #include "lldb/Core/ModuleList.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Progress.h" #include "lldb/Core/Section.h" #include "lldb/Core/StreamFile.h" #include "lldb/Core/Value.h" @@ -74,6 +75,7 @@ #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" #include #include @@ -467,22 +469,32 @@ Log *log = LogChannelDWARF::GetLogIfAll(DWARF_LOG_DEBUG_INFO); if (!GetGlobalPluginProperties()->IgnoreFileIndexes()) { + StreamString module_desc; + GetObjectFile()->GetModule()->GetDescription(module_desc.AsRawOstream(), + lldb::eDescriptionLevelBrief); DWARFDataExtractor apple_names, apple_namespaces, apple_types, apple_objc; LoadSectionData(eSectionTypeDWARFAppleNames, apple_names); LoadSectionData(eSectionTypeDWARFAppleNamespaces, apple_namespaces); LoadSectionData(eSectionTypeDWARFAppleTypes, apple_types); LoadSectionData(eSectionTypeDWARFAppleObjC, apple_objc); - m_index = AppleDWARFIndex::Create( - *GetObjectFile()->GetModule(), apple_names, apple_namespaces, - apple_types, apple_objc, m_context.getOrLoadStrData()); + if (apple_names.GetByteSize() > 0 || apple_namespaces.GetByteSize() > 0 || + apple_types.GetByteSize() > 0 || apple_objc.GetByteSize() > 0) { + Progress progress(llvm::formatv("Loading Apple DWARF index for {0}", + module_desc.GetData())); + m_index = AppleDWARFIndex::Create( + *GetObjectFile()->GetModule(), apple_names, apple_namespaces, + apple_types, apple_objc, m_context.getOrLoadStrData()); - if (m_index) - return; + if (m_index) + return; + } DWARFDataExtractor debug_names; LoadSectionData(eSectionTypeDWARFDebugNames, debug_names); if (debug_names.GetByteSize() > 0) { + Progress progress( + llvm::formatv("Loading DWARF5 index for {0}", module_desc.GetData())); llvm::Expected> index_or = DebugNamesDWARFIndex::Create(*GetObjectFile()->GetModule(), debug_names, diff --git a/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py b/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py --- a/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py +++ b/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py @@ -440,7 +440,7 @@ ''' self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - + terminateCommands = ['expr 4+2'] self.launch(program=program, terminateCommands=terminateCommands) @@ -450,3 +450,57 @@ self.vscode.request_disconnect(terminateDebuggee=True) output = self.collect_console(duration=1.0) self.verify_commands('terminateCommands', output, terminateCommands) + + + @skipIfWindows + @skipIfRemote + def test_progress_events(self): + ''' + Tests the progress events to ensure we are receiving them. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + # Set a breakpoint at 'main'. This will cause all of the symbol tables + # for all modules in LLDB to be parsed and we should get a progress + # event for each shared library. + breakpoint_ids = self.set_function_breakpoints(['main']) + self.continue_to_breakpoints(breakpoint_ids) + # Make sure we at least got some progress events + self.assertTrue(len(self.vscode.progress_events) > 0) + # Track all 'progressStart' events by saving all 'progressId' values. + progressStart_ids = set() + # Track all 'progressEnd' events by saving all 'progressId' values. + progressEnd_ids = set() + # We will watch for events whose title starts with + # 'Parsing symbol table for ' and we will save the remainder of the + # line which will contain the shared library basename. Since we set a + # breakpoint by name for 'main', we will expect to see progress events + # for all shared libraries that say that the symbol table is being + # parsed. + symtab_progress_shlibs = set() + # Get a list of modules in the current target so we can verify that + # we do in fact get a progress event for each shared library. + target_shlibs = self.vscode.get_modules() + + # Iterate over all progress events and save all start and end IDs, and + # remember any shared libraries that got symbol table parsing progress + # events. + for progress_event in self.vscode.progress_events: + event_type = progress_event['event'] + if event_type == 'progressStart': + progressStart_ids.add(progress_event['body']['progressId']) + title = progress_event['body']['title'] + if title.startswith('Parsing symbol table for '): + symtab_progress_shlibs.add(title[25:]) + if event_type == 'progressEnd': + progressEnd_ids.add(progress_event['body']['progressId']) + # Make sure for each 'progressStart' event, we got a matching + # 'progressEnd' event. + self.assertTrue(progressStart_ids == progressEnd_ids, + ('Make sure we got a "progressEnd" for each ' + '"progressStart" event that we have.')) + # Verify we got a symbol table parsing progress event for each shared + # library in our target. + for target_shlib_basename in target_shlibs.keys(): + self.assertTrue(target_shlib_basename in symtab_progress_shlibs, + 'Make sure we got a symbol table progress event for "%s"' % (target_shlib_basename)) diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -68,7 +68,10 @@ typedef llvm::StringMap FunctionBreakpointMap; enum class OutputType { Console, Stdout, Stderr, Telemetry }; -enum VSCodeBroadcasterBits { eBroadcastBitStopEventThread = 1u << 0 }; +enum VSCodeBroadcasterBits { + eBroadcastBitStopEventThread = 1u << 0, + eBroadcastBitStopProgressThread = 1u << 1 +}; typedef void (*RequestCallback)(const llvm::json::Object &command); @@ -91,6 +94,7 @@ int64_t num_locals; int64_t num_globals; std::thread event_thread; + std::thread progress_event_thread; std::unique_ptr log; llvm::DenseMap addr_to_source_ref; llvm::DenseMap source_map; @@ -132,6 +136,9 @@ void SendOutput(OutputType o, const llvm::StringRef output); + void SendProgressEvent(uint64_t progress_id, const char *message, + uint64_t completed, uint64_t total); + void __attribute__((format(printf, 3, 4))) SendFormattedOutput(OutputType o, const char *format, ...); diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp --- a/lldb/tools/lldb-vscode/VSCode.cpp +++ b/lldb/tools/lldb-vscode/VSCode.cpp @@ -6,8 +6,10 @@ // //===----------------------------------------------------------------------===// +#include #include #include +#include #include #include "LLDBUtils.h" @@ -225,6 +227,146 @@ SendJSON(llvm::json::Value(std::move(event))); } +// interface ProgressStartEvent extends Event { +// event: 'progressStart'; +// +// body: { +// /** +// * An ID that must be used in subsequent 'progressUpdate' and +// 'progressEnd' +// * events to make them refer to the same progress reporting. +// * IDs must be unique within a debug session. +// */ +// progressId: string; +// +// /** +// * Mandatory (short) title of the progress reporting. Shown in the UI to +// * describe the long running operation. +// */ +// title: string; +// +// /** +// * The request ID that this progress report is related to. If specified a +// * debug adapter is expected to emit +// * progress events for the long running request until the request has +// been +// * either completed or cancelled. +// * If the request ID is omitted, the progress report is assumed to be +// * related to some general activity of the debug adapter. +// */ +// requestId?: number; +// +// /** +// * If true, the request that reports progress may be canceled with a +// * 'cancel' request. +// * So this property basically controls whether the client should use UX +// that +// * supports cancellation. +// * Clients that don't support cancellation are allowed to ignore the +// * setting. +// */ +// cancellable?: boolean; +// +// /** +// * Optional, more detailed progress message. +// */ +// message?: string; +// +// /** +// * Optional progress percentage to display (value range: 0 to 100). If +// * omitted no percentage will be shown. +// */ +// percentage?: number; +// }; +// } +// +// interface ProgressUpdateEvent extends Event { +// event: 'progressUpdate'; +// +// body: { +// /** +// * The ID that was introduced in the initial 'progressStart' event. +// */ +// progressId: string; +// +// /** +// * Optional, more detailed progress message. If omitted, the previous +// * message (if any) is used. +// */ +// message?: string; +// +// /** +// * Optional progress percentage to display (value range: 0 to 100). If +// * omitted no percentage will be shown. +// */ +// percentage?: number; +// }; +// } +// +// interface ProgressEndEvent extends Event { +// event: 'progressEnd'; +// +// body: { +// /** +// * The ID that was introduced in the initial 'ProgressStartEvent'. +// */ +// progressId: string; +// +// /** +// * Optional, more detailed progress message. If omitted, the previous +// * message (if any) is used. +// */ +// message?: string; +// }; +// } + +void VSCode::SendProgressEvent(uint64_t progress_id, const char *message, + uint64_t completed, uint64_t total) { + enum ProgressEventType { + progressInvalid, + progressStart, + progressUpdate, + progressEnd + }; + const char *event_name = nullptr; + ProgressEventType event_type = progressInvalid; + if (completed == 0) { + event_type = progressStart; + event_name = "progressStart"; + } else if (completed == total) { + event_type = progressEnd; + event_name = "progressEnd"; + } else if (completed < total) { + event_type = progressUpdate; + event_name = "progressUpdate"; + } + if (event_type == progressInvalid) + return; + + llvm::json::Object event(CreateEventObject(event_name)); + llvm::json::Object body; + std::string progress_id_str; + llvm::raw_string_ostream progress_id_strm(progress_id_str); + progress_id_strm << progress_id; + progress_id_strm.flush(); + body.try_emplace("progressId", progress_id_str); + if (event_type == progressStart) { + EmplaceSafeString(body, "title", message); + body.try_emplace("cancellable", false); + } + auto now = std::chrono::duration( + std::chrono::system_clock::now().time_since_epoch()); + std::string timestamp(llvm::formatv("{0:f9}", now.count())); + EmplaceSafeString(body, "timestamp", timestamp); + + if (0 < total && total < UINT64_MAX) { + uint32_t percentage = (uint32_t)(((float)completed / (float)total) * 100.0); + body.try_emplace("percentage", percentage); + } + event.try_emplace("body", std::move(body)); + SendJSON(llvm::json::Value(std::move(event))); +} + void __attribute__((format(printf, 3, 4))) VSCode::SendFormattedOutput(OutputType o, const char *format, ...) { char buffer[1024]; diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -349,6 +349,34 @@ g_vsc.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count)); } +void ProgressEventThreadFunction() { + lldb::SBListener listener("lldb-vscode.progress.listener"); + g_vsc.debugger.GetBroadcaster().AddListener( + listener, lldb::SBDebugger::eBroadcastBitProgress); + g_vsc.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread); + lldb::SBEvent event; + bool done = false; + while (!done) { + if (listener.WaitForEvent(1, event)) { + const auto event_mask = event.GetType(); + if (event.BroadcasterMatchesRef(g_vsc.broadcaster)) { + if (event_mask & eBroadcastBitStopProgressThread) { + done = true; + } + } else { + uint64_t progress_id = 0; + uint64_t completed = 0; + uint64_t total = 0; + bool is_debugger_specific = false; + const char *message = lldb::SBDebugger::GetProgressFromEvent( + event, progress_id, completed, total, is_debugger_specific); + if (message) + g_vsc.SendProgressEvent(progress_id, message, completed, total); + } + } + } +} + // All events from the debugger, target, process, thread and frames are // received in this function that runs in its own thread. We are using a // "FILE *" to output packets back to VS Code and they have mutexes in them @@ -806,6 +834,10 @@ g_vsc.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); g_vsc.event_thread.join(); } + if (g_vsc.progress_event_thread.joinable()) { + g_vsc.broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread); + g_vsc.progress_event_thread.join(); + } } void request_exceptionInfo(const llvm::json::Object &request) { @@ -1357,6 +1389,8 @@ // } void request_initialize(const llvm::json::Object &request) { g_vsc.debugger = lldb::SBDebugger::Create(true /*source_init_files*/); + g_vsc.progress_event_thread = std::thread(ProgressEventThreadFunction); + // Create an empty target right away since we might get breakpoint requests // before we are given an executable to launch in a "launch" request, or a // executable when attaching to a process by process ID in a "attach" @@ -1453,6 +1487,8 @@ body.try_emplace("supportsDelayedStackTraceLoading", true); // The debug adapter supports the 'loadedSources' request. body.try_emplace("supportsLoadedSourcesRequest", false); + // The debug adapter supports sending progress reporting events. + body.try_emplace("supportsProgressReporting", true); response.try_emplace("body", std::move(body)); g_vsc.SendJSON(llvm::json::Value(std::move(response)));