diff options
Diffstat (limited to 'lldb/tools')
53 files changed, 2114 insertions, 998 deletions
diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp index 0cd48d9..4d5afcf 100644 --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -1101,7 +1101,7 @@ DNBGetLibrariesInfoForAddresses(nub_process_t pid, JSONGenerator::ObjectSP DNBGetSharedCacheInfo(nub_process_t pid) { MachProcessSP procSP; if (GetProcessSP(pid, procSP)) { - return procSP->GetSharedCacheInfo(pid); + return procSP->GetInferiorSharedCacheInfo(pid); } return JSONGenerator::ObjectSP(); } diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h index 56bc9d6..67b27b9 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.h +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -283,7 +283,10 @@ public: JSONGenerator::ObjectSP GetAllLoadedLibrariesInfos(nub_process_t pid, bool fetch_report_load_commands); - JSONGenerator::ObjectSP GetSharedCacheInfo(nub_process_t pid); + bool GetDebugserverSharedCacheInfo(uuid_t &uuid, + std::string &shared_cache_path); + bool GetInferiorSharedCacheFilepath(std::string &inferior_sc_path); + JSONGenerator::ObjectSP GetInferiorSharedCacheInfo(nub_process_t pid); nub_size_t GetNumThreads() const; nub_thread_t GetThreadAtIndex(nub_size_t thread_idx) const; @@ -474,6 +477,14 @@ private: void *(*m_dyld_process_info_create)(task_t task, uint64_t timestamp, kern_return_t *kernelError); + void *(*m_dyld_process_create_for_task)(task_read_t task, kern_return_t *kr); + void *(*m_dyld_process_snapshot_create_for_process)(void *process, + kern_return_t *kr); + void *(*m_dyld_process_snapshot_get_shared_cache)(void *snapshot); + void (*m_dyld_shared_cache_for_each_file)( + void *cache, void (^block)(const char *file_path)); + void (*m_dyld_process_snapshot_dispose)(void *snapshot); + void (*m_dyld_process_dispose)(void *process); void (*m_dyld_process_info_for_each_image)( void *info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char *path)); @@ -481,6 +492,7 @@ private: void (*m_dyld_process_info_get_cache)(void *info, void *cacheInfo); uint32_t (*m_dyld_process_info_get_platform)(void *info); void (*m_dyld_process_info_get_state)(void *info, void *stateInfo); + const char *(*m_dyld_shared_cache_file_path)(); }; #endif // LLDB_TOOLS_DEBUGSERVER_SOURCE_MACOSX_MACHPROCESS_H diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm index 3afaaa2..10ed804 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm @@ -534,13 +534,35 @@ MachProcess::MachProcess() m_image_infos_baton(NULL), m_sent_interrupt_signo(0), m_auto_resume_signo(0), m_did_exec(false), m_dyld_process_info_create(nullptr), + m_dyld_process_create_for_task(nullptr), + m_dyld_process_snapshot_create_for_process(nullptr), + m_dyld_process_snapshot_get_shared_cache(nullptr), + m_dyld_shared_cache_for_each_file(nullptr), + m_dyld_process_snapshot_dispose(nullptr), m_dyld_process_dispose(nullptr), m_dyld_process_info_for_each_image(nullptr), m_dyld_process_info_release(nullptr), m_dyld_process_info_get_cache(nullptr), - m_dyld_process_info_get_state(nullptr) { + m_dyld_process_info_get_state(nullptr), + m_dyld_shared_cache_file_path(nullptr) { m_dyld_process_info_create = (void *(*)(task_t task, uint64_t timestamp, kern_return_t * kernelError)) dlsym(RTLD_DEFAULT, "_dyld_process_info_create"); + + m_dyld_process_create_for_task = + (void *(*)(task_read_t, kern_return_t *))dlsym( + RTLD_DEFAULT, "dyld_process_create_for_task"); + m_dyld_process_snapshot_create_for_process = + (void *(*)(void *, kern_return_t *))dlsym( + RTLD_DEFAULT, "dyld_process_snapshot_create_for_process"); + m_dyld_process_snapshot_get_shared_cache = (void *(*)(void *))dlsym( + RTLD_DEFAULT, "dyld_process_snapshot_get_shared_cache"); + m_dyld_shared_cache_for_each_file = + (void (*)(void *, void (^)(const char *)))dlsym( + RTLD_DEFAULT, "dyld_shared_cache_for_each_file"); + m_dyld_process_snapshot_dispose = + (void (*)(void *))dlsym(RTLD_DEFAULT, "dyld_process_snapshot_dispose"); + m_dyld_process_dispose = + (void (*)(void *))dlsym(RTLD_DEFAULT, "dyld_process_dispose"); m_dyld_process_info_for_each_image = (void (*)(void *info, void (^)(uint64_t machHeaderAddress, const uuid_t uuid, const char *path))) @@ -553,6 +575,8 @@ MachProcess::MachProcess() RTLD_DEFAULT, "_dyld_process_info_get_platform"); m_dyld_process_info_get_state = (void (*)(void *info, void *stateInfo))dlsym( RTLD_DEFAULT, "_dyld_process_info_get_state"); + m_dyld_shared_cache_file_path = + (const char *(*)())dlsym(RTLD_DEFAULT, "dyld_shared_cache_file_path"); DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__); } @@ -1179,13 +1203,82 @@ JSONGenerator::ObjectSP MachProcess::GetLibrariesInfoForAddresses( /* report_load_commands = */ true); } -// From dyld's internal podyld_process_info.h: +bool MachProcess::GetDebugserverSharedCacheInfo( + uuid_t &uuid, std::string &shared_cache_path) { + uuid_clear(uuid); + shared_cache_path.clear(); + + if (m_dyld_process_info_create && m_dyld_process_info_get_cache) { + kern_return_t kern_ret; + dyld_process_info info = + m_dyld_process_info_create(mach_task_self(), 0, &kern_ret); + if (info) { + struct dyld_process_cache_info shared_cache_info; + m_dyld_process_info_get_cache(info, &shared_cache_info); + uuid_copy(uuid, shared_cache_info.cacheUUID); + m_dyld_process_info_release(info); + } + } + if (m_dyld_shared_cache_file_path) { + const char *cache_path = m_dyld_shared_cache_file_path(); + if (cache_path) + shared_cache_path = cache_path; + } + if (!uuid_is_null(uuid)) + return true; + return false; +} + +bool MachProcess::GetInferiorSharedCacheFilepath( + std::string &inferior_sc_path) { + inferior_sc_path.clear(); + + if (!m_dyld_process_create_for_task || + !m_dyld_process_snapshot_create_for_process || + !m_dyld_process_snapshot_get_shared_cache || + !m_dyld_shared_cache_for_each_file || !m_dyld_process_snapshot_dispose || + !m_dyld_process_dispose) + return false; + + __block std::string sc_path; + kern_return_t kr; + void *process = m_dyld_process_create_for_task(m_task.TaskPort(), &kr); + if (kr != KERN_SUCCESS) + return false; + void *snapshot = m_dyld_process_snapshot_create_for_process(process, &kr); + if (kr != KERN_SUCCESS) + return false; + void *cache = m_dyld_process_snapshot_get_shared_cache(snapshot); + + // The shared cache is a collection of files on disk, this callback + // will iterate over all of them. + // The first filepath provided is the base filename of the cache. + __block bool done = false; + m_dyld_shared_cache_for_each_file(cache, ^(const char *path) { + if (done) { + return; + } + done = true; + sc_path = path; + }); + m_dyld_process_snapshot_dispose(snapshot); + m_dyld_process_dispose(process); + + inferior_sc_path = sc_path; + if (!sc_path.empty()) + return true; + return false; +} + +// From dyld's internal dyld_process_info.h: -JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) { +JSONGenerator::ObjectSP +MachProcess::GetInferiorSharedCacheInfo(nub_process_t pid) { JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary()); - kern_return_t kern_ret; + uuid_t inferior_sc_uuid; if (m_dyld_process_info_create && m_dyld_process_info_get_cache) { + kern_return_t kern_ret; dyld_process_info info = m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret); if (info) { @@ -1197,6 +1290,7 @@ JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) { uuid_string_t uuidstr; uuid_unparse_upper(shared_cache_info.cacheUUID, uuidstr); + uuid_copy(inferior_sc_uuid, shared_cache_info.cacheUUID); reply_sp->AddStringItem("shared_cache_uuid", uuidstr); reply_sp->AddBooleanItem("no_shared_cache", shared_cache_info.noCache); @@ -1206,6 +1300,29 @@ JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) { m_dyld_process_info_release(info); } } + + // If debugserver and the inferior are have the same cache UUID, + // use the simple call to get the filepath to debugserver's shared + // cache, return that. + uuid_t debugserver_sc_uuid; + std::string debugserver_sc_path; + bool found_sc_filepath = false; + if (GetDebugserverSharedCacheInfo(debugserver_sc_uuid, debugserver_sc_path)) { + if (uuid_compare(inferior_sc_uuid, debugserver_sc_uuid) == 0 && + !debugserver_sc_path.empty()) { + reply_sp->AddStringItem("shared_cache_path", debugserver_sc_path); + found_sc_filepath = true; + } + } + + // Use SPI that are only available on newer OSes to fetch the + // filepath of the shared cache of the inferior, if available. + if (!found_sc_filepath) { + std::string inferior_sc_path; + if (GetInferiorSharedCacheFilepath(inferior_sc_path)) + reply_sp->AddStringItem("shared_cache_path", inferior_sc_path); + } + return reply_sp; } @@ -1739,7 +1856,7 @@ bool MachProcess::Detach() { ReplyToAllExceptions(); } - m_task.ShutDownExcecptionThread(); + m_task.ShutDownExceptionThread(); // Detach from our process errno = 0; @@ -2853,12 +2970,6 @@ pid_t MachProcess::AttachForDebug( if (err.Success()) { m_flags |= eMachProcessFlagsAttached; - // Sleep a bit to let the exception get received and set our process - // status - // to stopped. - ::usleep(250000); - DNBLog("[LaunchAttach] (%d) Done napping after ptrace(PT_ATTACHEXC)'ing", - getpid()); DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", pid); return m_pid; } else { diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.h b/lldb/tools/debugserver/source/MacOSX/MachTask.h index c4a20b8..40fdbe9 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.h +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.h @@ -68,7 +68,7 @@ public: bool ExceptionPortIsValid() const; kern_return_t SaveExceptionPortInfo(); kern_return_t RestoreExceptionPortInfo(); - kern_return_t ShutDownExcecptionThread(); + void ShutDownExceptionThread(); bool StartExceptionThread( const RNBContext::IgnoredExceptions &ignored_exceptions, DNBError &err); @@ -81,9 +81,7 @@ public: void TaskPortChanged(task_t task); task_t TaskPort() const { return m_task; } task_t TaskPortForProcessID(DNBError &err, bool force = false); - static task_t TaskPortForProcessID(pid_t pid, DNBError &err, - uint32_t num_retries = 10, - uint32_t usec_interval = 10000); + static task_t TaskPortForProcessID(pid_t pid, DNBError &err); MachProcess *Process() { return m_process; } const MachProcess *Process() const { return m_process; } diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.mm b/lldb/tools/debugserver/source/MacOSX/MachTask.mm index 21156fe..22ad0c4 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.mm @@ -145,10 +145,8 @@ bool MachTask::ExceptionPortIsValid() const { //---------------------------------------------------------------------- void MachTask::Clear() { // Do any cleanup needed for this task - if (m_exception_thread) - ShutDownExcecptionThread(); + ShutDownExceptionThread(); m_task = TASK_NULL; - m_exception_thread = 0; m_exception_port = MACH_PORT_NULL; m_exec_will_be_suspended = false; m_do_double_resume = false; @@ -523,14 +521,15 @@ task_t MachTask::TaskPortForProcessID(DNBError &err, bool force) { //---------------------------------------------------------------------- // MachTask::TaskPortForProcessID //---------------------------------------------------------------------- -task_t MachTask::TaskPortForProcessID(pid_t pid, DNBError &err, - uint32_t num_retries, - uint32_t usec_interval) { +task_t MachTask::TaskPortForProcessID(pid_t pid, DNBError &err) { + static constexpr uint32_t k_num_retries = 10; + static constexpr uint32_t k_usec_delay = 10000; + if (pid != INVALID_NUB_PROCESS) { DNBError err; mach_port_t task_self = mach_task_self(); task_t task = TASK_NULL; - for (uint32_t i = 0; i < num_retries; i++) { + for (uint32_t i = 0; i < k_num_retries; i++) { DNBLog("[LaunchAttach] (%d) about to task_for_pid(%d)", getpid(), pid); err = ::task_for_pid(task_self, pid, &task); @@ -557,7 +556,7 @@ task_t MachTask::TaskPortForProcessID(pid_t pid, DNBError &err, } // Sleep a bit and try again - ::usleep(usec_interval); + ::usleep(k_usec_delay); } } return TASK_NULL; @@ -684,8 +683,11 @@ bool MachTask::StartExceptionThread( return false; } -kern_return_t MachTask::ShutDownExcecptionThread() { +void MachTask::ShutDownExceptionThread() { DNBError err; + + if (!m_exception_thread) + return; err = RestoreExceptionPortInfo(); @@ -701,6 +703,8 @@ kern_return_t MachTask::ShutDownExcecptionThread() { if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread); + + m_exception_thread = nullptr; // Deallocate our exception port that we used to track our child process mach_port_t task_self = mach_task_self(); @@ -712,7 +716,7 @@ kern_return_t MachTask::ShutDownExcecptionThread() { m_exec_will_be_suspended = false; m_do_double_resume = false; - return err.Status(); + return; } void *MachTask::ExceptionThread(void *arg) { diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp index 9d0d60f..c8dce75a 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp @@ -14,6 +14,12 @@ #include "DNBLog.h" #include <cassert> #include <mach/mach_vm.h> +#include <mach/vm_statistics.h> + +// From <mach/vm_statistics.h>, but not on older OSs. +#ifndef VM_MEMORY_SANITIZER +#define VM_MEMORY_SANITIZER 99 +#endif MachVMRegion::MachVMRegion(task_t task) : m_task(task), m_addr(INVALID_NUB_ADDRESS), m_err(), diff --git a/lldb/tools/driver/CMakeLists.txt b/lldb/tools/driver/CMakeLists.txt index 67956af..efe5150 100644 --- a/lldb/tools/driver/CMakeLists.txt +++ b/lldb/tools/driver/CMakeLists.txt @@ -37,6 +37,9 @@ add_dependencies(lldb if(DEFINED LLDB_PYTHON_DLL_RELATIVE_PATH) target_compile_definitions(lldb PRIVATE LLDB_PYTHON_DLL_RELATIVE_PATH="${LLDB_PYTHON_DLL_RELATIVE_PATH}") endif() +if(DEFINED LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME) + target_compile_definitions(lldb PRIVATE LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME="${LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME}") +endif() if(LLDB_BUILD_FRAMEWORK) # In the build-tree, we know the exact path to the framework directory. diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index 733331f..4810771 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -433,7 +433,8 @@ SBError Driver::ProcessArgs(const opt::InputArgList &args, bool &exiting) { return error; } -#if defined(_WIN32) && defined(LLDB_PYTHON_DLL_RELATIVE_PATH) +#ifdef _WIN32 +#ifdef LLDB_PYTHON_DLL_RELATIVE_PATH /// Returns the full path to the lldb.exe executable. inline std::wstring GetPathToExecutableW() { // Iterate until we reach the Windows API maximum path length (32,767). @@ -447,30 +448,72 @@ inline std::wstring GetPathToExecutableW() { return L""; } -/// Resolve the full path of the directory defined by +/// \brief Resolve the full path of the directory defined by /// LLDB_PYTHON_DLL_RELATIVE_PATH. If it exists, add it to the list of DLL /// search directories. -void AddPythonDLLToSearchPath() { +/// \return `true` if the library was added to the search path. +/// `false` otherwise. +bool AddPythonDLLToSearchPath() { std::wstring modulePath = GetPathToExecutableW(); - if (modulePath.empty()) { - llvm::errs() << "error: unable to find python.dll." << '\n'; - return; - } + if (modulePath.empty()) + return false; SmallVector<char, MAX_PATH> utf8Path; if (sys::windows::UTF16ToUTF8(modulePath.c_str(), modulePath.length(), utf8Path)) - return; + return false; sys::path::remove_filename(utf8Path); sys::path::append(utf8Path, LLDB_PYTHON_DLL_RELATIVE_PATH); sys::fs::make_absolute(utf8Path); SmallVector<wchar_t, 1> widePath; if (sys::windows::widenPath(utf8Path.data(), widePath)) - return; + return false; if (sys::fs::exists(utf8Path)) - SetDllDirectoryW(widePath.data()); + return SetDllDirectoryW(widePath.data()); + return false; +} +#endif + +#ifdef LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME +/// Returns true if `python3x.dll` can be loaded. +bool IsPythonDLLInPath() { +#define WIDEN2(x) L##x +#define WIDEN(x) WIDEN2(x) + HMODULE h = LoadLibraryW(WIDEN(LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME)); + if (!h) + return false; + FreeLibrary(h); + return true; +#undef WIDEN2 +#undef WIDEN +} +#endif + +/// Try to setup the DLL search path for the Python Runtime Library +/// (python3xx.dll). +/// +/// If `LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME` is set, we first check if +/// python3xx.dll is in the search path. If it's not, we try to add it and +/// check for it a second time. +/// If only `LLDB_PYTHON_DLL_RELATIVE_PATH` is set, we try to add python3xx.dll +/// to the search path python.dll is already in the search path or not. +void SetupPythonRuntimeLibrary() { +#ifdef LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME + if (IsPythonDLLInPath()) + return; +#ifdef LLDB_PYTHON_DLL_RELATIVE_PATH + if (AddPythonDLLToSearchPath() && IsPythonDLLInPath()) + return; +#endif + WithColor::error() << "unable to find '" + << LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME << "'.\n"; + return; +#elif defined(LLDB_PYTHON_DLL_RELATIVE_PATH) + if (!AddPythonDLLToSearchPath()) + WithColor::error() << "unable to find the Python runtime library.\n"; +#endif } #endif @@ -776,8 +819,8 @@ int main(int argc, char const *argv[]) { "~/Library/Logs/DiagnosticReports/.\n"); #endif -#if defined(_WIN32) && defined(LLDB_PYTHON_DLL_RELATIVE_PATH) - AddPythonDLLToSearchPath(); +#ifdef _WIN32 + SetupPythonRuntimeLibrary(); #endif // Parse arguments. diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 7db334c..237c304 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -1,17 +1,16 @@ # We need to include the llvm components we depend on manually, as liblldb does # not re-export those. set(LLVM_LINK_COMPONENTS Support) -set(LLVM_TARGET_DEFINITIONS Options.td) -tablegen(LLVM Options.inc -gen-opt-parser-defs) -add_public_tablegen_target(LLDBDAPOptionsTableGen) add_lldb_library(lldbDAP Breakpoint.cpp BreakpointBase.cpp + ClientLauncher.cpp CommandPlugins.cpp DAP.cpp DAPError.cpp DAPLog.cpp + DAPSessionManager.cpp EventHelper.cpp ExceptionBreakpoint.cpp FifoFiles.cpp diff --git a/lldb/tools/lldb-dap/ClientLauncher.cpp b/lldb/tools/lldb-dap/ClientLauncher.cpp new file mode 100644 index 0000000..4cac1d6 --- /dev/null +++ b/lldb/tools/lldb-dap/ClientLauncher.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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 "ClientLauncher.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/FormatVariadic.h" + +using namespace lldb_dap; + +std::optional<ClientLauncher::Client> +ClientLauncher::GetClientFrom(llvm::StringRef str) { + return llvm::StringSwitch<std::optional<ClientLauncher::Client>>(str.lower()) + .Case("vscode", ClientLauncher::VSCode) + .Case("vscode-url", ClientLauncher::VSCodeURL) + .Default(std::nullopt); +} + +std::unique_ptr<ClientLauncher> +ClientLauncher::GetLauncher(ClientLauncher::Client client) { + switch (client) { + case ClientLauncher::VSCode: + return std::make_unique<VSCodeLauncher>(); + case ClientLauncher::VSCodeURL: + return std::make_unique<VSCodeURLPrinter>(); + } + return nullptr; +} + +std::string VSCodeLauncher::URLEncode(llvm::StringRef str) { + std::string out; + llvm::raw_string_ostream os(out); + for (char c : str) { + if (std::isalnum(c) || llvm::StringRef("-_.~").contains(c)) + os << c; + else + os << '%' << llvm::utohexstr(c, false, 2); + } + return os.str(); +} + +std::string +VSCodeLauncher::GetLaunchURL(const std::vector<llvm::StringRef> args) const { + assert(!args.empty() && "empty launch args"); + + std::vector<std::string> encoded_launch_args; + for (llvm::StringRef arg : args) + encoded_launch_args.push_back(URLEncode(arg)); + + const std::string args_str = llvm::join(encoded_launch_args, "&args="); + return llvm::formatv( + "vscode://llvm-vs-code-extensions.lldb-dap/start?program={0}", + args_str) + .str(); +} + +llvm::Error VSCodeLauncher::Launch(const std::vector<llvm::StringRef> args) { + const std::string launch_url = GetLaunchURL(args); + const std::string command = + llvm::formatv("code --open-url {0}", launch_url).str(); + + std::system(command.c_str()); + return llvm::Error::success(); +} + +llvm::Error VSCodeURLPrinter::Launch(const std::vector<llvm::StringRef> args) { + llvm::outs() << GetLaunchURL(args) << '\n'; + return llvm::Error::success(); +} diff --git a/lldb/tools/lldb-dap/ClientLauncher.h b/lldb/tools/lldb-dap/ClientLauncher.h new file mode 100644 index 0000000..780b178 --- /dev/null +++ b/lldb/tools/lldb-dap/ClientLauncher.h @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H +#define LLDB_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <vector> + +namespace lldb_dap { + +class ClientLauncher { +public: + enum Client { + VSCode, + VSCodeURL, + }; + + virtual ~ClientLauncher() = default; + virtual llvm::Error Launch(const std::vector<llvm::StringRef> args) = 0; + + static std::optional<Client> GetClientFrom(llvm::StringRef str); + static std::unique_ptr<ClientLauncher> GetLauncher(Client client); +}; + +class VSCodeLauncher : public ClientLauncher { +public: + using ClientLauncher::ClientLauncher; + + llvm::Error Launch(const std::vector<llvm::StringRef> args) override; + + std::string GetLaunchURL(const std::vector<llvm::StringRef> args) const; + static std::string URLEncode(llvm::StringRef str); +}; + +class VSCodeURLPrinter : public VSCodeLauncher { + using VSCodeLauncher::VSCodeLauncher; + + llvm::Error Launch(const std::vector<llvm::StringRef> args) override; +}; + +} // namespace lldb_dap + +#endif diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index f009a90..58c9922 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "CommandPlugins.h" #include "DAPLog.h" #include "EventHelper.h" #include "ExceptionBreakpoint.h" @@ -121,7 +122,7 @@ static std::string capitalize(llvm::StringRef str) { llvm::StringRef DAP::debug_adapter_path = ""; -DAP::DAP(Log *log, const ReplMode default_repl_mode, +DAP::DAP(Log &log, const ReplMode default_repl_mode, std::vector<std::string> pre_init_commands, bool no_lldbinit, llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop) : log(log), transport(transport), broadcaster("lldb-dap"), @@ -133,8 +134,6 @@ DAP::DAP(Log *log, const ReplMode default_repl_mode, RegisterRequests(); } -DAP::~DAP() = default; - void DAP::PopulateExceptionBreakpoints() { if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) { exception_breakpoints.emplace_back(*this, "cpp_catch", "C++ Catch", @@ -242,10 +241,12 @@ llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) { } void DAP::StopEventHandlers() { - if (event_thread.joinable()) { - broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); - event_thread.join(); - } + event_thread_sp.reset(); + + // Clean up expired event threads from the session manager. + DAPSessionManager::GetInstance().ReleaseExpiredEventThreads(); + + // Still handle the progress thread normally since it's per-DAP instance. if (progress_event_thread.joinable()) { broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread); progress_event_thread.join(); @@ -260,8 +261,7 @@ void DAP::SendJSON(const llvm::json::Value &json) { Message message; llvm::json::Path::Root root; if (!fromJSON(json, message, root)) { - DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}", - m_client_name); + DAP_LOG_ERROR(log, root.getError(), "encoding failed: {0}"); return; } Send(message); @@ -271,23 +271,24 @@ Id DAP::Send(const Message &message) { std::lock_guard<std::mutex> guard(call_mutex); Message msg = std::visit( [this](auto &&msg) -> Message { - if (msg.seq == kCalculateSeq) - msg.seq = seq++; + if (msg.seq == kCalculateSeq) { + seq++; + msg.seq = seq; + } + assert(msg.seq > 0 && "message sequence must be greater than zero."); return msg; }, Message(message)); if (const protocol::Event *event = std::get_if<protocol::Event>(&msg)) { if (llvm::Error err = transport.Send(*event)) - DAP_LOG_ERROR(log, std::move(err), "({0}) sending event failed", - m_client_name); + DAP_LOG_ERROR(log, std::move(err), "sending event failed: {0}"); return event->seq; } if (const Request *req = std::get_if<Request>(&msg)) { if (llvm::Error err = transport.Send(*req)) - DAP_LOG_ERROR(log, std::move(err), "({0}) sending request failed", - m_client_name); + DAP_LOG_ERROR(log, std::move(err), "sending request failed: {0}"); return req->seq; } @@ -307,8 +308,7 @@ Id DAP::Send(const Message &message) { }) : transport.Send(*resp); if (err) - DAP_LOG_ERROR(log, std::move(err), "({0}) sending response failed", - m_client_name); + DAP_LOG_ERROR(log, std::move(err), "sending response failed: {0}"); return resp->seq; } @@ -657,18 +657,20 @@ std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) { if (!frame.IsValid()) return std::nullopt; - const lldb::SBAddress frame_pc = frame.GetPCAddress(); - if (DisplayAssemblySource(debugger, frame_pc)) + const lldb::SBLineEntry frame_line_entry = frame.GetLineEntry(); + if (DisplayAssemblySource(debugger, frame_line_entry)) { + const lldb::SBAddress frame_pc = frame.GetPCAddress(); return ResolveAssemblySource(frame_pc); + } - return CreateSource(frame.GetLineEntry().GetFileSpec()); + return CreateSource(frame_line_entry.GetFileSpec()); } std::optional<protocol::Source> DAP::ResolveSource(lldb::SBAddress address) { - if (DisplayAssemblySource(debugger, address)) + lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address); + if (DisplayAssemblySource(debugger, line_entry)) return ResolveAssemblySource(address); - lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address); if (!line_entry.IsValid()) return std::nullopt; @@ -814,7 +816,8 @@ void DAP::SetTarget(const lldb::SBTarget target) { lldb::SBTarget::eBroadcastBitModulesLoaded | lldb::SBTarget::eBroadcastBitModulesUnloaded | lldb::SBTarget::eBroadcastBitSymbolsLoaded | - lldb::SBTarget::eBroadcastBitSymbolsChanged); + lldb::SBTarget::eBroadcastBitSymbolsChanged | + lldb::SBTarget::eBroadcastBitNewTargetCreated); listener.StartListeningForEvents(this->broadcaster, eBroadcastBitStopEventThread); } @@ -848,8 +851,7 @@ bool DAP::HandleObject(const Message &M) { dispatcher.Set("error", llvm::Twine("unhandled-command:" + req->command).str()); - DAP_LOG(log, "({0}) error: unhandled command '{1}'", m_client_name, - req->command); + DAP_LOG(log, "error: unhandled command '{0}'", req->command); return false; // Fail } @@ -995,35 +997,33 @@ void DAP::Received(const protocol::Request &request) { // effort attempt to interrupt. std::lock_guard<std::mutex> guard(m_active_request_mutex); if (m_active_request && cancel_args->requestId == m_active_request->seq) { - DAP_LOG(log, "({0}) interrupting inflight request (command={1} seq={2})", - m_client_name, m_active_request->command, m_active_request->seq); + DAP_LOG(log, "interrupting inflight request (command={0} seq={1})", + m_active_request->command, m_active_request->seq); debugger.RequestInterrupt(); } } std::lock_guard<std::mutex> guard(m_queue_mutex); - DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name, - request.command, request.seq); + DAP_LOG(log, "queued (command={0} seq={1})", request.command, request.seq); m_queue.push_back(request); m_queue_cv.notify_one(); } void DAP::Received(const protocol::Response &response) { std::lock_guard<std::mutex> guard(m_queue_mutex); - DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name, - response.command, response.request_seq); + DAP_LOG(log, "queued (command={0} seq={1})", response.command, + response.request_seq); m_queue.push_back(response); m_queue_cv.notify_one(); } void DAP::OnError(llvm::Error error) { - DAP_LOG_ERROR(log, std::move(error), "({1}) received error: {0}", - m_client_name); + DAP_LOG_ERROR(log, std::move(error), "transport error: {0}"); TerminateLoop(/*failed=*/true); } void DAP::OnClosed() { - DAP_LOG(log, "({0}) received EOF", m_client_name); + DAP_LOG(log, "transport closed"); TerminateLoop(); } @@ -1049,16 +1049,14 @@ void DAP::TransportHandler() { auto handle = transport.RegisterMessageHandler(m_loop, *this); if (!handle) { DAP_LOG_ERROR(log, handle.takeError(), - "({1}) registering message handler failed: {0}", - m_client_name); + "registering message handler failed: {0}"); std::lock_guard<std::mutex> guard(m_queue_mutex); m_error_occurred = true; return; } if (Status status = m_loop.Run(); status.Fail()) { - DAP_LOG_ERROR(log, status.takeError(), "({1}) MainLoop run failed: {0}", - m_client_name); + DAP_LOG_ERROR(log, status.takeError(), "MainLoop run failed: {0}"); std::lock_guard<std::mutex> guard(m_queue_mutex); m_error_occurred = true; return; @@ -1301,13 +1299,99 @@ protocol::Capabilities DAP::GetCustomCapabilities() { } void DAP::StartEventThread() { - event_thread = std::thread(&DAP::EventThread, this); + // Get event thread for this debugger (creates it if it doesn't exist). + event_thread_sp = DAPSessionManager::GetInstance().GetEventThreadForDebugger( + debugger, this); } void DAP::StartProgressEventThread() { progress_event_thread = std::thread(&DAP::ProgressEventThread, this); } +void DAP::StartEventThreads() { + if (clientFeatures.contains(eClientFeatureProgressReporting)) + StartProgressEventThread(); + + StartEventThread(); +} + +llvm::Error DAP::InitializeDebugger(int debugger_id, + lldb::user_id_t target_id) { + // Find the existing debugger by ID + debugger = lldb::SBDebugger::FindDebuggerWithID(debugger_id); + if (!debugger.IsValid()) { + return llvm::createStringError( + "Unable to find existing debugger for debugger ID"); + } + + // Find the target within the debugger by its globally unique ID + lldb::SBTarget target = debugger.FindTargetByGloballyUniqueID(target_id); + if (!target.IsValid()) { + return llvm::createStringError( + "Unable to find existing target for target ID"); + } + + // Set the target for this DAP session. + SetTarget(target); + StartEventThreads(); + return llvm::Error::success(); +} + +llvm::Error DAP::InitializeDebugger() { + debugger = lldb::SBDebugger::Create(/*argument_name=*/false); + + // Configure input/output/error file descriptors. + debugger.SetInputFile(in); + target = debugger.GetDummyTarget(); + + llvm::Expected<int> out_fd = out.GetWriteFileDescriptor(); + if (!out_fd) + return out_fd.takeError(); + debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); + + llvm::Expected<int> err_fd = err.GetWriteFileDescriptor(); + if (!err_fd) + return err_fd.takeError(); + debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); + + // The sourceInitFile option is not part of the DAP specification. It is an + // extension used by the test suite to prevent sourcing `.lldbinit` and + // changing its behavior. The CLI flag --no-lldbinit takes precedence over + // the DAP parameter. + bool should_source_init_files = !no_lldbinit && sourceInitFile; + if (should_source_init_files) { + debugger.SkipLLDBInitFiles(false); + debugger.SkipAppInitFiles(false); + lldb::SBCommandReturnObject init; + auto interp = debugger.GetCommandInterpreter(); + interp.SourceInitFileInGlobalDirectory(init); + interp.SourceInitFileInHomeDirectory(init); + } + + // Run initialization commands. + if (llvm::Error err = RunPreInitCommands()) + return err; + + auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand( + "lldb-dap", "Commands for managing lldb-dap."); + + if (clientFeatures.contains(eClientFeatureStartDebuggingRequest)) { + cmd.AddCommand( + "start-debugging", new StartDebuggingCommand(*this), + "Sends a startDebugging request from the debug adapter to the client " + "to start a child debug session of the same type as the caller."); + } + + cmd.AddCommand( + "repl-mode", new ReplModeCommand(*this), + "Get or set the repl behavior of lldb-dap evaluation requests."); + cmd.AddCommand("send-event", new SendEventCommand(*this), + "Sends an DAP event to the client."); + + StartEventThreads(); + return llvm::Error::success(); +} + void DAP::ProgressEventThread() { lldb::SBListener listener("lldb-dap.progress.listener"); debugger.GetBroadcaster().AddListener( @@ -1317,7 +1401,7 @@ void DAP::ProgressEventThread() { lldb::SBEvent event; bool done = false; while (!done) { - if (listener.WaitForEvent(1, event)) { + if (listener.WaitForEvent(UINT32_MAX, event)) { const auto event_mask = event.GetType(); if (event.BroadcasterMatchesRef(broadcaster)) { if (event_mask & eBroadcastBitStopProgressThread) { @@ -1368,195 +1452,6 @@ void DAP::ProgressEventThread() { } } -// All events from the debugger, target, process, thread and frames are -// received in this function that runs in its own thread. We are using a -// "FILE *" to output packets back to VS Code and they have mutexes in them -// them prevent multiple threads from writing simultaneously so no locking -// is required. -void DAP::EventThread() { - llvm::set_thread_name("lldb.DAP.client." + m_client_name + ".event_handler"); - lldb::SBEvent event; - lldb::SBListener listener = debugger.GetListener(); - broadcaster.AddListener(listener, eBroadcastBitStopEventThread); - debugger.GetBroadcaster().AddListener( - listener, lldb::eBroadcastBitError | lldb::eBroadcastBitWarning); - - // listen for thread events. - listener.StartListeningForEventClass( - debugger, lldb::SBThread::GetBroadcasterClassName(), - lldb::SBThread::eBroadcastBitStackChanged); - - bool done = false; - while (!done) { - if (listener.WaitForEvent(1, event)) { - const auto event_mask = event.GetType(); - if (lldb::SBProcess::EventIsProcessEvent(event)) { - lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); - if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { - auto state = lldb::SBProcess::GetStateFromEvent(event); - switch (state) { - case lldb::eStateConnected: - case lldb::eStateDetached: - case lldb::eStateInvalid: - case lldb::eStateUnloaded: - break; - case lldb::eStateAttaching: - case lldb::eStateCrashed: - case lldb::eStateLaunching: - case lldb::eStateStopped: - case lldb::eStateSuspended: - // Only report a stopped event if the process was not - // automatically restarted. - if (!lldb::SBProcess::GetRestartedFromEvent(event)) { - SendStdOutStdErr(*this, process); - if (llvm::Error err = SendThreadStoppedEvent(*this)) - DAP_LOG_ERROR(log, std::move(err), - "({1}) reporting thread stopped: {0}", - m_client_name); - } - break; - case lldb::eStateRunning: - case lldb::eStateStepping: - WillContinue(); - SendContinuedEvent(*this); - break; - case lldb::eStateExited: - lldb::SBStream stream; - process.GetStatus(stream); - SendOutput(OutputType::Console, stream.GetData()); - - // When restarting, we can get an "exited" event for the process we - // just killed with the old PID, or even with no PID. In that case - // we don't have to terminate the session. - if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || - process.GetProcessID() == restarting_process_id) { - restarting_process_id = LLDB_INVALID_PROCESS_ID; - } else { - // Run any exit LLDB commands the user specified in the - // launch.json - RunExitCommands(); - SendProcessExitedEvent(*this, process); - SendTerminatedEvent(); - done = true; - } - break; - } - } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || - (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { - SendStdOutStdErr(*this, process); - } - } else if (lldb::SBTarget::EventIsTargetEvent(event)) { - if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded || - event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded || - event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || - event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) { - const uint32_t num_modules = - lldb::SBTarget::GetNumModulesFromEvent(event); - const bool remove_module = - event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded; - - // NOTE: Both mutexes must be acquired to prevent deadlock when - // handling `modules_request`, which also requires both locks. - lldb::SBMutex api_mutex = GetAPIMutex(); - const std::scoped_lock<lldb::SBMutex, std::mutex> guard( - api_mutex, modules_mutex); - for (uint32_t i = 0; i < num_modules; ++i) { - lldb::SBModule module = - lldb::SBTarget::GetModuleAtIndexFromEvent(i, event); - - std::optional<protocol::Module> p_module = - CreateModule(target, module, remove_module); - if (!p_module) - continue; - - llvm::StringRef module_id = p_module->id; - - const bool module_exists = modules.contains(module_id); - if (remove_module && module_exists) { - modules.erase(module_id); - Send(protocol::Event{ - "module", ModuleEventBody{std::move(p_module).value(), - ModuleEventBody::eReasonRemoved}}); - } else if (module_exists) { - Send(protocol::Event{ - "module", ModuleEventBody{std::move(p_module).value(), - ModuleEventBody::eReasonChanged}}); - } else if (!remove_module) { - modules.insert(module_id); - Send(protocol::Event{ - "module", ModuleEventBody{std::move(p_module).value(), - ModuleEventBody::eReasonNew}}); - } - } - } - } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { - if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { - auto event_type = - lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); - auto bp = Breakpoint( - *this, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); - // If the breakpoint was set through DAP, it will have the - // BreakpointBase::kDAPBreakpointLabel. Regardless of whether - // locations were added, removed, or resolved, the breakpoint isn't - // going away and the reason is always "changed". - if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || - event_type & lldb::eBreakpointEventTypeLocationsRemoved || - event_type & lldb::eBreakpointEventTypeLocationsResolved) && - bp.MatchesName(BreakpointBase::kDAPBreakpointLabel)) { - // As the DAP client already knows the path of this breakpoint, we - // don't need to send it back as part of the "changed" event. This - // avoids sending paths that should be source mapped. Note that - // CreateBreakpoint doesn't apply source mapping and certain - // implementation ignore the source part of this event anyway. - protocol::Breakpoint protocol_bp = bp.ToProtocolBreakpoint(); - - // "source" is not needed here, unless we add adapter data to be - // saved by the client. - if (protocol_bp.source && !protocol_bp.source->adapterData) - protocol_bp.source = std::nullopt; - - llvm::json::Object body; - body.try_emplace("breakpoint", protocol_bp); - body.try_emplace("reason", "changed"); - - llvm::json::Object bp_event = CreateEventObject("breakpoint"); - bp_event.try_emplace("body", std::move(body)); - - SendJSON(llvm::json::Value(std::move(bp_event))); - } - } - - } else if (lldb::SBThread::EventIsThreadEvent(event)) { - HandleThreadEvent(event); - } else if (event_mask & lldb::eBroadcastBitError || - event_mask & lldb::eBroadcastBitWarning) { - lldb::SBStructuredData data = - lldb::SBDebugger::GetDiagnosticFromEvent(event); - if (!data.IsValid()) - continue; - std::string type = GetStringValue(data.GetValueForKey("type")); - std::string message = GetStringValue(data.GetValueForKey("message")); - SendOutput(OutputType::Important, - llvm::formatv("{0}: {1}", type, message).str()); - } else if (event.BroadcasterMatchesRef(broadcaster)) { - if (event_mask & eBroadcastBitStopEventThread) { - done = true; - } - } - } - } -} - -void DAP::HandleThreadEvent(const lldb::SBEvent &event) { - uint32_t event_type = event.GetType(); - - if (event_type & lldb::SBThread::eBroadcastBitStackChanged) { - const lldb::SBThread evt_thread = lldb::SBThread::GetThreadFromEvent(event); - SendInvalidatedEvent(*this, {InvalidatedEventBody::eAreaStacks}, - evt_thread.GetThreadID()); - } -} - std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints( const protocol::Source &source, const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints) { diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index b4f111e..0113922 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -10,6 +10,7 @@ #define LLDB_TOOLS_LLDB_DAP_DAP_H #include "DAPForward.h" +#include "DAPSessionManager.h" #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "InstructionBreakpoint.h" @@ -32,11 +33,9 @@ #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" #include "lldb/Host/MainLoop.h" -#include "lldb/Utility/Status.h" #include "lldb/lldb-types.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -47,6 +46,7 @@ #include <condition_variable> #include <cstdint> #include <deque> +#include <map> #include <memory> #include <mutex> #include <optional> @@ -81,10 +81,12 @@ enum class ReplMode { Variable = 0, Command, Auto }; using DAPTransport = lldb_private::transport::JSONTransport<ProtocolDescriptor>; struct DAP final : public DAPTransport::MessageHandler { + friend class DAPSessionManager; + /// Path to the lldb-dap binary itself. static llvm::StringRef debug_adapter_path; - Log *log; + Log &log; DAPTransport &transport; lldb::SBFile in; OutputRedirector out; @@ -157,6 +159,11 @@ struct DAP final : public DAPTransport::MessageHandler { /// Whether to disable sourcing .lldbinit files. bool no_lldbinit; + /// Stores whether the initialize request specified a value for + /// lldbExtSourceInitFile. Used by the test suite to prevent sourcing + /// `.lldbinit` and changing its behavior. + bool sourceInitFile = true; + /// The initial thread list upon attaching. std::vector<protocol::Thread> initial_thread_list; @@ -185,12 +192,12 @@ struct DAP final : public DAPTransport::MessageHandler { /// Transport for this debug session. /// \param[in] loop /// Main loop associated with this instance. - DAP(Log *log, const ReplMode default_repl_mode, + DAP(Log &log, const ReplMode default_repl_mode, std::vector<std::string> pre_init_commands, bool no_lldbinit, llvm::StringRef client_name, DAPTransport &transport, lldb_private::MainLoop &loop); - ~DAP(); + ~DAP() override = default; /// DAP is not copyable. /// @{ @@ -408,9 +415,33 @@ struct DAP final : public DAPTransport::MessageHandler { lldb::SBMutex GetAPIMutex() const { return target.GetAPIMutex(); } + /// Get the client name for this DAP session. + llvm::StringRef GetClientName() const { return m_client_name; } + void StartEventThread(); void StartProgressEventThread(); + /// DAP debugger initialization functions. + /// @{ + + /// Perform complete DAP initialization for a new debugger. + llvm::Error InitializeDebugger(); + + /// Perform complete DAP initialization by reusing an existing debugger and + /// target. + /// + /// \param[in] debugger_id + /// The ID of the existing debugger to reuse. + /// + /// \param[in] target_id + /// The globally unique ID of the existing target to reuse. + llvm::Error InitializeDebugger(int debugger_id, lldb::user_id_t target_id); + + /// Start event handling threads based on client capabilities. + void StartEventThreads(); + + /// @} + /// Sets the given protocol `breakpoints` in the given `source`, while /// removing any existing breakpoints in the given source if they are not in /// `breakpoint`. @@ -453,11 +484,11 @@ private: /// Event threads. /// @{ - void EventThread(); - void HandleThreadEvent(const lldb::SBEvent &event); void ProgressEventThread(); - std::thread event_thread; + /// Event thread is a shared pointer in case we have a multiple + /// DAP instances sharing the same event thread. + std::shared_ptr<ManagedEventThread> event_thread_sp; std::thread progress_event_thread; /// @} diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h index 6620d5f..e7fbbf6 100644 --- a/lldb/tools/lldb-dap/DAPForward.h +++ b/lldb/tools/lldb-dap/DAPForward.h @@ -28,6 +28,7 @@ namespace lldb { class SBAttachInfo; class SBBreakpoint; class SBBreakpointLocation; +class SBBroadcaster; class SBCommandInterpreter; class SBCommandReturnObject; class SBCommunication; diff --git a/lldb/tools/lldb-dap/DAPLog.cpp b/lldb/tools/lldb-dap/DAPLog.cpp index f66784e..f3c8040 100644 --- a/lldb/tools/lldb-dap/DAPLog.cpp +++ b/lldb/tools/lldb-dap/DAPLog.cpp @@ -8,22 +8,27 @@ #include "DAPLog.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include <chrono> #include <mutex> -#include <system_error> using namespace llvm; namespace lldb_dap { -Log::Log(StringRef filename, std::error_code &EC) : m_stream(filename, EC) {} +void Log::Emit(StringRef message) { Emit(message, "", 0); } -void Log::WriteMessage(StringRef message) { - std::lock_guard<std::mutex> lock(m_mutex); - std::chrono::duration<double> now{ - std::chrono::system_clock::now().time_since_epoch()}; - m_stream << formatv("{0:f9} ", now.count()).str() << message << "\n"; +void Log::Emit(StringRef message, StringRef file, size_t line) { + std::lock_guard<Log::Mutex> lock(m_mutex); + const llvm::sys::TimePoint<> time = std::chrono::system_clock::now(); + m_stream << formatv("[{0:%H:%M:%S.%L}]", time) << " "; + if (!file.empty()) + m_stream << sys::path::filename(file) << ":" << line << " "; + if (!m_prefix.empty()) + m_stream << m_prefix; + m_stream << message << "\n"; m_stream.flush(); } diff --git a/lldb/tools/lldb-dap/DAPLog.h b/lldb/tools/lldb-dap/DAPLog.h index 484001a..2170e66 100644 --- a/lldb/tools/lldb-dap/DAPLog.h +++ b/lldb/tools/lldb-dap/DAPLog.h @@ -11,33 +11,28 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" -#include "llvm/Support/FormatAdapters.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" #include <mutex> #include <string> -#include <system_error> // Write a message to log, if logging is enabled. #define DAP_LOG(log, ...) \ do { \ - ::lldb_dap::Log *log_private = (log); \ - if (log_private) { \ - log_private->WriteMessage(::llvm::formatv(__VA_ARGS__).str()); \ - } \ + ::lldb_dap::Log &log_private = (log); \ + log_private.Emit(::llvm::formatv(__VA_ARGS__).str(), __FILE__, __LINE__); \ } while (0) // Write message to log, if error is set. In the log message refer to the error // with {0}. Error is cleared regardless of whether logging is enabled. #define DAP_LOG_ERROR(log, error, ...) \ do { \ - ::lldb_dap::Log *log_private = (log); \ + ::lldb_dap::Log &log_private = (log); \ ::llvm::Error error_private = (error); \ - if (log_private && error_private) { \ - log_private->WriteMessage( \ - ::lldb_dap::FormatError(::std::move(error_private), __VA_ARGS__)); \ - } else \ - ::llvm::consumeError(::std::move(error_private)); \ + if (error_private) \ + log_private.Emit( \ + ::lldb_dap::FormatError(::std::move(error_private), __VA_ARGS__), \ + __FILE__, __LINE__); \ } while (0) namespace lldb_dap { @@ -46,14 +41,32 @@ namespace lldb_dap { /// `DAP_LOG_ERROR` helpers. class Log final { public: - /// Creates a log file with the given filename. - Log(llvm::StringRef filename, std::error_code &EC); + using Mutex = std::mutex; - void WriteMessage(llvm::StringRef message); + Log(llvm::raw_ostream &stream, Mutex &mutex) + : m_stream(stream), m_mutex(mutex) {} + Log(llvm::StringRef prefix, const Log &log) + : m_prefix(prefix), m_stream(log.m_stream), m_mutex(log.m_mutex) {} + + /// Retuns a new Log instance with the associated prefix for all messages. + inline Log WithPrefix(llvm::StringRef prefix) const { + std::string full_prefix = + m_prefix.empty() ? prefix.str() : m_prefix + prefix.str(); + full_prefix += " "; + return Log(full_prefix, *this); + } + + /// Emit writes a message to the underlying stream. + void Emit(llvm::StringRef message); + + /// Emit writes a message to the underlying stream, including the file and + /// line the message originated from. + void Emit(llvm::StringRef message, llvm::StringRef file, size_t line); private: - std::mutex m_mutex; - llvm::raw_fd_ostream m_stream; + std::string m_prefix; + llvm::raw_ostream &m_stream; + Mutex &m_mutex; }; template <typename... Args> diff --git a/lldb/tools/lldb-dap/DAPSessionManager.cpp b/lldb/tools/lldb-dap/DAPSessionManager.cpp new file mode 100644 index 0000000..3fbdc26 --- /dev/null +++ b/lldb/tools/lldb-dap/DAPSessionManager.cpp @@ -0,0 +1,143 @@ +//===----------------------------------------------------------------------===// +// +// 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 "DAPSessionManager.h" +#include "DAP.h" +#include "EventHelper.h" +#include "lldb/API/SBBroadcaster.h" +#include "lldb/API/SBEvent.h" +#include "lldb/API/SBTarget.h" +#include "lldb/Host/MainLoopBase.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/WithColor.h" + +#include <chrono> +#include <mutex> + +namespace lldb_dap { + +ManagedEventThread::ManagedEventThread(lldb::SBBroadcaster broadcaster, + std::thread t) + : m_broadcaster(broadcaster), m_event_thread(std::move(t)) {} + +ManagedEventThread::~ManagedEventThread() { + if (m_event_thread.joinable()) { + m_broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); + m_event_thread.join(); + } +} + +DAPSessionManager &DAPSessionManager::GetInstance() { + static std::once_flag initialized; + static DAPSessionManager *instance = + nullptr; // NOTE: intentional leak to avoid issues with C++ destructor + // chain + + std::call_once(initialized, []() { instance = new DAPSessionManager(); }); + + return *instance; +} + +void DAPSessionManager::RegisterSession(lldb_private::MainLoop *loop, + DAP *dap) { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + m_active_sessions[loop] = dap; +} + +void DAPSessionManager::UnregisterSession(lldb_private::MainLoop *loop) { + std::unique_lock<std::mutex> lock(m_sessions_mutex); + m_active_sessions.erase(loop); + std::notify_all_at_thread_exit(m_sessions_condition, std::move(lock)); +} + +std::vector<DAP *> DAPSessionManager::GetActiveSessions() { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + std::vector<DAP *> sessions; + for (const auto &[loop, dap] : m_active_sessions) + if (dap) + sessions.emplace_back(dap); + return sessions; +} + +void DAPSessionManager::DisconnectAllSessions() { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + m_client_failed = false; + for (auto [loop, dap] : m_active_sessions) { + if (dap) { + if (llvm::Error error = dap->Disconnect()) { + m_client_failed = true; + llvm::WithColor::error() << "DAP client disconnected failed: " + << llvm::toString(std::move(error)) << "\n"; + } + loop->AddPendingCallback( + [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); }); + } + } +} + +llvm::Error DAPSessionManager::WaitForAllSessionsToDisconnect() { + std::unique_lock<std::mutex> lock(m_sessions_mutex); + m_sessions_condition.wait(lock, [this] { return m_active_sessions.empty(); }); + + // Check if any disconnection failed and return appropriate error. + if (m_client_failed) + return llvm::make_error<llvm::StringError>( + "disconnecting all clients failed", llvm::inconvertibleErrorCode()); + + return llvm::Error::success(); +} + +std::shared_ptr<ManagedEventThread> +DAPSessionManager::GetEventThreadForDebugger(lldb::SBDebugger debugger, + DAP *requesting_dap) { + lldb::user_id_t debugger_id = debugger.GetID(); + std::lock_guard<std::mutex> lock(m_sessions_mutex); + + // Try to use shared event thread, if it exists. + if (auto it = m_debugger_event_threads.find(debugger_id); + it != m_debugger_event_threads.end()) { + if (std::shared_ptr<ManagedEventThread> thread_sp = it->second.lock()) + return thread_sp; + // Our weak pointer has expired. + m_debugger_event_threads.erase(it); + } + + // Create a new event thread and store it. + auto new_thread_sp = std::make_shared<ManagedEventThread>( + requesting_dap->broadcaster, + std::thread(EventThread, debugger, requesting_dap->broadcaster, + requesting_dap->m_client_name, + std::ref(requesting_dap->log))); + m_debugger_event_threads[debugger_id] = new_thread_sp; + return new_thread_sp; +} + +DAP *DAPSessionManager::FindDAPForTarget(lldb::SBTarget target) { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + + for (const auto &[loop, dap] : m_active_sessions) + if (dap && dap->target.IsValid() && dap->target == target) + return dap; + + return nullptr; +} + +void DAPSessionManager::ReleaseExpiredEventThreads() { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + for (auto it = m_debugger_event_threads.begin(); + it != m_debugger_event_threads.end();) { + // Check if the weak_ptr has expired (no DAP instances are using it + // anymore). + if (it->second.expired()) { + it = m_debugger_event_threads.erase(it); + } else { + ++it; + } + } +} + +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/DAPSessionManager.h b/lldb/tools/lldb-dap/DAPSessionManager.h new file mode 100644 index 0000000..ad76b08 --- /dev/null +++ b/lldb/tools/lldb-dap/DAPSessionManager.h @@ -0,0 +1,119 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the declarations of the DAPSessionManager and +/// ManagedEventThread classes, which are used to multiple concurrent DAP +/// sessions in a single lldb-dap process. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H +#define LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H + +#include "lldb/API/SBBroadcaster.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBTarget.h" +#include "lldb/Host/MainLoop.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/Error.h" +#include <condition_variable> +#include <map> +#include <memory> +#include <mutex> +#include <optional> +#include <thread> +#include <vector> + +namespace lldb_dap { + +// Forward declarations +struct DAP; + +class ManagedEventThread { +public: + // Constructor declaration + ManagedEventThread(lldb::SBBroadcaster broadcaster, std::thread t); + + ~ManagedEventThread(); + + ManagedEventThread(const ManagedEventThread &) = delete; + ManagedEventThread &operator=(const ManagedEventThread &) = delete; + +private: + lldb::SBBroadcaster m_broadcaster; + std::thread m_event_thread; +}; + +/// Global DAP session manager that manages multiple concurrent DAP sessions in +/// a single lldb-dap process. Handles session lifecycle tracking, coordinates +/// shared debugger event threads, and facilitates target handoff between +/// sessions for dynamically created targets. +class DAPSessionManager { +public: + /// Get the singleton instance of the DAP session manager. + static DAPSessionManager &GetInstance(); + + /// Register a DAP session. + void RegisterSession(lldb_private::MainLoop *loop, DAP *dap); + + /// Unregister a DAP session. Called by sessions when they complete their + /// disconnection, which unblocks WaitForAllSessionsToDisconnect(). + void UnregisterSession(lldb_private::MainLoop *loop); + + /// Get all active DAP sessions. + std::vector<DAP *> GetActiveSessions(); + + /// Disconnect all registered sessions by calling Disconnect() on + /// each and requesting their event loops to terminate. Used during + /// shutdown to force all sessions to begin disconnecting. + void DisconnectAllSessions(); + + /// Block until all sessions disconnect and unregister. Returns an error if + /// DisconnectAllSessions() was called and any disconnection failed. + llvm::Error WaitForAllSessionsToDisconnect(); + + /// Get or create event thread for a specific debugger. + std::shared_ptr<ManagedEventThread> + GetEventThreadForDebugger(lldb::SBDebugger debugger, DAP *requesting_dap); + + /// Find the DAP instance that owns the given target. + DAP *FindDAPForTarget(lldb::SBTarget target); + + /// Static convenience method for FindDAPForTarget. + static DAP *FindDAP(lldb::SBTarget target) { + return GetInstance().FindDAPForTarget(target); + } + + /// Clean up expired event threads from the collection. + void ReleaseExpiredEventThreads(); + +private: + DAPSessionManager() = default; + ~DAPSessionManager() = default; + + // Non-copyable and non-movable. + DAPSessionManager(const DAPSessionManager &) = delete; + DAPSessionManager &operator=(const DAPSessionManager &) = delete; + DAPSessionManager(DAPSessionManager &&) = delete; + DAPSessionManager &operator=(DAPSessionManager &&) = delete; + + bool m_client_failed = false; + std::mutex m_sessions_mutex; + std::condition_variable m_sessions_condition; + std::map<lldb_private::MainLoop *, DAP *> m_active_sessions; + + /// Map from debugger ID to its event thread, used when multiple DAP sessions + /// share the same debugger instance. + std::map<lldb::user_id_t, std::weak_ptr<ManagedEventThread>> + m_debugger_event_threads; +}; + +} // namespace lldb_dap + +#endif // LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index c5d5f2b..01d4547 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -7,16 +7,28 @@ //===----------------------------------------------------------------------===// #include "EventHelper.h" +#include "Breakpoint.h" +#include "BreakpointBase.h" #include "DAP.h" #include "DAPError.h" +#include "DAPLog.h" +#include "DAPSessionManager.h" +#include "Handler/ResponseHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "Protocol/ProtocolEvents.h" #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" +#include "ProtocolUtils.h" +#include "lldb/API/SBEvent.h" #include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBListener.h" #include "lldb/API/SBPlatform.h" +#include "lldb/API/SBStream.h" #include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Threading.h" +#include <mutex> #include <utility> #if defined(_WIN32) @@ -176,7 +188,7 @@ llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) { llvm::DenseSet<lldb::tid_t> old_thread_ids; old_thread_ids.swap(dap.thread_ids); - uint32_t stop_id = process.GetStopID(); + uint32_t stop_id = on_entry ? 0 : process.GetStopID(); const uint32_t num_threads = process.GetNumThreads(); // First make a pass through the threads to see if the focused thread @@ -306,4 +318,312 @@ void SendMemoryEvent(DAP &dap, lldb::SBValue variable) { dap.Send(protocol::Event{"memory", std::move(body)}); } +// Event handler functions that are called by EventThread. +// These handlers extract the necessary objects from events and find the +// appropriate DAP instance to handle them, maintaining compatibility with +// the original DAP::Handle*Event pattern while supporting multi-session +// debugging. + +static void HandleProcessEvent(const lldb::SBEvent &event, bool &process_exited, + Log &log) { + lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); + + // Find the DAP instance that owns this process's target. + DAP *dap = DAPSessionManager::FindDAP(process.GetTarget()); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for process {0}", + process.GetProcessID()); + return; + } + + const uint32_t event_mask = event.GetType(); + + if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { + auto state = lldb::SBProcess::GetStateFromEvent(event); + switch (state) { + case lldb::eStateConnected: + case lldb::eStateDetached: + case lldb::eStateInvalid: + case lldb::eStateUnloaded: + break; + case lldb::eStateAttaching: + case lldb::eStateCrashed: + case lldb::eStateLaunching: + case lldb::eStateStopped: + case lldb::eStateSuspended: + // Only report a stopped event if the process was not + // automatically restarted. + if (!lldb::SBProcess::GetRestartedFromEvent(event)) { + SendStdOutStdErr(*dap, process); + if (llvm::Error err = SendThreadStoppedEvent(*dap)) + DAP_LOG_ERROR(dap->log, std::move(err), + "({1}) reporting thread stopped: {0}", + dap->GetClientName()); + } + break; + case lldb::eStateRunning: + case lldb::eStateStepping: + dap->WillContinue(); + SendContinuedEvent(*dap); + break; + case lldb::eStateExited: + lldb::SBStream stream; + process.GetStatus(stream); + dap->SendOutput(OutputType::Console, stream.GetData()); + + // When restarting, we can get an "exited" event for the process we + // just killed with the old PID, or even with no PID. In that case + // we don't have to terminate the session. + if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || + process.GetProcessID() == dap->restarting_process_id) { + dap->restarting_process_id = LLDB_INVALID_PROCESS_ID; + } else { + // Run any exit LLDB commands the user specified in the + // launch.json + dap->RunExitCommands(); + SendProcessExitedEvent(*dap, process); + dap->SendTerminatedEvent(); + process_exited = true; + } + break; + } + } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || + (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { + SendStdOutStdErr(*dap, process); + } +} + +static void HandleTargetEvent(const lldb::SBEvent &event, Log &log) { + lldb::SBTarget target = lldb::SBTarget::GetTargetFromEvent(event); + + // Find the DAP instance that owns this target. + DAP *dap = DAPSessionManager::FindDAP(target); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for target"); + return; + } + + const uint32_t event_mask = event.GetType(); + if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded || + event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded || + event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || + event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) { + const uint32_t num_modules = lldb::SBTarget::GetNumModulesFromEvent(event); + const bool remove_module = + event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded; + + // NOTE: Both mutexes must be acquired to prevent deadlock when + // handling `modules_request`, which also requires both locks. + lldb::SBMutex api_mutex = dap->GetAPIMutex(); + const std::scoped_lock<lldb::SBMutex, std::mutex> guard(api_mutex, + dap->modules_mutex); + for (uint32_t i = 0; i < num_modules; ++i) { + lldb::SBModule module = + lldb::SBTarget::GetModuleAtIndexFromEvent(i, event); + + std::optional<protocol::Module> p_module = + CreateModule(dap->target, module, remove_module); + if (!p_module) + continue; + + llvm::StringRef module_id = p_module->id; + + const bool module_exists = dap->modules.contains(module_id); + if (remove_module && module_exists) { + dap->modules.erase(module_id); + dap->Send(protocol::Event{ + "module", protocol::ModuleEventBody{ + std::move(p_module).value(), + protocol::ModuleEventBody::eReasonRemoved}}); + } else if (module_exists) { + dap->Send(protocol::Event{ + "module", protocol::ModuleEventBody{ + std::move(p_module).value(), + protocol::ModuleEventBody::eReasonChanged}}); + } else if (!remove_module) { + dap->modules.insert(module_id); + dap->Send(protocol::Event{ + "module", + protocol::ModuleEventBody{std::move(p_module).value(), + protocol::ModuleEventBody::eReasonNew}}); + } + } + } else if (event_mask & lldb::SBTarget::eBroadcastBitNewTargetCreated) { + // For NewTargetCreated events, GetTargetFromEvent returns the parent + // target, and GetCreatedTargetFromEvent returns the newly created target. + lldb::SBTarget created_target = + lldb::SBTarget::GetCreatedTargetFromEvent(event); + + if (!target.IsValid() || !created_target.IsValid()) { + DAP_LOG(log, "Received NewTargetCreated event but parent or " + "created target is invalid"); + return; + } + + // Send a startDebugging reverse request with the debugger and target + // IDs. The new DAP instance will use these IDs to find the existing + // debugger and target via FindDebuggerWithID and + // FindTargetByGloballyUniqueID. + llvm::json::Object configuration; + configuration.try_emplace("type", "lldb"); + configuration.try_emplace("debuggerId", + created_target.GetDebugger().GetID()); + configuration.try_emplace("targetId", created_target.GetGloballyUniqueID()); + configuration.try_emplace("name", created_target.GetTargetSessionName()); + + llvm::json::Object request; + request.try_emplace("request", "attach"); + request.try_emplace("configuration", std::move(configuration)); + + dap->SendReverseRequest<LogFailureResponseHandler>("startDebugging", + std::move(request)); + } +} + +static void HandleBreakpointEvent(const lldb::SBEvent &event, Log &log) { + const uint32_t event_mask = event.GetType(); + if (!(event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged)) + return; + + lldb::SBBreakpoint bp = lldb::SBBreakpoint::GetBreakpointFromEvent(event); + if (!bp.IsValid()) + return; + + // Find the DAP instance that owns this breakpoint's target. + DAP *dap = DAPSessionManager::FindDAP(bp.GetTarget()); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for breakpoint"); + return; + } + + auto event_type = lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); + auto breakpoint = Breakpoint(*dap, bp); + // If the breakpoint was set through DAP, it will have the + // BreakpointBase::kDAPBreakpointLabel. Regardless of whether + // locations were added, removed, or resolved, the breakpoint isn't + // going away and the reason is always "changed". + if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || + event_type & lldb::eBreakpointEventTypeLocationsRemoved || + event_type & lldb::eBreakpointEventTypeLocationsResolved) && + breakpoint.MatchesName(BreakpointBase::kDAPBreakpointLabel)) { + // As the DAP client already knows the path of this breakpoint, we + // don't need to send it back as part of the "changed" event. This + // avoids sending paths that should be source mapped. Note that + // CreateBreakpoint doesn't apply source mapping and certain + // implementation ignore the source part of this event anyway. + protocol::Breakpoint protocol_bp = breakpoint.ToProtocolBreakpoint(); + + // "source" is not needed here, unless we add adapter data to be + // saved by the client. + if (protocol_bp.source && !protocol_bp.source->adapterData) + protocol_bp.source = std::nullopt; + + llvm::json::Object body; + body.try_emplace("breakpoint", protocol_bp); + body.try_emplace("reason", "changed"); + + llvm::json::Object bp_event = CreateEventObject("breakpoint"); + bp_event.try_emplace("body", std::move(body)); + + dap->SendJSON(llvm::json::Value(std::move(bp_event))); + } +} + +static void HandleThreadEvent(const lldb::SBEvent &event, Log &log) { + uint32_t event_type = event.GetType(); + + if (!(event_type & lldb::SBThread::eBroadcastBitStackChanged)) + return; + + lldb::SBThread thread = lldb::SBThread::GetThreadFromEvent(event); + if (!thread.IsValid()) + return; + + // Find the DAP instance that owns this thread's process/target. + DAP *dap = DAPSessionManager::FindDAP(thread.GetProcess().GetTarget()); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for thread"); + return; + } + + SendInvalidatedEvent(*dap, {protocol::InvalidatedEventBody::eAreaStacks}, + thread.GetThreadID()); +} + +static void HandleDiagnosticEvent(const lldb::SBEvent &event, Log &log) { + // Global debugger events - send to all DAP instances. + std::vector<DAP *> active_instances = + DAPSessionManager::GetInstance().GetActiveSessions(); + for (DAP *dap_instance : active_instances) { + if (!dap_instance) + continue; + + lldb::SBStructuredData data = + lldb::SBDebugger::GetDiagnosticFromEvent(event); + if (!data.IsValid()) + continue; + + std::string type = GetStringValue(data.GetValueForKey("type")); + std::string message = GetStringValue(data.GetValueForKey("message")); + dap_instance->SendOutput(OutputType::Important, + llvm::formatv("{0}: {1}", type, message).str()); + } +} + +// Note: EventThread() is architecturally different from the other functions in +// this file. While the functions above are event helpers that operate on a +// single DAP instance (taking `DAP &dap` as a parameter), EventThread() is a +// shared event processing loop that: +// 1. Listens to events from a shared debugger instance +// 2. Dispatches events to the appropriate handler, which internally finds the +// DAP instance using DAPSessionManager::FindDAP() +// 3. Handles events for multiple different DAP sessions +// This allows multiple DAP sessions to share a single debugger and event +// thread, which is essential for the target handoff mechanism where child +// processes/targets are debugged in separate DAP sessions. +// +// All events from the debugger, target, process, thread and frames are +// received in this function that runs in its own thread. We are using a +// "FILE *" to output packets back to VS Code and they have mutexes in them +// them prevent multiple threads from writing simultaneously so no locking +// is required. +void EventThread(lldb::SBDebugger debugger, lldb::SBBroadcaster broadcaster, + llvm::StringRef client_name, Log &log) { + llvm::set_thread_name("lldb.DAP.client." + client_name + ".event_handler"); + lldb::SBListener listener = debugger.GetListener(); + broadcaster.AddListener(listener, eBroadcastBitStopEventThread); + debugger.GetBroadcaster().AddListener( + listener, lldb::eBroadcastBitError | lldb::eBroadcastBitWarning); + + // listen for thread events. + listener.StartListeningForEventClass( + debugger, lldb::SBThread::GetBroadcasterClassName(), + lldb::SBThread::eBroadcastBitStackChanged); + + lldb::SBEvent event; + bool done = false; + while (!done) { + if (!listener.WaitForEvent(UINT32_MAX, event)) + continue; + + const uint32_t event_mask = event.GetType(); + if (lldb::SBProcess::EventIsProcessEvent(event)) { + HandleProcessEvent(event, /*&process_exited=*/done, log); + } else if (lldb::SBTarget::EventIsTargetEvent(event)) { + HandleTargetEvent(event, log); + } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { + HandleBreakpointEvent(event, log); + } else if (lldb::SBThread::EventIsThreadEvent(event)) { + HandleThreadEvent(event, log); + } else if (event_mask & lldb::eBroadcastBitError || + event_mask & lldb::eBroadcastBitWarning) { + HandleDiagnosticEvent(event, log); + } else if (event.BroadcasterMatchesRef(broadcaster)) { + if (event_mask & eBroadcastBitStopEventThread) { + done = true; + } + } + } +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h index be783d0..b46d5ae 100644 --- a/lldb/tools/lldb-dap/EventHelper.h +++ b/lldb/tools/lldb-dap/EventHelper.h @@ -42,6 +42,17 @@ void SendInvalidatedEvent( void SendMemoryEvent(DAP &dap, lldb::SBValue variable); +/// Event thread function that handles debugger events for multiple DAP sessions +/// sharing the same debugger instance. This runs in its own thread and +/// dispatches events to the appropriate DAP instance. +/// +/// \param debugger The debugger instance to listen for events from. +/// \param broadcaster The broadcaster for stop event thread notifications. +/// \param client_name The client name for thread naming/logging purposes. +/// \param log The log instance for logging. +void EventThread(lldb::SBDebugger debugger, lldb::SBBroadcaster broadcaster, + llvm::StringRef client_name, Log &log); + } // namespace lldb_dap #endif diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp index 490513f..f0996eb 100644 --- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp @@ -17,6 +17,7 @@ #include "lldb/lldb-defines.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +#include <cstdint> using namespace llvm; using namespace lldb_dap::protocol; @@ -29,14 +30,31 @@ namespace lldb_dap { /// Since attaching is debugger/runtime specific, the arguments for this request /// are not part of this specification. Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { + // Initialize DAP debugger and related components if not sharing previously + // launched debugger. + std::optional<int> debugger_id = args.debuggerId; + std::optional<lldb::user_id_t> target_id = args.targetId; + + // Validate that both debugger_id and target_id are provided together. + if (debugger_id.has_value() != target_id.has_value()) { + return llvm::createStringError( + "Both debuggerId and targetId must be specified together for debugger " + "reuse, or both must be omitted to create a new debugger"); + } + + if (Error err = debugger_id && target_id + ? dap.InitializeDebugger(*debugger_id, *target_id) + : dap.InitializeDebugger()) + return err; + // Validate that we have a well formed attach request. if (args.attachCommands.empty() && args.coreFile.empty() && args.configuration.program.empty() && args.pid == LLDB_INVALID_PROCESS_ID && - args.gdbRemotePort == LLDB_DAP_INVALID_PORT) + args.gdbRemotePort == LLDB_DAP_INVALID_PORT && !target_id.has_value()) return make_error<DAPError>( "expected one of 'pid', 'program', 'attachCommands', " - "'coreFile' or 'gdb-remote-port' to be specified"); + "'coreFile', 'gdb-remote-port', or target_id to be specified"); // Check if we have mutually exclusive arguments. if ((args.pid != LLDB_INVALID_PROCESS_ID) && @@ -64,7 +82,20 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { dap.ConfigureSourceMaps(); lldb::SBError error; - lldb::SBTarget target = dap.CreateTarget(error); + lldb::SBTarget target; + if (target_id) { + // Use the unique target ID to get the target. + target = dap.debugger.FindTargetByGloballyUniqueID(*target_id); + if (!target.IsValid()) { + error.SetErrorString( + llvm::formatv("invalid target_id {0} in attach config", *target_id) + .str() + .c_str()); + } + } else { + target = dap.CreateTarget(error); + } + if (error.Fail()) return ToError(error); @@ -114,7 +145,7 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { connect_url += std::to_string(args.gdbRemotePort); dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", error); - } else { + } else if (!target_id.has_value()) { // Attach by pid or process name. lldb::SBAttachInfo attach_info; if (args.pid != LLDB_INVALID_PROCESS_ID) diff --git a/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp index cd93711..0e5c2b2 100644 --- a/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp @@ -60,12 +60,12 @@ void CompileUnitsRequestHandler::operator()( llvm::json::Object body; llvm::json::Array units; const auto *arguments = request.getObject("arguments"); - const std::string module_id = - GetString(arguments, "moduleId").value_or("").str(); + const llvm::StringRef module_id = + GetString(arguments, "moduleId").value_or(""); int num_modules = dap.target.GetNumModules(); for (int i = 0; i < num_modules; i++) { auto curr_module = dap.target.GetModuleAtIndex(i); - if (module_id == curr_module.GetUUIDString()) { + if (module_id == llvm::StringRef(curr_module.GetUUIDString())) { int num_units = curr_module.GetNumCompileUnits(); for (int j = 0; j < num_units; j++) { auto curr_unit = curr_module.GetCompileUnitAtIndex(j); diff --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp index 87b93fc..245d92c 100644 --- a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp @@ -7,15 +7,33 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "DAPError.h" #include "EventHelper.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" +#include "lldb/API/SBAddress.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "llvm/ADT/StringExtras.h" #include <optional> namespace lldb_dap { +static bool IsRW(DAP &dap, lldb::addr_t load_addr) { + if (!lldb::SBAddress(load_addr, dap.target).IsValid()) + return false; + lldb::SBMemoryRegionInfo region; + lldb::SBError err = + dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region); + // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this + // request if SBProcess::GetMemoryRegionInfo returns error. + if (err.Success()) { + if (!(region.IsReadable() || region.IsWritable())) { + return false; + } + } + return true; +} + /// Obtains information on a possible data breakpoint that could be set on an /// expression or variable. Clients should only call this request if the /// corresponding capability supportsDataBreakpoints is true. @@ -23,7 +41,6 @@ llvm::Expected<protocol::DataBreakpointInfoResponseBody> DataBreakpointInfoRequestHandler::Run( const protocol::DataBreakpointInfoArguments &args) const { protocol::DataBreakpointInfoResponseBody response; - lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId); lldb::SBValue variable = dap.variables.FindVariable( args.variablesReference.value_or(0), args.name); std::string addr, size; @@ -43,7 +60,8 @@ DataBreakpointInfoRequestHandler::Run( addr = llvm::utohexstr(load_addr); size = llvm::utostr(byte_size); } - } else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) { + } else if (lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId); + args.variablesReference.value_or(0) == 0 && frame.IsValid()) { lldb::SBValue value = frame.EvaluateExpression(args.name.c_str()); if (value.GetError().Fail()) { lldb::SBError error = value.GetError(); @@ -58,17 +76,10 @@ DataBreakpointInfoRequestHandler::Run( if (data.IsValid()) { size = llvm::utostr(data.GetByteSize()); addr = llvm::utohexstr(load_addr); - lldb::SBMemoryRegionInfo region; - lldb::SBError err = - dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region); - // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this - // request if SBProcess::GetMemoryRegionInfo returns error. - if (err.Success()) { - if (!(region.IsReadable() || region.IsWritable())) { - is_data_ok = false; - response.description = "memory region for address " + addr + - " has no read or write permissions"; - } + if (!IsRW(dap, load_addr)) { + is_data_ok = false; + response.description = "memory region for address " + addr + + " has no read or write permissions"; } } else { is_data_ok = false; @@ -76,6 +87,17 @@ DataBreakpointInfoRequestHandler::Run( "unable to get byte size for expression: " + args.name; } } + } else if (args.asAddress) { + size = llvm::utostr(args.bytes.value_or(dap.target.GetAddressByteSize())); + lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; + if (llvm::StringRef(args.name).getAsInteger<lldb::addr_t>(0, load_addr)) + return llvm::make_error<DAPError>(args.name + " is not a valid address", + llvm::inconvertibleErrorCode(), false); + addr = llvm::utohexstr(load_addr); + if (!IsRW(dap, load_addr)) + return llvm::make_error<DAPError>("memory region for address " + addr + + " has no read or write permissions", + llvm::inconvertibleErrorCode(), false); } else { is_data_ok = false; response.description = "variable not found: " + args.name; @@ -86,7 +108,10 @@ DataBreakpointInfoRequestHandler::Run( response.accessTypes = {protocol::eDataBreakpointAccessTypeRead, protocol::eDataBreakpointAccessTypeWrite, protocol::eDataBreakpointAccessTypeReadWrite}; - response.description = size + " bytes at " + addr + " " + args.name; + if (args.asAddress) + response.description = size + " bytes at " + addr; + else + response.description = size + " bytes at " + addr + " " + args.name; } return response; diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp index e155684..ec26bb6 100644 --- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp @@ -10,148 +10,31 @@ #include "EventHelper.h" #include "JSONUtils.h" #include "LLDBUtils.h" +#include "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" +#include "lldb/lldb-enumerations.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +using namespace llvm; +using namespace lldb_dap; +using namespace lldb_dap::protocol; namespace lldb_dap { -// "EvaluateRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Evaluate request; value of command field is 'evaluate'. -// Evaluates the given expression in the context of the -// top most stack frame. The expression has access to any -// variables and arguments that are in scope.", -// "properties": { -// "command": { -// "type": "string", -// "enum": [ "evaluate" ] -// }, -// "arguments": { -// "$ref": "#/definitions/EvaluateArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "EvaluateArguments": { -// "type": "object", -// "description": "Arguments for 'evaluate' request.", -// "properties": { -// "expression": { -// "type": "string", -// "description": "The expression to evaluate." -// }, -// "frameId": { -// "type": "integer", -// "description": "Evaluate the expression in the scope of this stack -// frame. If not specified, the expression is evaluated -// in the global scope." -// }, -// "context": { -// "type": "string", -// "_enum": [ "watch", "repl", "hover" ], -// "enumDescriptions": [ -// "evaluate is run in a watch.", -// "evaluate is run from REPL console.", -// "evaluate is run from a data hover." -// ], -// "description": "The context in which the evaluate request is run." -// }, -// "format": { -// "$ref": "#/definitions/ValueFormat", -// "description": "Specifies details on how to format the Evaluate -// result." -// } -// }, -// "required": [ "expression" ] -// }, -// "EvaluateResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to 'evaluate' request.", -// "properties": { -// "body": { -// "type": "object", -// "properties": { -// "result": { -// "type": "string", -// "description": "The result of the evaluate request." -// }, -// "type": { -// "type": "string", -// "description": "The optional type of the evaluate result." -// }, -// "presentationHint": { -// "$ref": "#/definitions/VariablePresentationHint", -// "description": "Properties of a evaluate result that can be -// used to determine how to render the result in -// the UI." -// }, -// "variablesReference": { -// "type": "number", -// "description": "If variablesReference is > 0, the evaluate -// result is structured and its children can be -// retrieved by passing variablesReference to the -// VariablesRequest." -// }, -// "namedVariables": { -// "type": "number", -// "description": "The number of named child variables. The -// client can use this optional information to -// present the variables in a paged UI and fetch -// them in chunks." -// }, -// "indexedVariables": { -// "type": "number", -// "description": "The number of indexed child variables. The -// client can use this optional information to -// present the variables in a paged UI and fetch -// them in chunks." -// }, -// "valueLocationReference": { -// "type": "integer", -// "description": "A reference that allows the client to request -// the location where the returned value is -// declared. For example, if a function pointer is -// returned, the adapter may be able to look up the -// function's location. This should be present only -// if the adapter is likely to be able to resolve -// the location.\n\nThis reference shares the same -// lifetime as the `variablesReference`. See -// 'Lifetime of Object References' in the -// Overview section for details." -// } -// "memoryReference": { -// "type": "string", -// "description": "A memory reference to a location appropriate -// for this result. For pointer type eval -// results, this is generally a reference to the -// memory address contained in the pointer. This -// attribute may be returned by a debug adapter -// if corresponding capability -// `supportsMemoryReferences` is true." -// }, -// }, -// "required": [ "result", "variablesReference" ] -// } -// }, -// "required": [ "body" ] -// }] -// } -void EvaluateRequestHandler::operator()( - const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); - llvm::json::Object body; - const auto *arguments = request.getObject("arguments"); - lldb::SBFrame frame = dap.GetLLDBFrame(*arguments); - std::string expression = - GetString(arguments, "expression").value_or("").str(); - const llvm::StringRef context = GetString(arguments, "context").value_or(""); +/// Evaluates the given expression in the context of a stack frame. +/// +/// The expression has access to any variables and arguments that are in scope. +Expected<EvaluateResponseBody> +EvaluateRequestHandler::Run(const EvaluateArguments &arguments) const { + EvaluateResponseBody body; + lldb::SBFrame frame = dap.GetLLDBFrame(arguments.frameId); + std::string expression = arguments.expression; bool repeat_last_command = expression.empty() && dap.last_nonempty_var_expression.empty(); - if (context == "repl" && + if (arguments.context == protocol::eEvaluateContextRepl && (repeat_last_command || (!expression.empty() && dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) { @@ -165,70 +48,62 @@ void EvaluateRequestHandler::operator()( } bool required_command_failed = false; - std::string result = RunLLDBCommands( + body.result = RunLLDBCommands( dap.debugger, llvm::StringRef(), {expression}, required_command_failed, /*parse_command_directives=*/false, /*echo_commands=*/false); + return body; + } - EmplaceSafeString(body, "result", result); - body.try_emplace("variablesReference", (int64_t)0); - } else { - if (context == "repl") { - // If the expression is empty and the last expression was for a - // variable, set the expression to the previous expression (repeat the - // evaluation); otherwise save the current non-empty expression for the - // next (possibly empty) variable expression. - if (expression.empty()) - expression = dap.last_nonempty_var_expression; - else - dap.last_nonempty_var_expression = expression; - } - // Always try to get the answer from the local variables if possible. If - // this fails, then if the context is not "hover", actually evaluate an - // expression using the expression parser. - // - // "frame variable" is more reliable than the expression parser in - // many cases and it is faster. - lldb::SBValue value = frame.GetValueForVariablePath( - expression.data(), lldb::eDynamicDontRunTarget); - - // Freeze dry the value in case users expand it later in the debug console - if (value.GetError().Success() && context == "repl") - value = value.Persist(); - - if (value.GetError().Fail() && context != "hover") - value = frame.EvaluateExpression(expression.data()); - - if (value.GetError().Fail()) { - response["success"] = llvm::json::Value(false); - // This error object must live until we're done with the pointer returned - // by GetCString(). - lldb::SBError error = value.GetError(); - const char *error_cstr = error.GetCString(); - if (error_cstr && error_cstr[0]) - EmplaceSafeString(response, "message", error_cstr); - else - EmplaceSafeString(response, "message", "evaluate failed"); - } else { - VariableDescription desc(value, - dap.configuration.enableAutoVariableSummaries); - EmplaceSafeString(body, "result", desc.GetResult(context)); - EmplaceSafeString(body, "type", desc.display_type_name); - int64_t var_ref = 0; - if (value.MightHaveChildren() || ValuePointsToCode(value)) - var_ref = dap.variables.InsertVariable( - value, /*is_permanent=*/context == "repl"); - if (value.MightHaveChildren()) - body.try_emplace("variablesReference", var_ref); - else - body.try_emplace("variablesReference", (int64_t)0); - if (lldb::addr_t addr = value.GetLoadAddress(); - addr != LLDB_INVALID_ADDRESS) - body.try_emplace("memoryReference", EncodeMemoryReference(addr)); - if (ValuePointsToCode(value)) - body.try_emplace("valueLocationReference", var_ref); - } + if (arguments.context == eEvaluateContextRepl) { + // If the expression is empty and the last expression was for a + // variable, set the expression to the previous expression (repeat the + // evaluation); otherwise save the current non-empty expression for the + // next (possibly empty) variable expression. + if (expression.empty()) + expression = dap.last_nonempty_var_expression; + else + dap.last_nonempty_var_expression = expression; } - response.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(response))); + + // Always try to get the answer from the local variables if possible. If + // this fails, then if the context is not "hover", actually evaluate an + // expression using the expression parser. + // + // "frame variable" is more reliable than the expression parser in + // many cases and it is faster. + lldb::SBValue value = frame.GetValueForVariablePath( + expression.data(), lldb::eDynamicDontRunTarget); + + // Freeze dry the value in case users expand it later in the debug console + if (value.GetError().Success() && arguments.context == eEvaluateContextRepl) + value = value.Persist(); + + if (value.GetError().Fail() && arguments.context != eEvaluateContextHover) + value = frame.EvaluateExpression(expression.data()); + + if (value.GetError().Fail()) + return ToError(value.GetError(), /*show_user=*/false); + + const bool hex = arguments.format ? arguments.format->hex : false; + + VariableDescription desc(value, dap.configuration.enableAutoVariableSummaries, + hex); + + body.result = desc.GetResult(arguments.context); + body.type = desc.display_type_name; + + if (value.MightHaveChildren() || ValuePointsToCode(value)) + body.variablesReference = dap.variables.InsertVariable( + value, /*is_permanent=*/arguments.context == eEvaluateContextRepl); + + if (lldb::addr_t addr = value.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS) + body.memoryReference = EncodeMemoryReference(addr); + + if (ValuePointsToCode(value) && + body.variablesReference != LLDB_DAP_INVALID_VAR_REF) + body.valueLocationReference = PackLocation(body.variablesReference, true); + + return body; } + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index c1c2adb..ddf55e6f 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -7,168 +7,75 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "EventHelper.h" -#include "JSONUtils.h" +#include "DAPError.h" +#include "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" #include "lldb/API/SBStream.h" +using namespace lldb_dap::protocol; + namespace lldb_dap { -// "ExceptionInfoRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Retrieves the details of the exception that -// caused this event to be raised. Clients should only call this request if -// the corresponding capability `supportsExceptionInfoRequest` is true.", -// "properties": { -// "command": { -// "type": "string", -// "enum": [ "exceptionInfo" ] -// }, -// "arguments": { -// "$ref": "#/definitions/ExceptionInfoArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "ExceptionInfoArguments": { -// "type": "object", -// "description": "Arguments for `exceptionInfo` request.", -// "properties": { -// "threadId": { -// "type": "integer", -// "description": "Thread for which exception information should be -// retrieved." -// } -// }, -// "required": [ "threadId" ] -// }, -// "ExceptionInfoResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to `exceptionInfo` request.", -// "properties": { -// "body": { -// "type": "object", -// "properties": { -// "exceptionId": { -// "type": "string", -// "description": "ID of the exception that was thrown." -// }, -// "description": { -// "type": "string", -// "description": "Descriptive text for the exception." -// }, -// "breakMode": { -// "$ref": "#/definitions/ExceptionBreakMode", -// "description": "Mode that caused the exception notification to -// be raised." -// }, -// "details": { -// "$ref": "#/definitions/ExceptionDetails", -// "description": "Detailed information about the exception." -// } -// }, -// "required": [ "exceptionId", "breakMode" ] -// } -// }, -// "required": [ "body" ] -// }] -// } -// "ExceptionDetails": { -// "type": "object", -// "description": "Detailed information about an exception that has -// occurred.", "properties": { -// "message": { -// "type": "string", -// "description": "Message contained in the exception." -// }, -// "typeName": { -// "type": "string", -// "description": "Short type name of the exception object." -// }, -// "fullTypeName": { -// "type": "string", -// "description": "Fully-qualified type name of the exception object." -// }, -// "evaluateName": { -// "type": "string", -// "description": "An expression that can be evaluated in the current -// scope to obtain the exception object." -// }, -// "stackTrace": { -// "type": "string", -// "description": "Stack trace at the time the exception was thrown." -// }, -// "innerException": { -// "type": "array", -// "items": { -// "$ref": "#/definitions/ExceptionDetails" -// }, -// "description": "Details of the exception contained by this exception, -// if any." -// } -// } -// }, -void ExceptionInfoRequestHandler::operator()( - const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); - const auto *arguments = request.getObject("arguments"); - llvm::json::Object body; - lldb::SBThread thread = dap.GetLLDBThread(*arguments); - if (thread.IsValid()) { - auto stopReason = thread.GetStopReason(); - if (stopReason == lldb::eStopReasonSignal) - body.try_emplace("exceptionId", "signal"); - else if (stopReason == lldb::eStopReasonBreakpoint) { - ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); - if (exc_bp) { - EmplaceSafeString(body, "exceptionId", exc_bp->GetFilter()); - EmplaceSafeString(body, "description", exc_bp->GetLabel()); - } else { - body.try_emplace("exceptionId", "exception"); - } +/// Retrieves the details of the exception that caused this event to be raised. +/// +/// Clients should only call this request if the corresponding capability +/// `supportsExceptionInfoRequest` is true. +llvm::Expected<ExceptionInfoResponseBody> +ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { + + lldb::SBThread thread = dap.GetLLDBThread(args.threadId); + if (!thread.IsValid()) + return llvm::make_error<DAPError>( + llvm::formatv("Invalid thread id: {}", args.threadId).str()); + + ExceptionInfoResponseBody response; + response.breakMode = eExceptionBreakModeAlways; + const lldb::StopReason stop_reason = thread.GetStopReason(); + switch (stop_reason) { + case lldb::eStopReasonSignal: + response.exceptionId = "signal"; + break; + case lldb::eStopReasonBreakpoint: { + const ExceptionBreakpoint *exc_bp = + dap.GetExceptionBPFromStopReason(thread); + if (exc_bp) { + response.exceptionId = exc_bp->GetFilter(); + response.description = exc_bp->GetLabel(); } else { - body.try_emplace("exceptionId", "exception"); + response.exceptionId = "exception"; } - if (!ObjectContainsKey(body, "description")) { - char description[1024]; - if (thread.GetStopDescription(description, sizeof(description))) { - EmplaceSafeString(body, "description", description); - } + } break; + default: + response.exceptionId = "exception"; + } + + lldb::SBStream stream; + if (response.description.empty()) { + if (thread.GetStopDescription(stream)) { + response.description = {stream.GetData(), stream.GetSize()}; } - body.try_emplace("breakMode", "always"); - auto exception = thread.GetCurrentException(); - if (exception.IsValid()) { - llvm::json::Object details; - lldb::SBStream stream; - if (exception.GetDescription(stream)) { - EmplaceSafeString(details, "message", stream.GetData()); - } + } - auto exceptionBacktrace = thread.GetCurrentExceptionBacktrace(); - if (exceptionBacktrace.IsValid()) { - lldb::SBStream stream; - exceptionBacktrace.GetDescription(stream); - for (uint32_t i = 0; i < exceptionBacktrace.GetNumFrames(); i++) { - lldb::SBFrame frame = exceptionBacktrace.GetFrameAtIndex(i); - frame.GetDescription(stream); - } - EmplaceSafeString(details, "stackTrace", stream.GetData()); - } + if (lldb::SBValue exception = thread.GetCurrentException()) { + stream.Clear(); + response.details = ExceptionDetails{}; + if (exception.GetDescription(stream)) { + response.details->message = {stream.GetData(), stream.GetSize()}; + } + + if (lldb::SBThread exception_backtrace = + thread.GetCurrentExceptionBacktrace()) { + stream.Clear(); + exception_backtrace.GetDescription(stream); - body.try_emplace("details", std::move(details)); + for (uint32_t idx = 0; idx < exception_backtrace.GetNumFrames(); idx++) { + lldb::SBFrame frame = exception_backtrace.GetFrameAtIndex(idx); + frame.GetDescription(stream); + } + response.details->stackTrace = {stream.GetData(), stream.GetSize()}; } - // auto excInfoCount = thread.GetStopReasonDataCount(); - // for (auto i=0; i<excInfoCount; ++i) { - // uint64_t exc_data = thread.GetStopReasonDataAtIndex(i); - // } - } else { - response["success"] = llvm::json::Value(false); } - response.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(response))); + return response; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index 9069de4..2d30e08 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -21,63 +21,9 @@ using namespace lldb_dap::protocol; /// Initialize request; value of command field is 'initialize'. llvm::Expected<InitializeResponse> InitializeRequestHandler::Run( const InitializeRequestArguments &arguments) const { + // Store initialization arguments for later use in Launch/Attach. dap.clientFeatures = arguments.supportedFeatures; - - // Do not source init files until in/out/err are configured. - dap.debugger = lldb::SBDebugger::Create(false); - dap.debugger.SetInputFile(dap.in); - dap.target = dap.debugger.GetDummyTarget(); - - llvm::Expected<int> out_fd = dap.out.GetWriteFileDescriptor(); - if (!out_fd) - return out_fd.takeError(); - dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); - - llvm::Expected<int> err_fd = dap.err.GetWriteFileDescriptor(); - if (!err_fd) - return err_fd.takeError(); - dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); - - auto interp = dap.debugger.GetCommandInterpreter(); - - // The sourceInitFile option is not part of the DAP specification. It is an - // extension used by the test suite to prevent sourcing `.lldbinit` and - // changing its behavior. The CLI flag --no-lldbinit takes precedence over - // the DAP parameter. - bool should_source_init_files = - !dap.no_lldbinit && arguments.lldbExtSourceInitFile.value_or(true); - if (should_source_init_files) { - dap.debugger.SkipLLDBInitFiles(false); - dap.debugger.SkipAppInitFiles(false); - lldb::SBCommandReturnObject init; - interp.SourceInitFileInGlobalDirectory(init); - interp.SourceInitFileInHomeDirectory(init); - } - - if (llvm::Error err = dap.RunPreInitCommands()) - return err; - - auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand( - "lldb-dap", "Commands for managing lldb-dap."); - if (arguments.supportedFeatures.contains( - eClientFeatureStartDebuggingRequest)) { - cmd.AddCommand( - "start-debugging", new StartDebuggingCommand(dap), - "Sends a startDebugging request from the debug adapter to the client " - "to start a child debug session of the same type as the caller."); - } - cmd.AddCommand( - "repl-mode", new ReplModeCommand(dap), - "Get or set the repl behavior of lldb-dap evaluation requests."); - cmd.AddCommand("send-event", new SendEventCommand(dap), - "Sends an DAP event to the client."); - - if (arguments.supportedFeatures.contains(eClientFeatureProgressReporting)) - dap.StartProgressEventThread(); - - // Start our event thread so we can receive events from the debugger, target, - // process and more. - dap.StartEventThread(); + dap.sourceInitFile = arguments.lldbExtSourceInitFile; return dap.GetCapabilities(); } diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp index 553cbea..329f0a7 100644 --- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp @@ -22,6 +22,10 @@ namespace lldb_dap { /// Launch request; value of command field is 'launch'. Error LaunchRequestHandler::Run(const LaunchRequestArguments &arguments) const { + // Initialize DAP debugger. + if (Error err = dap.InitializeDebugger()) + return err; + // Validate that we have a well formed launch request. if (!arguments.launchCommands.empty() && arguments.console != protocol::eConsoleInternal) diff --git a/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp index 99917b2..9419f1a 100644 --- a/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp @@ -8,53 +8,20 @@ #include "DAP.h" #include "EventHelper.h" -#include "JSONUtils.h" +#include "LLDBUtils.h" +#include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" namespace lldb_dap { -// "PauseRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Pause request; value of command field is 'pause'. The -// request suspenses the debuggee. The debug adapter first sends the -// PauseResponse and then a StoppedEvent (event type 'pause') after the -// thread has been paused successfully.", "properties": { -// "command": { -// "type": "string", -// "enum": [ "pause" ] -// }, -// "arguments": { -// "$ref": "#/definitions/PauseArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "PauseArguments": { -// "type": "object", -// "description": "Arguments for 'pause' request.", -// "properties": { -// "threadId": { -// "type": "integer", -// "description": "Pause execution for this thread." -// } -// }, -// "required": [ "threadId" ] -// }, -// "PauseResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to 'pause' request. This is just an -// acknowledgement, so no body field is required." -// }] -// } -void PauseRequestHandler::operator()(const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); +/// The request suspenses the debuggee. The debug adapter first sends the +/// PauseResponse and then a StoppedEvent (event type 'pause') after the thread +/// has been paused successfully. +llvm::Error +PauseRequestHandler::Run(const protocol::PauseArguments &args) const { lldb::SBProcess process = dap.target.GetProcess(); lldb::SBError error = process.Stop(); - dap.SendJSON(llvm::json::Value(std::move(response))); + return ToError(error); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index 977a247..fdce33d 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -292,24 +292,31 @@ public: Run(const std::optional<protocol::DisconnectArguments> &args) const override; }; -class EvaluateRequestHandler : public LegacyRequestHandler { +class EvaluateRequestHandler + : public RequestHandler<protocol::EvaluateArguments, + llvm::Expected<protocol::EvaluateResponseBody>> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "evaluate"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected<protocol::EvaluateResponseBody> + Run(const protocol::EvaluateArguments &) const override; FeatureSet GetSupportedFeatures() const override { return {protocol::eAdapterFeatureEvaluateForHovers}; } }; -class ExceptionInfoRequestHandler : public LegacyRequestHandler { +class ExceptionInfoRequestHandler final + : public RequestHandler< + protocol::ExceptionInfoArguments, + llvm::Expected<protocol::ExceptionInfoResponseBody>> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "exceptionInfo"; } FeatureSet GetSupportedFeatures() const override { return {protocol::eAdapterFeatureExceptionInfoRequest}; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected<protocol::ExceptionInfoResponseBody> + Run(const protocol::ExceptionInfoArguments &args) const override; }; class InitializeRequestHandler @@ -428,6 +435,9 @@ class DataBreakpointInfoRequestHandler public: using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "dataBreakpointInfo"; } + FeatureSet GetSupportedFeatures() const override { + return {protocol::eAdapterFeatureDataBreakpointBytes}; + } llvm::Expected<protocol::DataBreakpointInfoResponseBody> Run(const protocol::DataBreakpointInfoArguments &args) const override; }; @@ -482,11 +492,12 @@ public: Run(const std::optional<protocol::ModulesArguments> &args) const override; }; -class PauseRequestHandler : public LegacyRequestHandler { +class PauseRequestHandler + : public RequestHandler<protocol::PauseArguments, protocol::PauseResponse> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "pause"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Error Run(const protocol::PauseArguments &args) const override; }; class ScopesRequestHandler final diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 2780a5b..40b4f5b 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -11,6 +11,7 @@ #include "ExceptionBreakpoint.h" #include "LLDBUtils.h" #include "Protocol/ProtocolBase.h" +#include "Protocol/ProtocolRequests.h" #include "ProtocolUtils.h" #include "lldb/API/SBAddress.h" #include "lldb/API/SBCompileUnit.h" @@ -553,9 +554,8 @@ llvm::json::Value CreateStackFrame(DAP &dap, lldb::SBFrame &frame, lldb::SBModule module = frame.GetModule(); if (module.IsValid()) { - std::string uuid = module.GetUUIDString(); - if (!uuid.empty()) - object.try_emplace("moduleId", uuid); + if (const llvm::StringRef uuid = module.GetUUIDString(); !uuid.empty()) + object.try_emplace("moduleId", uuid.str()); } return llvm::json::Value(std::move(object)); @@ -676,7 +676,14 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, EmplaceSafeString(body, "description", desc_str); } } break; - case lldb::eStopReasonWatchpoint: + case lldb::eStopReasonWatchpoint: { + body.try_emplace("reason", "data breakpoint"); + lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0); + body.try_emplace("hitBreakpointIds", + llvm::json::Array{llvm::json::Value(bp_id)}); + EmplaceSafeString(body, "description", + llvm::formatv("data breakpoint {0}", bp_id).str()); + } break; case lldb::eStopReasonInstrumentation: body.try_emplace("reason", "breakpoint"); break; @@ -711,7 +718,7 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, break; } if (stop_id == 0) - body.try_emplace("reason", "entry"); + body["reason"] = "entry"; const lldb::tid_t tid = thread.GetThreadID(); body.try_emplace("threadId", (int64_t)tid); // If no description has been set, then set it to the default thread stopped @@ -817,10 +824,10 @@ VariableDescription::VariableDescription(lldb::SBValue v, evaluate_name = llvm::StringRef(evaluateStream.GetData()).str(); } -std::string VariableDescription::GetResult(llvm::StringRef context) { +std::string VariableDescription::GetResult(protocol::EvaluateContext context) { // In repl context, the results can be displayed as multiple lines so more // detailed descriptions can be returned. - if (context != "repl") + if (context != protocol::eEvaluateContextRepl) return display_value; if (!v.IsValid()) diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 0c865a3..329dc8a 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -10,7 +10,7 @@ #define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H #include "DAPForward.h" -#include "Protocol/ProtocolTypes.h" +#include "Protocol/ProtocolRequests.h" #include "lldb/API/SBCompileUnit.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBType.h" @@ -28,7 +28,7 @@ namespace lldb_dap { -/// Emplace a StringRef in a json::Object after enusring that the +/// Emplace a StringRef in a json::Object after ensuring that the /// string is valid UTF8. If not, first call llvm::json::fixUTF8 /// before emplacing. /// @@ -351,7 +351,7 @@ struct VariableDescription { std::optional<std::string> custom_name = {}); /// Returns a description of the value appropriate for the specified context. - std::string GetResult(llvm::StringRef context); + std::string GetResult(protocol::EvaluateContext context); }; /// Does the given variable have an associated value location? diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp index 4db6caa..e2ba2ee 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.cpp +++ b/lldb/tools/lldb-dap/LLDBUtils.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "LLDBUtils.h" +#include "DAPError.h" #include "JSONUtils.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" @@ -17,6 +18,7 @@ #include "lldb/API/SBThread.h" #include "lldb/lldb-enumerations.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" @@ -214,13 +216,14 @@ GetStopDisassemblyDisplay(lldb::SBDebugger &debugger) { return result; } -llvm::Error ToError(const lldb::SBError &error) { +llvm::Error ToError(const lldb::SBError &error, bool show_user) { if (error.Success()) return llvm::Error::success(); - return llvm::createStringError( - std::error_code(error.GetError(), std::generic_category()), - error.GetCString()); + return llvm::make_error<DAPError>( + /*message=*/error.GetCString(), + /*EC=*/std::error_code(error.GetError(), std::generic_category()), + /*show_user=*/show_user); } std::string GetStringValue(const lldb::SBStructuredData &data) { diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h index 9db721a..a29d3d8 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.h +++ b/lldb/tools/lldb-dap/LLDBUtils.h @@ -243,7 +243,7 @@ private: lldb::StopDisassemblyType GetStopDisassemblyDisplay(lldb::SBDebugger &debugger); /// Take ownership of the stored error. -llvm::Error ToError(const lldb::SBError &error); +llvm::Error ToError(const lldb::SBError &error, bool show_user = true); /// Provides the string value if this data structure is a string type. std::string GetStringValue(const lldb::SBStructuredData &data); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h index 42c6c88..09ce680 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h @@ -31,11 +31,11 @@ namespace lldb_dap::protocol { // MARK: Base Protocol /// Message unique identifier type. -using Id = int64_t; +using Id = uint64_t; /// A unique identifier that indicates the `seq` field should be calculated by /// the current session. -static constexpr Id kCalculateSeq = INT64_MAX; +static constexpr Id kCalculateSeq = UINT64_MAX; /// A client or debug adapter initiated request. struct Request { diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index b939335..95ecc7e 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -8,6 +8,7 @@ #include "Protocol/ProtocolRequests.h" #include "JSONUtils.h" +#include "Protocol/ProtocolTypes.h" #include "lldb/lldb-defines.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringMap.h" @@ -215,12 +216,13 @@ bool fromJSON(const json::Value &Params, InitializeRequestArguments &IRA, } return OM.map("adapterID", IRA.adapterID) && - OM.map("clientID", IRA.clientID) && - OM.map("clientName", IRA.clientName) && OM.map("locale", IRA.locale) && - OM.map("linesStartAt1", IRA.linesStartAt1) && - OM.map("columnsStartAt1", IRA.columnsStartAt1) && + OM.mapOptional("clientID", IRA.clientID) && + OM.mapOptional("clientName", IRA.clientName) && + OM.mapOptional("locale", IRA.locale) && + OM.mapOptional("linesStartAt1", IRA.linesStartAt1) && + OM.mapOptional("columnsStartAt1", IRA.columnsStartAt1) && OM.mapOptional("pathFormat", IRA.pathFormat) && - OM.map("$__lldb_sourceInitFile", IRA.lldbExtSourceInitFile); + OM.mapOptional("$__lldb_sourceInitFile", IRA.lldbExtSourceInitFile); } bool fromJSON(const json::Value &Params, Configuration &C, json::Path P) { @@ -316,7 +318,9 @@ bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA, O.mapOptional("waitFor", ARA.waitFor) && O.mapOptional("gdb-remote-port", ARA.gdbRemotePort) && O.mapOptional("gdb-remote-hostname", ARA.gdbRemoteHostname) && - O.mapOptional("coreFile", ARA.coreFile); + O.mapOptional("coreFile", ARA.coreFile) && + O.mapOptional("targetId", ARA.targetId) && + O.mapOptional("debuggerId", ARA.debuggerId); } bool fromJSON(const json::Value &Params, ContinueArguments &CA, json::Path P) { @@ -625,4 +629,76 @@ llvm::json::Value toJSON(const ModuleSymbolsResponseBody &DGMSR) { return result; } +bool fromJSON(const json::Value &Params, ExceptionInfoArguments &Args, + json::Path Path) { + json::ObjectMapper O(Params, Path); + return O && O.map("threadId", Args.threadId); +} + +json::Value toJSON(const ExceptionInfoResponseBody &ERB) { + json::Object result{{"exceptionId", ERB.exceptionId}, + {"breakMode", ERB.breakMode}}; + + if (!ERB.description.empty()) + result.insert({"description", ERB.description}); + if (ERB.details.has_value()) + result.insert({"details", *ERB.details}); + return result; +} + +static bool fromJSON(const llvm::json::Value &Params, EvaluateContext &C, + llvm::json::Path P) { + auto rawContext = Params.getAsString(); + if (!rawContext) { + P.report("expected a string"); + return false; + } + C = StringSwitch<EvaluateContext>(*rawContext) + .Case("watch", EvaluateContext::eEvaluateContextWatch) + .Case("repl", EvaluateContext::eEvaluateContextRepl) + .Case("hover", EvaluateContext::eEvaluateContextHover) + .Case("clipboard", EvaluateContext::eEvaluateContextClipboard) + .Case("variables", EvaluateContext::eEvaluateContextVariables) + .Default(eEvaluateContextUnknown); + return true; +} + +bool fromJSON(const llvm::json::Value &Params, EvaluateArguments &Args, + llvm::json::Path P) { + json::ObjectMapper O(Params, P); + return O && O.map("expression", Args.expression) && + O.mapOptional("frameId", Args.frameId) && + O.mapOptional("line", Args.line) && + O.mapOptional("column", Args.column) && + O.mapOptional("source", Args.source) && + O.mapOptional("context", Args.context) && + O.mapOptional("format", Args.format); +} + +llvm::json::Value toJSON(const EvaluateResponseBody &Body) { + json::Object result{{"result", Body.result}, + {"variablesReference", Body.variablesReference}}; + + if (!Body.type.empty()) + result.insert({"type", Body.type}); + if (Body.presentationHint) + result.insert({"presentationHint", Body.presentationHint}); + if (Body.namedVariables) + result.insert({"namedVariables", Body.namedVariables}); + if (Body.indexedVariables) + result.insert({"indexedVariables", Body.indexedVariables}); + if (!Body.memoryReference.empty()) + result.insert({"memoryReference", Body.memoryReference}); + if (Body.valueLocationReference != LLDB_DAP_INVALID_VALUE_LOC) + result.insert({"valueLocationReference", Body.valueLocationReference}); + + return result; +} + +bool fromJSON(const llvm::json::Value &Params, PauseArguments &Args, + llvm::json::Path Path) { + json::ObjectMapper O(Params, Path); + return O && O.map("threadId", Args.threadId); +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index a85a68b..dc84e90 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -108,23 +108,23 @@ struct InitializeRequestArguments { std::string adapterID; /// The ID of the client using this adapter. - std::optional<std::string> clientID; + std::string clientID; /// The human-readable name of the client using this adapter. - std::optional<std::string> clientName; + std::string clientName; /// The ISO-639 locale of the client using this adapter, e.g. en-US or de-CH. - std::optional<std::string> locale; + std::string locale; /// Determines in what format paths are specified. The default is `path`, /// which is the native format. PathFormat pathFormat = ePatFormatPath; /// If true all line numbers are 1-based (default). - std::optional<bool> linesStartAt1; + bool linesStartAt1 = true; /// If true all column numbers are 1-based (default). - std::optional<bool> columnsStartAt1; + bool columnsStartAt1 = true; /// The set of supported features reported by the client. llvm::DenseSet<ClientFeature> supportedFeatures; @@ -133,7 +133,7 @@ struct InitializeRequestArguments { /// @{ /// Source init files when initializing lldb::SBDebugger. - std::optional<bool> lldbExtSourceInitFile; + bool lldbExtSourceInitFile = true; /// @} }; @@ -350,6 +350,12 @@ struct AttachRequestArguments { /// Path to the core file to debug. std::string coreFile; + /// Unique ID of an existing target to attach to. + std::optional<lldb::user_id_t> targetId; + + /// ID of an existing debugger instance to use. + std::optional<int> debuggerId; + /// @} }; bool fromJSON(const llvm::json::Value &, AttachRequestArguments &, @@ -1039,6 +1045,156 @@ struct ModuleSymbolsResponseBody { }; llvm::json::Value toJSON(const ModuleSymbolsResponseBody &); +struct ExceptionInfoArguments { + /// Thread for which exception information should be retrieved. + lldb::tid_t threadId = LLDB_INVALID_THREAD_ID; +}; +bool fromJSON(const llvm::json::Value &, ExceptionInfoArguments &, + llvm::json::Path); + +struct ExceptionInfoResponseBody { + /// ID of the exception that was thrown. + std::string exceptionId; + + /// Descriptive text for the exception. + std::string description; + + /// Mode that caused the exception notification to be raised. + ExceptionBreakMode breakMode = eExceptionBreakModeNever; + + /// Detailed information about the exception. + std::optional<ExceptionDetails> details; +}; +llvm::json::Value toJSON(const ExceptionInfoResponseBody &); + +/// The context in which the evaluate request is used. +enum EvaluateContext : unsigned { + /// An unspecified or unknown evaluate context. + eEvaluateContextUnknown = 0, + /// 'watch': evaluate is called from a watch view context. + eEvaluateContextWatch = 1, + /// 'repl': evaluate is called from a REPL context. + eEvaluateContextRepl = 2, + /// 'hover': evaluate is called to generate the debug hover contents. + /// This value should only be used if the corresponding capability + /// `supportsEvaluateForHovers` is true. + eEvaluateContextHover = 3, + /// 'clipboard': evaluate is called to generate clipboard contents. + /// This value should only be used if the corresponding capability + /// `supportsClipboardContext` is true. + eEvaluateContextClipboard = 4, + /// 'variables': evaluate is called from a variables view context. + eEvaluateContextVariables = 5, +}; + +/// Arguments for `evaluate` request. +struct EvaluateArguments { + /// The expression to evaluate. + std::string expression; + + /// Evaluate the expression in the scope of this stack frame. If not + /// specified, the expression is evaluated in the global scope. + uint64_t frameId = LLDB_DAP_INVALID_FRAME_ID; + + /// The contextual line where the expression should be evaluated. In the + /// 'hover' context, this should be set to the start of the expression being + /// hovered. + uint32_t line = LLDB_INVALID_LINE_NUMBER; + + /// The contextual column where the expression should be evaluated. This may + /// be provided if `line` is also provided. + /// + /// It is measured in UTF-16 code units and the client capability + /// `columnsStartAt1` determines whether it is 0- or 1-based. + uint32_t column = LLDB_INVALID_COLUMN_NUMBER; + + /// The contextual source in which the `line` is found. This must be provided + /// if `line` is provided. + std::optional<Source> source; + + /// The context in which the evaluate request is used. + /// Values: + /// 'watch': evaluate is called from a watch view context. + /// 'repl': evaluate is called from a REPL context. + /// 'hover': evaluate is called to generate the debug hover contents. + /// This value should only be used if the corresponding capability + /// `supportsEvaluateForHovers` is true. + /// 'clipboard': evaluate is called to generate clipboard contents. + /// This value should only be used if the corresponding capability + /// `supportsClipboardContext` is true. + /// 'variables': evaluate is called from a variables view context. + /// etc. + EvaluateContext context = eEvaluateContextUnknown; + + /// Specifies details on how to format the result. + /// The attribute is only honored by a debug adapter if the corresponding + /// capability `supportsValueFormattingOptions` is true. + std::optional<ValueFormat> format; +}; +bool fromJSON(const llvm::json::Value &, EvaluateArguments &, llvm::json::Path); + +/// Response to 'evaluate' request. +struct EvaluateResponseBody { + /// The result of the evaluate request. + std::string result; + + /// The type of the evaluate result. + /// This attribute should only be returned by a debug adapter if the + /// corresponding capability `supportsVariableType` is true. + std::string type; + + /// Properties of an evaluate result that can be used to determine how to + /// render the result in the UI. + std::optional<VariablePresentationHint> presentationHint; + + /// If `variablesReference` is > 0, the evaluate result is structured and its + /// children can be retrieved by passing `variablesReference` to the + /// `variables` request as long as execution remains suspended. See 'Lifetime + /// of Object References' in the Overview section for details. + int64_t variablesReference = 0; + + /// The number of named child variables. + /// The client can use this information to present the variables in a paged + /// UI and fetch them in chunks. + /// The value should be less than or equal to 2147483647 (2^31-1). + uint32_t namedVariables = 0; + + /// The number of indexed child variables. + /// The client can use this information to present the variables in a paged + /// UI and fetch them in chunks. + /// The value should be less than or equal to 2147483647 (2^31-1). + uint32_t indexedVariables = 0; + + /// A memory reference to a location appropriate for this result. + /// For pointer type eval results, this is generally a reference to the + /// memory address contained in the pointer. + /// This attribute may be returned by a debug adapter if corresponding + /// capability `supportsMemoryReferences` is true. + std::string memoryReference; + + /// A reference that allows the client to request the location where the + /// returned value is declared. For example, if a function pointer is + /// returned, the adapter may be able to look up the function's location. + /// This should be present only if the adapter is likely to be able to + /// resolve the location. + /// + /// This reference shares the same lifetime as the `variablesReference`. See + /// 'Lifetime of Object References' in the Overview section for details. + uint64_t valueLocationReference = LLDB_DAP_INVALID_VALUE_LOC; +}; +llvm::json::Value toJSON(const EvaluateResponseBody &); + +/// Arguments for `pause` request. +struct PauseArguments { + /// Pause execution for this thread. + lldb::tid_t threadId = LLDB_INVALID_THREAD_ID; +}; +bool fromJSON(const llvm::json::Value &, PauseArguments &, llvm::json::Path); + +/// Response to `pause` request. This is just an acknowledgement, so no body +/// field is required. +using PauseResponse = VoidResponse; + } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index dc8edaa..9500701 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -1136,4 +1136,37 @@ bool fromJSON(const json::Value &Param, Variable &V, json::Path Path) { Path, /*required=*/false); } +json::Value toJSON(const ExceptionBreakMode Mode) { + switch (Mode) { + case eExceptionBreakModeNever: + return "never"; + case eExceptionBreakModeAlways: + return "always"; + case eExceptionBreakModeUnhandled: + return "unhandled"; + case eExceptionBreakModeUserUnhandled: + return "userUnhandled"; + } + llvm_unreachable("unhandled exception breakMode."); +} + +json::Value toJSON(const ExceptionDetails &ED) { + json::Object result; + + if (!ED.message.empty()) + result.insert({"message", ED.message}); + if (!ED.typeName.empty()) + result.insert({"typeName", ED.typeName}); + if (!ED.fullTypeName.empty()) + result.insert({"fullTypeName", ED.fullTypeName}); + if (!ED.evaluateName.empty()) + result.insert({"evaluateName", ED.evaluateName}); + if (!ED.stackTrace.empty()) + result.insert({"stackTrace", ED.stackTrace}); + if (!ED.innerException.empty()) + result.insert({"innerException", ED.innerException}); + + return result; +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index 7077df9..ee103dd 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -28,8 +28,9 @@ #include <optional> #include <string> -#define LLDB_DAP_INVALID_VARRERF UINT64_MAX +#define LLDB_DAP_INVALID_VAR_REF INT64_MAX #define LLDB_DAP_INVALID_SRC_REF 0 +#define LLDB_DAP_INVALID_VALUE_LOC 0 namespace lldb_dap::protocol { @@ -461,7 +462,7 @@ struct Scope { /// remains suspended. See 'Lifetime of Object References' in the Overview /// section for details. //// - uint64_t variablesReference = LLDB_DAP_INVALID_VARRERF; + uint64_t variablesReference = LLDB_DAP_INVALID_VAR_REF; /// The number of named variables in this scope. /// The client can use this information to present the variables in a paged UI @@ -1007,6 +1008,36 @@ struct Variable { llvm::json::Value toJSON(const Variable &); bool fromJSON(const llvm::json::Value &, Variable &, llvm::json::Path); +enum ExceptionBreakMode : unsigned { + eExceptionBreakModeNever, + eExceptionBreakModeAlways, + eExceptionBreakModeUnhandled, + eExceptionBreakModeUserUnhandled, +}; +llvm::json::Value toJSON(ExceptionBreakMode); + +struct ExceptionDetails { + /// Message contained in the exception. + std::string message; + + /// Short type name of the exception object. + std::string typeName; + + /// Fully-qualified type name of the exception object. + std::string fullTypeName; + + /// An expression that can be evaluated in the current scope to obtain the + /// exception object. + std::string evaluateName; + + /// Stack trace at the time the exception was thrown. + std::string stackTrace; + + /// Details of the exception contained by this exception, if any. + std::vector<ExceptionDetails> innerException; +}; +llvm::json::Value toJSON(const ExceptionDetails &); + } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp index 868c67c..acf31b0 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.cpp +++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp @@ -27,7 +27,7 @@ using namespace lldb_dap::protocol; namespace lldb_dap { static bool ShouldDisplayAssemblySource( - lldb::SBAddress address, + lldb::SBLineEntry line_entry, lldb::StopDisassemblyType stop_disassembly_display) { if (stop_disassembly_display == lldb::eStopDisassemblyTypeNever) return false; @@ -37,7 +37,6 @@ static bool ShouldDisplayAssemblySource( // A line entry of 0 indicates the line is compiler generated i.e. no source // file is associated with the frame. - auto line_entry = address.GetLineEntry(); auto file_spec = line_entry.GetFileSpec(); if (!file_spec.IsValid() || line_entry.GetLine() == 0 || line_entry.GetLine() == LLDB_INVALID_LINE_NUMBER) @@ -174,10 +173,10 @@ bool IsAssemblySource(const protocol::Source &source) { } bool DisplayAssemblySource(lldb::SBDebugger &debugger, - lldb::SBAddress address) { + lldb::SBLineEntry line_entry) { const lldb::StopDisassemblyType stop_disassembly_display = GetStopDisassemblyDisplay(debugger); - return ShouldDisplayAssemblySource(address, stop_disassembly_display); + return ShouldDisplayAssemblySource(line_entry, stop_disassembly_display); } std::string GetLoadAddressString(const lldb::addr_t addr) { diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h b/lldb/tools/lldb-dap/ProtocolUtils.h index a1f7ae0..f4d576b 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.h +++ b/lldb/tools/lldb-dap/ProtocolUtils.h @@ -53,7 +53,8 @@ std::optional<protocol::Source> CreateSource(const lldb::SBFileSpec &file); /// Checks if the given source is for assembly code. bool IsAssemblySource(const protocol::Source &source); -bool DisplayAssemblySource(lldb::SBDebugger &debugger, lldb::SBAddress address); +bool DisplayAssemblySource(lldb::SBDebugger &debugger, + lldb::SBLineEntry line_entry); /// Get the address as a 16-digit hex string, e.g. "0x0000000000012345" std::string GetLoadAddressString(const lldb::addr_t addr); diff --git a/lldb/tools/lldb-dap/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp index 8f71f88..b351238 100644 --- a/lldb/tools/lldb-dap/Transport.cpp +++ b/lldb/tools/lldb-dap/Transport.cpp @@ -17,13 +17,13 @@ using namespace lldb_private; namespace lldb_dap { -Transport::Transport(llvm::StringRef client_name, lldb_dap::Log *log, - lldb::IOObjectSP input, lldb::IOObjectSP output) - : HTTPDelimitedJSONTransport(input, output), m_client_name(client_name), - m_log(log) {} +Transport::Transport(lldb_dap::Log &log, lldb::IOObjectSP input, + lldb::IOObjectSP output) + : HTTPDelimitedJSONTransport(input, output), m_log(log) {} void Transport::Log(llvm::StringRef message) { - DAP_LOG(m_log, "({0}) {1}", m_client_name, message); + // Emit the message directly, since this log was forwarded. + m_log.Emit(message); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Transport.h b/lldb/tools/lldb-dap/Transport.h index 58c48c1..b20a934 100644 --- a/lldb/tools/lldb-dap/Transport.h +++ b/lldb/tools/lldb-dap/Transport.h @@ -35,15 +35,14 @@ class Transport final : public lldb_private::transport::HTTPDelimitedJSONTransport< ProtocolDescriptor> { public: - Transport(llvm::StringRef client_name, lldb_dap::Log *log, - lldb::IOObjectSP input, lldb::IOObjectSP output); + Transport(lldb_dap::Log &log, lldb::IOObjectSP input, + lldb::IOObjectSP output); virtual ~Transport() = default; void Log(llvm::StringRef message) override; private: - llvm::StringRef m_client_name; - lldb_dap::Log *m_log; + lldb_dap::Log &m_log; }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Watchpoint.cpp b/lldb/tools/lldb-dap/Watchpoint.cpp index 0acc980..e730e71 100644 --- a/lldb/tools/lldb-dap/Watchpoint.cpp +++ b/lldb/tools/lldb-dap/Watchpoint.cpp @@ -45,6 +45,7 @@ protocol::Breakpoint Watchpoint::ToProtocolBreakpoint() { breakpoint.message = m_error.GetCString(); } else { breakpoint.verified = true; + breakpoint.id = m_wp.GetID(); } return breakpoint; diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 05dce28..8e07c55 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -778,6 +778,10 @@ "description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail.", "default": [] }, + "targetId": { + "type": "number", + "description": "The globally unique target id to attach to. Used when a target is dynamically created." + }, "initCommands": { "type": "array", "items": { diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts index 7060638..433d48fa 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts @@ -6,6 +6,7 @@ import * as fs from "node:fs/promises"; import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message"; import { ErrorWithNotification } from "./ui/error-with-notification"; import { LogFilePathProvider, LogType } from "./logging"; +import { expandUser } from "./utils"; const exec = util.promisify(child_process.execFile); @@ -116,8 +117,9 @@ async function getDAPExecutable( configuration: vscode.DebugConfiguration, ): Promise<string> { // Check if the executable was provided in the launch configuration. - const launchConfigPath = configuration["debugAdapterExecutable"]; + let launchConfigPath = configuration["debugAdapterExecutable"]; if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) { + launchConfigPath = expandUser(launchConfigPath); if (!(await isExecutable(launchConfigPath))) { throw new ErrorWithNotification( `Debug adapter path "${launchConfigPath}" is not a valid file. The path comes from your launch configuration.`, @@ -129,7 +131,7 @@ async function getDAPExecutable( // Check if the executable was provided in the extension's configuration. const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder); - const configPath = config.get<string>("executable-path"); + const configPath = expandUser(config.get<string>("executable-path") ?? ""); if (configPath && configPath.length !== 0) { if (!(await isExecutable(configPath))) { throw new ErrorWithNotification( diff --git a/lldb/tools/lldb-dap/src-ts/utils.ts b/lldb/tools/lldb-dap/src-ts/utils.ts new file mode 100644 index 0000000..efebe0b --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/utils.ts @@ -0,0 +1,41 @@ +import * as os from "os"; +import * as path from "path"; + +/** + * Expands the character `~` to the user's home directory + */ +export function expandUser(file_path: string): string { + if (os.platform() == "win32") { + return file_path; + } + + if (!file_path) { + return ""; + } + + if (!file_path.startsWith("~")) { + return file_path; + } + + const path_len = file_path.length; + if (path_len == 1) { + return os.homedir(); + } + + if (file_path.charAt(1) == path.sep) { + return path.join(os.homedir(), file_path.substring(1)); + } + + const sep_index = file_path.indexOf(path.sep); + const user_name_end = sep_index == -1 ? file_path.length : sep_index; + const user_name = file_path.substring(1, user_name_end); + try { + if (user_name == os.userInfo().username) { + return path.join(os.homedir(), file_path.substring(user_name_end)); + } + } catch (err) { + return file_path; + } + + return file_path; +} diff --git a/lldb/tools/lldb-dap/tool/CMakeLists.txt b/lldb/tools/lldb-dap/tool/CMakeLists.txt index b39a4ed..5335d25 100644 --- a/lldb/tools/lldb-dap/tool/CMakeLists.txt +++ b/lldb/tools/lldb-dap/tool/CMakeLists.txt @@ -1,3 +1,7 @@ +set(LLVM_TARGET_DEFINITIONS Options.td) +tablegen(LLVM Options.inc -gen-opt-parser-defs) +add_public_tablegen_target(LLDBDAPOptionsTableGen) + add_lldb_tool(lldb-dap lldb-dap.cpp diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/tool/Options.td index 5e9dd7a..339a64f 100644 --- a/lldb/tools/lldb-dap/Options.td +++ b/lldb/tools/lldb-dap/tool/Options.td @@ -82,3 +82,11 @@ def connection_timeout: S<"connection-timeout">, "timeout is reached, the server will be closed and the process will exit. " "Not specifying this argument or specifying non-positive values will " "cause the server to wait for new connections indefinitely.">; + +def client + : S<"client">, + MetaVarName<"<client>">, + HelpText< + "Use lldb-dap as a launcher for a curated number of DAP client.">; + +def REM : R<["--"], "">; diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index 45caa1a..6d4eaf1 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -6,10 +6,12 @@ // //===----------------------------------------------------------------------===// +#include "ClientLauncher.h" #include "DAP.h" #include "DAPLog.h" #include "EventHelper.h" #include "Handler/RequestHandler.h" +#include "Handler/ResponseHandler.h" #include "RunInTerminal.h" #include "Transport.h" #include "lldb/API/SBDebugger.h" @@ -49,9 +51,7 @@ #include <cstddef> #include <cstdio> #include <cstdlib> -#include <exception> #include <fcntl.h> -#include <map> #include <memory> #include <mutex> #include <string> @@ -141,6 +141,12 @@ EXAMPLES: debugger to attach to the process. lldb-dap -g + + You can also use lldb-dap to launch a supported client, for example the + LLDB-DAP Visual Studio Code extension. + + lldb-dap --client vscode -- /path/to/binary <args> + )___"; } @@ -150,6 +156,29 @@ static void PrintVersion() { llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n'; } +static llvm::Error LaunchClient(const llvm::opt::InputArgList &args) { + auto *client_arg = args.getLastArg(OPT_client); + assert(client_arg && "must have client arg"); + + std::optional<ClientLauncher::Client> client = + ClientLauncher::GetClientFrom(client_arg->getValue()); + if (!client) + return llvm::createStringError( + llvm::formatv("unsupported client: {0}", client_arg->getValue())); + + std::vector<llvm::StringRef> launch_args; + if (auto *arg = args.getLastArgNoClaim(OPT_REM)) { + for (auto *value : arg->getValues()) { + launch_args.push_back(value); + } + } + + if (launch_args.empty()) + return llvm::createStringError("no launch arguments provided"); + + return ClientLauncher::GetLauncher(*client)->Launch(launch_args); +} + #if not defined(_WIN32) struct FDGroup { int GetFlags() const { @@ -380,7 +409,7 @@ validateConnection(llvm::StringRef conn) { } static llvm::Error serveConnection( - const Socket::SocketProtocol &protocol, const std::string &name, Log *log, + const Socket::SocketProtocol &protocol, llvm::StringRef name, Log &log, const ReplMode default_repl_mode, const std::vector<std::string> &pre_init_commands, bool no_lldbinit, std::optional<std::chrono::seconds> connection_timeout_seconds) { @@ -415,12 +444,8 @@ static llvm::Error serveConnection( g_connection_timeout_time_point, connection_timeout_seconds.value()); std::condition_variable dap_sessions_condition; - std::mutex dap_sessions_mutex; - std::map<MainLoop *, DAP *> dap_sessions; unsigned int clientCount = 0; - auto handle = listener->Accept(g_loop, [=, &dap_sessions_condition, - &dap_sessions_mutex, &dap_sessions, - &clientCount]( + auto handle = listener->Accept(g_loop, [=, &log, &clientCount]( std::unique_ptr<Socket> sock) { // Reset the keep alive timer, because we won't be killing the server // while this connection is being served. @@ -428,18 +453,19 @@ static llvm::Error serveConnection( ResetConnectionTimeout(g_connection_timeout_mutex, g_connection_timeout_time_point); std::string client_name = llvm::formatv("client_{0}", clientCount++).str(); - DAP_LOG(log, "({0}) client connected", client_name); - lldb::IOObjectSP io(std::move(sock)); // Move the client into a background thread to unblock accepting the next // client. - std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex, - &dap_sessions]() { + std::thread client([=, &log]() { llvm::set_thread_name(client_name + ".runloop"); + + Log client_log = log.WithPrefix("(" + client_name + ")"); + DAP_LOG(client_log, "client connected"); + MainLoop loop; - Transport transport(client_name, log, io, io); - DAP dap(log, default_repl_mode, pre_init_commands, no_lldbinit, + Transport transport(client_log, io, io); + DAP dap(client_log, default_repl_mode, pre_init_commands, no_lldbinit, client_name, transport, loop); if (auto Err = dap.ConfigureIO()) { @@ -448,10 +474,8 @@ static llvm::Error serveConnection( return; } - { - std::scoped_lock<std::mutex> lock(dap_sessions_mutex); - dap_sessions[&loop] = &dap; - } + // Register the DAP session with the global manager. + DAPSessionManager::GetInstance().RegisterSession(&loop, &dap); if (auto Err = dap.Loop()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -459,11 +483,9 @@ static llvm::Error serveConnection( ") error: "); } - DAP_LOG(log, "({0}) client disconnected", client_name); - std::unique_lock<std::mutex> lock(dap_sessions_mutex); - dap_sessions.erase(&loop); - std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock)); - + DAP_LOG(client_log, "client disconnected"); + // Unregister the DAP session from the global manager. + DAPSessionManager::GetInstance().UnregisterSession(&loop); // Start the countdown to kill the server at the end of each connection. if (connection_timeout_seconds) TrackConnectionTimeout(g_loop, g_connection_timeout_mutex, @@ -482,33 +504,13 @@ static llvm::Error serveConnection( return status.takeError(); } - DAP_LOG( - log, - "lldb-dap server shutdown requested, disconnecting remaining clients..."); - - bool client_failed = false; - { - std::scoped_lock<std::mutex> lock(dap_sessions_mutex); - for (auto [loop, dap] : dap_sessions) { - if (llvm::Error error = dap->Disconnect()) { - client_failed = true; - llvm::WithColor::error() << "DAP client disconnected failed: " - << llvm::toString(std::move(error)) << "\n"; - } - loop->AddPendingCallback( - [](MainLoopBase &loop) { loop.RequestTermination(); }); - } - } - - // Wait for all clients to finish disconnecting. - std::unique_lock<std::mutex> lock(dap_sessions_mutex); - dap_sessions_condition.wait(lock, [&] { return dap_sessions.empty(); }); + DAP_LOG(log, "server shutting down, disconnecting remaining clients"); - if (client_failed) - return llvm::make_error<llvm::StringError>( - "disconnecting all clients failed", llvm::inconvertibleErrorCode()); + // Disconnect all active sessions using the global manager. + DAPSessionManager::GetInstance().DisconnectAllSessions(); - return llvm::Error::success(); + // Wait for all clients to finish disconnecting and return any errors. + return DAPSessionManager::GetInstance().WaitForAllSessionsToDisconnect(); } int main(int argc, char *argv[]) { @@ -541,6 +543,14 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } + if (input_args.hasArg(OPT_client)) { + if (llvm::Error error = LaunchClient(input_args)) { + llvm::WithColor::error() << llvm::toString(std::move(error)) << '\n'; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + ReplMode default_repl_mode = ReplMode::Auto; if (input_args.hasArg(OPT_repl_mode)) { llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode); @@ -631,17 +641,19 @@ int main(int argc, char *argv[]) { } #endif - std::unique_ptr<Log> log = nullptr; - const char *log_file_path = getenv("LLDBDAP_LOG"); - if (log_file_path) { - std::error_code EC; - log = std::make_unique<Log>(log_file_path, EC); - if (EC) { - llvm::logAllUnhandledErrors(llvm::errorCodeToError(EC), llvm::errs(), - "Failed to create log file: "); + std::unique_ptr<llvm::raw_ostream> log_os; + if (const char *log_file_path = getenv("LLDBDAP_LOG"); log_file_path) { + int FD; + if (std::error_code EC = + llvm::sys::fs::openFileForWrite(log_file_path, FD)) { + llvm::errs() << "Failed to open log file: " << log_file_path << ": " + << EC.message() << "\n"; return EXIT_FAILURE; } + log_os = std::make_unique<llvm::raw_fd_ostream>(FD, /*shouldClose=*/true); } + Log::Mutex mutex; + Log log(log_os ? *log_os : llvm::nulls(), mutex); // Initialize LLDB first before we do anything. lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling(); @@ -655,7 +667,7 @@ int main(int argc, char *argv[]) { // Create a memory monitor. This can return nullptr if the host platform is // not supported. std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor = - lldb_private::MemoryMonitor::Create([log = log.get()]() { + lldb_private::MemoryMonitor::Create([&log]() { DAP_LOG(log, "memory pressure detected"); lldb::SBDebugger::MemoryPressureDetected(); }); @@ -689,7 +701,7 @@ int main(int argc, char *argv[]) { Socket::SocketProtocol protocol; std::string name; std::tie(protocol, name) = *maybeProtoclAndName; - if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode, + if (auto Err = serveConnection(protocol, name, log, default_repl_mode, pre_init_commands, no_lldbinit, connection_timeout_seconds)) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -726,8 +738,9 @@ int main(int argc, char *argv[]) { constexpr llvm::StringLiteral client_name = "stdio"; MainLoop loop; - Transport transport(client_name, log.get(), input, output); - DAP dap(log.get(), default_repl_mode, pre_init_commands, no_lldbinit, + Log client_log = log.WithPrefix("(stdio)"); + Transport transport(client_log, input, output); + DAP dap(client_log, default_repl_mode, pre_init_commands, no_lldbinit, client_name, transport, loop); // stdout/stderr redirection to the IDE's console @@ -737,16 +750,22 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } + // Register the DAP session with the global manager for stdio mode. + // This is needed for the event handling to find the correct DAP instance. + DAPSessionManager::GetInstance().RegisterSession(&loop, &dap); + // used only by TestVSCode_redirection_to_console.py if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr) redirection_test(); if (auto Err = dap.Loop()) { - DAP_LOG(log.get(), "({0}) DAP session error: {1}", client_name, + DAP_LOG(client_log, "DAP session error: {0}", llvm::toStringWithoutConsuming(Err)); llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "DAP session error: "); + DAPSessionManager::GetInstance().UnregisterSession(&loop); return EXIT_FAILURE; } + DAPSessionManager::GetInstance().UnregisterSession(&loop); return EXIT_SUCCESS; } diff --git a/lldb/tools/lldb-server/CMakeLists.txt b/lldb/tools/lldb-server/CMakeLists.txt index 1d8dc72..fb55c64 100644 --- a/lldb/tools/lldb-server/CMakeLists.txt +++ b/lldb/tools/lldb-server/CMakeLists.txt @@ -2,6 +2,10 @@ set(LLVM_TARGET_DEFINITIONS LLGSOptions.td) tablegen(LLVM LLGSOptions.inc -gen-opt-parser-defs) add_public_tablegen_target(LLGSOptionsTableGen) +set(LLVM_TARGET_DEFINITIONS PlatformOptions.td) +tablegen(LLVM PlatformOptions.inc -gen-opt-parser-defs) +add_public_tablegen_target(PlatformOptionsTableGen) + set(LLDB_PLUGINS) if(CMAKE_SYSTEM_NAME MATCHES "Linux|Android") @@ -67,6 +71,7 @@ add_lldb_tool(lldb-server add_dependencies(lldb-server LLGSOptionsTableGen + PlatformOptionsTableGen ${tablegen_deps} ) target_include_directories(lldb-server PRIVATE "${LLDB_SOURCE_DIR}/source") diff --git a/lldb/tools/lldb-server/PlatformOptions.td b/lldb/tools/lldb-server/PlatformOptions.td new file mode 100644 index 0000000..eedd1d8 --- /dev/null +++ b/lldb/tools/lldb-server/PlatformOptions.td @@ -0,0 +1,75 @@ +include "llvm/Option/OptParser.td" + +class F<string name>: Flag<["--", "-"], name>; +class R<list<string> prefixes, string name> + : Option<prefixes, name, KIND_REMAINING_ARGS>; + +multiclass SJ<string name, string help> { + def NAME: Separate<["--", "-"], name>, + HelpText<help>; + def NAME # _eq: Joined<["--", "-"], name # "=">, + Alias<!cast<Separate>(NAME)>; +} + +def grp_connect : OptionGroup<"connection">, HelpText<"CONNECTION OPTIONS">; + +defm listen: SJ<"listen", "Host and port to listen on. Format: [host]:port or protocol://[host]:port (e.g., tcp://localhost:1234, unix:///path/to/socket). Short form: -L">, + MetaVarName<"<[host]:port>">, + Group<grp_connect>; +def: Separate<["-"], "L">, Alias<listen>, + Group<grp_connect>; + +defm socket_file: SJ<"socket-file", "Write listening socket information (port number for TCP or path for Unix domain sockets) to the specified file. Short form: -f">, + MetaVarName<"<path>">, + Group<grp_connect>; +def: Separate<["-"], "f">, Alias<socket_file>, + Group<grp_connect>; + +defm gdbserver_port: SJ<"gdbserver-port", "Port to use for spawned gdbserver instances. If 0 or unspecified, a port will be chosen automatically. Short form: -P">, + MetaVarName<"<port>">, + Group<grp_connect>; +def: Separate<["-"], "P">, Alias<gdbserver_port>, + Group<grp_connect>; + +defm child_platform_fd: SJ<"child-platform-fd", "File descriptor for communication with parent platform process (internal use only).">, + MetaVarName<"<fd>">, + Group<grp_connect>, + Flags<[HelpHidden]>; + +def grp_general : OptionGroup<"general options">, HelpText<"GENERAL OPTIONS">; + +def server: F<"server">, + HelpText<"Run in server mode, accepting multiple client connections sequentially. Without this flag, the server exits after handling the first connection.">, + Group<grp_general>; + +defm log_channels: SJ<"log-channels", "Channels to log. A colon-separated list of entries. Each entry starts with a channel followed by a space-separated list of categories. Common channels: lldb, gdb-remote, platform, process. Short form: -c">, + MetaVarName<"<channel1 categories...:channel2 categories...>">, + Group<grp_general>; +def: Separate<["-"], "c">, Alias<log_channels>, + Group<grp_general>; + +defm log_file: SJ<"log-file", "Destination file to log to. If empty, log to stderr. Short form: -l">, + MetaVarName<"<file>">, + Group<grp_general>; +def: Separate<["-"], "l">, Alias<log_file>, + Group<grp_general>; + +def debug: F<"debug">, + HelpText<"(Unused, kept for backward compatibility)">, + Group<grp_general>, + Flags<[HelpHidden]>; + +def verbose: F<"verbose">, + HelpText<"(Unused, kept for backward compatibility)">, + Group<grp_general>, + Flags<[HelpHidden]>; + +def help: F<"help">, + HelpText<"Display this help message and exit.">, + Group<grp_general>; +def: Flag<["-"], "h">, Alias<help>, + Group<grp_general>; + +def REM : R<["--"], "">, + HelpText<"Arguments to pass to launched gdbserver instances.">, + MetaVarName<"program args">; diff --git a/lldb/tools/lldb-server/lldb-platform.cpp b/lldb/tools/lldb-server/lldb-platform.cpp index 0bd9285..59b1eb4 100644 --- a/lldb/tools/lldb-server/lldb-platform.cpp +++ b/lldb/tools/lldb-server/lldb-platform.cpp @@ -21,6 +21,9 @@ #include <fstream> #include <optional> +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/WithColor.h" @@ -56,22 +59,69 @@ using namespace llvm; // of target CPUs. For now, let's just use 100. static const int backlog = 100; static const int socket_error = -1; -static int g_debug = 0; -static int g_verbose = 0; -static int g_server = 0; - -// option descriptors for getopt_long_only() -static struct option g_long_options[] = { - {"debug", no_argument, &g_debug, 1}, - {"verbose", no_argument, &g_verbose, 1}, - {"log-file", required_argument, nullptr, 'l'}, - {"log-channels", required_argument, nullptr, 'c'}, - {"listen", required_argument, nullptr, 'L'}, - {"gdbserver-port", required_argument, nullptr, 'P'}, - {"socket-file", required_argument, nullptr, 'f'}, - {"server", no_argument, &g_server, 1}, - {"child-platform-fd", required_argument, nullptr, 2}, - {nullptr, 0, nullptr, 0}}; + +namespace { +using namespace llvm::opt; + +enum ID { + OPT_INVALID = 0, // This is not an option ID. +#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), +#include "PlatformOptions.inc" +#undef OPTION +}; + +#define OPTTABLE_STR_TABLE_CODE +#include "PlatformOptions.inc" +#undef OPTTABLE_STR_TABLE_CODE + +#define OPTTABLE_PREFIXES_TABLE_CODE +#include "PlatformOptions.inc" +#undef OPTTABLE_PREFIXES_TABLE_CODE + +static constexpr opt::OptTable::Info InfoTable[] = { +#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), +#include "PlatformOptions.inc" +#undef OPTION +}; + +class PlatformOptTable : public opt::GenericOptTable { +public: + PlatformOptTable() + : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {} + + void PrintHelp(llvm::StringRef Name) { + std::string Usage = + (Name + " [options] --listen <[host]:port> [[--] program args...]") + .str(); + + std::string Title = "lldb-server platform"; + + OptTable::printHelp(llvm::outs(), Usage.c_str(), Title.c_str()); + + llvm::outs() << R"( +DESCRIPTION + Acts as a platform server for remote debugging. When LLDB clients connect, + the platform server handles platform operations (file transfers, process + launching) and spawns debug server instances (lldb-server gdbserver) to + handle actual debugging sessions. + + By default, the server exits after handling one connection. Use --server + to keep running and accept multiple connections sequentially. + +EXAMPLES + # Listen on port 1234, exit after first connection + lldb-server platform --listen tcp://0.0.0.0:1234 + + # Listen on port 5555, accept multiple connections + lldb-server platform --server --listen tcp://localhost:5555 + + # Listen on Unix domain socket + lldb-server platform --listen unix:///tmp/lldb-server.sock + +)"; + } +}; +} // namespace #if defined(__APPLE__) #define LOW_PORT (IPPORT_RESERVED) @@ -97,12 +147,11 @@ static void signal_handler(int signo) { } #endif -static void display_usage(const char *progname, const char *subcommand) { - fprintf(stderr, "Usage:\n %s %s [--log-file log-file-name] [--log-channels " - "log-channel-list] [--port-file port-file-path] --server " - "--listen port\n", - progname, subcommand); - exit(0); +static void display_usage(PlatformOptTable &Opts, const char *progname, + const char *subcommand) { + std::string Name = + (llvm::sys::path::filename(progname) + " " + subcommand).str(); + Opts.PrintHelp(Name); } static Status parse_listen_host_port(Socket::SocketProtocol &protocol, @@ -261,7 +310,8 @@ static Status spawn_process(const char *progname, const FileSpec &prog, const Socket *conn_socket, uint16_t gdb_port, const lldb_private::Args &args, const std::string &log_file, - const StringRef log_channels, MainLoop &main_loop) { + const StringRef log_channels, MainLoop &main_loop, + bool multi_client) { Status error; SharedSocket shared_socket(conn_socket, error); if (error.Fail()) @@ -297,9 +347,12 @@ static Status spawn_process(const char *progname, const FileSpec &prog, launch_info.SetLaunchInSeparateProcessGroup(false); - if (g_server) + // Set up process monitor callback based on whether we're in server mode. + if (multi_client) + // In server mode: empty callback (don't terminate when child exits). launch_info.SetMonitorProcessCallback([](lldb::pid_t, int, int) {}); else + // In single-client mode: terminate main loop when child exits. launch_info.SetMonitorProcessCallback([&main_loop](lldb::pid_t, int, int) { main_loop.AddPendingCallback( [](MainLoopBase &loop) { loop.RequestTermination(); }); @@ -371,107 +424,101 @@ int main_platform(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); signal(SIGHUP, signal_handler); #endif - int long_option_index = 0; - Status error; - std::string listen_host_port; - int ch; - std::string log_file; - StringRef - log_channels; // e.g. "lldb process threads:gdb-remote default:linux all" + // Special handling for 'help' as first argument. + if (argc > 0 && strcmp(argv[0], "help") == 0) { + PlatformOptTable Opts; + display_usage(Opts, progname, subcommand); + return EXIT_SUCCESS; + } + Status error; shared_fd_t fd = SharedSocket::kInvalidFD; - uint16_t gdbserver_port = 0; - FileSpec socket_file; - bool show_usage = false; - int option_error = 0; - std::string short_options(OptionParser::GetShortOptionString(g_long_options)); + PlatformOptTable Opts; + BumpPtrAllocator Alloc; + StringSaver Saver(Alloc); + bool HasError = false; -#if __GLIBC__ - optind = 0; -#else - optreset = 1; - optind = 1; -#endif + opt::InputArgList Args = + Opts.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](llvm::StringRef Msg) { + WithColor::error() << Msg << "\n"; + HasError = true; + }); - while ((ch = getopt_long_only(argc, argv, short_options.c_str(), - g_long_options, &long_option_index)) != -1) { - switch (ch) { - case 0: // Any optional that auto set themselves will return 0 - break; + std::string Name = + (llvm::sys::path::filename(progname) + " " + subcommand).str(); + std::string HelpText = + "Use '" + Name + " --help' for a complete list of options.\n"; - case 'L': - listen_host_port.append(optarg); - break; + if (HasError) { + llvm::errs() << HelpText; + return EXIT_FAILURE; + } - case 'l': // Set Log File - if (optarg && optarg[0]) - log_file.assign(optarg); - break; + if (Args.hasArg(OPT_help)) { + display_usage(Opts, progname, subcommand); + return EXIT_SUCCESS; + } - case 'c': // Log Channels - if (optarg && optarg[0]) - log_channels = StringRef(optarg); - break; + // Parse arguments. + std::string listen_host_port = Args.getLastArgValue(OPT_listen).str(); + std::string log_file = Args.getLastArgValue(OPT_log_file).str(); + StringRef log_channels = Args.getLastArgValue(OPT_log_channels); + bool multi_client = Args.hasArg(OPT_server); + [[maybe_unused]] bool debug = Args.hasArg(OPT_debug); + [[maybe_unused]] bool verbose = Args.hasArg(OPT_verbose); + + if (Args.hasArg(OPT_socket_file)) { + socket_file.SetFile(Args.getLastArgValue(OPT_socket_file), + FileSpec::Style::native); + } - case 'f': // Socket file - if (optarg && optarg[0]) - socket_file.SetFile(optarg, FileSpec::Style::native); - break; + if (Args.hasArg(OPT_gdbserver_port)) { + if (!llvm::to_integer(Args.getLastArgValue(OPT_gdbserver_port), + gdbserver_port)) { + WithColor::error() << "invalid --gdbserver-port value\n"; + return EXIT_FAILURE; + } + } - case 'P': - case 'm': - case 'M': { - uint16_t portnum; - if (!llvm::to_integer(optarg, portnum)) { - WithColor::error() << "invalid port number string " << optarg << "\n"; - option_error = 2; - break; - } - // Note the condition gdbserver_port > HIGH_PORT is valid in case of using - // --child-platform-fd. Check gdbserver_port later. - if (ch == 'P') - gdbserver_port = portnum; - else if (gdbserver_port == 0) - gdbserver_port = portnum; - } break; - - case 2: { - uint64_t _fd; - if (!llvm::to_integer(optarg, _fd)) { - WithColor::error() << "invalid fd " << optarg << "\n"; - option_error = 6; - } else - fd = (shared_fd_t)_fd; - } break; - - case 'h': /* fall-through is intentional */ - case '?': - show_usage = true; - break; + if (Args.hasArg(OPT_child_platform_fd)) { + uint64_t _fd; + if (!llvm::to_integer(Args.getLastArgValue(OPT_child_platform_fd), _fd)) { + WithColor::error() << "invalid --child-platform-fd value\n"; + return EXIT_FAILURE; } + fd = (shared_fd_t)_fd; } if (!LLDBServerUtilities::SetupLogging(log_file, log_channels, 0)) return -1; // Print usage and exit if no listening port is specified. - if (listen_host_port.empty() && fd == SharedSocket::kInvalidFD) - show_usage = true; + if (listen_host_port.empty() && fd == SharedSocket::kInvalidFD) { + WithColor::error() << "either --listen or --child-platform-fd is required\n" + << HelpText; + return EXIT_FAILURE; + } - if (show_usage || option_error) { - display_usage(progname, subcommand); - exit(option_error); + // Get remaining arguments for inferior. + std::vector<llvm::StringRef> Inputs; + for (opt::Arg *Arg : Args.filtered(OPT_INPUT)) + Inputs.push_back(Arg->getValue()); + if (opt::Arg *Arg = Args.getLastArg(OPT_REM)) { + for (const char *Val : Arg->getValues()) + Inputs.push_back(Val); } - // Skip any options we consumed with getopt_long_only. - argc -= optind; - argv += optind; lldb_private::Args inferior_arguments; - inferior_arguments.SetArguments(argc, const_cast<const char **>(argv)); + if (!Inputs.empty()) { + std::vector<const char *> args_ptrs; + for (const auto &Input : Inputs) + args_ptrs.push_back(Input.data()); + inferior_arguments.SetArguments(args_ptrs.size(), args_ptrs.data()); + } FileSpec debugserver_path = GetDebugserverPath(); if (!debugserver_path) { @@ -514,7 +561,7 @@ int main_platform(int argc, char *argv[]) { platform.SetConnection( std::make_unique<ConnectionFileDescriptor>(std::move(socket))); client_handle(platform, inferior_arguments); - return 0; + return EXIT_SUCCESS; } if (gdbserver_port != 0 && @@ -522,7 +569,7 @@ int main_platform(int argc, char *argv[]) { WithColor::error() << llvm::formatv("Port number {0} is not in the " "valid user port range of {1} - {2}\n", gdbserver_port, LOW_PORT, HIGH_PORT); - return 1; + return EXIT_FAILURE; } Socket::SocketProtocol protocol = Socket::ProtocolUnixDomain; @@ -559,7 +606,7 @@ int main_platform(int argc, char *argv[]) { if (error.Fail()) { fprintf(stderr, "failed to write socket id to %s: %s\n", socket_file.GetPath().c_str(), error.AsCString()); - return 1; + return EXIT_FAILURE; } } @@ -577,22 +624,22 @@ int main_platform(int argc, char *argv[]) { llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>> platform_handles = platform_sock->Accept( main_loop, [progname, gdbserver_port, &inferior_arguments, log_file, - log_channels, &main_loop, + log_channels, &main_loop, multi_client, &platform_handles](std::unique_ptr<Socket> sock_up) { printf("Connection established.\n"); Status error = spawn_process( progname, HostInfo::GetProgramFileSpec(), sock_up.get(), gdbserver_port, inferior_arguments, log_file, log_channels, - main_loop); + main_loop, multi_client); if (error.Fail()) { Log *log = GetLog(LLDBLog::Platform); LLDB_LOGF(log, "spawn_process failed: %s", error.AsCString()); WithColor::error() << "spawn_process failed: " << error.AsCString() << "\n"; - if (!g_server) + if (!multi_client) main_loop.RequestTermination(); } - if (!g_server) + if (!multi_client) platform_handles->clear(); }); if (!platform_handles) { @@ -616,5 +663,5 @@ int main_platform(int argc, char *argv[]) { fprintf(stderr, "lldb-server exiting...\n"); - return 0; + return EXIT_SUCCESS; } diff --git a/lldb/tools/lldb-test/lldb-test.cpp b/lldb/tools/lldb-test/lldb-test.cpp index 3f198d9..84e83da 100644 --- a/lldb/tools/lldb-test/lldb-test.cpp +++ b/lldb/tools/lldb-test/lldb-test.cpp @@ -523,9 +523,10 @@ Error opts::symbols::findFunctions(lldb_private::Module &Module) { ContextOr->IsValid() ? *ContextOr : CompilerDeclContext(); List.Clear(); - lldb_private::Module::LookupInfo lookup_info( - ConstString(Name), getFunctionNameFlags(), eLanguageTypeUnknown); - Symfile.FindFunctions(lookup_info, ContextPtr, true, List); + std::vector<lldb_private::Module::LookupInfo> lookup_infos = + lldb_private::Module::LookupInfo::MakeLookupInfos( + ConstString(Name), getFunctionNameFlags(), eLanguageTypeUnknown); + Symfile.FindFunctions(lookup_infos, ContextPtr, true, List); } outs() << formatv("Found {0} functions:\n", List.GetSize()); StreamString Stream; |
