Vulkan/Debug: Add Server
Server implements a Debug Adapter Protocol server that listens on a specific port, that operates on the vk::dbg::Context passed to the constructor.
Bug: b/145351270
Change-Id: If09d92c6d298cfbd71a7b2366b6b8407a728ec43
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38899
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Vulkan/Debug/Server.cpp b/src/Vulkan/Debug/Server.cpp
new file mode 100644
index 0000000..05d7714
--- /dev/null
+++ b/src/Vulkan/Debug/Server.cpp
@@ -0,0 +1,596 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Server.hpp"
+
+#include "Context.hpp"
+#include "EventListener.hpp"
+#include "File.hpp"
+#include "Thread.hpp"
+#include "Variable.hpp"
+
+#include "dap/network.h"
+#include "dap/protocol.h"
+#include "dap/session.h"
+#include "marl/waitgroup.h"
+
+#include <thread>
+#include <unordered_set>
+
+// Switch for controlling DAP debug logging
+#define ENABLE_DAP_LOGGING 0
+
+#if ENABLE_DAP_LOGGING
+# define DAP_LOG(msg, ...) printf(msg "\n", __VA_ARGS__)
+#else
+# define DAP_LOG(...) \
+ do \
+ { \
+ } while(false)
+#endif
+
+namespace vk
+{
+namespace dbg
+{
+
+class Server::Impl : public Server, public EventListener
+{
+public:
+ Impl(const std::shared_ptr<Context>& ctx, int port);
+ ~Impl();
+
+ // EventListener
+ void onThreadStarted(ID<Thread>) override;
+ void onThreadStepped(ID<Thread>) override;
+ void onLineBreakpointHit(ID<Thread>) override;
+ void onFunctionBreakpointHit(ID<Thread>) override;
+
+ dap::Scope scope(const char* type, Scope*);
+ dap::Source source(File*);
+ std::shared_ptr<File> file(const dap::Source& source);
+
+ const std::shared_ptr<Context> ctx;
+ const std::unique_ptr<dap::net::Server> server;
+ const std::unique_ptr<dap::Session> session;
+ std::atomic<bool> clientIsVisualStudio = { false };
+};
+
+Server::Impl::Impl(const std::shared_ptr<Context>& context, int port) :
+ ctx(context),
+ server(dap::net::Server::create()),
+ session(dap::Session::create())
+{
+ session->registerHandler([](const dap::DisconnectRequest& req) {
+ DAP_LOG("DisconnectRequest receieved");
+ return dap::DisconnectResponse();
+ });
+
+ session->registerHandler([&](const dap::InitializeRequest& req) {
+ DAP_LOG("InitializeRequest receieved");
+ dap::InitializeResponse response;
+ response.supportsFunctionBreakpoints = true;
+ response.supportsConfigurationDoneRequest = true;
+ response.supportsEvaluateForHovers = true;
+ clientIsVisualStudio = (req.clientID.value("") == "visualstudio");
+ return response;
+ });
+
+ session->registerSentHandler(
+ [&](const dap::ResponseOrError<dap::InitializeResponse>& response) {
+ DAP_LOG("InitializeResponse sent");
+ session->send(dap::InitializedEvent());
+ });
+
+ session->registerHandler([](const dap::SetExceptionBreakpointsRequest& req) {
+ DAP_LOG("SetExceptionBreakpointsRequest receieved");
+ dap::SetExceptionBreakpointsResponse response;
+ return response;
+ });
+
+ session->registerHandler(
+ [this](const dap::SetFunctionBreakpointsRequest& req) {
+ DAP_LOG("SetFunctionBreakpointsRequest receieved");
+ auto lock = ctx->lock();
+ dap::SetFunctionBreakpointsResponse response;
+ for(auto const& bp : req.breakpoints)
+ {
+ lock.addFunctionBreakpoint(bp.name.c_str());
+ response.breakpoints.push_back({});
+ }
+ return response;
+ });
+
+ session->registerHandler(
+ [this](const dap::SetBreakpointsRequest& req)
+ -> dap::ResponseOrError<dap::SetBreakpointsResponse> {
+ DAP_LOG("SetBreakpointsRequest receieved");
+ bool verified = false;
+
+ size_t numBreakpoints = 0;
+ if(req.breakpoints.has_value())
+ {
+ auto const& breakpoints = req.breakpoints.value();
+ numBreakpoints = breakpoints.size();
+ if(auto file = this->file(req.source))
+ {
+ file->clearBreakpoints();
+ for(auto const& bp : breakpoints)
+ {
+ file->addBreakpoint(bp.line);
+ }
+ verified = true;
+ }
+ else if(req.source.name.has_value())
+ {
+ std::vector<int> lines;
+ lines.reserve(breakpoints.size());
+ for(auto const& bp : breakpoints)
+ {
+ lines.push_back(bp.line);
+ }
+ ctx->lock().addPendingBreakpoints(req.source.name.value(),
+ lines);
+ }
+ }
+
+ dap::SetBreakpointsResponse response;
+ for(size_t i = 0; i < numBreakpoints; i++)
+ {
+ dap::Breakpoint bp;
+ bp.verified = verified;
+ bp.source = req.source;
+ response.breakpoints.push_back(bp);
+ }
+ return response;
+ });
+
+ session->registerHandler([this](const dap::ThreadsRequest& req) {
+ DAP_LOG("ThreadsRequest receieved");
+ auto lock = ctx->lock();
+ dap::ThreadsResponse response;
+ for(auto thread : lock.threads())
+ {
+ std::string name = thread->name();
+ if(clientIsVisualStudio)
+ {
+ // WORKAROUND: https://github.com/microsoft/VSDebugAdapterHost/issues/15
+ for(size_t i = 0; i < name.size(); i++)
+ {
+ if(name[i] == '.')
+ {
+ name[i] = '_';
+ }
+ }
+ }
+
+ dap::Thread out;
+ out.id = thread->id.value();
+ out.name = name;
+ response.threads.push_back(out);
+ };
+ return response;
+ });
+
+ session->registerHandler(
+ [this](const dap::StackTraceRequest& req)
+ -> dap::ResponseOrError<dap::StackTraceResponse> {
+ DAP_LOG("StackTraceRequest receieved");
+
+ auto lock = ctx->lock();
+ auto thread = lock.get(Thread::ID(req.threadId));
+ if(!thread)
+ {
+ return dap::Error("Thread %d not found", req.threadId);
+ }
+
+ auto stack = thread->stack();
+
+ dap::StackTraceResponse response;
+ response.totalFrames = stack.size();
+ response.stackFrames.reserve(stack.size());
+ for(auto const& frame : stack)
+ {
+ auto const& loc = frame.location;
+ dap::StackFrame sf;
+ sf.column = 0;
+ sf.id = frame.id.value();
+ sf.name = frame.function;
+ sf.line = loc.line;
+ if(loc.file)
+ {
+ sf.source = source(loc.file.get());
+ }
+ response.stackFrames.emplace_back(std::move(sf));
+ }
+ return response;
+ });
+
+ session->registerHandler([this](const dap::ScopesRequest& req)
+ -> dap::ResponseOrError<dap::ScopesResponse> {
+ DAP_LOG("ScopesRequest receieved");
+
+ auto lock = ctx->lock();
+ auto frame = lock.get(Frame::ID(req.frameId));
+ if(!frame)
+ {
+ return dap::Error("Frame %d not found", req.frameId);
+ }
+
+ dap::ScopesResponse response;
+ response.scopes = {
+ scope("locals", frame->locals.get()),
+ scope("arguments", frame->arguments.get()),
+ scope("registers", frame->registers.get()),
+ };
+ return response;
+ });
+
+ session->registerHandler([this](const dap::VariablesRequest& req)
+ -> dap::ResponseOrError<dap::VariablesResponse> {
+ DAP_LOG("VariablesRequest receieved");
+
+ auto lock = ctx->lock();
+ auto vars = lock.get(VariableContainer::ID(req.variablesReference));
+ if(!vars)
+ {
+ return dap::Error("VariablesReference %d not found",
+ int(req.variablesReference));
+ }
+
+ dap::VariablesResponse response;
+ vars->foreach(req.start.value(0), [&](const Variable& v) {
+ if(!req.count.has_value() ||
+ req.count.value() < int(response.variables.size()))
+ {
+ dap::Variable out;
+ out.evaluateName = v.name;
+ out.name = v.name;
+ out.type = v.value->type()->string();
+ out.value = v.value->string();
+ if(v.value->type()->kind == Kind::VariableContainer)
+ {
+ auto const vc = static_cast<const VariableContainer*>(v.value.get());
+ out.variablesReference = vc->id.value();
+ }
+ response.variables.push_back(out);
+ }
+ });
+ return response;
+ });
+
+ session->registerHandler([this](const dap::SourceRequest& req)
+ -> dap::ResponseOrError<dap::SourceResponse> {
+ DAP_LOG("SourceRequest receieved");
+
+ dap::SourceResponse response;
+ uint64_t id = req.sourceReference;
+
+ auto lock = ctx->lock();
+ auto file = lock.get(File::ID(id));
+ if(!file)
+ {
+ return dap::Error("Source %d not found", id);
+ }
+ response.content = file->source;
+ return response;
+ });
+
+ session->registerHandler([this](const dap::PauseRequest& req)
+ -> dap::ResponseOrError<dap::PauseResponse> {
+ DAP_LOG("PauseRequest receieved");
+
+ dap::StoppedEvent event;
+ event.reason = "pause";
+
+ auto lock = ctx->lock();
+ if(auto thread = lock.get(Thread::ID(req.threadId)))
+ {
+ thread->pause();
+ event.threadId = req.threadId;
+ }
+ else
+ {
+ auto threads = lock.threads();
+ for(auto thread : threads)
+ {
+ thread->pause();
+ }
+ event.allThreadsStopped = true;
+
+ // Workaround for
+ // https://github.com/microsoft/VSDebugAdapterHost/issues/11
+ if(clientIsVisualStudio)
+ {
+ for(auto thread : threads)
+ {
+ event.threadId = thread->id.value();
+ break;
+ }
+ }
+ }
+
+ session->send(event);
+
+ dap::PauseResponse response;
+ return response;
+ });
+
+ session->registerHandler([this](const dap::ContinueRequest& req)
+ -> dap::ResponseOrError<dap::ContinueResponse> {
+ DAP_LOG("ContinueRequest receieved");
+
+ dap::ContinueResponse response;
+
+ auto lock = ctx->lock();
+ if(auto thread = lock.get(Thread::ID(req.threadId)))
+ {
+ thread->resume();
+ response.allThreadsContinued = false;
+ }
+ else
+ {
+ for(auto it : lock.threads())
+ {
+ thread->resume();
+ }
+ response.allThreadsContinued = true;
+ }
+
+ return response;
+ });
+
+ session->registerHandler([this](const dap::NextRequest& req)
+ -> dap::ResponseOrError<dap::NextResponse> {
+ DAP_LOG("NextRequest receieved");
+
+ auto lock = ctx->lock();
+ auto thread = lock.get(Thread::ID(req.threadId));
+ if(!thread)
+ {
+ return dap::Error("Unknown thread %d", int(req.threadId));
+ }
+
+ thread->stepOver();
+ return dap::NextResponse();
+ });
+
+ session->registerHandler([this](const dap::StepInRequest& req)
+ -> dap::ResponseOrError<dap::StepInResponse> {
+ DAP_LOG("StepInRequest receieved");
+
+ auto lock = ctx->lock();
+ auto thread = lock.get(Thread::ID(req.threadId));
+ if(!thread)
+ {
+ return dap::Error("Unknown thread %d", int(req.threadId));
+ }
+
+ thread->stepIn();
+ return dap::StepInResponse();
+ });
+
+ session->registerHandler([this](const dap::StepOutRequest& req)
+ -> dap::ResponseOrError<dap::StepOutResponse> {
+ DAP_LOG("StepOutRequest receieved");
+
+ auto lock = ctx->lock();
+ auto thread = lock.get(Thread::ID(req.threadId));
+ if(!thread)
+ {
+ return dap::Error("Unknown thread %d", int(req.threadId));
+ }
+
+ thread->stepOut();
+ return dap::StepOutResponse();
+ });
+
+ session->registerHandler([this](const dap::EvaluateRequest& req)
+ -> dap::ResponseOrError<dap::EvaluateResponse> {
+ DAP_LOG("EvaluateRequest receieved");
+
+ auto lock = ctx->lock();
+ if(req.frameId.has_value())
+ {
+ auto frame = lock.get(Frame::ID(req.frameId.value(0)));
+ if(!frame)
+ {
+ return dap::Error("Unknown frame %d", int(req.frameId.value()));
+ }
+
+ auto fmt = FormatFlags::Default;
+ auto subfmt = FormatFlags::Default;
+
+ if(req.context.value("") == "hover")
+ {
+ subfmt.listPrefix = "\n";
+ subfmt.listSuffix = "";
+ subfmt.listDelimiter = "\n";
+ subfmt.listIndent = " ";
+ fmt.listPrefix = "";
+ fmt.listSuffix = "";
+ fmt.listDelimiter = "\n";
+ fmt.listIndent = "";
+ fmt.subListFmt = &subfmt;
+ }
+
+ dap::EvaluateResponse response;
+ auto findHandler = [&](const Variable& var) {
+ response.result = var.value->string(fmt);
+ response.type = var.value->type()->string();
+ };
+
+ std::vector<std::shared_ptr<vk::dbg::VariableContainer>> variables = {
+ frame->locals->variables,
+ frame->arguments->variables,
+ frame->registers->variables,
+ frame->hovers->variables,
+ };
+
+ for(auto const& vars : variables)
+ {
+ if(vars->find(req.expression, findHandler)) { return response; }
+ }
+
+ // HACK: VSCode does not appear to include the % in %123 tokens
+ // TODO: This might be a configuration problem of the SPIRV-Tools
+ // spirv-ls plugin. Investigate.
+ auto withPercent = "%" + req.expression;
+ for(auto const& vars : variables)
+ {
+ if(vars->find(withPercent, findHandler)) { return response; }
+ }
+ }
+
+ return dap::Error("Could not evaluate expression");
+ });
+
+ session->registerHandler([](const dap::LaunchRequest& req) {
+ DAP_LOG("LaunchRequest receieved");
+ return dap::LaunchResponse();
+ });
+
+ marl::WaitGroup configurationDone(1);
+ session->registerHandler([=](const dap::ConfigurationDoneRequest& req) {
+ DAP_LOG("ConfigurationDoneRequest receieved");
+ configurationDone.done();
+ return dap::ConfigurationDoneResponse();
+ });
+
+ DAP_LOG("Waiting for debugger connection...");
+ server->start(port, [&](const std::shared_ptr<dap::ReaderWriter>& rw) {
+ session->bind(rw);
+ ctx->addListener(this);
+ });
+ configurationDone.wait();
+}
+
+Server::Impl::~Impl()
+{
+ ctx->removeListener(this);
+ server->stop();
+}
+
+void Server::Impl::onThreadStarted(ID<Thread> id)
+{
+ dap::ThreadEvent event;
+ event.reason = "started";
+ event.threadId = id.value();
+ session->send(event);
+}
+
+void Server::Impl::onThreadStepped(ID<Thread> id)
+{
+ dap::StoppedEvent event;
+ event.threadId = id.value();
+ event.reason = "step";
+ session->send(event);
+}
+
+void Server::Impl::onLineBreakpointHit(ID<Thread> id)
+{
+ dap::StoppedEvent event;
+ event.threadId = id.value();
+ event.reason = "breakpoint";
+ session->send(event);
+}
+
+void Server::Impl::onFunctionBreakpointHit(ID<Thread> id)
+{
+ dap::StoppedEvent event;
+ event.threadId = id.value();
+ event.reason = "function breakpoint";
+ session->send(event);
+}
+
+dap::Scope Server::Impl::scope(const char* type, Scope* s)
+{
+ dap::Scope out;
+ // out.line = s->startLine;
+ // out.endLine = s->endLine;
+ out.source = source(s->file.get());
+ out.name = type;
+ out.presentationHint = type;
+ out.variablesReference = s->variables->id.value();
+ return out;
+}
+
+dap::Source Server::Impl::source(File* file)
+{
+ dap::Source out;
+ out.name = file->name;
+ if(file->isVirtual())
+ {
+ out.sourceReference = file->id.value();
+ }
+ else
+ {
+ out.path = file->path();
+ }
+ return out;
+}
+
+std::shared_ptr<File> Server::Impl::file(const dap::Source& source)
+{
+ auto lock = ctx->lock();
+ if(source.sourceReference.has_value())
+ {
+ auto id = source.sourceReference.value();
+ if(auto file = lock.get(File::ID(id)))
+ {
+ return file;
+ }
+ }
+
+ auto files = lock.files();
+ if(source.path.has_value())
+ {
+ auto path = source.path.value();
+ std::shared_ptr<File> out;
+ for(auto file : files)
+ {
+ if(file->path() == path)
+ {
+ out = file;
+ break;
+ }
+ }
+ return out;
+ }
+
+ if(source.name.has_value())
+ {
+ auto name = source.name.value();
+ std::shared_ptr<File> out;
+ for(auto file : files)
+ {
+ if(file->name == name)
+ {
+ out = file;
+ break;
+ }
+ }
+ return out;
+ }
+
+ return nullptr;
+}
+
+std::shared_ptr<Server> Server::create(const std::shared_ptr<Context>& ctx, int port)
+{
+ return std::make_shared<Server::Impl>(ctx, port);
+}
+
+} // namespace dbg
+} // namespace vk
\ No newline at end of file
diff --git a/src/Vulkan/Debug/Server.hpp b/src/Vulkan/Debug/Server.hpp
new file mode 100644
index 0000000..dead78e
--- /dev/null
+++ b/src/Vulkan/Debug/Server.hpp
@@ -0,0 +1,43 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VK_DEBUG_SERVER_HPP_
+#define VK_DEBUG_SERVER_HPP_
+
+#include <memory>
+
+namespace vk
+{
+namespace dbg
+{
+
+class Context;
+
+// Server implements a Debug Adapter Protocol server that listens on a specific
+// port, that operates on the vk::dbg::Context passed to the constructor.
+class Server
+{
+public:
+ static std::shared_ptr<Server> create(const std::shared_ptr<Context>&, int port);
+
+ virtual ~Server() = default;
+
+private:
+ class Impl;
+};
+
+} // namespace dbg
+} // namespace vk
+
+#endif // VK_DEBUG_SERVER_HPP_