https://github.com/eronnen updated https://github.com/llvm/llvm-project/pull/155021
>From 894e70274cd2ea71af854df6fc5f3c7575c15408 Mon Sep 17 00:00:00 2001 From: Ely Ronnen <elyron...@gmail.com> Date: Fri, 22 Aug 2025 21:31:04 +0200 Subject: [PATCH 1/3] Reapply "[lldb-dap] Add module symbol table viewer to VS Code extension #140626 (#153836)" This reverts commit 2b8e80694263fb404d1d0b816f33df731e617625. --- lldb/include/lldb/API/SBSymbol.h | 15 + lldb/include/lldb/API/SBTarget.h | 10 + lldb/include/lldb/Symbol/Symbol.h | 5 + .../test/tools/lldb-dap/dap_server.py | 19 + lldb/source/API/SBSymbol.cpp | 28 + lldb/source/API/SBTarget.cpp | 12 + lldb/source/Symbol/Symbol.cpp | 144 ++--- .../API/tools/lldb-dap/moduleSymbols/Makefile | 3 + .../moduleSymbols/TestDAP_moduleSymbols.py | 37 ++ .../API/tools/lldb-dap/moduleSymbols/main.c | 9 + lldb/tools/lldb-dap/CMakeLists.txt | 1 + lldb/tools/lldb-dap/DAP.cpp | 22 + lldb/tools/lldb-dap/DAP.h | 3 + lldb/tools/lldb-dap/EventHelper.cpp | 30 +- lldb/tools/lldb-dap/EventHelper.h | 4 +- .../ConfigurationDoneRequestHandler.cpp | 6 +- .../Handler/ModuleSymbolsRequestHandler.cpp | 89 +++ lldb/tools/lldb-dap/Handler/RequestHandler.h | 14 + lldb/tools/lldb-dap/Protocol/DAPTypes.cpp | 35 +- lldb/tools/lldb-dap/Protocol/DAPTypes.h | 32 ++ .../lldb-dap/Protocol/ProtocolRequests.cpp | 15 + .../lldb-dap/Protocol/ProtocolRequests.h | 24 + .../tools/lldb-dap/Protocol/ProtocolTypes.cpp | 4 + lldb/tools/lldb-dap/Protocol/ProtocolTypes.h | 5 +- lldb/tools/lldb-dap/package-lock.json | 513 +++++++++++++++++- lldb/tools/lldb-dap/package.json | 32 +- .../src-ts/debug-configuration-provider.ts | 4 + .../lldb-dap/src-ts/debug-session-tracker.ts | 22 + lldb/tools/lldb-dap/src-ts/extension.ts | 10 +- lldb/tools/lldb-dap/src-ts/index.d.ts | 14 + .../src-ts/ui/modules-data-provider.ts | 1 + .../lldb-dap/src-ts/ui/symbols-provider.ts | 127 +++++ .../src-ts/ui/symbols-webview-html.ts | 51 ++ .../src-ts/webview/symbols-table-view.ts | 114 ++++ .../lldb-dap/src-ts/webview/tsconfig.json | 15 + lldb/tools/lldb-dap/tsconfig.json | 2 + lldb/unittests/DAP/CMakeLists.txt | 1 + lldb/unittests/DAP/DAPTypesTest.cpp | 60 ++ 38 files changed, 1443 insertions(+), 89 deletions(-) create mode 100644 lldb/test/API/tools/lldb-dap/moduleSymbols/Makefile create mode 100644 lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py create mode 100644 lldb/test/API/tools/lldb-dap/moduleSymbols/main.c create mode 100644 lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp create mode 100644 lldb/tools/lldb-dap/src-ts/index.d.ts create mode 100644 lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts create mode 100644 lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts create mode 100644 lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts create mode 100644 lldb/tools/lldb-dap/src-ts/webview/tsconfig.json create mode 100644 lldb/unittests/DAP/DAPTypesTest.cpp diff --git a/lldb/include/lldb/API/SBSymbol.h b/lldb/include/lldb/API/SBSymbol.h index 94521881f82f9..a93bc7a7ae074 100644 --- a/lldb/include/lldb/API/SBSymbol.h +++ b/lldb/include/lldb/API/SBSymbol.h @@ -85,6 +85,12 @@ class LLDB_API SBSymbol { SymbolType GetType(); + /// Get the ID of this symbol, usually the original symbol table index. + /// + /// \returns + /// Returns the ID of this symbol. + uint32_t GetID(); + bool operator==(const lldb::SBSymbol &rhs) const; bool operator!=(const lldb::SBSymbol &rhs) const; @@ -99,6 +105,15 @@ class LLDB_API SBSymbol { // other than the actual symbol table itself in the object file. bool IsSynthetic(); + /// Returns true if the symbol is a debug symbol. + bool IsDebug(); + + /// Get the string representation of a symbol type. + static const char *GetTypeAsString(lldb::SymbolType symbol_type); + + /// Get the symbol type from a string representation. + static lldb::SymbolType GetTypeFromString(const char *str); + protected: lldb_private::Symbol *get(); diff --git a/lldb/include/lldb/API/SBTarget.h b/lldb/include/lldb/API/SBTarget.h index 22b6c63ed5b97..62cdd342a05e4 100644 --- a/lldb/include/lldb/API/SBTarget.h +++ b/lldb/include/lldb/API/SBTarget.h @@ -324,6 +324,16 @@ class LLDB_API SBTarget { lldb::SBModule FindModule(const lldb::SBFileSpec &file_spec); + /// Find a module with the given module specification. + /// + /// \param[in] module_spec + /// A lldb::SBModuleSpec object that contains module specification. + /// + /// \return + /// A lldb::SBModule object that represents the found module, or an + /// invalid SBModule object if no module was found. + lldb::SBModule FindModule(const lldb::SBModuleSpec &module_spec); + /// Find compile units related to *this target and passed source /// file. /// diff --git a/lldb/include/lldb/Symbol/Symbol.h b/lldb/include/lldb/Symbol/Symbol.h index 0674e56ef43f5..b994c34e46493 100644 --- a/lldb/include/lldb/Symbol/Symbol.h +++ b/lldb/include/lldb/Symbol/Symbol.h @@ -15,6 +15,7 @@ #include "lldb/Symbol/SymbolContextScope.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/UserID.h" +#include "lldb/lldb-enumerations.h" #include "lldb/lldb-private.h" #include "llvm/Support/JSON.h" @@ -301,6 +302,10 @@ class Symbol : public SymbolContextScope { bool operator==(const Symbol &rhs) const; + static const char *GetTypeAsString(lldb::SymbolType symbol_type); + + static lldb::SymbolType GetTypeFromString(const char *str); + protected: // This is the internal guts of ResolveReExportedSymbol, it assumes // reexport_name is not null, and that module_spec is valid. We track the diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 311c9f089f86e..0608ac3fd83be 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -1299,6 +1299,25 @@ def request_modules( {"command": "modules", "type": "request", "arguments": args_dict} ) + def request_moduleSymbols( + self, + moduleId: str = "", + moduleName: str = "", + startIndex: int = 0, + count: int = 0, + ): + command_dict = { + "command": "__lldb_moduleSymbols", + "type": "request", + "arguments": { + "moduleId": moduleId, + "moduleName": moduleName, + "startIndex": startIndex, + "count": count, + }, + } + return self._send_recv(command_dict) + def request_stackTrace( self, threadId=None, startFrame=None, levels=None, format=None, dump=False ): diff --git a/lldb/source/API/SBSymbol.cpp b/lldb/source/API/SBSymbol.cpp index 79477dd3a70fc..3b59119494f37 100644 --- a/lldb/source/API/SBSymbol.cpp +++ b/lldb/source/API/SBSymbol.cpp @@ -193,6 +193,14 @@ SymbolType SBSymbol::GetType() { return eSymbolTypeInvalid; } +uint32_t SBSymbol::GetID() { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_ptr) + return m_opaque_ptr->GetID(); + return 0; +} + bool SBSymbol::IsExternal() { LLDB_INSTRUMENT_VA(this); @@ -208,3 +216,23 @@ bool SBSymbol::IsSynthetic() { return m_opaque_ptr->IsSynthetic(); return false; } + +bool SBSymbol::IsDebug() { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_ptr) + return m_opaque_ptr->IsDebug(); + return false; +} + +const char *SBSymbol::GetTypeAsString(lldb::SymbolType symbol_type) { + LLDB_INSTRUMENT_VA(symbol_type); + + return Symbol::GetTypeAsString(symbol_type); +} + +lldb::SymbolType SBSymbol::GetTypeFromString(const char *str) { + LLDB_INSTRUMENT_VA(str); + + return Symbol::GetTypeFromString(str); +} diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp index 6aa41c52f3731..eb56337de3c44 100644 --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -1570,6 +1570,18 @@ SBModule SBTarget::FindModule(const SBFileSpec &sb_file_spec) { return sb_module; } +SBModule SBTarget::FindModule(const SBModuleSpec &sb_module_spec) { + LLDB_INSTRUMENT_VA(this, sb_module_spec); + + SBModule sb_module; + if (TargetSP target_sp = GetSP(); target_sp && sb_module_spec.IsValid()) { + // The module list is thread safe, no need to lock. + sb_module.SetSP( + target_sp->GetImages().FindFirstModule(*sb_module_spec.m_opaque_up)); + } + return sb_module; +} + SBSymbolContextList SBTarget::FindCompileUnits(const SBFileSpec &sb_file_spec) { LLDB_INSTRUMENT_VA(this, sb_file_spec); diff --git a/lldb/source/Symbol/Symbol.cpp b/lldb/source/Symbol/Symbol.cpp index d6689a647062a..40497dbccc5c3 100644 --- a/lldb/source/Symbol/Symbol.cpp +++ b/lldb/source/Symbol/Symbol.cpp @@ -392,45 +392,8 @@ bool Symbol::Compare(ConstString name, SymbolType type) const { return false; } -#define ENUM_TO_CSTRING(x) \ - case eSymbolType##x: \ - return #x; - const char *Symbol::GetTypeAsString() const { - switch (m_type) { - ENUM_TO_CSTRING(Invalid); - ENUM_TO_CSTRING(Absolute); - ENUM_TO_CSTRING(Code); - ENUM_TO_CSTRING(Resolver); - ENUM_TO_CSTRING(Data); - ENUM_TO_CSTRING(Trampoline); - ENUM_TO_CSTRING(Runtime); - ENUM_TO_CSTRING(Exception); - ENUM_TO_CSTRING(SourceFile); - ENUM_TO_CSTRING(HeaderFile); - ENUM_TO_CSTRING(ObjectFile); - ENUM_TO_CSTRING(CommonBlock); - ENUM_TO_CSTRING(Block); - ENUM_TO_CSTRING(Local); - ENUM_TO_CSTRING(Param); - ENUM_TO_CSTRING(Variable); - ENUM_TO_CSTRING(VariableType); - ENUM_TO_CSTRING(LineEntry); - ENUM_TO_CSTRING(LineHeader); - ENUM_TO_CSTRING(ScopeBegin); - ENUM_TO_CSTRING(ScopeEnd); - ENUM_TO_CSTRING(Additional); - ENUM_TO_CSTRING(Compiler); - ENUM_TO_CSTRING(Instrumentation); - ENUM_TO_CSTRING(Undefined); - ENUM_TO_CSTRING(ObjCClass); - ENUM_TO_CSTRING(ObjCMetaClass); - ENUM_TO_CSTRING(ObjCIVar); - ENUM_TO_CSTRING(ReExported); - default: - break; - } - return "<unknown SymbolType>"; + return GetTypeAsString(static_cast<lldb::SymbolType>(m_type)); } void Symbol::CalculateSymbolContext(SymbolContext *sc) { @@ -774,6 +737,79 @@ bool Symbol::operator==(const Symbol &rhs) const { return true; } +#define ENUM_TO_CSTRING(x) \ + case eSymbolType##x: \ + return #x; + +const char *Symbol::GetTypeAsString(lldb::SymbolType symbol_type) { + switch (symbol_type) { + ENUM_TO_CSTRING(Invalid); + ENUM_TO_CSTRING(Absolute); + ENUM_TO_CSTRING(Code); + ENUM_TO_CSTRING(Resolver); + ENUM_TO_CSTRING(Data); + ENUM_TO_CSTRING(Trampoline); + ENUM_TO_CSTRING(Runtime); + ENUM_TO_CSTRING(Exception); + ENUM_TO_CSTRING(SourceFile); + ENUM_TO_CSTRING(HeaderFile); + ENUM_TO_CSTRING(ObjectFile); + ENUM_TO_CSTRING(CommonBlock); + ENUM_TO_CSTRING(Block); + ENUM_TO_CSTRING(Local); + ENUM_TO_CSTRING(Param); + ENUM_TO_CSTRING(Variable); + ENUM_TO_CSTRING(VariableType); + ENUM_TO_CSTRING(LineEntry); + ENUM_TO_CSTRING(LineHeader); + ENUM_TO_CSTRING(ScopeBegin); + ENUM_TO_CSTRING(ScopeEnd); + ENUM_TO_CSTRING(Additional); + ENUM_TO_CSTRING(Compiler); + ENUM_TO_CSTRING(Instrumentation); + ENUM_TO_CSTRING(Undefined); + ENUM_TO_CSTRING(ObjCClass); + ENUM_TO_CSTRING(ObjCMetaClass); + ENUM_TO_CSTRING(ObjCIVar); + ENUM_TO_CSTRING(ReExported); + } + return "<unknown SymbolType>"; +} + +lldb::SymbolType Symbol::GetTypeFromString(const char *str) { + std::string str_lower = llvm::StringRef(str).lower(); + return llvm::StringSwitch<lldb::SymbolType>(str_lower) + .Case("absolute", eSymbolTypeAbsolute) + .Case("code", eSymbolTypeCode) + .Case("resolver", eSymbolTypeResolver) + .Case("data", eSymbolTypeData) + .Case("trampoline", eSymbolTypeTrampoline) + .Case("runtime", eSymbolTypeRuntime) + .Case("exception", eSymbolTypeException) + .Case("sourcefile", eSymbolTypeSourceFile) + .Case("headerfile", eSymbolTypeHeaderFile) + .Case("objectfile", eSymbolTypeObjectFile) + .Case("commonblock", eSymbolTypeCommonBlock) + .Case("block", eSymbolTypeBlock) + .Case("local", eSymbolTypeLocal) + .Case("param", eSymbolTypeParam) + .Case("variable", eSymbolTypeVariable) + .Case("variableType", eSymbolTypeVariableType) + .Case("lineentry", eSymbolTypeLineEntry) + .Case("lineheader", eSymbolTypeLineHeader) + .Case("scopebegin", eSymbolTypeScopeBegin) + .Case("scopeend", eSymbolTypeScopeEnd) + .Case("additional,", eSymbolTypeAdditional) + .Case("compiler", eSymbolTypeCompiler) + .Case("instrumentation", eSymbolTypeInstrumentation) + .Case("undefined", eSymbolTypeUndefined) + .Case("objcclass", eSymbolTypeObjCClass) + .Case("objcmetaclass", eSymbolTypeObjCMetaClass) + .Case("objcivar", eSymbolTypeObjCIVar) + .Case("reexported", eSymbolTypeReExported) + .Default(eSymbolTypeInvalid); +} + namespace llvm { namespace json { @@ -804,36 +840,8 @@ bool fromJSON(const llvm::json::Value &value, lldb_private::JSONSymbol &symbol, bool fromJSON(const llvm::json::Value &value, lldb::SymbolType &type, llvm::json::Path path) { if (auto str = value.getAsString()) { - type = llvm::StringSwitch<lldb::SymbolType>(*str) - .Case("absolute", eSymbolTypeAbsolute) - .Case("code", eSymbolTypeCode) - .Case("resolver", eSymbolTypeResolver) - .Case("data", eSymbolTypeData) - .Case("trampoline", eSymbolTypeTrampoline) - .Case("runtime", eSymbolTypeRuntime) - .Case("exception", eSymbolTypeException) - .Case("sourcefile", eSymbolTypeSourceFile) - .Case("headerfile", eSymbolTypeHeaderFile) - .Case("objectfile", eSymbolTypeObjectFile) - .Case("commonblock", eSymbolTypeCommonBlock) - .Case("block", eSymbolTypeBlock) - .Case("local", eSymbolTypeLocal) - .Case("param", eSymbolTypeParam) - .Case("variable", eSymbolTypeVariable) - .Case("variableType", eSymbolTypeVariableType) - .Case("lineentry", eSymbolTypeLineEntry) - .Case("lineheader", eSymbolTypeLineHeader) - .Case("scopebegin", eSymbolTypeScopeBegin) - .Case("scopeend", eSymbolTypeScopeEnd) - .Case("additional,", eSymbolTypeAdditional) - .Case("compiler", eSymbolTypeCompiler) - .Case("instrumentation", eSymbolTypeInstrumentation) - .Case("undefined", eSymbolTypeUndefined) - .Case("objcclass", eSymbolTypeObjCClass) - .Case("objcmetaClass", eSymbolTypeObjCMetaClass) - .Case("objcivar", eSymbolTypeObjCIVar) - .Case("reexporte", eSymbolTypeReExported) - .Default(eSymbolTypeInvalid); + llvm::StringRef str_ref = str.value_or(""); + type = Symbol::GetTypeFromString(str_ref.data()); if (type == eSymbolTypeInvalid) { path.report("invalid symbol type"); diff --git a/lldb/test/API/tools/lldb-dap/moduleSymbols/Makefile b/lldb/test/API/tools/lldb-dap/moduleSymbols/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/moduleSymbols/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py b/lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py new file mode 100644 index 0000000000000..b99edf369a7fd --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py @@ -0,0 +1,37 @@ +""" +Test lldb-dap moduleSymbols request +""" + +import lldbdap_testcase + + +class TestDAP_moduleSymbols(lldbdap_testcase.DAPTestCaseBase): + def test_moduleSymbols(self): + """ + Test that the moduleSymbols request returns correct symbols from the module. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + symbol_names = [] + i = 0 + while True: + next_symbol = self.dap_server.request_moduleSymbols( + moduleName="a.out", startIndex=i, count=1 + ) + self.assertIn("symbols", next_symbol["body"]) + result_symbols = next_symbol["body"]["symbols"] + self.assertLessEqual(len(result_symbols), 1) + if len(result_symbols) == 0: + break + + self.assertIn("name", result_symbols[0]) + symbol_names.append(result_symbols[0]["name"]) + i += 1 + if i >= 1000: + break + + self.assertGreater(len(symbol_names), 0) + self.assertIn("main", symbol_names) + self.assertIn("func1", symbol_names) + self.assertIn("func2", symbol_names) diff --git a/lldb/test/API/tools/lldb-dap/moduleSymbols/main.c b/lldb/test/API/tools/lldb-dap/moduleSymbols/main.c new file mode 100644 index 0000000000000..b038b10480b80 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/moduleSymbols/main.c @@ -0,0 +1,9 @@ +int func1() { return 42; } + +int func2() { return 84; } + +int main() { + func1(); + func2(); + return 0; +} diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 5e0ad53b82f89..7db334ca56bcf 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -45,6 +45,7 @@ add_lldb_library(lldbDAP Handler/LaunchRequestHandler.cpp Handler/LocationsRequestHandler.cpp Handler/ModulesRequestHandler.cpp + Handler/ModuleSymbolsRequestHandler.cpp Handler/NextRequestHandler.cpp Handler/PauseRequestHandler.cpp Handler/ReadMemoryRequestHandler.cpp diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 0ecd5f05c73bb..b1ad38d983893 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -1261,6 +1261,27 @@ protocol::Capabilities DAP::GetCapabilities() { return capabilities; } +protocol::Capabilities DAP::GetCustomCapabilities() { + protocol::Capabilities capabilities; + + // Add all custom capabilities here. + const llvm::DenseSet<AdapterFeature> all_custom_features = { + protocol::eAdapterFeatureSupportsModuleSymbolsRequest, + }; + + for (auto &kv : request_handlers) { + llvm::SmallDenseSet<AdapterFeature, 1> features = + kv.second->GetSupportedFeatures(); + + for (auto &feature : features) { + if (all_custom_features.contains(feature)) + capabilities.supportedFeatures.insert(feature); + } + } + + return capabilities; +} + void DAP::StartEventThread() { event_thread = std::thread(&DAP::EventThread, this); } @@ -1617,6 +1638,7 @@ void DAP::RegisterRequests() { // Custom requests RegisterRequest<CompileUnitsRequestHandler>(); RegisterRequest<ModulesRequestHandler>(); + RegisterRequest<ModuleSymbolsRequestHandler>(); // Testing requests RegisterRequest<TestGetTargetBreakpointsRequestHandler>(); diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 0b6373fb80381..04f70f76a09cd 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -367,6 +367,9 @@ struct DAP final : private DAPTransport::MessageHandler { /// The set of capabilities supported by this adapter. protocol::Capabilities GetCapabilities(); + /// The set of custom capabilities supported by this adapter. + protocol::Capabilities GetCustomCapabilities(); + /// Debuggee will continue from stopped state. void WillContinue() { variables.Clear(); } diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index 364cc7ab4ef8c..bfb05a387d04d 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -38,25 +38,37 @@ static void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) { dap.SendJSON(llvm::json::Value(std::move(event))); } -void SendTargetBasedCapabilities(DAP &dap) { +/// Get capabilities based on the configured target. +static llvm::DenseSet<AdapterFeature> GetTargetBasedCapabilities(DAP &dap) { + llvm::DenseSet<AdapterFeature> capabilities; if (!dap.target.IsValid()) - return; - - protocol::CapabilitiesEventBody body; + return capabilities; const llvm::StringRef target_triple = dap.target.GetTriple(); if (target_triple.starts_with("x86")) - body.capabilities.supportedFeatures.insert( - protocol::eAdapterFeatureStepInTargetsRequest); + capabilities.insert(protocol::eAdapterFeatureStepInTargetsRequest); // We only support restarting launch requests not attach requests. if (dap.last_launch_request) - body.capabilities.supportedFeatures.insert( - protocol::eAdapterFeatureRestartRequest); + capabilities.insert(protocol::eAdapterFeatureRestartRequest); + + return capabilities; +} + +void SendExtraCapabilities(DAP &dap) { + protocol::Capabilities capabilities = dap.GetCustomCapabilities(); + llvm::DenseSet<AdapterFeature> target_capabilities = + GetTargetBasedCapabilities(dap); + + capabilities.supportedFeatures.insert(target_capabilities.begin(), + target_capabilities.end()); + + protocol::CapabilitiesEventBody body; + body.capabilities = std::move(capabilities); // Only notify the client if supportedFeatures changed. if (!body.capabilities.supportedFeatures.empty()) - dap.Send(protocol::Event{"capabilities", body}); + dap.Send(protocol::Event{"capabilities", std::move(body)}); } // "ProcessEvent": { diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h index 72ad5308a2b0c..592c1b81c46af 100644 --- a/lldb/tools/lldb-dap/EventHelper.h +++ b/lldb/tools/lldb-dap/EventHelper.h @@ -17,8 +17,8 @@ struct DAP; enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; -/// Update capabilities based on the configured target. -void SendTargetBasedCapabilities(DAP &dap); +/// Sends target based capabilities and lldb-dap custom capabilities. +void SendExtraCapabilities(DAP &dap); void SendProcessEvent(DAP &dap, LaunchMethod launch_method); diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp index e7735a705d0aa..1bfe7b7f6ef5c 100644 --- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp @@ -9,6 +9,7 @@ #include "DAP.h" #include "EventHelper.h" #include "LLDBUtils.h" +#include "Protocol/ProtocolEvents.h" #include "Protocol/ProtocolRequests.h" #include "ProtocolUtils.h" #include "RequestHandler.h" @@ -44,7 +45,10 @@ ConfigurationDoneRequestHandler::Run(const ConfigurationDoneArguments &) const { // Waiting until 'configurationDone' to send target based capabilities in case // the launch or attach scripts adjust the target. The initial dummy target // may have different capabilities than the final target. - SendTargetBasedCapabilities(dap); + + /// Also send here custom capabilities to the client, which is consumed by the + /// lldb-dap specific editor extension. + SendExtraCapabilities(dap); // Clients can request a baseline of currently existing threads after // we acknowledge the configurationDone request. diff --git a/lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp new file mode 100644 index 0000000000000..6e50ee122c41f --- /dev/null +++ b/lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// 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 "DAP.h" +#include "DAPError.h" +#include "Protocol/DAPTypes.h" +#include "RequestHandler.h" +#include "lldb/API/SBAddress.h" +#include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBModule.h" +#include "lldb/API/SBModuleSpec.h" +#include "lldb/Utility/UUID.h" +#include "llvm/Support/Error.h" +#include <cstddef> + +using namespace lldb_dap::protocol; +namespace lldb_dap { + +llvm::Expected<ModuleSymbolsResponseBody> +ModuleSymbolsRequestHandler::Run(const ModuleSymbolsArguments &args) const { + ModuleSymbolsResponseBody response; + + lldb::SBModuleSpec module_spec; + if (!args.moduleId.empty()) { + llvm::SmallVector<uint8_t, 20> uuid_bytes; + if (!lldb_private::UUID::DecodeUUIDBytesFromString(args.moduleId, + uuid_bytes) + .empty()) + return llvm::make_error<DAPError>("invalid module ID"); + + module_spec.SetUUIDBytes(uuid_bytes.data(), uuid_bytes.size()); + } + + if (!args.moduleName.empty()) { + lldb::SBFileSpec file_spec; + file_spec.SetFilename(args.moduleName.c_str()); + module_spec.SetFileSpec(file_spec); + } + + // Empty request, return empty response. + if (!module_spec.IsValid()) + return response; + + std::vector<Symbol> &symbols = response.symbols; + lldb::SBModule module = dap.target.FindModule(module_spec); + if (!module.IsValid()) + return llvm::make_error<DAPError>("module not found"); + + const size_t num_symbols = module.GetNumSymbols(); + const size_t start_index = args.startIndex.value_or(0); + const size_t end_index = + std::min(start_index + args.count.value_or(num_symbols), num_symbols); + for (size_t i = start_index; i < end_index; ++i) { + lldb::SBSymbol symbol = module.GetSymbolAtIndex(i); + if (!symbol.IsValid()) + continue; + + Symbol dap_symbol; + dap_symbol.id = symbol.GetID(); + dap_symbol.type = symbol.GetType(); + dap_symbol.isDebug = symbol.IsDebug(); + dap_symbol.isSynthetic = symbol.IsSynthetic(); + dap_symbol.isExternal = symbol.IsExternal(); + + lldb::SBAddress start_address = symbol.GetStartAddress(); + if (start_address.IsValid()) { + lldb::addr_t file_address = start_address.GetFileAddress(); + if (file_address != LLDB_INVALID_ADDRESS) + dap_symbol.fileAddress = file_address; + + lldb::addr_t load_address = start_address.GetLoadAddress(dap.target); + if (load_address != LLDB_INVALID_ADDRESS) + dap_symbol.loadAddress = load_address; + } + + dap_symbol.size = symbol.GetSize(); + dap_symbol.name = symbol.GetName(); + symbols.push_back(std::move(dap_symbol)); + } + + return response; +} + +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index 5469cfbfa0321..977a247996750 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -597,6 +597,20 @@ class CancelRequestHandler : public RequestHandler<protocol::CancelArguments, llvm::Error Run(const protocol::CancelArguments &args) const override; }; +class ModuleSymbolsRequestHandler + : public RequestHandler< + protocol::ModuleSymbolsArguments, + llvm::Expected<protocol::ModuleSymbolsResponseBody>> { +public: + using RequestHandler::RequestHandler; + static llvm::StringLiteral GetCommand() { return "__lldb_moduleSymbols"; } + FeatureSet GetSupportedFeatures() const override { + return {protocol::eAdapterFeatureSupportsModuleSymbolsRequest}; + } + llvm::Expected<protocol::ModuleSymbolsResponseBody> + Run(const protocol::ModuleSymbolsArguments &args) const override; +}; + /// A request used in testing to get the details on all breakpoints that are /// currently set in the target. This helps us to test "setBreakpoints" and /// "setFunctionBreakpoints" requests to verify we have the correct set of diff --git a/lldb/tools/lldb-dap/Protocol/DAPTypes.cpp b/lldb/tools/lldb-dap/Protocol/DAPTypes.cpp index ecb4baef56e80..a14ed9e521f48 100644 --- a/lldb/tools/lldb-dap/Protocol/DAPTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/DAPTypes.cpp @@ -1,4 +1,6 @@ #include "Protocol/DAPTypes.h" +#include "lldb/API/SBSymbol.h" +#include "lldb/lldb-enumerations.h" using namespace llvm; @@ -33,4 +35,35 @@ llvm::json::Value toJSON(const SourceLLDBData &SLD) { return result; } -} // namespace lldb_dap::protocol \ No newline at end of file +bool fromJSON(const llvm::json::Value &Params, Symbol &DS, llvm::json::Path P) { + json::ObjectMapper O(Params, P); + std::string type_str; + if (!(O && O.map("id", DS.id) && O.map("isDebug", DS.isDebug) && + O.map("isSynthetic", DS.isSynthetic) && + O.map("isExternal", DS.isExternal) && O.map("type", type_str) && + O.map("fileAddress", DS.fileAddress) && + O.mapOptional("loadAddress", DS.loadAddress) && + O.map("size", DS.size) && O.map("name", DS.name))) + return false; + + DS.type = lldb::SBSymbol::GetTypeFromString(type_str.c_str()); + return true; +} + +llvm::json::Value toJSON(const Symbol &DS) { + json::Object result{ + {"id", DS.id}, + {"isDebug", DS.isDebug}, + {"isSynthetic", DS.isSynthetic}, + {"isExternal", DS.isExternal}, + {"type", lldb::SBSymbol::GetTypeAsString(DS.type)}, + {"fileAddress", DS.fileAddress}, + {"loadAddress", DS.loadAddress}, + {"size", DS.size}, + {"name", DS.name}, + }; + + return result; +} + +} // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/DAPTypes.h b/lldb/tools/lldb-dap/Protocol/DAPTypes.h index 716d8b491b258..7fccf1359a737 100644 --- a/lldb/tools/lldb-dap/Protocol/DAPTypes.h +++ b/lldb/tools/lldb-dap/Protocol/DAPTypes.h @@ -48,6 +48,38 @@ struct SourceLLDBData { bool fromJSON(const llvm::json::Value &, SourceLLDBData &, llvm::json::Path); llvm::json::Value toJSON(const SourceLLDBData &); +struct Symbol { + /// The symbol id, usually the original symbol table index. + uint32_t id; + + /// True if this symbol is debug information in a symbol. + bool isDebug; + + /// True if this symbol is not actually in the symbol table, but synthesized + /// from other info in the object file. + bool isSynthetic; + + /// True if this symbol is globally visible. + bool isExternal; + + /// The symbol type. + lldb::SymbolType type; + + /// The symbol file address. + lldb::addr_t fileAddress; + + /// The symbol load address. + std::optional<lldb::addr_t> loadAddress; + + /// The symbol size. + lldb::addr_t size; + + /// The symbol name. + std::string name; +}; +bool fromJSON(const llvm::json::Value &, Symbol &, llvm::json::Path); +llvm::json::Value toJSON(const Symbol &); + } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index 40634d52a66fd..eab7211e18973 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -609,4 +609,19 @@ json::Value toJSON(const WriteMemoryResponseBody &WMR) { return result; } +bool fromJSON(const llvm::json::Value &Params, ModuleSymbolsArguments &Args, + llvm::json::Path P) { + json::ObjectMapper O(Params, P); + return O && O.map("moduleId", Args.moduleId) && + O.map("moduleName", Args.moduleName) && + O.mapOptional("startIndex", Args.startIndex) && + O.mapOptional("count", Args.count); +} + +llvm::json::Value toJSON(const ModuleSymbolsResponseBody &DGMSR) { + json::Object result; + result.insert({"symbols", DGMSR.symbols}); + return result; +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 7c08a8887c081..0848ee53b4410 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -1012,6 +1012,30 @@ struct WriteMemoryResponseBody { }; llvm::json::Value toJSON(const WriteMemoryResponseBody &); +struct ModuleSymbolsArguments { + /// The module UUID for which to retrieve symbols. + std::string moduleId; + + /// The module path. + std::string moduleName; + + /// The index of the first symbol to return; if omitted, start at the + /// beginning. + std::optional<uint32_t> startIndex; + + /// The number of symbols to return; if omitted, all symbols are returned. + std::optional<uint32_t> count; +}; +bool fromJSON(const llvm::json::Value &, ModuleSymbolsArguments &, + llvm::json::Path); + +/// Response to `getModuleSymbols` request. +struct ModuleSymbolsResponseBody { + /// The symbols for the specified module. + std::vector<Symbol> symbols; +}; +llvm::json::Value toJSON(const ModuleSymbolsResponseBody &); + } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index 0708901d9ca05..dc8edaadcd9bb 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -452,6 +452,8 @@ static llvm::StringLiteral ToString(AdapterFeature feature) { return "supportsWriteMemoryRequest"; case eAdapterFeatureTerminateDebuggee: return "supportTerminateDebuggee"; + case eAdapterFeatureSupportsModuleSymbolsRequest: + return "supportsModuleSymbolsRequest"; } llvm_unreachable("unhandled adapter feature."); } @@ -523,6 +525,8 @@ bool fromJSON(const llvm::json::Value &Params, AdapterFeature &feature, eAdapterFeatureValueFormattingOptions) .Case("supportsWriteMemoryRequest", eAdapterFeatureWriteMemoryRequest) .Case("supportTerminateDebuggee", eAdapterFeatureTerminateDebuggee) + .Case("supportsModuleSymbolsRequest", + eAdapterFeatureSupportsModuleSymbolsRequest) .Default(std::nullopt); if (!parsedFeature) { diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index 7a7609797c104..7077df90a85b5 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -320,8 +320,11 @@ enum AdapterFeature : unsigned { /// The debug adapter supports the `terminateDebuggee` attribute on the /// `disconnect` request. eAdapterFeatureTerminateDebuggee, + /// The debug adapter supports the `supportsModuleSymbols` request. + /// This request is a custom request of lldb-dap. + eAdapterFeatureSupportsModuleSymbolsRequest, eAdapterFeatureFirst = eAdapterFeatureANSIStyling, - eAdapterFeatureLast = eAdapterFeatureTerminateDebuggee, + eAdapterFeatureLast = eAdapterFeatureSupportsModuleSymbolsRequest, }; bool fromJSON(const llvm::json::Value &, AdapterFeature &, llvm::json::Path); llvm::json::Value toJSON(const AdapterFeature &); diff --git a/lldb/tools/lldb-dap/package-lock.json b/lldb/tools/lldb-dap/package-lock.json index 1969b196accc6..26db1ce6df2fd 100644 --- a/lldb/tools/lldb-dap/package-lock.json +++ b/lldb/tools/lldb-dap/package-lock.json @@ -1,20 +1,24 @@ { "name": "lldb-dap", - "version": "0.2.15", + "version": "0.2.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lldb-dap", - "version": "0.2.15", + "version": "0.2.16", "license": "Apache 2.0 License with LLVM exceptions", "devDependencies": { "@types/node": "^18.19.41", + "@types/tabulator-tables": "^6.2.10", "@types/vscode": "1.75.0", + "@types/vscode-webview": "^1.57.5", "@vscode/debugprotocol": "^1.68.0", "@vscode/vsce": "^3.2.2", + "esbuild": "^0.25.9", "prettier": "^3.4.2", "prettier-plugin-curly": "^0.3.1", + "tabulator-tables": "^6.3.1", "typescript": "^5.7.3" }, "engines": { @@ -318,6 +322,448 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -399,6 +845,13 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/tabulator-tables": { + "version": "6.2.10", + "resolved": "https://registry.npmjs.org/@types/tabulator-tables/-/tabulator-tables-6.2.10.tgz", + "integrity": "sha512-g6o0gG3lu/ozmxPw9rLY1p57T6rvV8OhbJKyzWwPwjdnN3JuSQ3gWxb06v2+dl2tdoqNXTvlylipSSKpS8UzzQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/vscode": { "version": "1.75.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.0.tgz", @@ -406,6 +859,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/vscode-webview": { + "version": "1.57.5", + "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.5.tgz", + "integrity": "sha512-iBAUYNYkz+uk1kdsq05fEcoh8gJmwT3lqqFPN7MGyjQ3HVloViMdo7ZJ8DFIP8WOK74PjOEilosqAyxV2iUFUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@vscode/debugprotocol": { "version": "1.68.0", "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz", @@ -1169,6 +1629,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2557,6 +3059,13 @@ "node": ">=4" } }, + "node_modules/tabulator-tables": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-6.3.1.tgz", + "integrity": "sha512-qFW7kfadtcaISQIibKAIy0f3eeIXUVi8242Vly1iJfMD79kfEGzfczNuPBN/80hDxHzQJXYbmJ8VipI40hQtfA==", + "dev": true, + "license": "MIT" + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index d677a81cc7974..f11b64aa72ae4 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -29,11 +29,15 @@ ], "devDependencies": { "@types/node": "^18.19.41", + "@types/tabulator-tables": "^6.2.10", "@types/vscode": "1.75.0", + "@types/vscode-webview": "^1.57.5", "@vscode/debugprotocol": "^1.68.0", "@vscode/vsce": "^3.2.2", + "esbuild": "^0.25.9", "prettier": "^3.4.2", "prettier-plugin-curly": "^0.3.1", + "tabulator-tables": "^6.3.1", "typescript": "^5.7.3" }, "activationEvents": [ @@ -42,8 +46,11 @@ ], "main": "./out/extension", "scripts": { - "vscode:prepublish": "tsc -p ./", - "watch": "tsc -watch -p ./", + "bundle-symbols-table-view": "npx tsc -p src-ts/webview --noEmit && npx esbuild src-ts/webview/symbols-table-view.ts --bundle --format=iife --outdir=./out/webview", + "bundle-tabulator": "cp node_modules/tabulator-tables/dist/js/tabulator.min.js ./out/webview/ && cp node_modules/tabulator-tables/dist/css/tabulator_midnight.min.css ./out/webview/ && cp node_modules/tabulator-tables/dist/css/tabulator_simple.min.css ./out/webview/", + "bundle-webview": "npm run bundle-symbols-table-view && npm run bundle-tabulator", + "vscode:prepublish": "npm run bundle-webview && tsc -p ./", + "watch": "npm run bundle-webview && tsc -watch -p ./", "format": "npx prettier './src-ts/' --write", "package": "rm -rf ./out/lldb-dap.vsix && vsce package --out ./out/lldb-dap.vsix", "publish": "vsce publish", @@ -259,6 +266,15 @@ { "command": "lldb-dap.modules.copyProperty", "title": "Copy Value" + }, + { + "command": "lldb-dap.modules.showSymbols", + "title": "Show Module Symbols" + }, + { + "category": "lldb-dap", + "command": "lldb-dap.debug.showSymbols", + "title": "Show Symbols of a Module" } ], "menus": { @@ -266,12 +282,24 @@ { "command": "lldb-dap.modules.copyProperty", "when": "false" + }, + { + "command": "lldb-dap.modules.showSymbols", + "when": "false" + }, + { + "command": "lldb-dap.debug.showSymbols", + "when": "debuggersAvailable && debugType == 'lldb-dap' && lldb-dap.supportsModuleSymbolsRequest" } ], "view/item/context": [ { "command": "lldb-dap.modules.copyProperty", "when": "view == lldb-dap.modules && viewItem == property" + }, + { + "command": "lldb-dap.modules.showSymbols", + "when": "view == lldb-dap.modules && viewItem == module && lldb-dap.supportsModuleSymbolsRequest" } ] }, diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts index 1e16dac031125..1ae87116141f1 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts @@ -69,6 +69,10 @@ const configurations: Record<string, DefaultConfig> = { terminateCommands: { type: "stringArray", default: [] }, }; +export function getDefaultConfigKey(key: string): string | number | boolean | string[] | undefined { + return configurations[key]?.default; +} + export class LLDBDapConfigurationProvider implements vscode.DebugConfigurationProvider { diff --git a/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts index 7d7f73dbff92d..6e89d441bbcf0 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts @@ -1,11 +1,17 @@ import { DebugProtocol } from "@vscode/debugprotocol"; import * as vscode from "vscode"; +export interface LLDBDapCapabilities extends DebugProtocol.Capabilities { + /** The debug adapter supports the `moduleSymbols` request. */ + supportsModuleSymbolsRequest?: boolean; +} + /** A helper type for mapping event types to their corresponding data type. */ // prettier-ignore interface EventMap { "module": DebugProtocol.ModuleEvent; "exited": DebugProtocol.ExitedEvent; + "capabilities": DebugProtocol.CapabilitiesEvent; } /** A type assertion to check if a ProtocolMessage is an event or if it is a specific event. */ @@ -39,6 +45,9 @@ export class DebugSessionTracker private modulesChanged = new vscode.EventEmitter< vscode.DebugSession | undefined >(); + private sessionReceivedCapabilities = + new vscode.EventEmitter<[ vscode.DebugSession, LLDBDapCapabilities ]>(); + private sessionExited = new vscode.EventEmitter<vscode.DebugSession>(); /** * Fired when modules are changed for any active debug session. @@ -48,6 +57,15 @@ export class DebugSessionTracker onDidChangeModules: vscode.Event<vscode.DebugSession | undefined> = this.modulesChanged.event; + /** Fired when a debug session is initialized. */ + onDidReceiveSessionCapabilities: + vscode.Event<[ vscode.DebugSession, LLDBDapCapabilities ]> = + this.sessionReceivedCapabilities.event; + + /** Fired when a debug session is exiting. */ + onDidExitSession: vscode.Event<vscode.DebugSession> = + this.sessionExited.event; + constructor(private logger: vscode.LogOutputChannel) { this.onDidChangeModules(this.moduleChangedListener, this); vscode.debug.onDidChangeActiveDebugSession((session) => @@ -146,6 +164,10 @@ export class DebugSessionTracker this.logger.info( `Session "${session.name}" exited with code ${exitCode}`, ); + + this.sessionExited.fire(session); + } else if (isEvent(message, "capabilities")) { + this.sessionReceivedCapabilities.fire([ session, message.body.capabilities ]); } } } diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts b/lldb/tools/lldb-dap/src-ts/extension.ts index 4b7a35e6944c6..7119cba972fa4 100644 --- a/lldb/tools/lldb-dap/src-ts/extension.ts +++ b/lldb/tools/lldb-dap/src-ts/extension.ts @@ -12,6 +12,7 @@ import { ModuleProperty, } from "./ui/modules-data-provider"; import { LogFilePathProvider } from "./logging"; +import { SymbolsProvider } from "./ui/symbols-provider"; /** * This class represents the extension and manages its life cycle. Other extensions @@ -19,6 +20,7 @@ import { LogFilePathProvider } from "./logging"; */ export class LLDBDapExtension extends DisposableContext { constructor( + context: vscode.ExtensionContext, logger: vscode.LogOutputChannel, logFilePath: LogFilePathProvider, outputChannel: vscode.OutputChannel, @@ -52,10 +54,12 @@ export class LLDBDapExtension extends DisposableContext { vscode.window.registerUriHandler(new LaunchUriHandler()), ); - vscode.commands.registerCommand( + this.pushSubscription(vscode.commands.registerCommand( "lldb-dap.modules.copyProperty", (node: ModuleProperty) => vscode.env.clipboard.writeText(node.value), - ); + )); + + this.pushSubscription(new SymbolsProvider(sessionTracker, context)); } } @@ -67,7 +71,7 @@ export async function activate(context: vscode.ExtensionContext) { outputChannel.info("LLDB-DAP extension activating..."); const logFilePath = new LogFilePathProvider(context, outputChannel); context.subscriptions.push( - new LLDBDapExtension(outputChannel, logFilePath, outputChannel), + new LLDBDapExtension(context, outputChannel, logFilePath, outputChannel), ); outputChannel.info("LLDB-DAP extension activated"); } diff --git a/lldb/tools/lldb-dap/src-ts/index.d.ts b/lldb/tools/lldb-dap/src-ts/index.d.ts new file mode 100644 index 0000000000000..d4618f44dee7b --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/index.d.ts @@ -0,0 +1,14 @@ +export {}; + +/// The symbol type we get from the lldb-dap server +export declare interface SymbolType { + id: number; + isDebug: boolean; + isSynthetic: boolean; + isExternal: boolean; + type: string; + fileAddress: number; + loadAddress?: number; + size: number; + name: string; +} diff --git a/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts b/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts index d0fb9270c734f..96343cb0a8da6 100644 --- a/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts +++ b/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts @@ -19,6 +19,7 @@ class ModuleItem extends vscode.TreeItem { constructor(module: DebugProtocol.Module) { super(module.name, vscode.TreeItemCollapsibleState.Collapsed); this.description = module.symbolStatus; + this.contextValue = "module"; } static getProperties(module: DebugProtocol.Module): ModuleProperty[] { diff --git a/lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts b/lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts new file mode 100644 index 0000000000000..84b9387ffe49f --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts @@ -0,0 +1,127 @@ +import * as vscode from "vscode"; +import { DebugProtocol } from "@vscode/debugprotocol"; + +import { DebugSessionTracker } from "../debug-session-tracker"; +import { DisposableContext } from "../disposable-context"; + +import { SymbolType } from ".."; +import { getSymbolsTableHTMLContent } from "./symbols-webview-html"; +import { getDefaultConfigKey } from "../debug-configuration-provider"; + +export class SymbolsProvider extends DisposableContext { + constructor( + private readonly tracker: DebugSessionTracker, + private readonly extensionContext: vscode.ExtensionContext, + ) { + super(); + + this.pushSubscription(vscode.commands.registerCommand( + "lldb-dap.debug.showSymbols", + () => { + const session = vscode.debug.activeDebugSession; + if (!session) return; + + this.SelectModuleAndShowSymbols(session); + }, + )); + + this.pushSubscription(vscode.commands.registerCommand( + "lldb-dap.modules.showSymbols", + (moduleItem: DebugProtocol.Module) => { + const session = vscode.debug.activeDebugSession; + if (!session) return; + + this.showSymbolsForModule(session, moduleItem); + }, + )); + + this.tracker.onDidReceiveSessionCapabilities(([ _session, capabilities ]) => { + if (capabilities.supportsModuleSymbolsRequest) { + vscode.commands.executeCommand( + "setContext", "lldb-dap.supportsModuleSymbolsRequest", true); + } + }); + + this.tracker.onDidExitSession((_session) => { + vscode.commands.executeCommand("setContext", "lldb-dap.supportsModuleSymbolsRequest", false); + }); + } + + private async SelectModuleAndShowSymbols(session: vscode.DebugSession) { + const modules = this.tracker.debugSessionModules(session); + if (!modules || modules.length === 0) { + return; + } + + // Let the user select a module to show symbols for + const selectedModule = await vscode.window.showQuickPick(modules.map(m => new ModuleQuickPickItem(m)), { + placeHolder: "Select a module to show symbols for" + }); + if (!selectedModule) { + return; + } + + this.showSymbolsForModule(session, selectedModule.module); + } + + private async showSymbolsForModule(session: vscode.DebugSession, module: DebugProtocol.Module) { + try { + const symbols = await this.getSymbolsForModule(session, module.id.toString()); + this.showSymbolsInNewTab(module.name.toString(), symbols); + } catch (error) { + if (error instanceof Error) { + vscode.window.showErrorMessage("Failed to retrieve symbols: " + error.message); + } else { + vscode.window.showErrorMessage("Failed to retrieve symbols due to an unknown error."); + } + + return; + } + } + + private async getSymbolsForModule(session: vscode.DebugSession, moduleId: string): Promise<SymbolType[]> { + const symbols_response: { symbols: Array<SymbolType> } = await session.customRequest("__lldb_moduleSymbols", { moduleId, moduleName: '' }); + return symbols_response?.symbols || []; + } + + private async showSymbolsInNewTab(moduleName: string, symbols: SymbolType[]) { + const panel = vscode.window.createWebviewPanel( + "lldb-dap.symbols", + `Symbols for ${moduleName}`, + vscode.ViewColumn.Active, + { + enableScripts: true, + localResourceRoots: [ + this.getExtensionResourcePath() + ] + } + ); + + let tabulatorJsFilename = "tabulator_simple.min.css"; + if (vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark || vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.HighContrast) { + tabulatorJsFilename = "tabulator_midnight.min.css"; + } + const tabulatorCssPath = panel.webview.asWebviewUri(vscode.Uri.joinPath(this.getExtensionResourcePath(), tabulatorJsFilename)); + const tabulatorJsPath = panel.webview.asWebviewUri(vscode.Uri.joinPath(this.getExtensionResourcePath(), "tabulator.min.js")); + const symbolsTableScriptPath = panel.webview.asWebviewUri(vscode.Uri.joinPath(this.getExtensionResourcePath(), "symbols-table-view.js")); + + panel.webview.html = getSymbolsTableHTMLContent(tabulatorJsPath, tabulatorCssPath, symbolsTableScriptPath); + panel.webview.postMessage({ command: "updateSymbols", symbols: symbols }); + } + + private getExtensionResourcePath(): vscode.Uri { + return vscode.Uri.joinPath(this.extensionContext.extensionUri, "out", "webview"); + } +} + +class ModuleQuickPickItem implements vscode.QuickPickItem { + constructor(public readonly module: DebugProtocol.Module) {} + + get label(): string { + return this.module.name; + } + + get description(): string { + return this.module.id.toString(); + } +} diff --git a/lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts b/lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts new file mode 100644 index 0000000000000..88e24f3108787 --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts @@ -0,0 +1,51 @@ +import * as vscode from "vscode"; + +export function getSymbolsTableHTMLContent(tabulatorJsPath: vscode.Uri, tabulatorCssPath: vscode.Uri, symbolsTableScriptPath: vscode.Uri): string { + return `<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <link href="${tabulatorCssPath}" rel="stylesheet"> + <style> + .tabulator { + background-color: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + } + + .tabulator .tabulator-header .tabulator-col { + background-color: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + } + + .tabulator-row { + background-color: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + } + + .tabulator-row.tabulator-row-even { + background-color: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + } + + .tabulator-row.tabulator-selected { + background-color: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + } + + .tabulator-cell { + text-overflow: clip !important; + } + + #symbols-table { + width: 100%; + height: 100vh; + } + </style> +</head> +<body> + <div id="symbols-table"></div> + <script src="${tabulatorJsPath}"></script> + <script src="${symbolsTableScriptPath}"></script> +</body> +</html>`; +} \ No newline at end of file diff --git a/lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts b/lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts new file mode 100644 index 0000000000000..8454378abef16 --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts @@ -0,0 +1,114 @@ +import type { CellComponent, ColumnDefinition } from "tabulator-tables"; +import type { SymbolType } from ".." + +/// SVG from https://github.com/olifolkerd/tabulator/blob/master/src/js/modules/Format/defaults/formatters/tickCross.js +/// but with the default font color. +/// hopefully in the future we can set the color as parameter: https://github.com/olifolkerd/tabulator/pull/4791 +const TICK_ELEMENT = `<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="var(--vscode-editor-foreground)" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34 c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351 l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07 l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>`; + +function getTabulatorHexaFormatter(padding: number): (cell: CellComponent) => string { + return (cell: CellComponent) => { + const val = cell.getValue(); + if (val === undefined || val === null) { + return ""; + } + + return val !== undefined ? "0x" + val.toString(16).toLowerCase().padStart(padding, "0") : ""; + }; +} + +const SYMBOL_TABLE_COLUMNS: ColumnDefinition[] = [ + { title: "ID", field: "id", headerTooltip: true, sorter: "number", widthGrow: 0.6 }, + { + title: "Name", + field: "name", + headerTooltip: true, + sorter: "string", + widthGrow: 2.5, + minWidth: 200, + tooltip : (_event: MouseEvent, cell: CellComponent) => { + const rowData = cell.getRow().getData(); + return rowData.name; + } + }, + { + title: "Debug", + field: "isDebug", + headerTooltip: true, + hozAlign: "center", + widthGrow: 0.8, + formatter: "tickCross", + formatterParams: { + tickElement: TICK_ELEMENT, + crossElement: false, + } + }, + { + title: "Synthetic", + field: "isSynthetic", + headerTooltip: true, + hozAlign: "center", + widthGrow: 0.8, + formatter: "tickCross", + formatterParams: { + tickElement: TICK_ELEMENT, + crossElement: false, + } + }, + { + title: "External", + field: "isExternal", + headerTooltip: true, + hozAlign: "center", + widthGrow: 0.8, + formatter: "tickCross", + formatterParams: { + tickElement: TICK_ELEMENT, + crossElement: false, + } + }, + { title: "Type", field: "type", sorter: "string" }, + { + title: "File Address", + field: "fileAddress", + headerTooltip: true, + sorter: "number", + widthGrow : 1.25, + formatter: getTabulatorHexaFormatter(16), + }, + { + title: "Load Address", + field: "loadAddress", + headerTooltip: true, + sorter: "number", + widthGrow : 1.25, + formatter: getTabulatorHexaFormatter(16), + }, + { title: "Size", field: "size", headerTooltip: true, sorter: "number", formatter: getTabulatorHexaFormatter(8) }, +]; + +const vscode = acquireVsCodeApi(); +const previousState: any = vscode.getState(); + +declare const Tabulator: any; // HACK: real definition comes from tabulator.min.js +const SYMBOLS_TABLE = new Tabulator("#symbols-table", { + height: "100vh", + columns: SYMBOL_TABLE_COLUMNS, + layout: "fitColumns", + data: previousState?.symbols || [], +}); + +function updateSymbolsTable(symbols: SymbolType[]) { + SYMBOLS_TABLE.setData(symbols); +} + +window.addEventListener("message", (event: MessageEvent<any>) => { + const message = event.data; + switch (message.command) { + case "updateSymbols": + vscode.setState({ symbols: message.symbols }); + updateSymbolsTable(message.symbols); + break; + } +}); + diff --git a/lldb/tools/lldb-dap/src-ts/webview/tsconfig.json b/lldb/tools/lldb-dap/src-ts/webview/tsconfig.json new file mode 100644 index 0000000000000..cfe64fc4b989f --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/webview/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "module": "esnext", + "outDir": "out", + "rootDir": ".", + "sourceMap": true, + "strict": true, + "noEmit": true, + "target": "es2017" + }, + "include": [ + "./" + ], +} diff --git a/lldb/tools/lldb-dap/tsconfig.json b/lldb/tools/lldb-dap/tsconfig.json index 2092148888904..06a484a1fc263 100644 --- a/lldb/tools/lldb-dap/tsconfig.json +++ b/lldb/tools/lldb-dap/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "moduleResolution": "node", "module": "commonjs", "outDir": "out", "rootDir": "src-ts", @@ -12,5 +13,6 @@ ], "exclude": [ "node_modules", + "src-ts/webview", ] } diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt index 156cd625546bd..716159b454231 100644 --- a/lldb/unittests/DAP/CMakeLists.txt +++ b/lldb/unittests/DAP/CMakeLists.txt @@ -1,6 +1,7 @@ add_lldb_unittest(DAPTests DAPErrorTest.cpp DAPTest.cpp + DAPTypesTest.cpp FifoFilesTest.cpp Handler/DisconnectTest.cpp Handler/ContinueTest.cpp diff --git a/lldb/unittests/DAP/DAPTypesTest.cpp b/lldb/unittests/DAP/DAPTypesTest.cpp new file mode 100644 index 0000000000000..f398c54b724a0 --- /dev/null +++ b/lldb/unittests/DAP/DAPTypesTest.cpp @@ -0,0 +1,60 @@ +//===-- DAPTypesTest.cpp ----------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "Protocol/DAPTypes.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/lldb-enumerations.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" +#include <optional> + +using namespace llvm; +using namespace lldb; +using namespace lldb_dap; +using namespace lldb_dap::protocol; +using lldb_private::roundtripJSON; + +TEST(DAPTypesTest, SourceLLDBData) { + SourceLLDBData source_data; + source_data.persistenceData = + PersistenceData{"module_path123", "symbol_name456"}; + + llvm::Expected<SourceLLDBData> deserialized_data = roundtripJSON(source_data); + ASSERT_THAT_EXPECTED(deserialized_data, llvm::Succeeded()); + + EXPECT_EQ(source_data.persistenceData->module_path, + deserialized_data->persistenceData->module_path); + EXPECT_EQ(source_data.persistenceData->symbol_name, + deserialized_data->persistenceData->symbol_name); +} + +TEST(DAPTypesTest, DAPSymbol) { + Symbol symbol; + symbol.id = 42; + symbol.isDebug = true; + symbol.isExternal = false; + symbol.isSynthetic = true; + symbol.type = lldb::eSymbolTypeTrampoline; + symbol.fileAddress = 0x12345678; + symbol.loadAddress = 0x87654321; + symbol.size = 64; + symbol.name = "testSymbol"; + + llvm::Expected<Symbol> deserialized_symbol = roundtripJSON(symbol); + ASSERT_THAT_EXPECTED(deserialized_symbol, llvm::Succeeded()); + + EXPECT_EQ(symbol.id, deserialized_symbol->id); + EXPECT_EQ(symbol.isDebug, deserialized_symbol->isDebug); + EXPECT_EQ(symbol.isExternal, deserialized_symbol->isExternal); + EXPECT_EQ(symbol.isSynthetic, deserialized_symbol->isSynthetic); + EXPECT_EQ(symbol.type, deserialized_symbol->type); + EXPECT_EQ(symbol.fileAddress, deserialized_symbol->fileAddress); + EXPECT_EQ(symbol.loadAddress, deserialized_symbol->loadAddress); + EXPECT_EQ(symbol.size, deserialized_symbol->size); + EXPECT_EQ(symbol.name, deserialized_symbol->name); +} >From 4d1cc346824a530d53682f423e5046d55fccd109 Mon Sep 17 00:00:00 2001 From: Ely Ronnen <elyron...@gmail.com> Date: Fri, 22 Aug 2025 21:32:31 +0200 Subject: [PATCH 2/3] fix null dereference when symbol doesn't have a name --- lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp index 6e50ee122c41f..4a9d256cfa975 100644 --- a/lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ModuleSymbolsRequestHandler.cpp @@ -79,7 +79,8 @@ ModuleSymbolsRequestHandler::Run(const ModuleSymbolsArguments &args) const { } dap_symbol.size = symbol.GetSize(); - dap_symbol.name = symbol.GetName(); + if (const char *symbol_name = symbol.GetName()) + dap_symbol.name = symbol_name; symbols.push_back(std::move(dap_symbol)); } >From 741ad6ae27b5158366ff101fc56e8edc99d1aff7 Mon Sep 17 00:00:00 2001 From: Ely Ronnen <elyron...@gmail.com> Date: Fri, 22 Aug 2025 21:48:44 +0200 Subject: [PATCH 3/3] skipIfWindows for test_moduleSymbols --- .../API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py b/lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py index b99edf369a7fd..2336b9f2a5a1a 100644 --- a/lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py +++ b/lldb/test/API/tools/lldb-dap/moduleSymbols/TestDAP_moduleSymbols.py @@ -3,9 +3,12 @@ """ import lldbdap_testcase +from lldbsuite.test.decorators import * class TestDAP_moduleSymbols(lldbdap_testcase.DAPTestCaseBase): + # On windows LLDB doesn't recognize symbols in a.out. + @skipIfWindows def test_moduleSymbols(self): """ Test that the moduleSymbols request returns correct symbols from the module. _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits