[Lldb-commits] [lldb] [lldb-dap] Support cancel requests and add explicit DAP structures. (PR #128276)

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Fri Feb 21 19:40:46 PST 2025


https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/128276

>From 0e4cc277229e19fde768fe6faecbef1496d00b61 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 11 Feb 2025 16:11:55 -0800
Subject: [PATCH] [lldb-dap] Support cancel requests and add explicit DAP
 structures.

This is a refactor of lldb-dap focused on supporting cancel requests and well defined Debug Adapter Protocol (DAP) structures.

Existing request handlers today tend to use unstructured `llvm::json::Object`'s to represent requests and replies. The lack of structure makes it hard for us to apply some unfirom handling around requests to better support cancellation. To address this, this change includes a new `Protocol.h` file with a set of well defined POD structures to represent the types in the DAP and includes `toJSON`/`fromJSON` serialization support.

Building on these types, this includes a new approach to registering request handlers with these new well defined types.

```
template <typename Args, typename Body>
void DAP::RegisterRequest(llvm::StringLiteral command,
                          RequestHandler<Args, Body> handler);
...
llvm::Expected<SourceResponseBody> request_source(DAP &dap, const SourceArguments &args) {
  SourceResponseBody body;
  ...
  if (/*some error*/)
    return llvm::make_error<DAPError>("msg");
  return std::move(body);
}
...
  dap.RegisterRequest("source", request_source);
...
```

The main features of this refactor are:

* Created a queue of pending protocol messages to allow for preemptive cancellations and active cancellations.
* A cleaner separation between the Debug Adapter Protocol types and lldb types that include serialization support toJSON/fromJSON.
* Improved error handling using toJSON/fromJSON.
* Unified handling of responses and errors.
* Unified handling of cancel requests.

This change include minimal support to implement the Debug Adapter Protocol 'source' request, 'evaluate' request and the 'exited' event.
---
 lldb/test/API/tools/lldb-dap/cancel/Makefile  |   3 +
 .../tools/lldb-dap/cancel/TestDAP_cancel.py   |  84 ++
 lldb/test/API/tools/lldb-dap/cancel/main.c    |   6 +
 .../lldb-dap/evaluate/TestDAP_evaluate.py     |  27 +-
 .../tools/lldb-dap/launch/TestDAP_launch.py   |   9 +-
 .../lldb-dap/optimized/TestDAP_optimized.py   |   6 +-
 lldb/tools/lldb-dap/CMakeLists.txt            |   1 +
 lldb/tools/lldb-dap/DAP.cpp                   | 218 +++--
 lldb/tools/lldb-dap/DAP.h                     | 228 ++++-
 lldb/tools/lldb-dap/IOStream.cpp              |   8 +-
 lldb/tools/lldb-dap/IOStream.h                |   4 +-
 lldb/tools/lldb-dap/JSONUtils.cpp             | 280 +++---
 lldb/tools/lldb-dap/JSONUtils.h               |  16 +-
 lldb/tools/lldb-dap/Protocol.cpp              | 451 +++++++++
 lldb/tools/lldb-dap/Protocol.h                | 905 ++++++++++++++++++
 lldb/tools/lldb-dap/lldb-dap.cpp              | 141 +--
 16 files changed, 2064 insertions(+), 323 deletions(-)
 create mode 100644 lldb/test/API/tools/lldb-dap/cancel/Makefile
 create mode 100644 lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
 create mode 100644 lldb/test/API/tools/lldb-dap/cancel/main.c
 create mode 100644 lldb/tools/lldb-dap/Protocol.cpp
 create mode 100644 lldb/tools/lldb-dap/Protocol.h

diff --git a/lldb/test/API/tools/lldb-dap/cancel/Makefile b/lldb/test/API/tools/lldb-dap/cancel/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
new file mode 100644
index 0000000000000..4410fc477bdb4
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
@@ -0,0 +1,84 @@
+"""
+Test lldb-dap cancel request
+"""
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
+    def send_async_req(self, command: str, arguments={}) -> int:
+        seq = self.dap_server.sequence
+        self.dap_server.send_packet(
+            {
+                "type": "request",
+                "command": command,
+                "arguments": arguments,
+            }
+        )
+        return seq
+
+    def async_blocking_request(self, duration: float) -> int:
+        return self.send_async_req(
+            command="evaluate",
+            arguments={
+                "expression": "`script import time; time.sleep({})".format(duration),
+                "context": "repl",
+            },
+        )
+
+    def async_cancel(self, requestId: int) -> int:
+        return self.send_async_req(command="cancel", arguments={"requestId": requestId})
+
+    def test_pending_request(self):
+        """
+        Tests cancelling a pending request.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, stopOnEntry=True)
+        self.continue_to_next_stop()
+
+        # Use a relatively short timeout since this is only to ensure the
+        # following request is queued.
+        blocking_seq = self.async_blocking_request(duration=1.0)
+        # Use a longer timeout to ensure we catch if the request was interrupted
+        # properly.
+        pending_seq = self.async_blocking_request(duration=self.timeoutval * 2)
+        cancel_seq = self.async_cancel(requestId=pending_seq)
+
+        blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
+        print("blocking_resp", blocking_resp)
+        self.assertEqual(blocking_resp["request_seq"], blocking_seq)
+        self.assertEqual(blocking_resp["success"], True)
+
+        pending_resp = self.dap_server.recv_packet(filter_type=["response"])
+        print("pending_resp", pending_resp)
+        self.assertEqual(pending_resp["request_seq"], pending_seq)
+        self.assertEqual(pending_resp["success"], False)
+        self.assertEqual(pending_resp["message"], "cancelled")
+
+        cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
+        self.assertEqual(cancel_resp["request_seq"], cancel_seq)
+        self.continue_to_exit()
+
+    def test_inflight_request(self):
+        """
+        Tests cancelling an inflight request.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, stopOnEntry=True)
+        self.continue_to_next_stop()
+
+        blocking_seq = self.async_blocking_request(duration=self.timeoutval * 2)
+        cancel_seq = self.async_cancel(requestId=blocking_seq)
+
+        blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
+        print("blocking_resp", blocking_resp)
+        self.assertEqual(blocking_resp["request_seq"], blocking_seq)
+        self.assertEqual(blocking_resp["success"], False)
+        self.assertEqual(blocking_resp["message"], "cancelled")
+
+        cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
+        self.assertEqual(cancel_resp["request_seq"], cancel_seq)
+        self.continue_to_exit()
diff --git a/lldb/test/API/tools/lldb-dap/cancel/main.c b/lldb/test/API/tools/lldb-dap/cancel/main.c
new file mode 100644
index 0000000000000..ecc0d99ec8db7
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+  printf("Hello world!\n");
+  return 0;
+}
diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
index 251d77d79d080..6bbca9f311cf9 100644
--- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
+++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
@@ -3,6 +3,7 @@
 """
 
 import re
+import json
 
 import lldbdap_testcase
 import dap_server
@@ -13,17 +14,21 @@
 
 class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase):
     def assertEvaluate(self, expression, regex):
-        self.assertRegex(
-            self.dap_server.request_evaluate(expression, context=self.context)["body"][
-                "result"
-            ],
-            regex,
+        resp = self.dap_server.request_evaluate(expression, context=self.context)
+        self.assertEqual(
+            resp["success"],
+            True,
+            "evaluate with expression = '{}' context = '{}' failed unexpectedly: {}".format(
+                expression, self.context, json.dumps(resp)
+            ),
         )
+        self.assertRegex(resp["body"]["result"], regex)
 
     def assertEvaluateFailure(self, expression):
-        self.assertNotIn(
-            "result",
-            self.dap_server.request_evaluate(expression, context=self.context)["body"],
+        self.assertFalse(
+            self.dap_server.request_evaluate(expression, context=self.context)[
+                "success"
+            ],
         )
 
     def isResultExpandedDescription(self):
@@ -231,5 +236,7 @@ def test_hover_evaluate_expressions(self):
 
     @skipIfWindows
     def test_variable_evaluate_expressions(self):
-        # Tests expression evaluations that are triggered in the variable explorer
-        self.run_test_evaluate_expressions("variable", enableAutoVariableSummaries=True)
+        # Tests expression evaluations that are triggered in the variables explorer
+        self.run_test_evaluate_expressions(
+            "variables", enableAutoVariableSummaries=True
+        )
diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index 7898d01457afc..55e724c3f048b 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -1,13 +1,10 @@
 """
-Test lldb-dap setBreakpoints request
+Test lldb-dap launch request
 """
 
-import dap_server
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
-from lldbsuite.test import lldbutil
 import lldbdap_testcase
-import time
 import os
 import re
 
@@ -41,7 +38,9 @@ def test_termination(self):
         self.dap_server.request_disconnect()
 
         # Wait until the underlying lldb-dap process dies.
-        self.dap_server.process.wait(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
+        self.dap_server.process.wait(
+            timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval
+        )
 
         # Check the return code
         self.assertEqual(self.dap_server.process.poll(), 0)
diff --git a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
index ae144ebdca46b..6cbe0b8858482 100644
--- a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
+++ b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
@@ -24,11 +24,11 @@ def test_stack_frame_name(self):
         )
         self.continue_to_breakpoints(breakpoint_ids)
         leaf_frame = self.dap_server.get_stackFrame(frameIndex=0)
-        self.assertTrue(leaf_frame["name"].endswith(" [opt]"))
+        self.assertRegex(leaf_frame["name"], r"\[.*opt.*\]")
         parent_frame = self.dap_server.get_stackFrame(frameIndex=1)
-        self.assertTrue(parent_frame["name"].endswith(" [opt]"))
+        self.assertRegex(parent_frame["name"], r"\[.*opt.*\]")
 
-    @skipIfAsan # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
+    @skipIfAsan  # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
     @skipIfWindows
     def test_optimized_variable(self):
         """Test optimized variable value contains error."""
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 43fc18873feb3..2506fa36b1644 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -32,6 +32,7 @@ add_lldb_tool(lldb-dap
   LLDBUtils.cpp
   OutputRedirector.cpp
   ProgressEvent.cpp
+  Protocol.cpp
   RunInTerminal.cpp
   SourceBreakpoint.cpp
   Watchpoint.cpp
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index f5a9b10ebe0f9..d996e184d2b8e 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -10,6 +10,7 @@
 #include "JSONUtils.h"
 #include "LLDBUtils.h"
 #include "OutputRedirector.h"
+#include "Protocol.h"
 #include "lldb/API/SBBreakpoint.h"
 #include "lldb/API/SBCommandInterpreter.h"
 #include "lldb/API/SBCommandReturnObject.h"
@@ -23,18 +24,23 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <cassert>
 #include <chrono>
+#include <condition_variable>
 #include <cstdarg>
 #include <cstdio>
+#include <deque>
 #include <fstream>
 #include <mutex>
+#include <optional>
 #include <utility>
 
 #if defined(_WIN32)
@@ -47,17 +53,19 @@
 #endif
 
 using namespace lldb_dap;
+using namespace lldb_dap::protocol;
 
-namespace {
 #ifdef _WIN32
-const char DEV_NULL[] = "nul";
+static const char DEV_NULL[] = "nul";
 #else
-const char DEV_NULL[] = "/dev/null";
+static const char DEV_NULL[] = "/dev/null";
 #endif
-} // namespace
 
 namespace lldb_dap {
 
+char DAPError::ID;
+char CancelledError::ID;
+
 DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
          StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
          std::vector<std::string> pre_init_commands)
@@ -288,13 +296,6 @@ std::string DAP::ReadJSON() {
   if (!input.read_full(log, length, json_str))
     return json_str;
 
-  if (log) {
-    auto now = std::chrono::duration<double>(
-        std::chrono::system_clock::now().time_since_epoch());
-    *log << llvm::formatv("{0:f9} {1} --> ", now.count(), name).str()
-         << std::endl
-         << "Content-Length: " << length << "\r\n\r\n";
-  }
   return json_str;
 }
 
@@ -532,6 +533,10 @@ lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) {
 
 lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) {
   const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
+  return GetLLDBFrame(frame_id);
+}
+
+lldb::SBFrame DAP::GetLLDBFrame(const uint64_t frame_id) {
   lldb::SBProcess process = target.GetProcess();
   // Upper 32 bits is the thread index ID
   lldb::SBThread thread =
@@ -720,64 +725,80 @@ void DAP::SetTarget(const lldb::SBTarget target) {
   }
 }
 
-PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
+std::optional<protocol::ProtocolMessage> DAP::GetNextProtocolMessage() {
   std::string json = ReadJSON();
   if (json.empty())
-    return PacketStatus::EndOfFile;
+    return std::nullopt;
 
-  llvm::StringRef json_sref(json);
-  llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
-  if (!json_value) {
-    auto error = json_value.takeError();
+  llvm::Expected<llvm::json::Value> value = llvm::json::parse(json);
+  if (!value) {
     if (log) {
-      std::string error_str;
-      llvm::raw_string_ostream strm(error_str);
-      strm << error;
-      *log << "error: failed to parse JSON: " << error_str << std::endl
-           << json << std::endl;
+      auto now = std::chrono::duration<double>(
+          std::chrono::system_clock::now().time_since_epoch());
+      *log << llvm::formatv("{0:f9} -->", now.count()).str()
+           << "\nContent-Length: " << json.length() << "\r\n\r\n"
+           << json
+           << "\nfailed to parse json: " << llvm::toString(value.takeError())
+           << "\n";
+    } else {
+      llvm::consumeError(value.takeError());
     }
-    return PacketStatus::JSONMalformed;
+    return std::nullopt;
   }
 
   if (log) {
-    *log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
+    auto now = std::chrono::duration<double>(
+        std::chrono::system_clock::now().time_since_epoch());
+    *log << llvm::formatv("{0:f9} -->", now.count()).str()
+         << "\nContent-Length: " << json.length() << "\r\n\r\n"
+         << llvm::formatv("{0:2}", *value).str() << "\n";
   }
 
-  llvm::json::Object *object_ptr = json_value->getAsObject();
-  if (!object_ptr) {
-    if (log)
-      *log << "error: json packet isn't a object" << std::endl;
-    return PacketStatus::JSONNotObject;
+  protocol::ProtocolMessage message;
+  llvm::json::Path::Root root;
+  if (fromJSON(*value, message, root))
+    return std::move(message);
+
+  if (log) {
+    std::string error;
+    llvm::raw_string_ostream ss(error);
+    root.printErrorContext(*value, ss);
+    *log << "Failed to parse protocol message: " << error << "\n";
   }
-  object = *object_ptr;
-  return PacketStatus::Success;
+
+  return std::nullopt;
 }
 
-bool DAP::HandleObject(const llvm::json::Object &object) {
-  const auto packet_type = GetString(object, "type");
-  if (packet_type == "request") {
-    const auto command = GetString(object, "command");
-    auto handler_pos = request_handlers.find(command);
+bool DAP::HandleObject(const protocol::ProtocolMessage &message) {
+  if (const protocol::Request *req = std::get_if<protocol::Request>(&message)) {
+    auto handler_pos = request_handlers.find(req->command);
     if (handler_pos == request_handlers.end()) {
       if (log)
-        *log << "error: unhandled command \"" << command.data() << "\""
-             << std::endl;
+        *log << "error: unhandled command \"" << req->command << "\""
+             << "\n";
       return false; // Fail
     }
 
-    handler_pos->second(*this, object);
+    active_request_seq = req->seq;
+    handler_pos->second(*this, *req);
+    active_request_seq = 0;
+
+    // Clear cancellation marker.
+    if (debugger.InterruptRequested())
+      debugger.CancelInterruptRequest();
+
     return true; // Success
   }
 
-  if (packet_type == "response") {
-    auto id = GetSigned(object, "request_seq", 0);
+  if (const protocol::Response *resp =
+          std::get_if<protocol::Response>(&message)) {
     ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) {
       llvm::errs() << "Unhandled response\n";
     };
 
     {
-      std::lock_guard<std::mutex> locker(call_mutex);
-      auto inflight = inflight_reverse_requests.find(id);
+      std::lock_guard locker(call_mutex);
+      auto inflight = inflight_reverse_requests.find(resp->request_seq);
       if (inflight != inflight_reverse_requests.end()) {
         response_handler = std::move(inflight->second);
         inflight_reverse_requests.erase(inflight);
@@ -785,17 +806,11 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
     }
 
     // Result should be given, use null if not.
-    if (GetBoolean(object, "success", false)) {
-      llvm::json::Value Result = nullptr;
-      if (auto *B = object.get("body")) {
-        Result = std::move(*B);
-      }
-      response_handler(Result);
+    if (resp->success) {
+      response_handler(resp->rawBody);
     } else {
-      llvm::StringRef message = GetString(object, "message");
-      if (message.empty()) {
-        message = "Unknown error, response failed";
-      }
+      std::string message =
+          resp->message.value_or("Unknown error, response failed");
       response_handler(llvm::createStringError(
           std::error_code(-1, std::generic_category()), message));
     }
@@ -849,27 +864,91 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
   return error;
 }
 
-llvm::Error DAP::Loop() {
-  auto stop_io = llvm::make_scope_exit([this]() { StopIO(); });
-  while (!disconnecting) {
-    llvm::json::Object object;
-    lldb_dap::PacketStatus status = GetNextObject(object);
+template <typename T>
+static std::optional<T>
+getArgumentsIfRequest(const protocol::ProtocolMessage &pm,
+                      llvm::StringLiteral command) {
+  auto *const req = std::get_if<protocol::Request>(&pm);
+  if (!req || req->command != command)
+    return std::nullopt;
+
+  T args;
+  llvm::json::Path::Root root;
+  if (!fromJSON(req->rawArguments, args, root)) {
+    return std::nullopt;
+  }
 
-    if (status == lldb_dap::PacketStatus::EndOfFile) {
-      break;
+  return std::move(args);
+}
+
+llvm::Error DAP::Run() {
+  auto stop_io = llvm::make_scope_exit([this]() { StopIO(); });
+  std::thread reader([&]() {
+    while (!disconnecting) {
+      auto next = GetNextProtocolMessage();
+      if (!next)
+        break;
+
+      {
+        std::lock_guard lock(messages_mutex);
+        messages.push_back(std::move(*next));
+        if (const auto args = getArgumentsIfRequest<protocol::CancelArguments>(
+                *next, "cancel");
+            args && active_request_seq == args->requestId)
+          debugger.RequestInterrupt();
+      }
+      messages_cv.notify_one();
     }
 
-    if (status != lldb_dap::PacketStatus::Success) {
-      return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                     "failed to send packet");
+    std::unique_lock lock(messages_mutex);
+    std::notify_all_at_thread_exit(messages_cv, std::move(lock));
+  });
+
+  while (!disconnecting) {
+    protocol::ProtocolMessage next;
+    {
+      std::unique_lock lock(messages_mutex);
+      messages_cv.wait(lock,
+                       [&] { return disconnecting || !messages.empty(); });
+
+      if (messages.empty())
+        break;
+
+      next = messages.front();
+      messages.pop_front();
+
+      // If we have a request check if we should preemptively cancel it.
+      if (protocol::Request *req = std::get_if<protocol::Request>(&next)) {
+        bool cancelled = false;
+        for (const auto &message : messages) {
+          if (const auto args =
+                  getArgumentsIfRequest<protocol::CancelArguments>(message,
+                                                                   "cancel");
+              args && args->requestId == req->seq) {
+            cancelled = true;
+            break;
+          }
+        }
+
+        if (cancelled) {
+          ReplyOnce Reply(req->seq, req->command, this);
+          Reply(llvm::make_error<CancelledError>());
+          continue;
+        }
+      }
     }
 
-    if (!HandleObject(object)) {
-      return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                     "unhandled packet");
+    if (!HandleObject(next)) {
+      return llvm::make_error<llvm::StringError>(
+          llvm::formatv("Unsupported protocol message: {0:2}", toJSON(next))
+              .str(),
+          llvm::inconvertibleErrorCode());
     }
   }
 
+  if (reader.joinable())
+    reader.join();
+
   return llvm::Error::success();
 }
 
@@ -891,9 +970,12 @@ void DAP::SendReverseRequest(llvm::StringRef command,
   });
 }
 
-void DAP::RegisterRequestCallback(std::string request,
+void DAP::RegisterRequestCallback(llvm::StringLiteral command,
                                   RequestCallback callback) {
-  request_handlers[request] = callback;
+  request_handlers[command] = [callback](DAP &dap, const Request &request) {
+    llvm::json::Value rawRequest = toJSON(request);
+    callback(dap, *rawRequest.getAsObject());
+  };
 }
 
 lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) {
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 655bec2639595..a23147537f3bc 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -16,6 +16,7 @@
 #include "InstructionBreakpoint.h"
 #include "OutputRedirector.h"
 #include "ProgressEvent.h"
+#include "Protocol.h"
 #include "SourceBreakpoint.h"
 #include "lldb/API/SBBroadcaster.h"
 #include "lldb/API/SBCommandInterpreter.h"
@@ -24,6 +25,7 @@
 #include "lldb/API/SBFile.h"
 #include "lldb/API/SBFormat.h"
 #include "lldb/API/SBFrame.h"
+#include "lldb/API/SBSymbol.h"
 #include "lldb/API/SBTarget.h"
 #include "lldb/API/SBThread.h"
 #include "lldb/API/SBValue.h"
@@ -34,11 +36,16 @@
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Error.h"
+#include "llvm/Support/FormatAdapters.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/Threading.h"
+#include <atomic>
+#include <cstdint>
+#include <functional>
 #include <map>
 #include <mutex>
 #include <optional>
+#include <set>
 #include <thread>
 #include <vector>
 
@@ -66,18 +73,52 @@ enum DAPBroadcasterBits {
   eBroadcastBitStopProgressThread = 1u << 1
 };
 
-typedef void (*RequestCallback)(DAP &dap, const llvm::json::Object &command);
-typedef void (*ResponseCallback)(llvm::Expected<llvm::json::Value> value);
+using RequestCallback = std::function<void(DAP &, const llvm::json::Object &)>;
+using ResponseCallback = std::function<void(llvm::Expected<llvm::json::Value>)>;
 
-enum class PacketStatus {
-  Success = 0,
-  EndOfFile,
-  JSONMalformed,
-  JSONNotObject
-};
+template <typename Args, typename Body>
+using RequestHandler = std::function<llvm::Expected<Body>(DAP &, const Args &)>;
+template <typename Body>
+using ResponseHandler = std::function<void(llvm::Expected<Body>)>;
+
+/// A debug adapter initiated event.
+template <typename T> using OutgoingEvent = std::function<void(const T &)>;
+
+enum class PacketStatus { Success = 0, EndOfFile, JSONMalformed };
 
 enum class ReplMode { Variable = 0, Command, Auto };
 
+class DAPError : public llvm::ErrorInfo<DAPError> {
+public:
+  std::string Message;
+  std::optional<protocol::Message> UserMessage;
+  static char ID;
+
+  explicit DAPError(std::string Message)
+      : Message(std::move(Message)), UserMessage(std::nullopt) {}
+  DAPError(std::string Message, protocol::Message UserMessage)
+      : Message(std::move(Message)), UserMessage(std::move(UserMessage)) {}
+
+  void log(llvm::raw_ostream &OS) const override {
+    OS << "DAPError: " << Message;
+  }
+
+  std::error_code convertToErrorCode() const override {
+    return llvm::inconvertibleErrorCode();
+  }
+};
+
+class CancelledError : public llvm::ErrorInfo<CancelledError> {
+public:
+  static char ID;
+
+  void log(llvm::raw_ostream &OS) const override { OS << "Cancalled"; }
+
+  std::error_code convertToErrorCode() const override {
+    return llvm::inconvertibleErrorCode();
+  }
+};
+
 struct Variables {
   /// Variable_reference start index of permanent expandable variable.
   static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
@@ -172,7 +213,7 @@ struct DAP {
   // arguments if we get a RestartRequest.
   std::optional<llvm::json::Object> last_launch_or_attach_request;
   lldb::tid_t focus_tid;
-  bool disconnecting = false;
+  std::atomic<bool> disconnecting = false;
   llvm::once_flag terminated_event_flag;
   bool stop_at_entry;
   bool is_attach;
@@ -184,7 +225,11 @@ struct DAP {
   // the old process here so we can detect this case and keep running.
   lldb::pid_t restarting_process_id;
   bool configuration_done_sent;
-  std::map<std::string, RequestCallback, std::less<>> request_handlers;
+
+  using RequestWrapper =
+      std::function<void(DAP &dap, const protocol::Request &)>;
+  llvm::StringMap<RequestWrapper> request_handlers;
+
   bool waiting_for_run_in_terminal;
   ProgressEventReporter progress_event_reporter;
   // Keep track of the last stop thread index IDs as threads won't go away
@@ -204,6 +249,12 @@ struct DAP {
   // empty; if the previous expression was a variable expression, this string
   // will contain that expression.
   std::string last_nonempty_var_expression;
+  std::set<lldb::SBSymbol> source_references;
+
+  /// MARK: Event Handlers
+
+  /// onExited indicates that the debuggee has exited and returns its exit code.
+  OutgoingEvent<protocol::ExitedEventBody> onExited;
 
   DAP(std::string name, llvm::StringRef path, std::ofstream *log,
       StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
@@ -246,6 +297,8 @@ struct DAP {
 
   lldb::SBFrame GetLLDBFrame(const llvm::json::Object &arguments);
 
+  lldb::SBFrame GetLLDBFrame(const uint64_t frame_id);
+
   llvm::json::Value CreateTopLevelScopes();
 
   void PopulateExceptionBreakpoints();
@@ -302,10 +355,14 @@ struct DAP {
   /// listeing for its breakpoint events.
   void SetTarget(const lldb::SBTarget target);
 
-  const std::map<std::string, RequestCallback> &GetRequestHandlers();
+  /// Get the next protocol message from the client. The value may represent a
+  /// request, response or event.
+  ///
+  /// If the connection is closed then nullopt is returned.
+  std::optional<protocol::ProtocolMessage> GetNextProtocolMessage();
 
-  PacketStatus GetNextObject(llvm::json::Object &object);
-  bool HandleObject(const llvm::json::Object &object);
+  /// Handle the next protocol message.
+  bool HandleObject(const protocol::ProtocolMessage &);
 
   /// Disconnect the DAP session.
   lldb::SBError Disconnect();
@@ -316,7 +373,9 @@ struct DAP {
   /// Send a "terminated" event to indicate the process is done being debugged.
   void SendTerminatedEvent();
 
-  llvm::Error Loop();
+  /// Runs the debug session until either disconnected or an unrecoverable error
+  /// is encountered.
+  llvm::Error Run();
 
   /// Send a Debug Adapter Protocol reverse request to the IDE.
   ///
@@ -333,14 +392,32 @@ struct DAP {
 
   /// Registers a callback handler for a Debug Adapter Protocol request
   ///
-  /// \param[in] request
+  /// \param[in] command
+  ///     The name of the request following the Debug Adapter Protocol
+  ///     specification.
+  ///
+  /// \param[in] callback
+  ///     The callback to execute when the given request is triggered by the
+  ///     IDE.
+  void RegisterRequestCallback(llvm::StringLiteral command,
+                               RequestCallback callback);
+
+  /// Register a request handler for a Debug Adapter Protocol request.
+  ///
+  /// \param[in] command
   ///     The name of the request following the Debug Adapter Protocol
   ///     specification.
   ///
   /// \param[in] callback
   ///     The callback to execute when the given request is triggered by the
   ///     IDE.
-  void RegisterRequestCallback(std::string request, RequestCallback callback);
+  template <typename Args, typename Body>
+  void RegisterRequest(llvm::StringLiteral command,
+                       RequestHandler<Args, Body> handler);
+
+  /// Registeres an event handler for sending Debug Adapter Protocol events.
+  template <typename Body>
+  OutgoingEvent<Body> RegisterEvent(llvm::StringLiteral Event);
 
   /// Debuggee will continue from stopped state.
   void WillContinue() { variables.Clear(); }
@@ -375,12 +452,131 @@ struct DAP {
   InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread);
 
 private:
+  std::condition_variable messages_cv;
+  std::mutex messages_mutex;
+  std::atomic<int64_t> active_request_seq;
+  std::deque<protocol::ProtocolMessage> messages;
+
   // Send the JSON in "json_str" to the "out" stream. Correctly send the
   // "Content-Length:" field followed by the length, followed by the raw
   // JSON bytes.
   void SendJSON(const std::string &json_str);
+
+  template <typename T>
+  static llvm::Expected<T> parse(const llvm::json::Value &Raw,
+                                 llvm::StringRef PayloadName);
+
+  class ReplyOnce {
+    std::atomic<bool> replied = false;
+    int seq;
+    std::string command;
+    DAP *dap;
+
+  public:
+    ReplyOnce(int seq, std::string command, DAP *dap)
+        : seq(seq), command(command), dap(dap) {}
+    ReplyOnce(ReplyOnce &&other)
+        : replied(other.replied.load()), seq(other.seq), command(other.command),
+          dap(other.dap) {
+      other.dap = nullptr;
+    }
+    ReplyOnce &operator=(ReplyOnce &&) = delete;
+    ReplyOnce(const ReplyOnce &) = delete;
+    ReplyOnce &operator=(const ReplyOnce &) = delete;
+    ~ReplyOnce() {
+      if (dap && !dap->disconnecting && !replied) {
+        assert(false && "must reply to all requests");
+        (*this)(llvm::make_error<DAPError>("server failed to reply"));
+      }
+    }
+
+    void operator()(llvm::Expected<llvm::json::Value> maybeResponseBody) {
+      if (replied.exchange(true)) {
+        assert(false && "must reply to each call only once!");
+        return;
+      }
+
+      protocol::Response Resp;
+      Resp.request_seq = seq;
+      Resp.command = command;
+
+      if (auto Err = maybeResponseBody.takeError()) {
+        Resp.success = false;
+
+        auto newErr = handleErrors(
+            std::move(Err),
+            [&](const DAPError &dapErr) {
+              if (dapErr.UserMessage) {
+                protocol::ErrorResponseBody ERB;
+                ERB.error = *dapErr.UserMessage;
+                Resp.rawBody = std::move(ERB);
+              }
+              Resp.message = dapErr.Message;
+            },
+            [&](const CancelledError &cancelledErr) {
+              Resp.success = false;
+              Resp.message = "cancelled";
+            });
+        if (newErr)
+          Resp.message = llvm::toString(std::move(newErr));
+      } else {
+        // Check if the request was interrupted and mark the response as
+        // cancelled.
+        if (dap->debugger.InterruptRequested()) {
+          Resp.success = false;
+          Resp.message = "cancelled";
+        } else
+          Resp.success = true;
+        // Skip encoding a null body.
+        if (*maybeResponseBody != llvm::json::Value(nullptr))
+          Resp.rawBody = *maybeResponseBody;
+      }
+
+      dap->SendJSON(Resp);
+    }
+  };
 };
 
+template <typename T>
+llvm::Expected<T> DAP::parse(const llvm::json::Value &Raw,
+                             llvm::StringRef PayloadName) {
+  T Result;
+  llvm::json::Path::Root Root;
+  if (!fromJSON(Raw, Result, Root)) {
+    std::string Context;
+    llvm::raw_string_ostream OS(Context);
+    Root.printErrorContext(Raw, OS);
+    return llvm::make_error<DAPError>(
+        llvm::formatv("failed to decode {0}: {1}", PayloadName,
+                      llvm::fmt_consume(Root.getError())));
+  }
+  return std::move(Result);
+}
+
+template <typename Args, typename Body>
+void DAP::RegisterRequest(llvm::StringLiteral command,
+                          RequestHandler<Args, Body> handler) {
+  request_handlers[command] = [command, handler](DAP &dap,
+                                                 const protocol::Request &req) {
+    ReplyOnce reply(req.seq, req.command, &dap);
+    auto parsedArgs = DAP::parse<Args>(req.rawArguments, command);
+    if (!parsedArgs)
+      return reply(parsedArgs.takeError());
+    llvm::Expected<Body> response = handler(dap, *parsedArgs);
+    reply(std::move(response));
+  };
+}
+
+template <typename Body>
+OutgoingEvent<Body> DAP::RegisterEvent(llvm::StringLiteral Event) {
+  return [&, Event](const Body &B) {
+    protocol::Event Evt;
+    Evt.event = Event;
+    Evt.rawBody = B;
+    SendJSON(std::move(Evt));
+  };
+}
+
 } // namespace lldb_dap
 
 #endif
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index 7d0f363440f53..bc05a41a5bd21 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -26,10 +26,14 @@ StreamDescriptor::StreamDescriptor(StreamDescriptor &&other) {
   *this = std::move(other);
 }
 
-StreamDescriptor::~StreamDescriptor() {
+StreamDescriptor::~StreamDescriptor() { close(); }
+
+void StreamDescriptor::close() {
   if (!m_close)
     return;
 
+  m_close = false;
+
   if (m_is_socket)
 #if defined(_WIN32)
     ::closesocket(m_socket);
@@ -41,7 +45,7 @@ StreamDescriptor::~StreamDescriptor() {
 }
 
 StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
-  m_close = other.m_close;
+  m_close.store(other.m_close.load());
   other.m_close = false;
   m_is_socket = other.m_is_socket;
   if (m_is_socket)
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index c91b2f717893c..bdba9a9e4c4de 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -36,8 +36,10 @@ struct StreamDescriptor {
   static StreamDescriptor from_socket(SOCKET s, bool close);
   static StreamDescriptor from_file(int fd, bool close);
 
+  void close();
+
   bool m_is_socket = false;
-  bool m_close = false;
+  std::atomic<bool> m_close = false;
   union {
     int m_fd;
     SOCKET m_socket;
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 6ca4dfb4711a1..fded2c35fdf33 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -12,14 +12,18 @@
 #include "DAP.h"
 #include "ExceptionBreakpoint.h"
 #include "LLDBUtils.h"
+#include "Protocol.h"
 #include "lldb/API/SBAddress.h"
 #include "lldb/API/SBCompileUnit.h"
 #include "lldb/API/SBDeclaration.h"
 #include "lldb/API/SBEnvironment.h"
 #include "lldb/API/SBError.h"
 #include "lldb/API/SBFileSpec.h"
+#include "lldb/API/SBFormat.h"
 #include "lldb/API/SBFrame.h"
 #include "lldb/API/SBFunction.h"
+#include "lldb/API/SBInstruction.h"
+#include "lldb/API/SBInstructionList.h"
 #include "lldb/API/SBLineEntry.h"
 #include "lldb/API/SBModule.h"
 #include "lldb/API/SBQueue.h"
@@ -45,7 +49,6 @@
 #include "llvm/Support/ScopedPrinter.h"
 #include "llvm/Support/raw_ostream.h"
 #include <chrono>
-#include <climits>
 #include <cstddef>
 #include <iomanip>
 #include <optional>
@@ -698,134 +701,114 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) {
   return llvm::json::Value(std::move(source));
 }
 
-static std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) {
-  auto line_entry = frame.GetLineEntry();
-  // A line entry of 0 indicates the line is compiler generated i.e. no source
-  // file is associated with the frame.
-  if (line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0)
-    return CreateSource(line_entry);
-
-  return {};
-}
-
-// "StackFrame": {
-//   "type": "object",
-//   "description": "A Stackframe contains the source location.",
-//   "properties": {
-//     "id": {
-//       "type": "integer",
-//       "description": "An identifier for the stack frame. It must be unique
-//                       across all threads. This id can be used to retrieve
-//                       the scopes of the frame with the 'scopesRequest' or
-//                       to restart the execution of a stackframe."
-//     },
-//     "name": {
-//       "type": "string",
-//       "description": "The name of the stack frame, typically a method name."
-//     },
-//     "source": {
-//       "$ref": "#/definitions/Source",
-//       "description": "The optional source of the frame."
-//     },
-//     "line": {
-//       "type": "integer",
-//       "description": "The line within the file of the frame. If source is
-//                       null or doesn't exist, line is 0 and must be ignored."
-//     },
-//     "column": {
-//       "type": "integer",
-//       "description": "The column within the line. If source is null or
-//                       doesn't exist, column is 0 and must be ignored."
-//     },
-//     "endLine": {
-//       "type": "integer",
-//       "description": "An optional end line of the range covered by the
-//                       stack frame."
-//     },
-//     "endColumn": {
-//       "type": "integer",
-//       "description": "An optional end column of the range covered by the
-//                       stack frame."
-//     },
-//     "instructionPointerReference": {
-// 	     "type": "string",
-// 	     "description": "A memory reference for the current instruction
-//                         pointer in this frame."
-//     },
-//     "moduleId": {
-//       "type": ["integer", "string"],
-//       "description": "The module associated with this frame, if any."
-//     },
-//     "presentationHint": {
-//       "type": "string",
-//       "enum": [ "normal", "label", "subtle" ],
-//       "description": "An optional hint for how to present this frame in
-//                       the UI. A value of 'label' can be used to indicate
-//                       that the frame is an artificial frame that is used
-//                       as a visual label or separator. A value of 'subtle'
-//                       can be used to change the appearance of a frame in
-//                       a 'subtle' way."
-//     }
-//   },
-//   "required": [ "id", "name", "line", "column" ]
-// }
-llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
-                                   lldb::SBFormat &format) {
-  llvm::json::Object object;
-  int64_t frame_id = MakeDAPFrameID(frame);
-  object.try_emplace("id", frame_id);
-
-  std::string frame_name;
+static std::string GetFormattedFrameName(lldb::SBFrame &frame,
+                                         lldb::SBFormat &format) {
   lldb::SBStream stream;
   if (format && frame.GetDescriptionWithFormat(format, stream).Success()) {
-    frame_name = stream.GetData();
-
-    // `function_name` can be a nullptr, which throws an error when assigned to
-    // an `std::string`.
-  } else if (const char *name = frame.GetDisplayFunctionName()) {
-    frame_name = name;
+    return stream.GetData();
   }
 
-  if (frame_name.empty()) {
-    // If the function name is unavailable, display the pc address as a 16-digit
-    // hex string, e.g. "0x0000000000012345"
-    llvm::raw_string_ostream os(frame_name);
-    os << llvm::format_hex(frame.GetPC(), 18);
-  }
+  std::string name;
+  // `function_name` can be a nullptr, which throws an error when assigned to
+  // an `std::string`.
+  const char *display_name = frame.GetDisplayFunctionName();
+  if (display_name)
+    name = display_name;
+  const char *function_name = frame.GetFunctionName();
+  if (name.empty() && function_name)
+    name = function_name;
+  if (name.empty() && frame.GetLineEntry().IsValid() &&
+      frame.GetLineEntry().GetLine() == 0)
+    name = "<compiler-generated>";
+  if (name.empty())
+    // If the function name is unavailable, display the pc address as a 16 -
+    // digit hex string, e.g. "0x0000000000012345"
+    name = "0x" + llvm::utohexstr(frame.GetPC(), false, 15);
+
+  std::vector<std::string> tags;
+  // Inlined functions may have their local variables merged with their
+  // caller.
+  if (frame.IsInlined())
+    tags.push_back("inlined");
+  // Artififical frames may be synthesized by the compiler and may not have
+  // local variables.
+  if (frame.IsArtificial())
+    tags.push_back("artificial");
+  // Optimized frames may not have full debug information for all local
+  // variables since some may have been removed by the optimizer.
+  if (frame.GetFunction().GetIsOptimized())
+    tags.push_back("opt");
+
+  // Add any tags to help the user understand more information about the
+  // frame.
+  if (!tags.empty())
+    name += " [" + llvm::join(tags, ",") + "]";
+
+  return name;
+}
 
-  // We only include `[opt]` if a custom frame format is not specified.
-  if (!format && frame.GetFunction().GetIsOptimized())
-    frame_name += " [opt]";
+static lldb_dap::protocol::Source toSource(lldb::SBFrame &frame,
+                                           lldb::SBFormat &format) {
+  lldb_dap::protocol::Source source;
+  auto line_entry = frame.GetLineEntry();
+  auto file = line_entry.GetFileSpec();
+  // A line entry of 0 indicates the line is compiler generated i.e. no source
+  // file is associated with the frame.
+  if (file.IsValid() && line_entry.GetLine() != 0) {
+    char path[PATH_MAX];
+    file.GetPath(path, sizeof(path));
+    source.path = path;
+    source.name = llvm::sys::path::filename(path);
+  } else {
+    source.name = GetFormattedFrameName(frame, format);
 
-  EmplaceSafeString(object, "name", frame_name);
+    // If we don't have a source file then this frame is probably from a
+    // system library. Lets deemphasize the stack frame in the UI so the user
+    // can more easily find the stack frames from their code.
+    source.presentationHint = protocol::Source::PresentationHint::deemphasize;
+    source.sourceReference = MakeDAPFrameID(frame);
+  }
+
+  return source;
+}
 
-  auto source = CreateSource(frame);
+protocol::StackFrame toStackFrame(lldb::SBFrame &frame,
+                                  lldb::SBFormat &format) {
+  lldb_dap::protocol::StackFrame sf;
+  sf.id = MakeDAPFrameID(frame);
+  sf.name = GetFormattedFrameName(frame, format);
+  sf.source = toSource(frame, format);
 
-  if (source) {
-    object.try_emplace("source", *source);
-    auto line_entry = frame.GetLineEntry();
+  auto line_entry = frame.GetLineEntry();
+  if (line_entry.IsValid() && line_entry.GetLine() != 0) {
     auto line = line_entry.GetLine();
     if (line && line != LLDB_INVALID_LINE_NUMBER)
-      object.try_emplace("line", line);
-    else
-      object.try_emplace("line", 0);
+      sf.line = line;
     auto column = line_entry.GetColumn();
-    object.try_emplace("column", column);
+    if (column != LLDB_INVALID_COLUMN_NUMBER)
+      sf.column = column;
   } else {
-    object.try_emplace("line", 0);
-    object.try_emplace("column", 0);
+    lldb::SBInstructionList insts = frame.GetSymbol().GetInstructions(
+        frame.GetThread().GetProcess().GetTarget());
+    for (size_t idx = 0; idx <= insts.GetSize(); idx++) {
+      lldb::SBInstruction inst = insts.GetInstructionAtIndex(idx);
+      if (inst.GetAddress() == frame.GetPCAddress()) {
+        // lines are base-1 indexed and the first line of the disassembly is
+        // the symbol name.
+        sf.line = idx + 2;
+        break;
+      }
+    }
   }
 
   const auto pc = frame.GetPC();
-  if (pc != LLDB_INVALID_ADDRESS) {
-    std::string formatted_addr = "0x" + llvm::utohexstr(pc);
-    object.try_emplace("instructionPointerReference", formatted_addr);
-  }
+  if (pc != LLDB_INVALID_ADDRESS)
+    sf.instructionPointerReference = EncodeMemoryReference(pc);
 
   if (frame.IsArtificial() || frame.IsHidden())
-    object.try_emplace("presentationHint", "subtle");
+    sf.presentationHint = protocol::StackFrame::PresentationHint::subtle;
 
-  return llvm::json::Value(std::move(object));
+  return sf;
 }
 
 llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread,
@@ -905,10 +888,10 @@ llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format) {
 //   "allOf": [ { "$ref": "#/definitions/Event" }, {
 //     "type": "object",
 //     "description": "Event message for 'stopped' event type. The event
-//                     indicates that the execution of the debuggee has stopped
-//                     due to some condition. This can be caused by a break
-//                     point previously set, a stepping action has completed,
-//                     by executing a debugger statement etc.",
+//                     indicates that the execution of the debuggee has
+//                     stopped due to some condition. This can be caused by a
+//                     break point previously set, a stepping action has
+//                     completed, by executing a debugger statement etc.",
 //     "properties": {
 //       "event": {
 //         "type": "string",
@@ -923,7 +906,8 @@ llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format) {
 //                             compatibility this string is shown in the UI if
 //                             the 'description' attribute is missing (but it
 //                             must not be translated).",
-//             "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]
+//             "_enum": [ "step", "breakpoint", "exception", "pause", "entry"
+//             ]
 //           },
 //           "description": {
 //             "type": "string",
@@ -1025,8 +1009,8 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,
   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
-  // description. If we have breakpoints that get hit and shouldn't be reported
-  // as breakpoints, then they will set the description above.
+  // description. If we have breakpoints that get hit and shouldn't be
+  // reported as breakpoints, then they will set the description above.
   if (!ObjectContainsKey(body, "description")) {
     char description[1024];
     if (thread.GetStopDescription(description, sizeof(description))) {
@@ -1159,10 +1143,10 @@ llvm::json::Object VariableDescription::GetVariableExtensionsJSON() {
   return extensions;
 }
 
-std::string VariableDescription::GetResult(llvm::StringRef context) {
+std::string VariableDescription::GetResult(bool multiline) {
   // In repl context, the results can be displayed as multiple lines so more
   // detailed descriptions can be returned.
-  if (context != "repl")
+  if (!multiline)
     return display_value;
 
   if (!v.IsValid())
@@ -1227,7 +1211,8 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
 //     },
 //     "presentationHint": {
 //       "$ref": "#/definitions/VariablePresentationHint",
-//       "description": "Properties of a variable that can be used to determine
+//       "description": "Properties of a variable that can be used to
+//       determine
 //                       how to render the variable in the UI."
 //     },
 //     "evaluateName": {
@@ -1259,20 +1244,21 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
 //        "description": "A memory reference associated with this variable.
 //                        For pointer type variables, this is generally a
 //                        reference to the memory address contained in the
-//                        pointer. For executable data, this reference may later
-//                        be used in a `disassemble` request. This attribute may
-//                        be returned by a debug adapter if corresponding
-//                        capability `supportsMemoryReferences` is true."
+//                        pointer. For executable data, this reference may
+//                        later be used in a `disassemble` request. This
+//                        attribute may be returned by a debug adapter if
+//                        corresponding capability `supportsMemoryReferences`
+//                        is true."
 //     },
 //     "declarationLocationReference": {
 //       "type": "integer",
 //       "description": "A reference that allows the client to request the
-//                       location where the variable is declared. 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."
+//                       location where the variable is declared. 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."
 //     },
 //     "valueLocationReference": {
 //       "type": "integer",
@@ -1309,7 +1295,8 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
 //             },
 //             "column": {
 //               "type": "number",
-//               "description": "The 1-indexed source column where the variable
+//               "description": "The 1-indexed source column where the
+//               variable
 //                               was declared."
 //             }
 //           }
@@ -1317,10 +1304,10 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
 //         "value": {
 //           "type": "string",
 //           "description": "The internal value of the variable as returned by
-//                            This is effectively SBValue.GetValue(). The other
-//                            `value` entry in the top-level variable response
-//                            is, on the other hand, just a display string for
-//                            the variable."
+//                            This is effectively SBValue.GetValue(). The
+//                            other `value` entry in the top-level variable
+//                            response is, on the other hand, just a display
+//                            string for the variable."
 //         },
 //         "summary": {
 //           "type": "string",
@@ -1334,7 +1321,8 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
 //         },
 //         "error": {
 //           "type": "string",
-//           "description": "An error message generated if LLDB couldn't inspect
+//           "description": "An error message generated if LLDB couldn't
+//           inspect
 //                           the variable."
 //         }
 //       }
@@ -1361,10 +1349,10 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
   // request can be broken up in grabbing only a few children at a time. We
   // want to be careful and only call "v.GetNumChildren()" if we have an array
   // type or if we have a synthetic child provider producing indexed children.
-  // We don't want to call "v.GetNumChildren()" on all objects as class, struct
-  // and union types don't need to be completed if they are never expanded. So
-  // we want to avoid calling this to only cases where we it makes sense to keep
-  // performance high during normal debugging.
+  // We don't want to call "v.GetNumChildren()" on all objects as class,
+  // struct and union types don't need to be completed if they are never
+  // expanded. So we want to avoid calling this to only cases where we it
+  // makes sense to keep performance high during normal debugging.
 
   // If we have an array type, say that it is indexed and provide the number
   // of children in case we have a huge array. If we don't do this, then we
@@ -1373,9 +1361,9 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
   if (desc.type_obj.IsArrayType()) {
     object.try_emplace("indexedVariables", v.GetNumChildren());
   } else if (v.IsSynthetic()) {
-    // For a type with a synthetic child provider, the SBType of "v" won't tell
-    // us anything about what might be displayed. Instead, we check if the first
-    // child's name is "[0]" and then say it is indexed. We call
+    // For a type with a synthetic child provider, the SBType of "v" won't
+    // tell us anything about what might be displayed. Instead, we check if
+    // the first child's name is "[0]" and then say it is indexed. We call
     // GetNumChildren() only if the child name matches to avoid a potentially
     // expensive operation.
     if (lldb::SBValue first_child = v.GetChildAtIndex(0)) {
@@ -1392,8 +1380,8 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
   }
   EmplaceSafeString(object, "type", desc.display_type_name);
 
-  // A unique variable identifier to help in properly identifying variables with
-  // the same name. This is an extension to the VS protocol.
+  // A unique variable identifier to help in properly identifying variables
+  // with the same name. This is an extension to the VS protocol.
   object.try_emplace("id", var_ref);
 
   if (v.MightHaveChildren())
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index db56d98777347..9136754a6a78d 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -10,6 +10,7 @@
 #define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
 
 #include "DAPForward.h"
+#include "Protocol.h"
 #include "lldb/API/SBCompileUnit.h"
 #include "lldb/API/SBFileSpec.h"
 #include "lldb/API/SBFormat.h"
@@ -353,15 +354,7 @@ llvm::json::Value CreateSource(const lldb::SBLineEntry &line_entry);
 ///     definition outlined by Microsoft.
 llvm::json::Value CreateSource(llvm::StringRef source_path);
 
-/// Create a "StackFrame" object for a LLDB frame object.
-///
-/// This function will fill in the following keys in the returned
-/// object:
-///   "id" - the stack frame ID as an integer
-///   "name" - the function name as a string
-///   "source" - source file information as a "Source" DAP object
-///   "line" - the source file line number as an integer
-///   "column" - the source file column number as an integer
+/// Returns a DAP protocol StackFrame from the given lldb frame and format.
 ///
 /// \param[in] frame
 ///     The LLDB stack frame to use when populating out the "StackFrame"
@@ -374,8 +367,7 @@ llvm::json::Value CreateSource(llvm::StringRef source_path);
 /// \return
 ///     A "StackFrame" JSON object with that follows the formal JSON
 ///     definition outlined by Microsoft.
-llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
-                                   lldb::SBFormat &format);
+protocol::StackFrame toStackFrame(lldb::SBFrame &frame, lldb::SBFormat &format);
 
 /// Create a "StackFrame" label object for a LLDB thread.
 ///
@@ -497,7 +489,7 @@ struct VariableDescription {
   llvm::json::Object GetVariableExtensionsJSON();
 
   /// Returns a description of the value appropriate for the specified context.
-  std::string GetResult(llvm::StringRef context);
+  std::string GetResult(bool multiline);
 };
 
 /// Does the given variable have an associated value location?
diff --git a/lldb/tools/lldb-dap/Protocol.cpp b/lldb/tools/lldb-dap/Protocol.cpp
new file mode 100644
index 0000000000000..0cc36eab577bd
--- /dev/null
+++ b/lldb/tools/lldb-dap/Protocol.cpp
@@ -0,0 +1,451 @@
+//===-- Protocol.cpp --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Protocol.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/JSON.h"
+#include <optional>
+#include <utility>
+
+namespace llvm {
+namespace json {
+bool fromJSON(const llvm::json::Value &Params, llvm::json::Value &V,
+              llvm::json::Path P) {
+  V = std::move(Params);
+  return true;
+}
+} // namespace json
+} // namespace llvm
+
+namespace lldb_dap {
+namespace protocol {
+
+enum class MessageType { request, response, event };
+
+bool fromJSON(const llvm::json::Value &Params, MessageType &M,
+              llvm::json::Path P) {
+  auto rawType = Params.getAsString();
+  if (!rawType) {
+    P.report("expected a string");
+    return false;
+  }
+  std::optional<MessageType> type =
+      llvm::StringSwitch<std::optional<MessageType>>(*rawType)
+          .Case("request", MessageType::request)
+          .Case("response", MessageType::response)
+          .Case("event", MessageType::event)
+          .Default(std::nullopt);
+  if (!type) {
+    P.report("unexpected value");
+    return false;
+  }
+  M = *type;
+  return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params, ProtocolMessage &PM,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  if (!O)
+    return false;
+
+  MessageType type;
+  if (!O.map("type", type))
+    return false;
+
+  switch (type) {
+  case MessageType::request: {
+    Request req;
+    if (!fromJSON(Params, req, P)) {
+      return false;
+    }
+    PM = std::move(req);
+    return true;
+  }
+  case MessageType::response: {
+    Response resp;
+    if (!fromJSON(Params, resp, P)) {
+      return false;
+    }
+    PM = std::move(resp);
+    return true;
+  }
+  case MessageType::event:
+    Event evt;
+    if (!fromJSON(Params, evt, P)) {
+      return false;
+    }
+    PM = std::move(evt);
+    return true;
+  }
+  llvm_unreachable("Unsupported protocol message");
+}
+
+llvm::json::Value toJSON(const ProtocolMessage &PM) {
+  if (auto const *Req = std::get_if<Request>(&PM)) {
+    return toJSON(*Req);
+  }
+  if (auto const *Resp = std::get_if<Response>(&PM)) {
+    return toJSON(*Resp);
+  }
+  if (auto const *Evt = std::get_if<Event>(&PM)) {
+    return toJSON(*Evt);
+  }
+  llvm_unreachable("Unsupported protocol message");
+}
+
+llvm::json::Value toJSON(const Request &R) {
+  llvm::json::Object Result{
+      {"type", "request"},
+      {"seq", R.seq},
+      {"command", R.command},
+  };
+  if (R.rawArguments)
+    Result.insert({"arguments", R.rawArguments});
+  return std::move(Result);
+}
+
+bool fromJSON(llvm::json::Value const &Params, Request &R, llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  MessageType type;
+  if (!O.map("type", type)) {
+    return false;
+  }
+  if (type != MessageType::request) {
+    P.field("type").report("expected to be 'request'");
+    return false;
+  }
+
+  return O && O.map("command", R.command) && O.map("seq", R.seq) &&
+         O.map("arguments", R.rawArguments);
+}
+
+bool fromJSON(llvm::json::Value const &Params, Response &R,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  MessageType type;
+  if (!O.map("type", type)) {
+    return false;
+  }
+  if (type != MessageType::response) {
+    P.field("type").report("expected to be 'response'");
+    return false;
+  }
+  return O && O.map("command", R.command) &&
+         O.map("request_seq", R.request_seq) && O.map("success", R.success) &&
+         O.mapOptional("message", R.message) &&
+         O.mapOptional("body", R.rawBody);
+}
+
+bool fromJSON(llvm::json::Value const &Params, Event &E, llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  MessageType type;
+  if (!O.map("type", type)) {
+    return false;
+  }
+  if (type != MessageType::event) {
+    P.field("type").report("expected to be 'event'");
+    return false;
+  }
+
+  return O && O.map("event", E.event) && O.mapOptional("body", E.rawBody);
+}
+
+llvm::json::Value toJSON(const Response &R) {
+  llvm::json::Object Result{{"type", "response"},
+                            {"req", 0},
+                            {"command", R.command},
+                            {"request_seq", R.request_seq},
+                            {"success", R.success}};
+
+  if (R.message)
+    Result.insert({"message", R.message});
+  if (R.rawBody)
+    Result.insert({"body", R.rawBody});
+
+  return std::move(Result);
+}
+
+llvm::json::Value toJSON(lldb_dap::protocol::ErrorResponseBody const &ERB) {
+  return llvm::json::Object{{"error", ERB.error}};
+}
+
+llvm::json::Value toJSON(std::map<std::string, std::string> const &KV) {
+  llvm::json::Object Result;
+  for (const auto &[K, V] : KV)
+    Result.insert({K, V});
+  return std::move(Result);
+}
+
+llvm::json::Value toJSON(lldb_dap::protocol::Message const &M) {
+  return llvm::json::Object{{"id", M.id},
+                            {"format", M.format},
+                            {"showUser", M.showUser},
+                            {"sendTelemetry", M.sendTelemetry},
+                            {"variables", toJSON(*M.variables)},
+                            {"url", M.url},
+                            {"urlLabel", M.urlLabel}};
+}
+
+bool fromJSON(const llvm::json::Value &Params, Source::PresentationHint &PH,
+              llvm::json::Path P) {
+  auto rawHint = Params.getAsString();
+  if (!rawHint) {
+    P.report("expected a string");
+    return false;
+  }
+  std::optional<Source::PresentationHint> hint =
+      llvm::StringSwitch<std::optional<Source::PresentationHint>>(*rawHint)
+          .Case("normal", Source::PresentationHint::normal)
+          .Case("emphasize", Source::PresentationHint::emphasize)
+          .Case("deemphasize", Source::PresentationHint::deemphasize)
+          .Default(std::nullopt);
+  if (!hint) {
+    P.report("unexpected value");
+    return false;
+  }
+  PH = *hint;
+  return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params, Source &S, llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.mapOptional("name", S.name) && O.mapOptional("path", S.path) &&
+         O.mapOptional("presentationHint", S.presentationHint) &&
+         O.mapOptional("sourceReference", S.sourceReference) &&
+         O.mapOptional("origin", S.origin);
+}
+
+bool fromJSON(const llvm::json::Value &Params, CancelArguments &CA,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.mapOptional("requestId", CA.requestId) &&
+         O.mapOptional("progressId", CA.progressId);
+}
+
+llvm::json::Value toJSON(const Source &S) {
+  llvm::json::Object Result;
+
+  if (S.name)
+    Result.insert({"name", S.name});
+  if (S.path)
+    Result.insert({"path", S.path});
+  if (S.presentationHint)
+    switch (*S.presentationHint) {
+    case Source::PresentationHint::normal:
+      Result.insert({"presentationHint", "normal"});
+      break;
+    case Source::PresentationHint::emphasize:
+      Result.insert({"presentationHint", "emphasize"});
+      break;
+    case Source::PresentationHint::deemphasize:
+      Result.insert({"presentationHint", "deemphasize"});
+      break;
+    }
+  if (S.sourceReference)
+    Result.insert({"sourceReference", S.sourceReference});
+  if (S.origin)
+    Result.insert({"origin", S.origin});
+
+  return std::move(Result);
+}
+
+llvm::json::Value toJSON(const StackFrame &SF) {
+  llvm::json::Object Result{{"id", SF.id},
+                            {"name", SF.name},
+                            {"line", SF.line},
+                            {"column", SF.column}};
+
+  if (SF.source)
+    Result.insert({"source", SF.source});
+  if (SF.endLine)
+    Result.insert({"endLine", SF.endLine});
+  if (SF.endColumn)
+    Result.insert({"endColumn", SF.endColumn});
+  if (SF.canRestart)
+    Result.insert({"canRestart", SF.canRestart});
+  if (SF.instructionPointerReference)
+    Result.insert(
+        {"instructionPointerReference", SF.instructionPointerReference});
+  if (SF.presentationHint)
+    switch (*SF.presentationHint) {
+    case StackFrame::PresentationHint::normal:
+      Result.insert({"presentationHint", "normal"});
+      break;
+    case StackFrame::PresentationHint::label:
+      Result.insert({"presentationHint", "label"});
+      break;
+    case StackFrame::PresentationHint::subtle:
+      Result.insert({"presentationHint", "subtle"});
+      break;
+    }
+  return std::move(Result);
+}
+
+bool fromJSON(const llvm::json::Value &Params, SourceArguments &SA,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.mapOptional("source", SA.source) &&
+         O.map("sourceReference", SA.sourceReference);
+}
+
+llvm::json::Value toJSON(const SourceResponseBody &SA) {
+  llvm::json::Object Result{{"content", SA.content}};
+
+  if (SA.mimeType)
+    Result.insert({"mimeType", SA.mimeType});
+
+  return std::move(Result);
+}
+
+bool fromJSON(const llvm::json::Value &Params, EvaluateArguments::Context &C,
+              llvm::json::Path P) {
+  auto rawContext = Params.getAsString();
+  if (!rawContext) {
+    P.report("expected a string");
+    return false;
+  }
+  std::optional<EvaluateArguments::Context> context =
+      llvm::StringSwitch<std::optional<EvaluateArguments::Context>>(*rawContext)
+          .Case("repl", EvaluateArguments::Context::repl)
+          .Case("watch", EvaluateArguments::Context::watch)
+          .Case("clipboard", EvaluateArguments::Context::clipboard)
+          .Case("hover", EvaluateArguments::Context::hover)
+          .Case("variables", EvaluateArguments::Context::variables)
+          .Default(std::nullopt);
+  if (!context) {
+    P.report("unexpected value");
+    return false;
+  }
+  C = *context;
+  return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params, EvaluateArguments &EA,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("expression", EA.expression) &&
+         O.mapOptional("frameId", EA.frameId) &&
+         O.mapOptional("line", EA.line) && O.mapOptional("column", EA.column) &&
+         O.mapOptional("source", EA.source) &&
+         O.mapOptional("context", EA.context);
+}
+
+llvm::json::Value toJSON(const EvaluateResponseBody &ERB) {
+  llvm::json::Object Result{{"result", ERB.result},
+                            {"variablesReference", ERB.variablesReference}};
+
+  if (ERB.type)
+    Result.insert({"type", ERB.type});
+  if (ERB.presentationHint)
+    Result.insert({"presentationHint", ERB.presentationHint});
+  if (ERB.namedVariables)
+    Result.insert({"namedVariables", ERB.namedVariables});
+  if (ERB.indexedVariables)
+    Result.insert({"indexedVariables", ERB.indexedVariables});
+  if (ERB.memoryReference)
+    Result.insert({"memoryReference", ERB.memoryReference});
+  if (ERB.valueLocationReference)
+    Result.insert({"valueLocationReference", ERB.valueLocationReference});
+
+  return std::move(Result);
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint::Kind &K) {
+  switch (K) {
+  case VariablePresentationHint::Kind::property_:
+    return "property";
+  case VariablePresentationHint::Kind::method_:
+    return "method";
+  case VariablePresentationHint::Kind::class_:
+    return "class";
+  case VariablePresentationHint::Kind::data_:
+    return "data";
+  case VariablePresentationHint::Kind::event_:
+    return "event";
+  case VariablePresentationHint::Kind::baseClass_:
+    return "baseClass";
+  case VariablePresentationHint::Kind::innerClass_:
+    return "innerClass";
+  case VariablePresentationHint::Kind::interface_:
+    return "interface";
+  case VariablePresentationHint::Kind::mostDerivedClass_:
+    return "mostDerivedClass";
+  case VariablePresentationHint::Kind::virtual_:
+    return "virtual";
+  case VariablePresentationHint::Kind::dataBreakpoint_:
+    return "dataBreakpoint";
+  }
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint::Attributes &A) {
+  switch (A) {
+  case VariablePresentationHint::Attributes::static_:
+    return "static";
+  case VariablePresentationHint::Attributes::constant:
+    return "constant";
+  case VariablePresentationHint::Attributes::readOnly:
+    return "readOnly";
+  case VariablePresentationHint::Attributes::rawString:
+    return "rawString";
+  case VariablePresentationHint::Attributes::hasObjectId:
+    return "hasObjectId";
+  case VariablePresentationHint::Attributes::canHaveObjectId:
+    return "canHaveObjectId";
+  case VariablePresentationHint::Attributes::hasSideEffects:
+    return "hasSideEffects";
+  case VariablePresentationHint::Attributes::hasDataBreakpoint:
+    return "hasDataBreakpoint";
+  }
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint::Visibility &V) {
+  switch (V) {
+  case VariablePresentationHint::Visibility::public_:
+    return "public";
+  case VariablePresentationHint::Visibility::private_:
+    return "private";
+  case VariablePresentationHint::Visibility::protected_:
+    return "protected";
+  case VariablePresentationHint::Visibility::internal_:
+    return "internal";
+  case VariablePresentationHint::Visibility::final_:
+    return "final";
+  }
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint &VPH) {
+  llvm::json::Object Result;
+  if (VPH.kind)
+    Result.insert({"kind", VPH.kind});
+  if (VPH.attributes)
+    Result.insert({"attributes", VPH.attributes});
+  if (VPH.visibility)
+    Result.insert({"visibility", VPH.visibility});
+  if (VPH.lazy)
+    Result.insert({"lazy", VPH.lazy});
+  return std::move(Result);
+}
+
+llvm::json::Value toJSON(const Event &E) {
+  llvm::json::Object Result{
+      {"type", "event"},
+      {"seq", 0},
+      {"event", E.event},
+  };
+  if (E.rawBody)
+    Result.insert({"body", E.rawBody});
+  return std::move(Result);
+}
+
+llvm::json::Value toJSON(const ExitedEventBody &EEB) {
+  return llvm::json::Object{{"exitCode", EEB.exitCode}};
+}
+
+} // namespace protocol
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Protocol.h b/lldb/tools/lldb-dap/Protocol.h
new file mode 100644
index 0000000000000..a1125607219e9
--- /dev/null
+++ b/lldb/tools/lldb-dap/Protocol.h
@@ -0,0 +1,905 @@
+//===-- Protocol.h ----------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains POD structs based on the DAP specification at
+// https://microsoft.github.io/debug-adapter-protocol/specification
+//
+// This is not meant to be a complete implementation, new interfaces are added
+// when they're needed.
+//
+// Each struct has a toJSON and fromJSON function, that converts between
+// the struct and a JSON representation. (See JSON.h)
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
+#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
+
+#include "llvm/Support/JSON.h"
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+
+namespace lldb_dap {
+namespace protocol {
+
+// MARK: Base Protocol
+
+// "Request": {
+//   "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
+//     "type": "object",
+//     "description": "A client or debug adapter initiated request.",
+//     "properties": {
+//       "type": {
+//         "type": "string",
+//         "enum": [ "request" ]
+//       },
+//       "command": {
+//         "type": "string",
+//         "description": "The command to execute."
+//       },
+//       "arguments": {
+//         "type": [ "array", "boolean", "integer", "null", "number" , "object",
+//         "string" ], "description": "Object containing arguments for the
+//         command."
+//       }
+//     },
+//     "required": [ "type", "command" ]
+//   }]
+// },
+struct Request {
+  int64_t seq;
+  std::string command;
+  std::optional<llvm::json::Value> rawArguments;
+};
+llvm::json::Value toJSON(const Request &);
+bool fromJSON(const llvm::json::Value &, Request &, llvm::json::Path);
+
+// "Event": {
+//   "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
+//     "type": "object",
+//     "description": "A debug adapter initiated event.",
+//     "properties": {
+//       "type": {
+//         "type": "string",
+//         "enum": [ "event" ]
+//       },
+//       "event": {
+//         "type": "string",
+//         "description": "Type of event."
+//       },
+//       "body": {
+//         "type": [ "array", "boolean", "integer", "null", "number" , "object",
+//         "string" ], "description": "Event-specific information."
+//       }
+//     },
+//     "required": [ "type", "event" ]
+//   }]
+// },
+struct Event {
+  std::string event;
+  std::optional<llvm::json::Value> rawBody;
+};
+llvm::json::Value toJSON(const Event &);
+bool fromJSON(const llvm::json::Value &, Event &, llvm::json::Path);
+
+// "Response" : {
+//   "allOf" : [
+//     {"$ref" : "#/definitions/ProtocolMessage"}, {
+//       "type" : "object",
+//       "description" : "Response for a request.",
+//       "properties" : {
+//         "type" : {"type" : "string", "enum" : ["response"]},
+//         "request_seq" : {
+//           "type" : "integer",
+//           "description" : "Sequence number of the corresponding request."
+//         },
+//         "success" : {
+//           "type" : "boolean",
+//           "description" :
+//               "Outcome of the request.\nIf true, the request was successful "
+//               "and the `body` attribute may contain the result of the "
+//               "request.\nIf the value is false, the attribute `message` "
+//               "contains the error in short form and the `body` may contain "
+//               "additional information (see `ErrorResponse.body.error`)."
+//         },
+//         "command" :
+//             {"type" : "string", "description" : "The command requested."},
+//         "message" : {
+//           "type" : "string",
+//           "description" :
+//               "Contains the raw error in short form if `success` is "
+//               "false.\nThis raw error might be interpreted by the client and
+//               " "is not shown in the UI.\nSome predefined values exist.",
+//           "_enum" : [ "cancelled", "notStopped" ],
+//           "enumDescriptions" : [
+//             "the request was cancelled.", "the request may be retried once
+//             the "
+//                                           "adapter is in a 'stopped' state."
+//           ]
+//         },
+//         "body" : {
+//           "type" : [
+//             "array", "boolean", "integer", "null", "number", "object",
+//             "string"
+//           ],
+//           "description" : "Contains request result if success is true and "
+//                           "error details if success is false."
+//         }
+//       },
+//       "required" : [ "type", "request_seq", "success", "command" ]
+//     }
+//   ]
+// }
+struct Response {
+  int64_t request_seq;
+  bool success;
+  std::string command;
+  std::optional<std::string> message;
+  std::optional<llvm::json::Value> rawBody;
+};
+bool fromJSON(const llvm::json::Value &, Response &, llvm::json::Path);
+llvm::json::Value toJSON(const Response &);
+
+// A void response body for any response without a specific value.
+using VoidResponseBody = std::nullptr_t;
+
+// "ProtocolMessage": {
+//   "type": "object",
+//   "title": "Base Protocol",
+//   "description": "Base class of requests, responses, and events.",
+//   "properties": {
+//     "seq": {
+//       "type": "integer",
+//       "description": "Sequence number of the message (also known as
+//       message ID). The `seq` for the first message sent by a client or
+//       debug adapter is 1, and for each subsequent message is 1 greater
+//       than the previous message sent by that actor. `seq` can be used to
+//       order requests, responses, and events, and to associate requests
+//       with their corresponding responses. For protocol messages of type
+//       `request` the sequence number can be used to cancel the request."
+//     },
+//     "type": {
+//       "type": "string",
+//       "description": "Message type.",
+//       "_enum": [ "request", "response", "event" ]
+//     }
+//   },
+//   "required": [ "seq", "type" ]
+// },
+// struct ProtocolMessage {
+//   MessageType type;
+//   int64_t seq;
+// };
+// enum class MessageType { request, event, response };
+using ProtocolMessage = std::variant<Request, Response, Event>;
+bool fromJSON(const llvm::json::Value &, ProtocolMessage &, llvm::json::Path);
+llvm::json::Value toJSON(const ProtocolMessage &);
+
+// "CancelRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "The `cancel` request is used by the client in two
+//     situations:\n- to indicate that it is no longer interested in the result
+//     produced by a specific request issued earlier\n- to cancel a progress
+//     sequence.\nClients should only call this request if the corresponding
+//     capability `supportsCancelRequest` is true.\nThis request has a hint
+//     characteristic: a debug adapter can only be expected to make a 'best
+//     effort' in honoring this request but there are no guarantees.\nThe
+//     `cancel` request may return an error if it could not cancel an operation
+//     but a client should refrain from presenting this error to end users.\nThe
+//     request that got cancelled still needs to send a response back. This can
+//     either be a normal result (`success` attribute true) or an error response
+//     (`success` attribute false and the `message` set to
+//     `cancelled`).\nReturning partial results from a cancelled request is
+//     possible but please note that a client has no generic way for detecting
+//     that a response is partial or not.\nThe progress that got cancelled still
+//     needs to send a `progressEnd` event back.\n A client should not assume
+//     that progress just got cancelled after sending the `cancel` request.",
+//     "properties": {
+//       "command": {
+//         "type": "string",
+//         "enum": [ "cancel" ]
+//       },
+//       "arguments": {
+//         "$ref": "#/definitions/CancelArguments"
+//       }
+//     },
+//     "required": [ "command" ]
+//   }]
+// },
+// "CancelArguments": {
+//   "type": "object",
+//   "description": "Arguments for `cancel` request.",
+//   "properties": {
+//     "requestId": {
+//       "type": "integer",
+//       "description": "The ID (attribute `seq`) of the request to cancel. If
+//       missing no request is cancelled.\nBoth a `requestId` and a `progressId`
+//       can be specified in one request."
+//     },
+//     "progressId": {
+//       "type": "string",
+//       "description": "The ID (attribute `progressId`) of the progress to
+//       cancel. If missing no progress is cancelled.\nBoth a `requestId` and a
+//       `progressId` can be specified in one request."
+//     }
+//   }
+// },
+struct CancelArguments {
+  std::optional<int64_t> requestId;
+  std::optional<int64_t> progressId;
+};
+bool fromJSON(const llvm::json::Value &, CancelArguments &, llvm::json::Path);
+
+// "CancelResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "Response to `cancel` request. This is just an
+//     acknowledgement, so no body field is required."
+//   }]
+// },
+using CancelResponseBody = VoidResponseBody;
+
+// "Message": {
+//   "type": "object",
+//   "description": "A structured message object. Used to return errors from
+//   requests.", "properties": {
+//     "id": {
+//       "type": "integer",
+//       "description": "Unique (within a debug adapter implementation)
+//       identifier for the message. The purpose of these error IDs is to
+//       help extension authors that have the requirement that every user
+//       visible error message needs a corresponding error number, so that
+//       users or customer support can find information about the specific
+//       error more easily."
+//     },
+//     "format": {
+//       "type": "string",
+//       "description": "A format string for the message. Embedded variables
+//       have the form `{name}`.\nIf variable name starts with an underscore
+//       character, the variable does not contain user data (PII) and can be
+//       safely used for telemetry purposes."
+//     },
+//     "variables": {
+//       "type": "object",
+//       "description": "An object used as a dictionary for looking up the
+//       variables in the format string.", "additionalProperties": {
+//         "type": "string",
+//         "description": "All dictionary values must be strings."
+//       }
+//     },
+//     "sendTelemetry": {
+//       "type": "boolean",
+//       "description": "If true send to telemetry."
+//     },
+//     "showUser": {
+//       "type": "boolean",
+//       "description": "If true show user."
+//     },
+//     "url": {
+//       "type": "string",
+//       "description": "A url where additional information about this
+//       message can be found."
+//     },
+//     "urlLabel": {
+//       "type": "string",
+//       "description": "A label that is presented to the user as the UI for
+//       opening the url."
+//     }
+//   },
+//   "required": [ "id", "format" ]
+// },
+struct Message {
+  int id;
+  std::string format;
+  std::optional<bool> showUser;
+  std::optional<std::map<std::string, std::string>> variables;
+  std::optional<bool> sendTelemetry;
+  std::optional<std::string> url;
+  std::optional<std::string> urlLabel;
+};
+llvm::json::Value toJSON(const Message &);
+
+// "ErrorResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "On error (whenever `success` is false), the body can
+//     provide more details.", "properties": {
+//       "body": {
+//         "type": "object",
+//         "properties": {
+//           "error": {
+//             "$ref": "#/definitions/Message",
+//             "description": "A structured error message."
+//           }
+//         }
+//       }
+//     },
+//     "required": [ "body" ]
+//   }]
+// },
+struct ErrorResponseBody {
+  std::optional<Message> error;
+};
+llvm::json::Value toJSON(const ErrorResponseBody &);
+
+// MARK: Types
+
+// "Source" : {
+//   "type" : "object",
+//   "description" : "A `Source` is a descriptor for source
+//   code.\nIt is returned "
+//                   "from the debug adapter as part of a
+//                   `StackFrame` and it is " "used by clients when
+//                   specifying breakpoints.",
+//   "properties" : {
+//     "name" : {
+//       "type" : "string",
+//       "description" : "The short name of the source. Every
+//       source returned "
+//                       "from the debug adapter has a name.\nWhen
+//                       sending a " "source to the debug adapter
+//                       this name is optional."
+//     },
+//     "path" : {
+//       "type" : "string",
+//       "description" :
+//           "The path of the source to be shown in the UI.\nIt is
+//           only used to " "locate and load the content of the
+//           source if no `sourceReference` " "is specified (or its
+//           value is 0)."
+//     },
+//     "sourceReference" : {
+//       "type" : "integer",
+//       "description" :
+//           "If the value > 0 the contents of the source must be
+//           retrieved " "through the `source` request (even if a
+//           path is specified).\nSince " "a `sourceReference` is
+//           only valid for a session, it can not be used " "to
+//           persist a source.\nThe value should be less than or
+//           equal to " "2147483647 (2^31-1)."
+//     },
+//     "presentationHint" : {
+//       "type" : "string",
+//       "description" :
+//           "A hint for how to present the source in the UI.\nA
+//           value of "
+//           "`deemphasize` can be used to indicate that the source
+//           is not " "available or that it is skipped on
+//           stepping.",
+//       "enum" : [ "normal", "emphasize", "deemphasize" ]
+//     },
+//     "origin" : {
+//       "type" : "string",
+//       "description" : "The origin of this source. For example,
+//       'internal "
+//                       "module', 'inlined content from source
+//                       map', etc."
+//     },
+//     "sources" : {
+//       "type" : "array",
+//       "items" : {"$ref" : "#/definitions/Source"},
+//       "description" : "A list of sources that are related to
+//       this source. "
+//                       "These may be the source that generated
+//                       this source."
+//     },
+//     "adapterData" : {
+//       "type" : [
+//         "array", "boolean", "integer", "null", "number",
+//         "object", "string"
+//       ],
+//       "description" :
+//           "Additional data that a debug adapter might want to
+//           loop through the " "client.\nThe client should leave
+//           the data intact and persist it " "across sessions. The
+//           client should not interpret the data."
+//     },
+//     "checksums" : {
+//       "type" : "array",
+//       "items" : {"$ref" : "#/definitions/Checksum"},
+//       "description" : "The checksums associated with this file."
+//     }
+//   }
+struct Source {
+  enum class PresentationHint { normal, emphasize, deemphasize };
+
+  std::optional<std::string> name;
+  std::optional<std::string> path;
+  std::optional<int64_t> sourceReference;
+  std::optional<PresentationHint> presentationHint;
+  std::optional<std::string> origin;
+
+  // Unsupported fields: adapterData, checksums
+};
+bool fromJSON(const llvm::json::Value &, Source &, llvm::json::Path);
+llvm::json::Value toJSON(const Source &);
+
+// "StackFrame" : {
+//   "type" : "object",
+//   "description" : "A Stackframe contains the source location.",
+//   "properties" : {
+//     "id" : {
+//       "type" : "integer",
+//       "description" : "An identifier for the stack frame. It must be unique "
+//                       "across all threads.\nThis id can be used to retrieve "
+//                       "the scopes of the frame with the `scopes` request or
+//                       to " "restart the execution of a stack frame."
+//     },
+//     "name" : {
+//       "type" : "string",
+//       "description" : "The name of the stack frame, typically a method name."
+//     },
+//     "source" : {
+//       "$ref" : "#/definitions/Source",
+//       "description" : "The source of the frame."
+//     },
+//     "line" : {
+//       "type" : "integer",
+//       "description" : "The line within the source of the frame. If the source
+//       "
+//                       "attribute is missing or doesn't exist, `line` is 0 and
+//                       " "should be ignored by the client."
+//     },
+//     "column" : {
+//       "type" : "integer",
+//       "description" :
+//           "Start position of the range covered by the stack frame. It is "
+//           "measured in UTF-16 code units and the client capability "
+//           "`columnsStartAt1` determines whether it is 0- or 1-based. If "
+//           "attribute `source` is missing or doesn't exist, `column` is 0 and
+//           " "should be ignored by the client."
+//     },
+//     "endLine" : {
+//       "type" : "integer",
+//       "description" : "The end line of the range covered by the stack frame."
+//     },
+//     "endColumn" : {
+//       "type" : "integer",
+//       "description" :
+//           "End position of the range covered by the stack frame. It is "
+//           "measured in UTF-16 code units and the client capability "
+//           "`columnsStartAt1` determines whether it is 0- or 1-based."
+//     },
+//     "canRestart" : {
+//       "type" : "boolean",
+//       "description" :
+//           "Indicates whether this frame can be restarted with the "
+//           "`restartFrame` request. Clients should only use this if the debug
+//           " "adapter supports the `restart` request and the corresponding "
+//           "capability `supportsRestartFrame` is true. If a debug adapter has
+//           " "this capability, then `canRestart` defaults to `true` if the "
+//           "property is absent."
+//     },
+//     "instructionPointerReference" : {
+//       "type" : "string",
+//       "description" : "A memory reference for the current instruction pointer
+//       "
+//                       "in this frame."
+//     },
+//     "moduleId" : {
+//       "type" : [ "integer", "string" ],
+//       "description" : "The module associated with this frame, if any."
+//     },
+//     "presentationHint" : {
+//       "type" : "string",
+//       "enum" : [ "normal", "label", "subtle" ],
+//       "description" :
+//           "A hint for how to present this frame in the UI.\nA value of
+//           `label` " "can be used to indicate that the frame is an artificial
+//           frame that " "is used as a visual label or separator. A value of
+//           `subtle` can be " "used to change the appearance of a frame in a
+//           'subtle' way."
+//     }
+//   },
+//   "required" : [ "id", "name", "line", "column" ]
+// }
+struct StackFrame {
+  enum class PresentationHint { normal, label, subtle };
+  int64_t id = 0;
+  std::string name = "";
+  uint32_t line = 0;
+  uint32_t column = 0;
+  std::optional<Source> source;
+  std::optional<uint32_t> endLine;
+  std::optional<uint32_t> endColumn;
+  std::optional<bool> canRestart;
+  std::optional<std::string> instructionPointerReference;
+  std::optional<PresentationHint> presentationHint;
+  // Unsupported fields: "moduleId"
+};
+llvm::json::Value toJSON(const StackFrame &);
+
+// "VariablePresentationHint": {
+//   "type": "object",
+//   "description": "Properties of a variable that can be used to determine how
+//   to render the variable in the UI.", "properties": {
+//     "kind": {
+//       "description": "The kind of variable. Before introducing additional
+//       values, try to use the listed values.", "type": "string",
+//       "_enum": [ "property", "method", "class", "data", "event", "baseClass",
+//       "innerClass", "interface", "mostDerivedClass", "virtual",
+//       "dataBreakpoint" ], "enumDescriptions": [
+//         "Indicates that the object is a property.",
+//         "Indicates that the object is a method.",
+//         "Indicates that the object is a class.",
+//         "Indicates that the object is data.",
+//         "Indicates that the object is an event.",
+//         "Indicates that the object is a base class.",
+//         "Indicates that the object is an inner class.",
+//         "Indicates that the object is an interface.",
+//         "Indicates that the object is the most derived class.",
+//         "Indicates that the object is virtual, that means it is a synthetic
+//         object introduced by the adapter for rendering purposes, e.g. an
+//         index range for large arrays.", "Deprecated: Indicates that a data
+//         breakpoint is registered for the object. The `hasDataBreakpoint`
+//         attribute should generally be used instead."
+//       ]
+//     },
+//     "attributes": {
+//       "description": "Set of attributes represented as an array of strings.
+//       Before introducing additional values, try to use the listed values.",
+//       "type": "array",
+//       "items": {
+//         "type": "string",
+//         "_enum": [ "static", "constant", "readOnly", "rawString",
+//         "hasObjectId", "canHaveObjectId", "hasSideEffects",
+//         "hasDataBreakpoint" ], "enumDescriptions": [
+//           "Indicates that the object is static.",
+//           "Indicates that the object is a constant.",
+//           "Indicates that the object is read only.",
+//           "Indicates that the object is a raw string.",
+//           "Indicates that the object can have an Object ID created for it.
+//           This is a vestigial attribute that is used by some clients; 'Object
+//           ID's are not specified in the protocol.", "Indicates that the
+//           object has an Object ID associated with it. This is a vestigial
+//           attribute that is used by some clients; 'Object ID's are not
+//           specified in the protocol.", "Indicates that the evaluation had
+//           side effects.", "Indicates that the object has its value tracked by
+//           a data breakpoint."
+//         ]
+//       }
+//     },
+//     "visibility": {
+//       "description": "Visibility of variable. Before introducing additional
+//       values, try to use the listed values.", "type": "string",
+//       "_enum": [ "public", "private", "protected", "internal", "final" ]
+//     },
+//     "lazy": {
+//       "description": "If true, clients can present the variable with a UI
+//       that supports a specific gesture to trigger its evaluation.\nThis
+//       mechanism can be used for properties that require executing code when
+//       retrieving their value and where the code execution can be expensive
+//       and/or produce side-effects. A typical example are properties based on
+//       a getter function.\nPlease note that in addition to the `lazy` flag,
+//       the variable's `variablesReference` is expected to refer to a variable
+//       that will provide the value through another `variable` request.",
+//       "type": "boolean"
+//     }
+//   }
+// },
+struct VariablePresentationHint {
+  enum class Kind {
+    property_,
+    method_,
+    class_,
+    data_,
+    event_,
+    baseClass_,
+    innerClass_,
+    interface_,
+    mostDerivedClass_,
+    virtual_,
+    dataBreakpoint_,
+  };
+  enum class Attributes {
+    static_,
+    constant,
+    readOnly,
+    rawString,
+    hasObjectId,
+    canHaveObjectId,
+    hasSideEffects,
+    hasDataBreakpoint,
+  };
+  enum class Visibility {
+    public_,
+    private_,
+    protected_,
+    internal_,
+    final_,
+  };
+
+  std::optional<Kind> kind;
+  std::optional<std::vector<Attributes>> attributes;
+  std::optional<Visibility> visibility;
+  std::optional<bool> lazy;
+};
+llvm::json::Value toJSON(const VariablePresentationHint &);
+llvm::json::Value toJSON(const VariablePresentationHint::Kind &);
+llvm::json::Value toJSON(const VariablePresentationHint::Attributes &);
+llvm::json::Value toJSON(const VariablePresentationHint::Visibility &);
+
+// MARK: Events
+
+// "ExitedEvent": {
+//   "allOf": [ { "$ref": "#/definitions/Event" }, {
+//     "type": "object",
+//     "description": "The event indicates that the debuggee has exited and
+//     returns its exit code.", "properties": {
+//       "event": {
+//         "type": "string",
+//         "enum": [ "exited" ]
+//       },
+//       "body": {
+//         "type": "object",
+//         "properties": {
+//           "exitCode": {
+//             "type": "integer",
+//             "description": "The exit code returned from the debuggee."
+//           }
+//         },
+//         "required": [ "exitCode" ]
+//       }
+//     },
+//     "required": [ "event", "body" ]
+//   }]
+// }
+struct ExitedEventBody {
+  int exitCode;
+};
+llvm::json::Value toJSON(const ExitedEventBody &);
+
+// MARK: Requests
+
+// "EvaluateRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "Evaluates the given expression in the context of a stack
+//     frame.\nThe 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."
+//     },
+//     "line": {
+//       "type": "integer",
+//       "description": "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."
+//     },
+//     "column": {
+//       "type": "integer",
+//       "description": "The contextual column where the expression should be
+//       evaluated. This may be provided if `line` is also provided.\n\nIt is
+//       measured in UTF-16 code units and the client capability
+//       `columnsStartAt1` determines whether it is 0- or 1-based."
+//     },
+//     "source": {
+//       "$ref": "#/definitions/Source",
+//       "description": "The contextual source in which the `line` is found.
+//       This must be provided if `line` is provided."
+//     },
+//     "context": {
+//       "type": "string",
+//       "_enum": [ "watch", "repl", "hover", "clipboard", "variables" ],
+//       "enumDescriptions": [
+//         "evaluate is called from a watch view context.",
+//         "evaluate is called from a REPL context.",
+//         "evaluate is called to generate the debug hover contents.\nThis value
+//         should only be used if the corresponding capability
+//         `supportsEvaluateForHovers` is true.", "evaluate is called to
+//         generate clipboard contents.\nThis value should only be used if the
+//         corresponding capability `supportsClipboardContext` is true.",
+//         "evaluate is called from a variables view context."
+//       ],
+//       "description": "The context in which the evaluate request is used."
+//     },
+//     "format": {
+//       "$ref": "#/definitions/ValueFormat",
+//       "description": "Specifies details on how to format the result.\nThe
+//       attribute is only honored by a debug adapter if the corresponding
+//       capability `supportsValueFormattingOptions` is true."
+//     }
+//   },
+//   "required": [ "expression" ]
+// },
+struct EvaluateArguments {
+  enum class Context { watch, repl, hover, clipboard, variables };
+
+  std::string expression;
+  std::optional<int64_t> frameId;
+  std::optional<int> line;
+  std::optional<int> column;
+  std::optional<Source> source;
+  std::optional<Context> context;
+  // std::optional<ValueFormat> format; // unsupported
+};
+bool fromJSON(const llvm::json::Value &, EvaluateArguments &, llvm::json::Path);
+
+// "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 type of the evaluate result.\nThis attribute
+//             should only be returned by a debug adapter if the corresponding
+//             capability `supportsVariableType` is true."
+//           },
+//           "presentationHint": {
+//             "$ref": "#/definitions/VariablePresentationHint",
+//             "description": "Properties of an evaluate result that can be used
+//             to determine how to render the result in the UI."
+//           },
+//           "variablesReference": {
+//             "type": "integer",
+//             "description": "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."
+//           },
+//           "namedVariables": {
+//             "type": "integer",
+//             "description": "The number of named child variables.\nThe client
+//             can use this information to present the variables in a paged UI
+//             and fetch them in chunks.\nThe value should be less than or equal
+//             to 2147483647 (2^31-1)."
+//           },
+//           "indexedVariables": {
+//             "type": "integer",
+//             "description": "The number of indexed child variables.\nThe
+//             client can use this information to present the variables in a
+//             paged UI and fetch them in chunks.\nThe value should be less than
+//             or equal to 2147483647 (2^31-1)."
+//           },
+//           "memoryReference": {
+//             "type": "string",
+//             "description": "A memory reference to a location appropriate for
+//             this result.\nFor pointer type eval results, this is generally a
+//             reference to the memory address contained in the pointer.\nThis
+//             attribute may be returned by a debug adapter if corresponding
+//             capability `supportsMemoryReferences` is true."
+//           },
+//           "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."
+//           }
+//         },
+//         "required": [ "result", "variablesReference" ]
+//       }
+//     },
+//     "required": [ "body" ]
+//   }]
+// },
+struct EvaluateResponseBody {
+  std::string result = "";
+  std::optional<std::string> type;
+  std::optional<VariablePresentationHint> presentationHint;
+  int64_t variablesReference = 0;
+  std::optional<int> namedVariables;
+  std::optional<int> indexedVariables;
+  std::optional<std::string> memoryReference;
+  std::optional<int> valueLocationReference;
+};
+llvm::json::Value toJSON(const EvaluateResponseBody &);
+
+// "SourceRequest": {
+//   "allOf": [ { "$ref": "#/definitions/Request" }, {
+//     "type": "object",
+//     "description": "The request retrieves the source code for a given source
+//     reference.", "properties": {
+//       "command": {
+//         "type": "string",
+//         "enum": [ "source" ]
+//       },
+//       "arguments": {
+//         "$ref": "#/definitions/SourceArguments"
+//       }
+//     },
+//     "required": [ "command", "arguments"  ]
+//   }]
+// },
+// "SourceArguments": {
+//   "type": "object",
+//   "description": "Arguments for 'source' request.",
+//   "properties": {
+//     "source": {
+//       "$ref": "#/definitions/Source",
+//       "description": "Specifies the source content to load. Either
+//       source.path or source.sourceReference must be specified."
+//     },
+//     "sourceReference": {
+//       "type": "integer",
+//       "description": "The reference to the source. This is the same as
+//       source.sourceReference. This is provided for backward compatibility
+//       since old backends do not understand the 'source' attribute."
+//     }
+//   },
+//   "required": [ "sourceReference" ]
+// }
+struct SourceArguments {
+  std::optional<Source> source;
+  int64_t sourceReference;
+};
+bool fromJSON(const llvm::json::Value &, SourceArguments &, llvm::json::Path);
+
+// "SourceResponse": {
+//   "allOf": [ { "$ref": "#/definitions/Response" }, {
+//     "type": "object",
+//     "description": "Response to 'source' request.",
+//     "properties": {
+//       "body": {
+//         "type": "object",
+//         "properties": {
+//           "content": {
+//             "type": "string",
+//             "description": "Content of the source reference."
+//           },
+//           "mimeType": {
+//             "type": "string",
+//             "description": "Optional content type (mime type) of the source."
+//           }
+//         },
+//         "required": [ "content" ]
+//       }
+//     },
+//     "required": [ "body" ]
+//   }]
+// }
+struct SourceResponseBody {
+  std::string content = "";
+  std::optional<std::string> mimeType;
+};
+llvm::json::Value toJSON(const SourceResponseBody &);
+
+// MARK: Reverse Requests
+
+} // namespace protocol
+} // namespace lldb_dap
+
+#endif // LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 60465332a2605..a2dd52343dec2 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -10,6 +10,7 @@
 #include "FifoFiles.h"
 #include "JSONUtils.h"
 #include "LLDBUtils.h"
+#include "Protocol.h"
 #include "RunInTerminal.h"
 #include "Watchpoint.h"
 #include "lldb/API/SBDeclaration.h"
@@ -41,6 +42,7 @@
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/JSON.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/PrettyStackTrace.h"
 #include "llvm/Support/Signals.h"
@@ -101,6 +103,7 @@ using namespace lldb_dap;
 using lldb_private::NativeSocket;
 using lldb_private::Socket;
 using lldb_private::Status;
+using namespace lldb_dap::protocol;
 
 namespace {
 using namespace llvm::opt;
@@ -172,15 +175,6 @@ std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) {
   return argv;
 }
 
-// Send a "exited" event to indicate the process has exited.
-void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
-  llvm::json::Object event(CreateEventObject("exited"));
-  llvm::json::Object body;
-  body.try_emplace("exitCode", (int64_t)process.GetExitStatus());
-  event.try_emplace("body", std::move(body));
-  dap.SendJSON(llvm::json::Value(std::move(event)));
-}
-
 void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) {
   llvm::json::Object event(CreateEventObject("thread"));
   llvm::json::Object body;
@@ -484,7 +478,9 @@ void EventThreadFunction(DAP &dap) {
               // Run any exit LLDB commands the user specified in the
               // launch.json
               dap.RunExitCommands();
-              SendProcessExitedEvent(dap, process);
+              protocol::ExitedEventBody body;
+              body.exitCode = process.GetExitStatus();
+              dap.onExited(std::move(body));
               dap.SendTerminatedEvent();
               done = true;
             }
@@ -675,7 +671,7 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
       break;
     }
 
-    stack_frames.emplace_back(CreateStackFrame(frame, dap.frame_format));
+    stack_frames.emplace_back(toStackFrame(frame, dap.frame_format));
   }
 
   if (dap.display_extended_backtrace && reached_end_of_stack) {
@@ -1727,18 +1723,17 @@ void request_completions(DAP &dap, const llvm::json::Object &request) {
 //      "required": [ "body" ]
 //    }]
 //  }
-void request_evaluate(DAP &dap, const llvm::json::Object &request) {
-  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").str();
-  llvm::StringRef context = GetString(arguments, "context");
+llvm::Expected<EvaluateResponseBody>
+request_evaluate(DAP &dap, const EvaluateArguments &arguments) {
+  EvaluateResponseBody body;
+  lldb::SBFrame frame;
+  if (arguments.frameId)
+    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 == EvaluateArguments::Context::repl &&
       (repeat_last_command ||
        (!expression.empty() &&
         dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) {
@@ -1752,10 +1747,9 @@ void request_evaluate(DAP &dap, const llvm::json::Object &request) {
     }
     auto result = RunLLDBCommandsVerbatim(dap.debugger, llvm::StringRef(),
                                           {std::string(expression)});
-    EmplaceSafeString(body, "result", result);
-    body.try_emplace("variablesReference", (int64_t)0);
+    body.result = result;
   } else {
-    if (context == "repl") {
+    if (arguments.context == EvaluateArguments::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
@@ -1775,43 +1769,45 @@ void request_evaluate(DAP &dap, const llvm::json::Object &request) {
         expression.data(), lldb::eDynamicDontRunTarget);
 
     // Freeze dry the value in case users expand it later in the debug console
-    if (value.GetError().Success() && context == "repl")
+    if (value.GetError().Success() &&
+        arguments.context == EvaluateArguments::Context::repl)
       value = value.Persist();
 
-    if (value.GetError().Fail() && context != "hover")
+    if (value.GetError().Fail() &&
+        arguments.context != EvaluateArguments::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", std::string(error_cstr));
-      else
-        EmplaceSafeString(response, "message", "evaluate failed");
-    } else {
-      VariableDescription desc(value, dap.enable_auto_variable_summaries);
-      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);
+        return llvm::make_error<DAPError>(std::string(error_cstr));
+      return llvm::make_error<DAPError>("evaluate failed");
     }
+
+    VariableDescription desc(value, dap.enable_auto_variable_summaries);
+    body.result =
+        desc.GetResult(arguments.context == EvaluateArguments::Context::repl);
+    body.type = desc.display_type_name;
+    int64_t var_ref = 0;
+    if (value.MightHaveChildren() || ValuePointsToCode(value))
+      var_ref = dap.variables.InsertVariable(
+          value, /*is_permanent=*/arguments.context ==
+                     EvaluateArguments::Context::repl);
+    if (value.MightHaveChildren())
+      body.variablesReference = var_ref;
+    else
+      body.variablesReference = 0;
+    if (lldb::addr_t addr = value.GetLoadAddress();
+        addr != LLDB_INVALID_ADDRESS)
+      body.memoryReference = EncodeMemoryReference(addr);
+    if (ValuePointsToCode(value))
+      body.valueLocationReference = var_ref;
   }
-  response.try_emplace("body", std::move(body));
-  dap.SendJSON(llvm::json::Value(std::move(response)));
+
+  return std::move(body);
 }
 
 // "compileUnitsRequest": {
@@ -2152,6 +2148,8 @@ void request_initialize(DAP &dap, const llvm::json::Object &request) {
   body.try_emplace("supportsDataBreakpoints", true);
   // The debug adapter supports the `readMemory` request.
   body.try_emplace("supportsReadMemoryRequest", true);
+  // The debug adapter supports the `cancel` request.
+  body.try_emplace("supportsCancelRequest", true);
 
   // Put in non-DAP specification lldb specific information.
   llvm::json::Object lldb_json;
@@ -3427,12 +3425,20 @@ void request_setDataBreakpoints(DAP &dap, const llvm::json::Object &request) {
 //     "required": [ "body" ]
 //   }]
 // }
-void request_source(DAP &dap, const llvm::json::Object &request) {
-  llvm::json::Object response;
-  FillResponse(request, response);
-  llvm::json::Object body{{"content", ""}};
-  response.try_emplace("body", std::move(body));
-  dap.SendJSON(llvm::json::Value(std::move(response)));
+llvm::Expected<SourceResponseBody> request_source(DAP &dap,
+                                                  const SourceArguments &args) {
+  const int64_t srcRefId =
+      args.source ? *args.source->sourceReference : args.sourceReference;
+
+  lldb::SBFrame frame = dap.GetLLDBFrame(srcRefId);
+  if (!frame.IsValid()) {
+    return llvm::make_error<DAPError>("source not found");
+  }
+
+  SourceResponseBody body;
+  body.content = frame.Disassemble();
+  body.mimeType = "text/x-lldb.disassembly";
+  return std::move(body);
 }
 
 // "StackTraceRequest": {
@@ -4940,6 +4946,13 @@ void request_setInstructionBreakpoints(DAP &dap,
   dap.SendJSON(llvm::json::Value(std::move(response)));
 }
 
+llvm::Expected<CancelResponseBody> request_cancel(DAP &dap,
+                                                  const CancelArguments &args) {
+  // Ack the cancel request, cancellation is handled within the DAP protocol
+  // message queue.
+  return nullptr;
+}
+
 void RegisterRequestCallbacks(DAP &dap) {
   dap.RegisterRequestCallback("attach", request_attach);
   dap.RegisterRequestCallback("breakpointLocations",
@@ -4948,7 +4961,8 @@ void RegisterRequestCallbacks(DAP &dap) {
   dap.RegisterRequestCallback("continue", request_continue);
   dap.RegisterRequestCallback("configurationDone", request_configurationDone);
   dap.RegisterRequestCallback("disconnect", request_disconnect);
-  dap.RegisterRequestCallback("evaluate", request_evaluate);
+  dap.RegisterRequest<EvaluateArguments, EvaluateResponseBody>(
+      "evaluate", request_evaluate);
   dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo);
   dap.RegisterRequestCallback("initialize", request_initialize);
   dap.RegisterRequestCallback("launch", request_launch);
@@ -4964,7 +4978,6 @@ void RegisterRequestCallbacks(DAP &dap) {
   dap.RegisterRequestCallback("dataBreakpointInfo", request_dataBreakpointInfo);
   dap.RegisterRequestCallback("setDataBreakpoints", request_setDataBreakpoints);
   dap.RegisterRequestCallback("setVariable", request_setVariable);
-  dap.RegisterRequestCallback("source", request_source);
   dap.RegisterRequestCallback("stackTrace", request_stackTrace);
   dap.RegisterRequestCallback("stepIn", request_stepIn);
   dap.RegisterRequestCallback("stepInTargets", request_stepInTargets);
@@ -4976,12 +4989,20 @@ void RegisterRequestCallbacks(DAP &dap) {
   dap.RegisterRequestCallback("readMemory", request_readMemory);
   dap.RegisterRequestCallback("setInstructionBreakpoints",
                               request_setInstructionBreakpoints);
+  dap.RegisterRequest<SourceArguments, SourceResponseBody>("source",
+                                                           request_source);
+  dap.RegisterRequest<CancelArguments, CancelResponseBody>("cancel",
+                                                           request_cancel);
+
   // Custom requests
   dap.RegisterRequestCallback("compileUnits", request_compileUnits);
   dap.RegisterRequestCallback("modules", request_modules);
   // Testing requests
   dap.RegisterRequestCallback("_testGetTargetBreakpoints",
                               request__testGetTargetBreakpoints);
+
+  // Event handlers
+  dap.onExited = dap.RegisterEvent<protocol::ExitedEventBody>("exited");
 }
 
 } // anonymous namespace
@@ -5165,7 +5186,7 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
                         &dap_sessions, sock = std::move(sock)]() {
       llvm::set_thread_name(name + ".runloop");
       StreamDescriptor input =
-          StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
+          StreamDescriptor::from_socket(sock->GetNativeSocket(), true);
       // Close the output last for the best chance at error reporting.
       StreamDescriptor output =
           StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
@@ -5185,7 +5206,7 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
         dap_sessions[sock.get()] = &dap;
       }
 
-      if (auto Err = dap.Loop()) {
+      if (auto Err = dap.Run()) {
         llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
                                     "DAP session error: ");
       }
@@ -5390,7 +5411,7 @@ int main(int argc, char *argv[]) {
     return EXIT_FAILURE;
   }
 
-  StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
+  StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), true);
   StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
 
   DAP dap = DAP("stdin/stdout", program_path, log.get(), std::move(input),
@@ -5409,7 +5430,7 @@ int main(int argc, char *argv[]) {
   if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
     redirection_test();
 
-  if (auto Err = dap.Loop()) {
+  if (auto Err = dap.Run()) {
     llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
                                 "DAP session error: ");
     return EXIT_FAILURE;



More information about the lldb-commits mailing list