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 @@ -223,6 +223,27 @@ void SetLoggingCallback(lldb::LogOutputCallback log_callback, void *baton); + /// Set a progress callback for this debugger object. + /// + /// Long running operations in LLDB can cause delays when debugging, and + /// these delys make it seem like the debug session is deadlocked or stalled. + /// Clients can register a callback with debugger objects to get updates on + /// such activities so they can display feedback to the user so they stay + /// informed on what is happening. + /// + /// Long running operations include indexing debug information, parsing + /// symbol tables, and downloading debug info files. + /// + /// The callback can be called from multiple threads and clients should + /// create a callback that is thread safe. + /// + /// \param[in] callback A callback that will be called on any thread as + /// progress updates are reported. + /// + /// \param[in] baton A user specified baton that will be sent to the callback + /// when progress is reported. + void SetProgressCallback(ProgressCallback callback, void *baton); + // DEPRECATED void DispatchInput(void *baton, const void *data, size_t data_len); 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 @@ -226,6 +226,8 @@ void SetLoggingCallback(lldb::LogOutputCallback log_callback, void *baton); + void SetProgressCallback(lldb::ProgressCallback callback, void *baton); + // Properties Functions enum StopDisassemblyType { eStopDisassemblyTypeNever = 0, @@ -346,6 +348,11 @@ protected: friend class CommandInterpreter; friend class REPL; + friend class Progress; + + // Called by the lldb_private::Progress class only. + static void ReportProgress(uint64_t progress_id, const char *message, + uint64_t completed, uint64_t total); bool StartEventHandlerThread(); @@ -436,6 +443,8 @@ lldb::ListenerSP m_forward_listener_sp; llvm::once_flag m_clear_once; lldb::TargetSP m_dummy_target_sp; + lldb::ProgressCallback m_progress_callback; + void *m_progress_baton; // Events for m_sync_broadcaster enum { 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,115 @@ +//===-- 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 there should be no progress displayed and just show a + /// spinning progress indicator. + Progress(std::string title, uint64_t total = UINT64_MAX); + + /// 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); + + /// Set the progress notification callback. + /// + /// If the callback is set to a valid non NULL value, the \a callback + /// function will be called when ever a Progress object is constructed, + /// Increment is called, or the Progress object is destroyed and has not + /// already sent a progress complete notification. + static void SetProgressCallback(lldb::ProgressCallback callback, void *baton); + +private: + void ReportProgress(); + static std::atomic g_progress_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, llvm::None for non deterministic progress. + const uint64_t m_total; + /// 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/include/lldb/lldb-types.h b/lldb/include/lldb/lldb-types.h --- a/lldb/include/lldb/lldb-types.h +++ b/lldb/include/lldb/lldb-types.h @@ -73,6 +73,9 @@ void *baton, const char **argv, lldb_private::CommandReturnObject &result); typedef bool (*ExpressionCancelCallback)(ExpressionEvaluationPhase phase, void *baton); +typedef void (*ProgressCallback)(uint64_t progress_id, const char *message, + uint64_t completed, uint64_t total, + void *baton); } // namespace lldb #define LLDB_INVALID_PROCESS ((lldb::process_t)-1) 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" @@ -808,23 +809,25 @@ // The version of CreateTarget that takes an ArchSpec won't accept an // empty ArchSpec, so when the arch hasn't been specified, we need to // call the target triple version. - error = m_opaque_sp->GetTargetList().CreateTarget(*m_opaque_sp, filename, - arch_cstr, eLoadDependentsYes, nullptr, target_sp); + error = m_opaque_sp->GetTargetList().CreateTarget( + *m_opaque_sp, filename, arch_cstr, eLoadDependentsYes, nullptr, + target_sp); } else { PlatformSP platform_sp = m_opaque_sp->GetPlatformList() .GetSelectedPlatform(); - ArchSpec arch = Platform::GetAugmentedArchSpec(platform_sp.get(), - arch_cstr); + ArchSpec arch = + Platform::GetAugmentedArchSpec(platform_sp.get(), arch_cstr); if (arch.IsValid()) - error = m_opaque_sp->GetTargetList().CreateTarget(*m_opaque_sp, filename, - arch, eLoadDependentsYes, platform_sp, target_sp); + error = m_opaque_sp->GetTargetList().CreateTarget( + *m_opaque_sp, filename, arch, eLoadDependentsYes, platform_sp, + target_sp); else error.SetErrorStringWithFormat("invalid arch_cstr: %s", arch_cstr); } if (error.Success()) sb_target.SetSP(target_sp); } - + LLDB_LOGF(log, "SBDebugger(%p)::CreateTargetWithFileAndArch (filename=\"%s\", " "arch=%s) => SBTarget(%p)", @@ -1651,6 +1654,14 @@ } } +void SBDebugger::SetProgressCallback(ProgressCallback callback, void *baton) { + LLDB_RECORD_DUMMY(void, SBDebugger, SetProgressCallback, + (lldb::ProgressCallback, void *), callback, baton); + if (m_opaque_sp) { + return m_opaque_sp->SetProgressCallback(callback, baton); + } +} + namespace lldb_private { namespace repro { 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 @@ -44,6 +44,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 @@ -1137,6 +1137,26 @@ std::make_shared(log_callback, baton); } +void Debugger::SetProgressCallback(lldb::ProgressCallback callback, + void *baton) { + m_progress_callback = callback; + m_progress_baton = baton; +} + +void Debugger::ReportProgress(uint64_t progress_id, const char *message, + uint64_t completed, uint64_t total) { + + 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) { + if ((*pos)->m_progress_callback) + (*pos)->m_progress_callback(progress_id, message, completed, total, + (*pos)->m_progress_baton); + } + } +} + 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,56 @@ +//===-- 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_progress_id(0); + +Progress::Progress(std::string title, uint64_t total) + : m_title(title), m_completed(0), m_total(total), m_id(++g_progress_id) { + assert(total > 0); + 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.c_str(), m_completed, m_total); + } +} 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. @@ -1879,7 +1881,7 @@ unified_section_list.AddSection(symtab_section_sp); } } - } + } } std::shared_ptr ObjectFileELF::GetGnuDebugDataObjectFile() { @@ -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" @@ -1893,9 +1895,9 @@ filename = first_section_sp->GetObjectFile()->GetFileSpec().GetPath(); Host::SystemLog(Host::eSystemLogError, - "error: unable to find section %d for a symbol in %s, corrupt file?\n", - n_sect, - filename.c_str()); + "error: unable to find section %d for a symbol in " + "%s, corrupt file?\n", + n_sect, filename.c_str()); } } if (m_section_infos[n_sect].vm_range.Contains(file_addr)) { @@ -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/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 @@ -132,6 +132,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 @@ -40,7 +40,7 @@ focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false), stop_at_entry(false), is_attach(false), reverse_request_seq(0), waiting_for_run_in_terminal(false) { - const char *log_file_path = getenv("LLDBVSCODE_LOG"); + const char *log_file_path = "/tmp/vscode.txt"; // getenv("LLDBVSCODE_LOG"); #if defined(_WIN32) // Windows opens stdout and stdin in text mode which converts \n to 13,10 // while the value is just 10 on Darwin/Linux. Setting the file mode to binary @@ -225,6 +225,142 @@ 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); + } + + 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 @@ -100,6 +100,12 @@ typedef void (*RequestCallback)(const llvm::json::Object &command); +static void LLDBProgressCallback(uint64_t progress_id, const char *message, + uint64_t completed, uint64_t total, + void *baton) { + g_vsc.SendProgressEvent(progress_id, message, completed, total); +} + enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; SOCKET AcceptConnection(int portno) { @@ -1352,6 +1358,8 @@ // } void request_initialize(const llvm::json::Object &request) { g_vsc.debugger = lldb::SBDebugger::Create(true /*source_init_files*/); + g_vsc.debugger.SetProgressCallback(LLDBProgressCallback, nullptr); + // 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" @@ -1448,6 +1456,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)));