aboutsummaryrefslogtreecommitdiff
path: root/lldb/tools
diff options
context:
space:
mode:
Diffstat (limited to 'lldb/tools')
-rw-r--r--lldb/tools/debugserver/source/DNB.cpp2
-rw-r--r--lldb/tools/debugserver/source/MacOSX/MachProcess.h14
-rw-r--r--lldb/tools/debugserver/source/MacOSX/MachProcess.mm133
-rw-r--r--lldb/tools/debugserver/source/MacOSX/MachTask.h6
-rw-r--r--lldb/tools/debugserver/source/MacOSX/MachTask.mm24
-rw-r--r--lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp6
-rw-r--r--lldb/tools/driver/CMakeLists.txt3
-rw-r--r--lldb/tools/driver/Driver.cpp67
-rw-r--r--lldb/tools/lldb-dap/CMakeLists.txt5
-rw-r--r--lldb/tools/lldb-dap/ClientLauncher.cpp74
-rw-r--r--lldb/tools/lldb-dap/ClientLauncher.h50
-rw-r--r--lldb/tools/lldb-dap/DAP.cpp353
-rw-r--r--lldb/tools/lldb-dap/DAP.h47
-rw-r--r--lldb/tools/lldb-dap/DAPForward.h1
-rw-r--r--lldb/tools/lldb-dap/DAPLog.cpp19
-rw-r--r--lldb/tools/lldb-dap/DAPLog.h47
-rw-r--r--lldb/tools/lldb-dap/DAPSessionManager.cpp143
-rw-r--r--lldb/tools/lldb-dap/DAPSessionManager.h119
-rw-r--r--lldb/tools/lldb-dap/EventHelper.cpp322
-rw-r--r--lldb/tools/lldb-dap/EventHelper.h11
-rw-r--r--lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp39
-rw-r--r--lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp6
-rw-r--r--lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp53
-rw-r--r--lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp267
-rw-r--r--lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp209
-rw-r--r--lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp58
-rw-r--r--lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp4
-rw-r--r--lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp49
-rw-r--r--lldb/tools/lldb-dap/Handler/RequestHandler.h29
-rw-r--r--lldb/tools/lldb-dap/JSONUtils.cpp21
-rw-r--r--lldb/tools/lldb-dap/JSONUtils.h6
-rw-r--r--lldb/tools/lldb-dap/LLDBUtils.cpp11
-rw-r--r--lldb/tools/lldb-dap/LLDBUtils.h2
-rw-r--r--lldb/tools/lldb-dap/Protocol/ProtocolBase.h4
-rw-r--r--lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp88
-rw-r--r--lldb/tools/lldb-dap/Protocol/ProtocolRequests.h168
-rw-r--r--lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp33
-rw-r--r--lldb/tools/lldb-dap/Protocol/ProtocolTypes.h35
-rw-r--r--lldb/tools/lldb-dap/ProtocolUtils.cpp7
-rw-r--r--lldb/tools/lldb-dap/ProtocolUtils.h3
-rw-r--r--lldb/tools/lldb-dap/Transport.cpp10
-rw-r--r--lldb/tools/lldb-dap/Transport.h7
-rw-r--r--lldb/tools/lldb-dap/Watchpoint.cpp1
-rw-r--r--lldb/tools/lldb-dap/package.json4
-rw-r--r--lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts6
-rw-r--r--lldb/tools/lldb-dap/src-ts/utils.ts41
-rw-r--r--lldb/tools/lldb-dap/tool/CMakeLists.txt4
-rw-r--r--lldb/tools/lldb-dap/tool/Options.td (renamed from lldb/tools/lldb-dap/Options.td)8
-rw-r--r--lldb/tools/lldb-dap/tool/lldb-dap.cpp141
-rw-r--r--lldb/tools/lldb-server/CMakeLists.txt5
-rw-r--r--lldb/tools/lldb-server/PlatformOptions.td75
-rw-r--r--lldb/tools/lldb-server/lldb-platform.cpp265
-rw-r--r--lldb/tools/lldb-test/lldb-test.cpp7
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;