Initial drop of cppdap
This commit is contained in:
453
examples/hello_debugger.cpp
Normal file
453
examples/hello_debugger.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// https://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.
|
||||
|
||||
// hello_debugger is an example DAP server that provides single line stepping
|
||||
// through a synthetic file.
|
||||
|
||||
#include "dap/io.h"
|
||||
#include "dap/protocol.h"
|
||||
#include "dap/session.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
// Uncomment the line below and change <path-to-log-file> to a file path to
|
||||
// write all DAP communications to the given path.
|
||||
//
|
||||
// #define LOG_TO_FILE "<path-to-log-file>"
|
||||
|
||||
namespace {
|
||||
|
||||
// sourceContent holds the synthetic file source.
|
||||
constexpr char sourceContent[] = R"(// Hello Debugger!
|
||||
|
||||
This is a synthetic source file provided by the DAP debugger.
|
||||
|
||||
You can set breakpoints, and single line step.
|
||||
|
||||
You may also notice that the locals contains a single variable for the currently executing line number.)";
|
||||
|
||||
// Total number of newlines in source.
|
||||
constexpr int numSourceLines = 7;
|
||||
|
||||
// Debugger holds the dummy debugger state and fires events to the EventHandler
|
||||
// passed to the constructor.
|
||||
class Debugger {
|
||||
public:
|
||||
enum class Event { BreakpointHit, Stepped, Paused };
|
||||
using EventHandler = std::function<void(Event)>;
|
||||
|
||||
Debugger(const EventHandler&);
|
||||
|
||||
// run() instructs the debugger to continue execution.
|
||||
void run();
|
||||
|
||||
// pause() instructs the debugger to pause execution.
|
||||
void pause();
|
||||
|
||||
// currentLine() returns the currently executing line number.
|
||||
int currentLine();
|
||||
|
||||
// stepForward() instructs the debugger to step forward one line.
|
||||
void stepForward();
|
||||
|
||||
// clearBreakpoints() clears all set breakpoints.
|
||||
void clearBreakpoints();
|
||||
|
||||
// addBreakpoint() sets a new breakpoint on the given line.
|
||||
void addBreakpoint(int line);
|
||||
|
||||
private:
|
||||
EventHandler onEvent;
|
||||
std::mutex mutex;
|
||||
int line = 1;
|
||||
std::unordered_set<int> breakpoints;
|
||||
};
|
||||
|
||||
Debugger::Debugger(const EventHandler& onEvent) : onEvent(onEvent) {}
|
||||
|
||||
void Debugger::run() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
for (int i = 0; i < numSourceLines; i++) {
|
||||
auto l = ((line + i) % numSourceLines) + 1;
|
||||
if (breakpoints.count(l)) {
|
||||
line = l;
|
||||
lock.unlock();
|
||||
onEvent(Event::BreakpointHit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::pause() {
|
||||
onEvent(Event::Paused);
|
||||
}
|
||||
|
||||
int Debugger::currentLine() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
return line;
|
||||
}
|
||||
|
||||
void Debugger::stepForward() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
line = (line % numSourceLines) + 1;
|
||||
lock.unlock();
|
||||
onEvent(Event::Stepped);
|
||||
}
|
||||
|
||||
void Debugger::clearBreakpoints() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
this->breakpoints.clear();
|
||||
}
|
||||
|
||||
void Debugger::addBreakpoint(int l) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
this->breakpoints.emplace(l);
|
||||
}
|
||||
|
||||
// Event provides a basic wait and signal synchronization primitive.
|
||||
class Event {
|
||||
public:
|
||||
// wait() blocks until the event is fired.
|
||||
void wait();
|
||||
|
||||
// fire() sets signals the event, and unblocks any calls to wait().
|
||||
void fire();
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool fired = false;
|
||||
};
|
||||
|
||||
void Event::wait() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
cv.wait(lock, [&] { return fired; });
|
||||
}
|
||||
|
||||
void Event::fire() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
fired = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// main() entry point to the DAP server.
|
||||
int main(int, char*[]) {
|
||||
std::shared_ptr<dap::Writer> log;
|
||||
#ifdef LOG_TO_FILE
|
||||
log = dap::file(LOG_TO_FILE);
|
||||
#endif
|
||||
|
||||
// Create the DAP session.
|
||||
// This is used to implement the DAP server.
|
||||
auto session = dap::Session::create();
|
||||
|
||||
// Hard-coded identifiers for the one thread, frame, variable and source.
|
||||
// These numbers have no meaning, and just need to remain constant for the
|
||||
// duration of the service.
|
||||
const dap::integer threadId = 100;
|
||||
const dap::integer frameId = 200;
|
||||
const dap::integer variablesReferenceId = 300;
|
||||
const dap::integer sourceReferenceId = 400;
|
||||
|
||||
// Signal events
|
||||
Event configured;
|
||||
Event terminate;
|
||||
|
||||
// Event handlers from the Debugger.
|
||||
auto onDebuggerEvent = [&](Debugger::Event onEvent) {
|
||||
switch (onEvent) {
|
||||
case Debugger::Event::Stepped: {
|
||||
// The debugger has single-line stepped. Inform the client.
|
||||
dap::StoppedEvent event;
|
||||
event.reason = "step";
|
||||
event.threadId = threadId;
|
||||
session->send(event);
|
||||
break;
|
||||
}
|
||||
case Debugger::Event::BreakpointHit: {
|
||||
// The debugger has hit a breakpoint. Inform the client.
|
||||
dap::StoppedEvent event;
|
||||
event.reason = "breakpoint";
|
||||
event.threadId = threadId;
|
||||
session->send(event);
|
||||
break;
|
||||
}
|
||||
case Debugger::Event::Paused: {
|
||||
// The debugger has been suspended. Inform the client.
|
||||
dap::StoppedEvent event;
|
||||
event.reason = "pause";
|
||||
event.threadId = threadId;
|
||||
session->send(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Construct the debugger.
|
||||
Debugger debugger(onDebuggerEvent);
|
||||
|
||||
// Handle errors reported by the Session. These errors include protocol
|
||||
// parsing errors and receiving messages with no handler.
|
||||
session->onError([&](const char* msg) {
|
||||
if (log) {
|
||||
dap::writef(log, "dap::Session error: %s\n", msg);
|
||||
log->close();
|
||||
}
|
||||
terminate.fire();
|
||||
});
|
||||
|
||||
// The Initialize request is the first message sent from the client and
|
||||
// the response reports debugger capabilities.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
|
||||
session->registerHandler([](const dap::InitializeRequest&) {
|
||||
dap::InitializeResponse response;
|
||||
response.supportsConfigurationDoneRequest = true;
|
||||
return response;
|
||||
});
|
||||
|
||||
// When the Initialize response has been sent, we need to send the initialized
|
||||
// event.
|
||||
// We use the registerSentHandler() to ensure the event is sent *after* the
|
||||
// initialize response.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
|
||||
session->registerSentHandler(
|
||||
[&](const dap::ResponseOrError<dap::InitializeResponse>&) {
|
||||
session->send(dap::InitializedEvent());
|
||||
});
|
||||
|
||||
// The Threads request queries the debugger's list of active threads.
|
||||
// This example debugger only exposes a single thread.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Threads
|
||||
session->registerHandler([&](const dap::ThreadsRequest&) {
|
||||
dap::ThreadsResponse response;
|
||||
dap::Thread thread;
|
||||
thread.id = threadId;
|
||||
thread.name = "TheThread";
|
||||
response.threads.push_back(thread);
|
||||
return response;
|
||||
});
|
||||
|
||||
// The StackTrace request reports the stack frames (call stack) for a given
|
||||
// thread. This example debugger only exposes a single stack frame for the
|
||||
// single thread.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StackTrace
|
||||
session->registerHandler(
|
||||
[&](const dap::StackTraceRequest& request)
|
||||
-> dap::ResponseOrError<dap::StackTraceResponse> {
|
||||
if (request.threadId != threadId) {
|
||||
return dap::Error("Unknown threadId '%d'", int(request.threadId));
|
||||
}
|
||||
|
||||
dap::Source source;
|
||||
source.sourceReference = sourceReferenceId;
|
||||
source.name = "HelloDebuggerSource";
|
||||
|
||||
dap::StackFrame frame;
|
||||
frame.line = debugger.currentLine();
|
||||
frame.column = 1;
|
||||
frame.name = "HelloDebugger";
|
||||
frame.id = frameId;
|
||||
frame.source = source;
|
||||
|
||||
dap::StackTraceResponse response;
|
||||
response.stackFrames.push_back(frame);
|
||||
return response;
|
||||
});
|
||||
|
||||
// The Scopes request reports all the scopes of the given stack frame.
|
||||
// This example debugger only exposes a single 'Locals' scope for the single
|
||||
// frame.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Scopes
|
||||
session->registerHandler([&](const dap::ScopesRequest& request)
|
||||
-> dap::ResponseOrError<dap::ScopesResponse> {
|
||||
if (request.frameId != frameId) {
|
||||
return dap::Error("Unknown frameId '%d'", int(request.frameId));
|
||||
}
|
||||
|
||||
dap::Scope scope;
|
||||
scope.name = "Locals";
|
||||
scope.presentationHint = "locals";
|
||||
scope.variablesReference = variablesReferenceId;
|
||||
|
||||
dap::ScopesResponse response;
|
||||
response.scopes.push_back(scope);
|
||||
return response;
|
||||
});
|
||||
|
||||
// The Variables request reports all the variables for the given scope.
|
||||
// This example debugger only exposes a single 'currentLine' variable for the
|
||||
// single 'Locals' scope.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Variables
|
||||
session->registerHandler([&](const dap::VariablesRequest& request)
|
||||
-> dap::ResponseOrError<dap::VariablesResponse> {
|
||||
if (request.variablesReference != variablesReferenceId) {
|
||||
return dap::Error("Unknown variablesReference '%d'",
|
||||
int(request.variablesReference));
|
||||
}
|
||||
|
||||
dap::Variable currentLineVar;
|
||||
currentLineVar.name = "currentLine";
|
||||
currentLineVar.value = std::to_string(debugger.currentLine());
|
||||
currentLineVar.type = "int";
|
||||
|
||||
dap::VariablesResponse response;
|
||||
response.variables.push_back(currentLineVar);
|
||||
return response;
|
||||
});
|
||||
|
||||
// The Pause request instructs the debugger to pause execution of one or all
|
||||
// threads.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Pause
|
||||
session->registerHandler([&](const dap::PauseRequest&) {
|
||||
debugger.pause();
|
||||
return dap::PauseResponse();
|
||||
});
|
||||
|
||||
// The Continue request instructs the debugger to resume execution of one or
|
||||
// all threads.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Continue
|
||||
session->registerHandler([&](const dap::ContinueRequest&) {
|
||||
debugger.run();
|
||||
return dap::ContinueResponse();
|
||||
});
|
||||
|
||||
// The Next request instructs the debugger to single line step for a specific
|
||||
// thread.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Next
|
||||
session->registerHandler([&](const dap::NextRequest&) {
|
||||
debugger.stepForward();
|
||||
return dap::NextResponse();
|
||||
});
|
||||
|
||||
// The StepIn request instructs the debugger to step-in for a specific thread.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepIn
|
||||
session->registerHandler([&](const dap::StepInRequest&) {
|
||||
// Step-in treated as step-over as there's only one stack frame.
|
||||
debugger.stepForward();
|
||||
return dap::StepInResponse();
|
||||
});
|
||||
|
||||
// The StepOut request instructs the debugger to step-out for a specific
|
||||
// thread.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepOut
|
||||
session->registerHandler([&](const dap::StepOutRequest&) {
|
||||
// Step-out is not supported as there's only one stack frame.
|
||||
return dap::StepOutResponse();
|
||||
});
|
||||
|
||||
// The SetBreakpoints request instructs the debugger to clear and set a number
|
||||
// of line breakpoints for a specific source file.
|
||||
// This example debugger only exposes a single source file.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints
|
||||
session->registerHandler([&](const dap::SetBreakpointsRequest& request) {
|
||||
dap::SetBreakpointsResponse response;
|
||||
|
||||
auto breakpoints = request.breakpoints.value({});
|
||||
if (request.source.sourceReference.value(0) == sourceReferenceId) {
|
||||
debugger.clearBreakpoints();
|
||||
response.breakpoints.resize(breakpoints.size());
|
||||
for (size_t i = 0; i < breakpoints.size(); i++) {
|
||||
debugger.addBreakpoint(breakpoints[i].line);
|
||||
response.breakpoints[i].verified = breakpoints[i].line < numSourceLines;
|
||||
}
|
||||
} else {
|
||||
response.breakpoints.resize(breakpoints.size());
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
// The SetExceptionBreakpoints request configures the debugger's handling of
|
||||
// thrown exceptions.
|
||||
// This example debugger does not use any exceptions, so this is a no-op.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints
|
||||
session->registerHandler([&](const dap::SetExceptionBreakpointsRequest&) {
|
||||
return dap::SetExceptionBreakpointsResponse();
|
||||
});
|
||||
|
||||
// The Source request retrieves the source code for a given source file.
|
||||
// This example debugger only exposes one synthetic source file.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Source
|
||||
session->registerHandler([&](const dap::SourceRequest& request)
|
||||
-> dap::ResponseOrError<dap::SourceResponse> {
|
||||
if (request.sourceReference != sourceReferenceId) {
|
||||
return dap::Error("Unknown source reference '%d'",
|
||||
int(request.sourceReference));
|
||||
}
|
||||
|
||||
dap::SourceResponse response;
|
||||
response.content = sourceContent;
|
||||
return response;
|
||||
});
|
||||
|
||||
// The Launch request is made when the client instructs the debugger adapter
|
||||
// to start the debuggee. This request contains the launch arguments.
|
||||
// This example debugger does nothing with this request.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Launch
|
||||
session->registerHandler(
|
||||
[&](const dap::LaunchRequest&) { return dap::LaunchResponse(); });
|
||||
|
||||
// Handler for disconnect requests
|
||||
session->registerHandler([&](const dap::DisconnectRequest& request) {
|
||||
if (request.terminateDebuggee.value(false)) {
|
||||
terminate.fire();
|
||||
}
|
||||
return dap::DisconnectResponse();
|
||||
});
|
||||
|
||||
// The ConfigurationDone request is made by the client once all configuration
|
||||
// requests have been made.
|
||||
// This example debugger uses this request to 'start' the debugger.
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ConfigurationDone
|
||||
session->registerHandler([&](const dap::ConfigurationDoneRequest&) {
|
||||
configured.fire();
|
||||
return dap::ConfigurationDoneResponse();
|
||||
});
|
||||
|
||||
// All the handlers we care about have now been registered.
|
||||
// We now bind the session to stdin and stdout to connect to the client.
|
||||
// After the call to bind() we should start receiving requests, starting with
|
||||
// the Initialize request.
|
||||
std::shared_ptr<dap::Reader> in = dap::file(stdin, false);
|
||||
std::shared_ptr<dap::Writer> out = dap::file(stdout, false);
|
||||
if (log) {
|
||||
session->bind(spy(in, log), spy(out, log));
|
||||
} else {
|
||||
session->bind(in, out);
|
||||
}
|
||||
|
||||
// Wait for the ConfigurationDone request to be made.
|
||||
configured.wait();
|
||||
|
||||
// Broadcast the existance of the single thread to the client.
|
||||
dap::ThreadEvent threadStartedEvent;
|
||||
threadStartedEvent.reason = "started";
|
||||
threadStartedEvent.threadId = threadId;
|
||||
session->send(threadStartedEvent);
|
||||
|
||||
// Start the debugger in a paused state.
|
||||
// This sends a stopped event to the client.
|
||||
debugger.pause();
|
||||
|
||||
// Block until we receive a 'terminateDebuggee' request or encounter a session
|
||||
// error.
|
||||
terminate.wait();
|
||||
|
||||
return 0;
|
||||
}
|
||||
28
examples/vscode/package.json
Normal file
28
examples/vscode/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "cppdap-example-@target@",
|
||||
"displayName": "cppdap example: @target@",
|
||||
"description": "cppdap example: @target@",
|
||||
"version": "1.0.0",
|
||||
"preview": false,
|
||||
"publisher": "Google LLC",
|
||||
"author": {
|
||||
"name": "Google LLC"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"engines": {
|
||||
"vscode": "^1.32.0"
|
||||
},
|
||||
"categories": [
|
||||
"Debuggers"
|
||||
],
|
||||
"contributes": {
|
||||
"debuggers": [
|
||||
{
|
||||
"type": "@target@",
|
||||
"program": "@target@",
|
||||
"label": "cppdap example: @target@",
|
||||
"configurationAttributes": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user