* Add STL-standard type traits and functions to TMap to enable for loop iteration * add third-party range-map library I have e-mailed the author for clarifcation on the license, will update this when they respond * vcpkg: Add cppdap and eventpp libraries, update baseline * DAP implementation * Add `FileSystem::FileHash()` to get the CRC32 hash * add `starting_offset` param to `VMDisasm()` for debugger disassembly view Defaults to `0`, should not change output if it's not set * Add `VMFrameStack::HasFrames()` to prevent assertions when inspecting in the debugger * Add `PC` field to VMFrame, ensure that it is updated whenever vm increments/decrements the pc Does not change alignment, the offsets used in VMFrame still work We need this for the debugger because we otherwise have no way to get the pc; it was a local in `ExecScriptFunc()` * `ZCCCompiler::CreateClassTypes()`: ensure SourceLumpName is set for classes derived from non-native classes * start debug server in d_main, add `vm_debug` cvars and `-debug` CLI arg * Add documentation for `vm_debug` cvars * vm_exec: Add debugger hooks for instruction execution events * c_console: Add debugger hooks for logging events in `PrintString` * add `.cache/` to gitignore * vendor cppdap on main @ 6464cd7 Patches: removed .gitmodules (submodules were thirdparty/json, thirdparty/googletest) removed thirdparty/googletest, not needed removed thirdparty/json/docs, thirdparty/json/test, thirdparty/json/benchmarks to prevent massive bloat * vendor eventpp on master @ 1224dd6 * build: use internal cppdap and eventpp by default * dap: fix Binary::GetFunctionLineRange() * fix bug in range_map::find_ranges * make Binary dap::Source dynamic * cache source code upon retrieval * refactor Binary into a class, cleanup PexCache * fix ending session gracefully * d_main: Stop debug server in D_Cleanup() * Fix connecting to debugger when session already started * cleanup unused stuff in ZScriptDebugger * always send TerminateEvents on disconnect if initialized * tweak color display * Don't cache disassembly lines when scanning scripts * Cache nodes when getting runtime state * WIP display locals * Fix display of static arrays * Ensure names display in proper order * dap: Fix struct locals display, add args display * Support `start` parameter * Support `filter` parameter * remove struct unbound native data display Practically useless for debugging zscript and didn't work properly anyway * d_main: fix vm_jit and vm_jit not being disabled soon enough * support breaking on abort exceptions * dap: refactor game event emitter functions into seperate header * dap: show native functions on stack * dap: Remove "Native" from exception handling, simplify exception event emit * dap: add instruction breakpoints * dap: fix display of locals not in scope yet * dap: Make disassembly view display invalid instructions for non-code addresses * dap: remove dot initializers * dap: fix local structs in scope * dap: don't parse the non-used options in the launch/attach requests * dap: fix local struct view * dap: Fix displaying objects that aren't their actual types * dap: Fix action and state handling * dap: stack display view * dap: fix object display view * d_dehacked: set qualified name in addition to the printable name * dap: fix displaying breakpoint errors when script isn't loaded * dap: remove debug print * dap: Display parameter names * dap: Turn down verbosity of logging * dap: fix disassembly view * dap: fix performance problems with arrays * c_console: emit event only if not PRINT_NODAPEVENT * dap: improve logging * dap: update upstream cppdap library to fix deadlocks on no bind * dap: Fix ending session on client socket closed * dap: prevent DebugServer.h from pulling in `dap` and `ZScriptDebugger.h` * dap: fix pause event not being emitted on pause * dap: remove eventpp emitters, way too slow * Remove eventpp dependency * dap: Display correct register names * dap: Show special inits in registers * dap: Add stack offset to VMLocalVariable * dap: fix display of static arrays and local variables on stack vs. registers * dap: fix displaying function pointers * dap: tweak color display * dap: fix scalar display < 4 * dap: add Globals display to debugger * dap: unify methods to get vmvalue * dap: rename free method to freeValue to avoid running afoul of macro defs * fix windows builds * fix compile on linux * cleanup * Fix display of function breakpoints * dap: Don't send back binary files * dap: include sbarinfo in script types (no debug support for anything but zscript yet, this is just for returning source info) * dap: don't show ending session message unless initialized * fix erroneous commit * dap: handle evaluate requests * Fix getting bitfield values * Add CVars Scope and evaluation * add running console commands from repl * dap: disable commands via repl for now * fix loading functions DECORATE scripts * Add source information to Dehacked VM functions, add debugging support * dap: cleanup * fix resolving archive paths * don't send source back on native stack frames * handle `modules` request * cleanup * allow evaluating cvars on hover * fix oob bpinfos * fix restarting the game blowing out the debug server * dap: process input events while paused to prevent deadlocks * fix getting local state * dap: fix LocalState alignment * dap: fix DumpStateHelper * update cppdap protocol version to 1.68.0 * remove cppdap from vcpkg deps We can't use the upstream version anyway because the maintainers are not merging our patches * dap: make named variable nodes derive from the same class * dap: make cvar scope available in native stack frames * handle local variables with conflicting names * add I_GetWindowEvent() to win32 to only process window events when debugging is paused * dap: fix evaluate * dap: fix display of `out` variables
521 lines
15 KiB
C++
521 lines
15 KiB
C++
// 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.
|
|
|
|
#include "content_stream.h"
|
|
|
|
#include "dap/any.h"
|
|
#include "dap/session.h"
|
|
|
|
#include "chan.h"
|
|
#include "json_serializer.h"
|
|
#include "socket.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <atomic>
|
|
#include <deque>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
class Impl : public dap::Session {
|
|
public:
|
|
void setOnInvalidData(dap::OnInvalidData onInvalidData_) override {
|
|
this->onInvalidData = onInvalidData_;
|
|
}
|
|
|
|
void onError(const ErrorHandler& handler) override { handlers.put(handler); }
|
|
|
|
void registerHandler(const dap::TypeInfo* typeinfo,
|
|
const GenericRequestHandler& handler) override {
|
|
handlers.put(typeinfo, handler);
|
|
}
|
|
|
|
void registerHandler(const dap::TypeInfo* typeinfo,
|
|
const GenericEventHandler& handler) override {
|
|
handlers.put(typeinfo, handler);
|
|
}
|
|
|
|
void registerHandler(const dap::TypeInfo* typeinfo,
|
|
const GenericResponseSentHandler& handler) override {
|
|
handlers.put(typeinfo, handler);
|
|
}
|
|
|
|
std::function<void()> getPayload() override {
|
|
auto request = reader.read();
|
|
if (request.size() > 0) {
|
|
if (auto payload = processMessage(request)) {
|
|
return payload;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void connect(const std::shared_ptr<dap::Reader>& r,
|
|
const std::shared_ptr<dap::Writer>& w) override {
|
|
if (isBound.exchange(true)) {
|
|
handlers.error("Session::connect called twice");
|
|
return;
|
|
}
|
|
|
|
reader = dap::ContentReader(r, this->onInvalidData);
|
|
writer = dap::ContentWriter(w);
|
|
}
|
|
|
|
void startProcessingMessages(
|
|
const ClosedHandler& onClose /* = {} */) override {
|
|
if (isProcessingMessages.exchange(true)) {
|
|
handlers.error("Session::startProcessingMessages() called twice");
|
|
return;
|
|
}
|
|
recvThread = std::thread([this, onClose] {
|
|
while (reader.isOpen()) {
|
|
if (auto payload = getPayload()) {
|
|
inbox.put(std::move(payload));
|
|
}
|
|
}
|
|
if (onClose) {
|
|
onClose();
|
|
}
|
|
});
|
|
|
|
dispatchThread = std::thread([this] {
|
|
while (auto payload = inbox.take()) {
|
|
payload.value()();
|
|
}
|
|
});
|
|
}
|
|
|
|
bool send(const dap::TypeInfo* requestTypeInfo,
|
|
const dap::TypeInfo* responseTypeInfo,
|
|
const void* request,
|
|
const GenericResponseHandler& responseHandler) override {
|
|
int seq = nextSeq++;
|
|
|
|
handlers.put(seq, responseTypeInfo, responseHandler);
|
|
|
|
dap::json::Serializer s;
|
|
if (!s.object([&](dap::FieldSerializer* fs) {
|
|
return fs->field("seq", dap::integer(seq)) &&
|
|
fs->field("type", "request") &&
|
|
fs->field("command", requestTypeInfo->name()) &&
|
|
fs->field("arguments", [&](dap::Serializer* s) {
|
|
return requestTypeInfo->serialize(s, request);
|
|
});
|
|
})) {
|
|
return false;
|
|
}
|
|
return send(s.dump());
|
|
}
|
|
|
|
bool send(const dap::TypeInfo* typeinfo, const void* event) override {
|
|
dap::json::Serializer s;
|
|
if (!s.object([&](dap::FieldSerializer* fs) {
|
|
return fs->field("seq", dap::integer(nextSeq++)) &&
|
|
fs->field("type", "event") &&
|
|
fs->field("event", typeinfo->name()) &&
|
|
fs->field("body", [&](dap::Serializer* s) {
|
|
return typeinfo->serialize(s, event);
|
|
});
|
|
})) {
|
|
return false;
|
|
}
|
|
return send(s.dump());
|
|
}
|
|
|
|
~Impl() override {
|
|
inbox.close();
|
|
reader.close();
|
|
writer.close();
|
|
if (recvThread.joinable()) {
|
|
recvThread.join();
|
|
}
|
|
if (dispatchThread.joinable()) {
|
|
dispatchThread.join();
|
|
}
|
|
}
|
|
|
|
private:
|
|
using Payload = std::function<void()>;
|
|
|
|
class EventHandlers {
|
|
public:
|
|
void put(const ErrorHandler& handler) {
|
|
std::unique_lock<std::mutex> lock(errorMutex);
|
|
errorHandler = handler;
|
|
}
|
|
|
|
void error(const char* format, ...) {
|
|
va_list vararg;
|
|
va_start(vararg, format);
|
|
std::unique_lock<std::mutex> lock(errorMutex);
|
|
errorLocked(format, vararg);
|
|
va_end(vararg);
|
|
}
|
|
|
|
std::pair<const dap::TypeInfo*, GenericRequestHandler> request(
|
|
const std::string& name) {
|
|
std::unique_lock<std::mutex> lock(requestMutex);
|
|
auto it = requestMap.find(name);
|
|
return (it != requestMap.end()) ? it->second : decltype(it->second){};
|
|
}
|
|
|
|
void put(const dap::TypeInfo* typeinfo,
|
|
const GenericRequestHandler& handler) {
|
|
std::unique_lock<std::mutex> lock(requestMutex);
|
|
auto added =
|
|
requestMap
|
|
.emplace(typeinfo->name(), std::make_pair(typeinfo, handler))
|
|
.second;
|
|
if (!added) {
|
|
errorfLocked("Request handler for '%s' already registered",
|
|
typeinfo->name().c_str());
|
|
}
|
|
}
|
|
|
|
std::pair<const dap::TypeInfo*, GenericResponseHandler> response(
|
|
int64_t seq) {
|
|
std::unique_lock<std::mutex> lock(responseMutex);
|
|
auto responseIt = responseMap.find(seq);
|
|
if (responseIt == responseMap.end()) {
|
|
errorfLocked("Unknown response with sequence %d", seq);
|
|
return {};
|
|
}
|
|
auto out = std::move(responseIt->second);
|
|
responseMap.erase(seq);
|
|
return out;
|
|
}
|
|
|
|
void put(int seq,
|
|
const dap::TypeInfo* typeinfo,
|
|
const GenericResponseHandler& handler) {
|
|
std::unique_lock<std::mutex> lock(responseMutex);
|
|
auto added =
|
|
responseMap.emplace(seq, std::make_pair(typeinfo, handler)).second;
|
|
if (!added) {
|
|
errorfLocked("Response handler for sequence %d already registered",
|
|
seq);
|
|
}
|
|
}
|
|
|
|
std::pair<const dap::TypeInfo*, GenericEventHandler> event(
|
|
const std::string& name) {
|
|
std::unique_lock<std::mutex> lock(eventMutex);
|
|
auto it = eventMap.find(name);
|
|
return (it != eventMap.end()) ? it->second : decltype(it->second){};
|
|
}
|
|
|
|
void put(const dap::TypeInfo* typeinfo,
|
|
const GenericEventHandler& handler) {
|
|
std::unique_lock<std::mutex> lock(eventMutex);
|
|
auto added =
|
|
eventMap.emplace(typeinfo->name(), std::make_pair(typeinfo, handler))
|
|
.second;
|
|
if (!added) {
|
|
errorfLocked("Event handler for '%s' already registered",
|
|
typeinfo->name().c_str());
|
|
}
|
|
}
|
|
|
|
GenericResponseSentHandler responseSent(const dap::TypeInfo* typeinfo) {
|
|
std::unique_lock<std::mutex> lock(responseSentMutex);
|
|
auto it = responseSentMap.find(typeinfo);
|
|
return (it != responseSentMap.end()) ? it->second
|
|
: decltype(it->second){};
|
|
}
|
|
|
|
void put(const dap::TypeInfo* typeinfo,
|
|
const GenericResponseSentHandler& handler) {
|
|
std::unique_lock<std::mutex> lock(responseSentMutex);
|
|
auto added = responseSentMap.emplace(typeinfo, handler).second;
|
|
if (!added) {
|
|
errorfLocked("Response sent handler for '%s' already registered",
|
|
typeinfo->name().c_str());
|
|
}
|
|
}
|
|
|
|
private:
|
|
void errorfLocked(const char* format, ...) {
|
|
va_list vararg;
|
|
va_start(vararg, format);
|
|
errorLocked(format, vararg);
|
|
va_end(vararg);
|
|
}
|
|
|
|
void errorLocked(const char* format, va_list args) {
|
|
char buf[2048];
|
|
vsnprintf(buf, sizeof(buf), format, args);
|
|
if (errorHandler) {
|
|
errorHandler(buf);
|
|
}
|
|
}
|
|
|
|
std::mutex errorMutex;
|
|
ErrorHandler errorHandler;
|
|
|
|
std::mutex requestMutex;
|
|
std::unordered_map<std::string,
|
|
std::pair<const dap::TypeInfo*, GenericRequestHandler>>
|
|
requestMap;
|
|
|
|
std::mutex responseMutex;
|
|
std::unordered_map<int64_t,
|
|
std::pair<const dap::TypeInfo*, GenericResponseHandler>>
|
|
responseMap;
|
|
|
|
std::mutex eventMutex;
|
|
std::unordered_map<std::string,
|
|
std::pair<const dap::TypeInfo*, GenericEventHandler>>
|
|
eventMap;
|
|
|
|
std::mutex responseSentMutex;
|
|
std::unordered_map<const dap::TypeInfo*, GenericResponseSentHandler>
|
|
responseSentMap;
|
|
}; // EventHandlers
|
|
|
|
Payload processMessage(const std::string& str) {
|
|
auto d = dap::json::Deserializer(str);
|
|
dap::string type;
|
|
if (!d.field("type", &type)) {
|
|
handlers.error("Message missing string 'type' field");
|
|
return {};
|
|
}
|
|
|
|
dap::integer sequence = 0;
|
|
if (!d.field("seq", &sequence)) {
|
|
handlers.error("Message missing number 'seq' field");
|
|
return {};
|
|
}
|
|
|
|
if (type == "request") {
|
|
return processRequest(&d, sequence);
|
|
} else if (type == "event") {
|
|
return processEvent(&d);
|
|
} else if (type == "response") {
|
|
processResponse(&d);
|
|
return {};
|
|
} else {
|
|
handlers.error("Unknown message type '%s'", type.c_str());
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
Payload processRequest(dap::json::Deserializer* d, dap::integer sequence) {
|
|
dap::string command;
|
|
if (!d->field("command", &command)) {
|
|
handlers.error("Request missing string 'command' field");
|
|
return {};
|
|
}
|
|
|
|
const dap::TypeInfo* typeinfo;
|
|
GenericRequestHandler handler;
|
|
std::tie(typeinfo, handler) = handlers.request(command);
|
|
if (!typeinfo) {
|
|
handlers.error("No request handler registered for command '%s'",
|
|
command.c_str());
|
|
return {};
|
|
}
|
|
|
|
auto data = new uint8_t[typeinfo->size()];
|
|
typeinfo->construct(data);
|
|
|
|
if (!d->field("arguments", [&](dap::Deserializer* d) {
|
|
return typeinfo->deserialize(d, data);
|
|
})) {
|
|
handlers.error("Failed to deserialize request");
|
|
typeinfo->destruct(data);
|
|
delete[] data;
|
|
return {};
|
|
}
|
|
|
|
return [=] {
|
|
handler(
|
|
data,
|
|
[=](const dap::TypeInfo* typeinfo, const void* data) {
|
|
// onSuccess
|
|
dap::json::Serializer s;
|
|
s.object([&](dap::FieldSerializer* fs) {
|
|
return fs->field("seq", dap::integer(nextSeq++)) &&
|
|
fs->field("type", "response") &&
|
|
fs->field("request_seq", sequence) &&
|
|
fs->field("success", dap::boolean(true)) &&
|
|
fs->field("command", command) &&
|
|
fs->field("body", [&](dap::Serializer* s) {
|
|
return typeinfo->serialize(s, data);
|
|
});
|
|
});
|
|
send(s.dump());
|
|
|
|
if (auto handler = handlers.responseSent(typeinfo)) {
|
|
handler(data, nullptr);
|
|
}
|
|
},
|
|
[=](const dap::TypeInfo* typeinfo, const dap::Error& error) {
|
|
// onError
|
|
dap::json::Serializer s;
|
|
s.object([&](dap::FieldSerializer* fs) {
|
|
return fs->field("seq", dap::integer(nextSeq++)) &&
|
|
fs->field("type", "response") &&
|
|
fs->field("request_seq", sequence) &&
|
|
fs->field("success", dap::boolean(false)) &&
|
|
fs->field("command", command) &&
|
|
fs->field("message", error.message);
|
|
});
|
|
send(s.dump());
|
|
|
|
if (auto handler = handlers.responseSent(typeinfo)) {
|
|
handler(nullptr, &error);
|
|
}
|
|
});
|
|
typeinfo->destruct(data);
|
|
delete[] data;
|
|
};
|
|
}
|
|
|
|
Payload processEvent(dap::json::Deserializer* d) {
|
|
dap::string event;
|
|
if (!d->field("event", &event)) {
|
|
handlers.error("Event missing string 'event' field");
|
|
return {};
|
|
}
|
|
|
|
const dap::TypeInfo* typeinfo;
|
|
GenericEventHandler handler;
|
|
std::tie(typeinfo, handler) = handlers.event(event);
|
|
if (!typeinfo) {
|
|
handlers.error("No event handler registered for event '%s'",
|
|
event.c_str());
|
|
return {};
|
|
}
|
|
|
|
auto data = new uint8_t[typeinfo->size()];
|
|
typeinfo->construct(data);
|
|
|
|
// "body" is an optional field for some events, such as "Terminated Event".
|
|
bool body_ok = true;
|
|
d->field("body", [&](dap::Deserializer* d) {
|
|
if (!typeinfo->deserialize(d, data)) {
|
|
body_ok = false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (!body_ok) {
|
|
handlers.error("Failed to deserialize event '%s' body", event.c_str());
|
|
typeinfo->destruct(data);
|
|
delete[] data;
|
|
return {};
|
|
}
|
|
|
|
return [=] {
|
|
handler(data);
|
|
typeinfo->destruct(data);
|
|
delete[] data;
|
|
};
|
|
}
|
|
|
|
void processResponse(const dap::Deserializer* d) {
|
|
dap::integer requestSeq = 0;
|
|
if (!d->field("request_seq", &requestSeq)) {
|
|
handlers.error("Response missing int 'request_seq' field");
|
|
return;
|
|
}
|
|
|
|
const dap::TypeInfo* typeinfo;
|
|
GenericResponseHandler handler;
|
|
std::tie(typeinfo, handler) = handlers.response(requestSeq);
|
|
if (!typeinfo) {
|
|
handlers.error("Unknown response with sequence %d", requestSeq);
|
|
return;
|
|
}
|
|
|
|
dap::boolean success = false;
|
|
if (!d->field("success", &success)) {
|
|
handlers.error("Response missing int 'success' field");
|
|
return;
|
|
}
|
|
|
|
if (success) {
|
|
auto data = std::unique_ptr<uint8_t[]>(new uint8_t[typeinfo->size()]);
|
|
typeinfo->construct(data.get());
|
|
|
|
// "body" field in Response is an optional field.
|
|
d->field("body", [&](const dap::Deserializer* d) {
|
|
return typeinfo->deserialize(d, data.get());
|
|
});
|
|
|
|
handler(data.get(), nullptr);
|
|
typeinfo->destruct(data.get());
|
|
} else {
|
|
std::string message;
|
|
if (!d->field("message", &message)) {
|
|
handlers.error("Failed to deserialize message");
|
|
return;
|
|
}
|
|
auto error = dap::Error("%s", message.c_str());
|
|
handler(nullptr, &error);
|
|
}
|
|
}
|
|
|
|
bool send(const std::string& s) {
|
|
std::unique_lock<std::mutex> lock(sendMutex);
|
|
if (!writer.isOpen()) {
|
|
handlers.error("Send failed as the writer is closed");
|
|
return false;
|
|
}
|
|
return writer.write(s);
|
|
}
|
|
|
|
std::atomic<bool> isBound = {false};
|
|
std::atomic<bool> isProcessingMessages = {false};
|
|
dap::ContentReader reader;
|
|
dap::ContentWriter writer;
|
|
|
|
std::atomic<bool> shutdown = {false};
|
|
EventHandlers handlers;
|
|
std::thread recvThread;
|
|
std::thread dispatchThread;
|
|
dap::Chan<Payload> inbox;
|
|
std::atomic<uint32_t> nextSeq = {1};
|
|
std::mutex sendMutex;
|
|
dap::OnInvalidData onInvalidData = dap::kIgnore;
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace dap {
|
|
|
|
Error::Error(const std::string& message) : message(message) {}
|
|
|
|
Error::Error(const char* msg, ...) {
|
|
char buf[2048];
|
|
va_list vararg;
|
|
va_start(vararg, msg);
|
|
vsnprintf(buf, sizeof(buf), msg, vararg);
|
|
va_end(vararg);
|
|
message = buf;
|
|
}
|
|
|
|
Session::~Session() = default;
|
|
|
|
std::unique_ptr<Session> Session::create() {
|
|
return std::unique_ptr<Session>(new Impl());
|
|
}
|
|
|
|
} // namespace dap
|