--- /dev/null
+// Part of this code was taken from the example vscode debugger found here,
+// https://github.com/microsoft/vscode-mock-debug/blob/5524e7a3dd9ec176a987ffe3aebd489f1f543799/src/debugAdapter.ts`
+
+import { OVMDebugSession } from './ovmDebug';
+
+import * as Net from 'node:net';
+/*
+ * When the debug adapter is run as an external process,
+ * normally the helper function DebugSession.run(...) takes care of everything:
+ *
+ * MockDebugSession.run(MockDebugSession);
+ *
+ * but here the helper is not flexible enough to deal with a debug session constructors with a parameter.
+ * So for now we copied and modified the helper:
+ */
+
+// first parse command line arguments to see whether the debug adapter should run as a server
+let port = 0;
+const args = process.argv.slice(2);
+args.forEach(function (val, index, array) {
+ const portMatch = /^--server=(\d{4,5})$/.exec(val);
+ if (portMatch) {
+ port = parseInt(portMatch[1], 10);
+ }
+});
+
+if (port > 0) {
+ Net.createServer((socket) => {
+ const session = new OVMDebugSession();
+ session.setRunAsServer(true);
+ session.start(socket, socket);
+ }).listen(port);
+
+} else {
+
+ // start a single session that communicates via stdin/stdout
+ const session = new OVMDebugSession();
+ process.on('SIGTERM', () => {
+ session.shutdown();
+ });
+ session.start(process.stdin, process.stdout);
+}
\ No newline at end of file
--- /dev/null
+"use strict";
+// Part of this code was taken from the example vscode debugger found here,
+// https://github.com/microsoft/vscode-mock-debug/blob/5524e7a3dd9ec176a987ffe3aebd489f1f543799/src/debugAdapter.ts`
+Object.defineProperty(exports, "__esModule", { value: true });
+const ovmDebug_1 = require("./ovmDebug");
+const Net = require("node:net");
+/*
+ * When the debug adapter is run as an external process,
+ * normally the helper function DebugSession.run(...) takes care of everything:
+ *
+ * MockDebugSession.run(MockDebugSession);
+ *
+ * but here the helper is not flexible enough to deal with a debug session constructors with a parameter.
+ * So for now we copied and modified the helper:
+ */
+// first parse command line arguments to see whether the debug adapter should run as a server
+let port = 0;
+const args = process.argv.slice(2);
+args.forEach(function (val, index, array) {
+ const portMatch = /^--server=(\d{4,5})$/.exec(val);
+ if (portMatch) {
+ port = parseInt(portMatch[1], 10);
+ }
+});
+if (port > 0) {
+ Net.createServer((socket) => {
+ const session = new ovmDebug_1.OVMDebugSession();
+ session.setRunAsServer(true);
+ session.start(socket, socket);
+ }).listen(port);
+}
+else {
+ // start a single session that communicates via stdin/stdout
+ const session = new ovmDebug_1.OVMDebugSession();
+ process.on('SIGTERM', () => {
+ session.shutdown();
+ });
+ session.start(process.stdin, process.stdout);
+}
--- /dev/null
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.OVMDebugSession = void 0;
+const debugadapter_1 = require("@vscode/debugadapter");
+const EventEmitter = require("node:events");
+const await_notify_1 = require("await-notify");
+const net = require("node:net");
+class OVMDebugSession extends debugadapter_1.LoggingDebugSession {
+ constructor() {
+ super("ovm-debug-log.txt");
+ this._configurationDone = new await_notify_1.Subject();
+ this._clientConnected = new await_notify_1.Subject();
+ this.setDebuggerLinesStartAt1(true);
+ this.setDebuggerColumnsStartAt1(true);
+ this._loadedSources = new Map();
+ this.debugger = new OVMDebugger();
+ this.debugger.on("breakpointHit", (ev) => {
+ console.log("BREAKPOINT HIT");
+ this.sendEvent(new debugadapter_1.StoppedEvent("breakpoint", ev.threadId));
+ });
+ this.debugger.on("paused", (ev) => {
+ console.log("PAUSED");
+ this.sendEvent(new debugadapter_1.StoppedEvent("pause", ev.threadId));
+ });
+ this.debugger.on("terminated", () => {
+ this.sendEvent(new debugadapter_1.TerminatedEvent());
+ });
+ }
+ initializeRequest(response, args) {
+ console.log("INITIALIZE");
+ response.body = response.body || {};
+ // the adapter implements the configurationDone request.
+ response.body.supportsConfigurationDoneRequest = true;
+ // make VS Code use 'evaluate' when hovering over source
+ response.body.supportsEvaluateForHovers = false;
+ // make VS Code show a 'step back' button
+ response.body.supportsStepBack = false;
+ // make VS Code support data breakpoints
+ response.body.supportsDataBreakpoints = false;
+ // make VS Code support completion in REPL
+ response.body.supportsCompletionsRequest = false;
+ // response.body.completionTriggerCharacters = [ ".", "[" ];
+ // make VS Code send cancel request
+ response.body.supportsCancelRequest = true;
+ // make VS Code send the breakpointLocations request
+ response.body.supportsBreakpointLocationsRequest = false;
+ // make VS Code provide "Step in Target" functionality
+ response.body.supportsStepInTargetsRequest = false;
+ // the adapter defines two exceptions filters, one with support for conditions.
+ response.body.supportsExceptionFilterOptions = false;
+ /*response.body.exceptionBreakpointFilters = [
+ {
+ filter: 'namedException',
+ label: "Named Exception",
+ description: `Break on named exceptions. Enter the exception's name as the Condition.`,
+ default: false,
+ supportsCondition: true,
+ conditionDescription: `Enter the exception's name`
+ },
+ {
+ filter: 'otherExceptions',
+ label: "Other Exceptions",
+ description: 'This is a other exception',
+ default: true,
+ supportsCondition: false
+ }
+ ];*/
+ // make VS Code send exceptionInfo request
+ response.body.supportsExceptionInfoRequest = false;
+ // make VS Code send setVariable request
+ response.body.supportsSetVariable = false;
+ // make VS Code send setExpression request
+ response.body.supportsSetExpression = false;
+ // make VS Code send disassemble request
+ response.body.supportsDisassembleRequest = false;
+ response.body.supportsSteppingGranularity = false;
+ response.body.supportsInstructionBreakpoints = false;
+ // make VS Code able to read and write variable memory
+ response.body.supportsReadMemoryRequest = false;
+ response.body.supportsWriteMemoryRequest = false;
+ response.body.supportSuspendDebuggee = false;
+ response.body.supportTerminateDebuggee = true;
+ response.body.supportsFunctionBreakpoints = true;
+ this.sendResponse(response);
+ // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
+ // we request them early by sending an 'initializeRequest' to the frontend.
+ // The frontend will end the configuration sequence by calling 'configurationDone' request.
+ this.sendEvent(new debugadapter_1.InitializedEvent());
+ }
+ configurationDoneRequest(response, args, request) {
+ console.log("CONFIGURATION DONE");
+ super.configurationDoneRequest(response, args);
+ this._configurationDone.notify();
+ }
+ disconnectRequest(response, args, request) {
+ console.log(`disconnectRequest suspend: ${args.suspendDebuggee}, terminate: ${args.terminateDebuggee}`);
+ }
+ cancelRequest(response, args, request) {
+ this.sendResponse(response);
+ }
+ setBreakPointsRequest(response, args, request) {
+ return __awaiter(this, void 0, void 0, function* () {
+ console.log("BREAKPOINTS", args, response);
+ yield this._clientConnected.wait(1000);
+ const path = args.source.path;
+ const clientLines = args.lines || [];
+ // TODO: In theory, breakpoints should be cleared here
+ const actualBreakpointsPromise = clientLines.map((line) => __awaiter(this, void 0, void 0, function* () {
+ const res = yield this.debugger.set_breakpoint(path, line);
+ const bp = new debugadapter_1.Breakpoint(res.verified, this.convertDebuggerLineToClient(res.line));
+ bp.id = res.id;
+ return bp;
+ }));
+ const actualBreakpoints = yield Promise.all(actualBreakpointsPromise);
+ response.body = {
+ breakpoints: actualBreakpoints
+ };
+ this.sendResponse(response);
+ });
+ }
+ stackTraceRequest(response, args, request) {
+ return __awaiter(this, void 0, void 0, function* () {
+ console.log("LOCATION");
+ let location = yield this.debugger.request_location(1);
+ let source = new debugadapter_1.Source(this.fileNameToShortName(location.filename), this.convertDebuggerPathToClient(location.filename), undefined, undefined, "ovm-debug-src");
+ console.log(source);
+ if (!this._loadedSources.has(source.name)) {
+ this._loadedSources.set(source.name, source);
+ this.sendEvent(new debugadapter_1.LoadedSourceEvent("new", source));
+ }
+ response.body = {
+ stackFrames: [
+ new debugadapter_1.StackFrame(1, "test frame", source, location.line)
+ ]
+ };
+ this.sendResponse(response);
+ });
+ }
+ threadsRequest(response, request) {
+ console.log("THREADS");
+ response.body = {
+ threads: [
+ new debugadapter_1.Thread(1, "main thread"),
+ ]
+ };
+ this.sendResponse(response);
+ }
+ scopesRequest(response, args, request) {
+ console.log("SCOPES");
+ response.body = {
+ scopes: [
+ new debugadapter_1.Scope("Locals", 1, false),
+ new debugadapter_1.Scope("Globals", 2, true)
+ ]
+ };
+ this.sendResponse(response);
+ }
+ launchRequest(response, args, request) {
+ console.log("LAUNCH");
+ // console.error(`Unable to launch a new Onyx debugging session. Please use { "request": "attach" } instead.`);
+ this.attachRequest(response, { "socketPath": "/tmp/ovm-debug.0000", "stopOnEntry": true });
+ }
+ attachRequest(response, args, request) {
+ return __awaiter(this, void 0, void 0, function* () {
+ console.log("ATTACH");
+ yield this._configurationDone.wait(1000);
+ yield this.debugger.connect(args.socketPath);
+ this._clientConnected.notify();
+ this.sendResponse(response);
+ this.sendEvent(new debugadapter_1.ThreadEvent("started", 1));
+ if (!args.stopOnEntry) {
+ this.debugger.resume();
+ }
+ });
+ }
+ continueRequest(response, args, request) {
+ let thread_id = args.threadId;
+ if (!args.singleThread) {
+ thread_id = 0xffffffff;
+ }
+ response.body = {
+ allThreadsContinued: !!args.singleThread
+ };
+ this.debugger.resume(thread_id);
+ this.sendResponse(response);
+ }
+ fileNameToShortName(filename) {
+ return filename.substring(filename.lastIndexOf("/"));
+ }
+}
+exports.OVMDebugSession = OVMDebugSession;
+var OVMCommand;
+(function (OVMCommand) {
+ OVMCommand[OVMCommand["NOP"] = 0] = "NOP";
+ OVMCommand[OVMCommand["RES"] = 1] = "RES";
+ OVMCommand[OVMCommand["BRK"] = 2] = "BRK";
+ OVMCommand[OVMCommand["LOC"] = 3] = "LOC";
+})(OVMCommand || (OVMCommand = {}));
+var OVMEvent;
+(function (OVMEvent) {
+ OVMEvent[OVMEvent["NOP"] = 0] = "NOP";
+ OVMEvent[OVMEvent["BREAKPOINT_HIT"] = 1] = "BREAKPOINT_HIT";
+ OVMEvent[OVMEvent["PAUSED"] = 2] = "PAUSED";
+ OVMEvent[OVMEvent["RESPONSE"] = 4294967295] = "RESPONSE";
+})(OVMEvent || (OVMEvent = {}));
+class OVMDebugger extends EventEmitter {
+ constructor() {
+ super();
+ this._promiseResolution = new Map();
+ }
+ connect(path) {
+ this._next_cmd_id = 1;
+ this.pending_responses = {};
+ this.client = net.connect(path);
+ this.client.on("data", this.parseIncoming.bind(this));
+ this.client.on("end", () => {
+ console.log("terminated connection.");
+ this.sendEvent("terminated");
+ });
+ return new Promise((res, rej) => {
+ this.client.on("connect", res);
+ });
+ }
+ resume(thread_id = 0xffffffff) {
+ let data = new ArrayBuffer(12);
+ let view = new DataView(data);
+ let cmd_id = this.next_command_id;
+ view.setUint32(0, cmd_id, true);
+ view.setUint32(4, OVMCommand.RES, true);
+ view.setUint32(8, thread_id, true);
+ this.client.write(new Uint8Array(data));
+ this.pending_responses[cmd_id] = OVMCommand.RES;
+ }
+ set_breakpoint(filename, line) {
+ return __awaiter(this, void 0, void 0, function* () {
+ let data = new ArrayBuffer(16 + filename.length);
+ let view = new DataView(data);
+ let cmd_id = this.next_command_id;
+ view.setUint32(0, cmd_id, true);
+ view.setUint32(4, OVMCommand.BRK, true);
+ view.setUint32(8, filename.length, true);
+ for (let i = 0; i < filename.length; i++) {
+ view.setUint8(i + 12, filename.charCodeAt(i));
+ }
+ view.setUint32(12 + filename.length, line, true);
+ this.client.write(new Uint8Array(data));
+ this.pending_responses[cmd_id] = OVMCommand.BRK;
+ return this.preparePromise(cmd_id);
+ });
+ }
+ request_location(thread_id) {
+ let data = new ArrayBuffer(12);
+ let view = new DataView(data);
+ let cmd_id = this.next_command_id;
+ view.setUint32(0, cmd_id, true);
+ view.setUint32(4, OVMCommand.LOC, true);
+ view.setUint32(8, thread_id, true);
+ this.client.write(new Uint8Array(data));
+ this.pending_responses[cmd_id] = OVMCommand.LOC;
+ return this.preparePromise(cmd_id);
+ }
+ parseIncoming(data) {
+ let parser = new DataParser(data);
+ while (parser.offset != data.length) {
+ let event_id = parser.parseUint32();
+ switch (event_id) {
+ case OVMEvent.NOP: break;
+ case OVMEvent.BREAKPOINT_HIT: {
+ let bp_id = parser.parseUint32();
+ let thread_id = parser.parseUint32();
+ this.sendEvent("breakpointHit", {
+ breakpointId: bp_id,
+ threadId: thread_id,
+ });
+ break;
+ }
+ case OVMEvent.PAUSED: {
+ let thread_id = parser.parseUint32();
+ this.sendEvent("paused", { threadId: thread_id });
+ break;
+ }
+ case OVMEvent.RESPONSE: {
+ this.handleResponse(parser);
+ break;
+ }
+ default:
+ console.log("Unknown event: ", event_id, data);
+ }
+ }
+ }
+ handleResponse(parser) {
+ let msg_id = parser.parseUint32();
+ let cmd_id = this.pending_responses[msg_id] || OVMCommand.NOP;
+ switch (cmd_id) {
+ case OVMCommand.NOP: break;
+ case OVMCommand.RES: {
+ let success = parser.parseBool();
+ break;
+ }
+ case OVMCommand.BRK: {
+ let success = parser.parseBool();
+ let bp_id = parser.parseInt32();
+ let line = parser.parseInt32();
+ this.resolvePromise(msg_id, {
+ verified: success,
+ id: bp_id,
+ line: line
+ });
+ break;
+ }
+ case OVMCommand.LOC: {
+ console.log("Recv loc");
+ let success = parser.parseBool();
+ let filename = parser.parseString();
+ let line = parser.parseInt32();
+ if (!success)
+ break;
+ this.resolvePromise(msg_id, { filename, line });
+ break;
+ }
+ default:
+ console.log("Unrecognized command. ", cmd_id, msg_id);
+ }
+ }
+ preparePromise(msg_id) {
+ return new Promise((resolve, reject) => {
+ this._promiseResolution.set(msg_id, resolve);
+ });
+ }
+ resolvePromise(msg_id, data) {
+ if (this._promiseResolution.has(msg_id)) {
+ let func = this._promiseResolution.get(msg_id);
+ this._promiseResolution.delete(msg_id);
+ func(data);
+ }
+ }
+ sendEvent(event, ...args) {
+ setTimeout(() => {
+ this.emit(event, ...args);
+ }, 0);
+ }
+ get next_command_id() {
+ let val = this._next_cmd_id;
+ this._next_cmd_id += 1;
+ return val;
+ }
+}
+//
+// Utility class for parsing system data types out of
+// a buffer. Currently, it assumes a little endian byte
+// order in the buffer, and that should probably be changed?
+//
+class DataParser {
+ constructor(data) {
+ this.data = data;
+ this.view = new DataView(data.buffer);
+ console.log("PARSING", this.data);
+ this.offset = 0;
+ }
+ parseInt32() {
+ this.offset += 4;
+ return this.view.getInt32(this.offset - 4, true);
+ }
+ parseUint32() {
+ this.offset += 4;
+ return this.view.getUint32(this.offset - 4, true);
+ }
+ parseString() {
+ let len = this.parseUint32();
+ let str = "";
+ for (let i = 0; i < len; i++) {
+ str += String.fromCharCode(this.view.getUint8(this.offset + i));
+ }
+ this.offset += len;
+ return str;
+ }
+ parseBool() {
+ this.offset += 1;
+ return this.view.getUint8(this.offset - 1) != 0;
+ }
+}
--- /dev/null
+import {
+ Logger, logger,
+ LoggingDebugSession,
+ InitializedEvent, TerminatedEvent, StoppedEvent, BreakpointEvent, OutputEvent,
+ ProgressStartEvent, ProgressUpdateEvent, ProgressEndEvent, InvalidatedEvent,
+ Thread, StackFrame, Scope, Source, Handles, Breakpoint, MemoryEvent, ThreadEvent, LoadedSourceEvent
+} from '@vscode/debugadapter';
+import { DebugProtocol } from '@vscode/debugprotocol';
+import EventEmitter = require('node:events');
+
+import { Subject } from "await-notify";
+import * as net from "node:net";
+
+
+interface IOVMAttachRequestArguments extends DebugProtocol.AttachRequestArguments {
+ socketPath?: string;
+ stopOnEntry?: boolean;
+}
+
+export class OVMDebugSession extends LoggingDebugSession {
+
+ private debugger: OVMDebugger;
+
+ private _configurationDone = new Subject();
+ private _clientConnected = new Subject();
+
+ private _pending_breakpoints: DebugProtocol.SetBreakpointsArguments | null;
+ private _pending_breakpoint_response: DebugProtocol.SetBreakpointsResponse | null;
+
+ private _loadedSources: Map<string, Source>;
+
+ public constructor() {
+ super("ovm-debug-log.txt");
+
+ this.setDebuggerLinesStartAt1(true);
+ this.setDebuggerColumnsStartAt1(true);
+
+ this._loadedSources = new Map();
+
+ this.debugger = new OVMDebugger();
+
+ this.debugger.on("breakpointHit", (ev) => {
+ console.log("BREAKPOINT HIT");
+ this.sendEvent(new StoppedEvent("breakpoint", ev.threadId));
+ });
+
+ this.debugger.on("paused", (ev) => {
+ console.log("PAUSED");
+ this.sendEvent(new StoppedEvent("pause", ev.threadId));
+ });
+
+ this.debugger.on("terminated", () => {
+ this.sendEvent(new TerminatedEvent());
+ });
+ }
+
+ protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
+ console.log("INITIALIZE");
+
+ response.body = response.body || {};
+
+ // the adapter implements the configurationDone request.
+ response.body.supportsConfigurationDoneRequest = true;
+
+ // make VS Code use 'evaluate' when hovering over source
+ response.body.supportsEvaluateForHovers = false;
+
+ // make VS Code show a 'step back' button
+ response.body.supportsStepBack = false;
+
+ // make VS Code support data breakpoints
+ response.body.supportsDataBreakpoints = false;
+
+ // make VS Code support completion in REPL
+ response.body.supportsCompletionsRequest = false;
+ // response.body.completionTriggerCharacters = [ ".", "[" ];
+
+ // make VS Code send cancel request
+ response.body.supportsCancelRequest = true;
+
+ // make VS Code send the breakpointLocations request
+ response.body.supportsBreakpointLocationsRequest = false;
+
+ // make VS Code provide "Step in Target" functionality
+ response.body.supportsStepInTargetsRequest = false;
+
+ // the adapter defines two exceptions filters, one with support for conditions.
+ response.body.supportsExceptionFilterOptions = false;
+ /*response.body.exceptionBreakpointFilters = [
+ {
+ filter: 'namedException',
+ label: "Named Exception",
+ description: `Break on named exceptions. Enter the exception's name as the Condition.`,
+ default: false,
+ supportsCondition: true,
+ conditionDescription: `Enter the exception's name`
+ },
+ {
+ filter: 'otherExceptions',
+ label: "Other Exceptions",
+ description: 'This is a other exception',
+ default: true,
+ supportsCondition: false
+ }
+ ];*/
+
+ // make VS Code send exceptionInfo request
+ response.body.supportsExceptionInfoRequest = false;
+
+ // make VS Code send setVariable request
+ response.body.supportsSetVariable = false;
+
+ // make VS Code send setExpression request
+ response.body.supportsSetExpression = false;
+
+ // make VS Code send disassemble request
+ response.body.supportsDisassembleRequest = false;
+ response.body.supportsSteppingGranularity = false;
+ response.body.supportsInstructionBreakpoints = false;
+
+ // make VS Code able to read and write variable memory
+ response.body.supportsReadMemoryRequest = false;
+ response.body.supportsWriteMemoryRequest = false;
+
+ response.body.supportSuspendDebuggee = false;
+ response.body.supportTerminateDebuggee = true;
+ response.body.supportsFunctionBreakpoints = true;
+
+ this.sendResponse(response);
+
+ // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
+ // we request them early by sending an 'initializeRequest' to the frontend.
+ // The frontend will end the configuration sequence by calling 'configurationDone' request.
+ this.sendEvent(new InitializedEvent());
+ }
+
+ protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments, request?: DebugProtocol.Request): void {
+ console.log("CONFIGURATION DONE");
+ super.configurationDoneRequest(response, args);
+
+ this._configurationDone.notify();
+ }
+
+ protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments, request?: DebugProtocol.Request): void {
+ console.log(`disconnectRequest suspend: ${args.suspendDebuggee}, terminate: ${args.terminateDebuggee}`);
+ }
+
+ protected cancelRequest(response: DebugProtocol.CancelResponse, args: DebugProtocol.CancelArguments, request?: DebugProtocol.Request): void {
+ this.sendResponse(response);
+ }
+
+ protected async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments, request?: DebugProtocol.Request): Promise<void> {
+ console.log("BREAKPOINTS", args, response);
+ await this._clientConnected.wait(1000);
+
+ const path = args.source.path;
+ const clientLines = args.lines || [];
+
+ // TODO: In theory, breakpoints should be cleared here
+
+ const actualBreakpointsPromise = clientLines.map(async line => {
+ const res = await this.debugger.set_breakpoint(path, line);
+ const bp = new Breakpoint(res.verified, this.convertDebuggerLineToClient(res.line)) as DebugProtocol.Breakpoint;
+ bp.id = res.id;
+ return bp;
+ });
+
+ const actualBreakpoints = await Promise.all<DebugProtocol.Breakpoint>(actualBreakpointsPromise);
+
+ response.body = {
+ breakpoints: actualBreakpoints
+ };
+ this.sendResponse(response);
+ }
+
+ protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments, request?: DebugProtocol.Request): Promise<void> {
+ console.log("LOCATION");
+
+ let location = await this.debugger.request_location(1);
+ let source = new Source(
+ this.fileNameToShortName(location.filename),
+ this.convertDebuggerPathToClient(location.filename),
+ undefined, undefined, "ovm-debug-src");
+
+ console.log(source);
+
+ if (!this._loadedSources.has(source.name)) {
+ this._loadedSources.set(source.name, source);
+
+ this.sendEvent(new LoadedSourceEvent("new", source));
+ }
+
+ response.body = {
+ stackFrames: [
+ new StackFrame(1, "test frame", source, location.line)
+ ]
+ };
+
+ this.sendResponse(response);
+ }
+
+ protected threadsRequest(response: DebugProtocol.ThreadsResponse, request?: DebugProtocol.Request): void {
+ console.log("THREADS");
+
+ response.body = {
+ threads: [
+ new Thread(1, "main thread"),
+ ]
+ };
+
+ this.sendResponse(response);
+ }
+
+ protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments, request?: DebugProtocol.Request): void {
+ console.log("SCOPES");
+
+ response.body = {
+ scopes: [
+ new Scope("Locals", 1, false),
+ new Scope("Globals", 2, true)
+ ]
+ };
+ this.sendResponse(response);
+ }
+
+ protected launchRequest(response: DebugProtocol.LaunchResponse, args: DebugProtocol.LaunchRequestArguments, request?: DebugProtocol.Request): void {
+ console.log("LAUNCH");
+ // console.error(`Unable to launch a new Onyx debugging session. Please use { "request": "attach" } instead.`);
+ this.attachRequest(response, {"socketPath": "/tmp/ovm-debug.0000", "stopOnEntry": true});
+ }
+
+ protected async attachRequest(response: DebugProtocol.AttachResponse, args: IOVMAttachRequestArguments, request?: DebugProtocol.Request): Promise<void> {
+ console.log("ATTACH");
+ await this._configurationDone.wait(1000);
+
+ await this.debugger.connect(args.socketPath);
+ this._clientConnected.notify();
+
+ this.sendResponse(response);
+ this.sendEvent(new ThreadEvent("started", 1));
+
+ if (!args.stopOnEntry) {
+ this.debugger.resume();
+ }
+ }
+
+ protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments, request?: DebugProtocol.Request): void {
+ let thread_id = args.threadId;
+ if (!args.singleThread) {
+ thread_id = 0xffffffff;
+ }
+
+ response.body = {
+ allThreadsContinued: !!args.singleThread
+ };
+
+ this.debugger.resume(thread_id);
+ this.sendResponse(response);
+ }
+
+
+ private fileNameToShortName(filename: string): string {
+ return filename.substring(filename.lastIndexOf("/"));
+ }
+}
+
+interface IFileLocation {
+ filename: string;
+ line: number;
+}
+
+interface IBreakpointValidation {
+ verified: boolean;
+ id: number;
+ line: number;
+}
+
+enum OVMCommand {
+ NOP = 0,
+ RES = 1,
+ BRK = 2,
+ LOC = 3,
+}
+
+enum OVMEvent {
+ NOP = 0,
+ BREAKPOINT_HIT = 1,
+ PAUSED = 2,
+ RESPONSE = 0xffffffff
+}
+
+class OVMDebugger extends EventEmitter {
+
+ private client: net.Socket;
+ private pending_responses: any;
+
+ private _next_cmd_id: number;
+ private _promiseResolution: Map<number, (arg0: any) => void>;
+
+ constructor() {
+ super();
+ this._promiseResolution = new Map();
+ }
+
+ connect(path: string): Promise<void> {
+ this._next_cmd_id = 1;
+ this.pending_responses = {};
+
+ this.client = net.connect(path);
+
+ this.client.on("data", this.parseIncoming.bind(this));
+
+ this.client.on("end", () => {
+ console.log("terminated connection.");
+ this.sendEvent("terminated");
+ });
+
+ return new Promise((res, rej) => {
+ this.client.on("connect", res);
+ });
+ }
+
+ resume(thread_id: number = 0xffffffff): void {
+ let data = new ArrayBuffer(12);
+ let view = new DataView(data);
+
+ let cmd_id = this.next_command_id;
+
+ view.setUint32(0, cmd_id, true);
+ view.setUint32(4, OVMCommand.RES, true);
+ view.setUint32(8, thread_id, true);
+
+ this.client.write(new Uint8Array(data));
+
+ this.pending_responses[cmd_id] = OVMCommand.RES;
+ }
+
+ async set_breakpoint(filename: string, line: number): Promise<IBreakpointValidation> {
+ let data = new ArrayBuffer(16+filename.length);
+ let view = new DataView(data);
+
+ let cmd_id = this.next_command_id;
+
+ view.setUint32(0, cmd_id, true);
+ view.setUint32(4, OVMCommand.BRK, true);
+
+ view.setUint32(8, filename.length, true);
+ for (let i=0; i<filename.length; i++) {
+ view.setUint8(i+12, filename.charCodeAt(i));
+ }
+ view.setUint32(12+filename.length, line, true);
+
+ this.client.write(new Uint8Array(data));
+
+ this.pending_responses[cmd_id] = OVMCommand.BRK;
+
+ return this.preparePromise(cmd_id);
+ }
+
+ request_location(thread_id: number): Promise<IFileLocation> {
+ let data = new ArrayBuffer(12);
+ let view = new DataView(data);
+
+ let cmd_id = this.next_command_id;
+
+ view.setUint32(0, cmd_id, true);
+ view.setUint32(4, OVMCommand.LOC, true);
+ view.setUint32(8, thread_id, true);
+
+ this.client.write(new Uint8Array(data));
+
+ this.pending_responses[cmd_id] = OVMCommand.LOC;
+
+ return this.preparePromise(cmd_id);
+ }
+
+ private parseIncoming(data: Buffer): void {
+ let parser = new DataParser(data);
+
+ while (parser.offset != data.length) {
+ let event_id = parser.parseUint32();
+
+ switch (event_id) {
+ case OVMEvent.NOP: break;
+
+ case OVMEvent.BREAKPOINT_HIT: {
+ let bp_id = parser.parseUint32();
+ let thread_id = parser.parseUint32();
+
+ this.sendEvent("breakpointHit", {
+ breakpointId: bp_id,
+ threadId: thread_id,
+ });
+ break;
+ }
+
+ case OVMEvent.PAUSED: {
+ let thread_id = parser.parseUint32();
+
+ this.sendEvent("paused", { threadId: thread_id });
+ break;
+ }
+
+ case OVMEvent.RESPONSE: {
+ this.handleResponse(parser);
+ break;
+ }
+
+ default:
+ console.log("Unknown event: ", event_id, data);
+ }
+ }
+ }
+
+ private handleResponse(parser: DataParser) {
+ let msg_id = parser.parseUint32();
+ let cmd_id = this.pending_responses[msg_id] || OVMCommand.NOP;
+
+ switch (cmd_id) {
+ case OVMCommand.NOP: break;
+ case OVMCommand.RES: {
+ let success = parser.parseBool();
+ break;
+ }
+
+ case OVMCommand.BRK: {
+ let success = parser.parseBool();
+ let bp_id = parser.parseInt32();
+ let line = parser.parseInt32();
+
+ this.resolvePromise(msg_id, {
+ verified: success,
+ id: bp_id,
+ line: line
+ });
+ break;
+ }
+
+ case OVMCommand.LOC: {
+ console.log("Recv loc");
+ let success = parser.parseBool();
+ let filename = parser.parseString();
+ let line = parser.parseInt32();
+
+ if (!success) break;
+
+ this.resolvePromise(msg_id, {filename, line});
+ break;
+ }
+
+ default:
+ console.log("Unrecognized command. ", cmd_id, msg_id);
+ }
+ }
+
+ private preparePromise<T>(msg_id: number): Promise<T> {
+ return new Promise((resolve, reject) => {
+ this._promiseResolution.set(msg_id, resolve);
+ });
+ }
+
+ private resolvePromise(msg_id: number, data: any): void {
+ if (this._promiseResolution.has(msg_id)) {
+ let func = this._promiseResolution.get(msg_id);
+ this._promiseResolution.delete(msg_id);
+ func(data);
+ }
+ }
+
+ private sendEvent(event: string, ... args: any[]): void {
+ setTimeout(() => {
+ this.emit(event, ...args);
+ }, 0);
+ }
+
+ private get next_command_id(): number {
+ let val = this._next_cmd_id;
+ this._next_cmd_id += 1;
+ return val;
+ }
+
+}
+
+
+//
+// Utility class for parsing system data types out of
+// a buffer. Currently, it assumes a little endian byte
+// order in the buffer, and that should probably be changed?
+//
+class DataParser {
+ private data: Buffer;
+ private view: DataView;
+ public offset: number;
+
+ constructor(data: Buffer) {
+ this.data = data;
+ this.view = new DataView(data.buffer);
+ console.log("PARSING", this.data);
+ this.offset = 0;
+ }
+
+ parseInt32() {
+ this.offset += 4;
+ return this.view.getInt32(this.offset - 4, true);
+ }
+
+ parseUint32() {
+ this.offset += 4;
+ return this.view.getUint32(this.offset - 4, true);
+ }
+
+ parseString() {
+ let len = this.parseUint32();
+ let str = "";
+ for (let i=0; i<len; i++) {
+ str += String.fromCharCode(this.view.getUint8(this.offset + i));
+ }
+ this.offset += len;
+
+ return str;
+ }
+
+ parseBool() {
+ this.offset += 1;
+ return this.view.getUint8(this.offset - 1) != 0;
+ }
+}
+
"vscode-textmate-languageservice": "0.2.1"
},
"devDependencies": {
- "vscode": "^1.1.37"
+ "vscode": "^1.1.37",
+ "vscode-debugadapter": "^1.51.0"
},
"engines": {
"vscode": "^1.51.0"
"node": ">=8.9.3"
}
},
+ "node_modules/vscode-debugadapter": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.51.0.tgz",
+ "integrity": "sha512-mObaXD5/FH/z6aL2GDuyCLbnwLsYRCAJWgFid01vKW9Y5Si8OvINK+Tn+Yl/lRUbetjNuZW3j1euMEz6z8Yzqg==",
+ "deprecated": "This package has been renamed to @vscode/debugadapter, please update to the new name",
+ "dev": true,
+ "dependencies": {
+ "mkdirp": "^1.0.4",
+ "vscode-debugprotocol": "1.51.0"
+ }
+ },
+ "node_modules/vscode-debugadapter/node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/vscode-debugprotocol": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.51.0.tgz",
+ "integrity": "sha512-dzKWTMMyebIMPF1VYMuuQj7gGFq7guR8AFya0mKacu+ayptJfaRuM0mdHCqiOth4FnRP8mPhEroFPx6Ift8wHA==",
+ "deprecated": "This package has been renamed to @vscode/debugprotocol, please update to the new name",
+ "dev": true
+ },
"node_modules/vscode-test": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz",
"vscode-test": "^0.4.1"
}
},
+ "vscode-debugadapter": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.51.0.tgz",
+ "integrity": "sha512-mObaXD5/FH/z6aL2GDuyCLbnwLsYRCAJWgFid01vKW9Y5Si8OvINK+Tn+Yl/lRUbetjNuZW3j1euMEz6z8Yzqg==",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^1.0.4",
+ "vscode-debugprotocol": "1.51.0"
+ },
+ "dependencies": {
+ "mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true
+ }
+ }
+ },
+ "vscode-debugprotocol": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.51.0.tgz",
+ "integrity": "sha512-dzKWTMMyebIMPF1VYMuuQj7gGFq7guR8AFya0mKacu+ayptJfaRuM0mdHCqiOth4FnRP8mPhEroFPx6Ift8wHA==",
+ "dev": true
+ },
"vscode-test": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz",
"description": "Onyx syntax highlighting.",
"version": "0.0.3",
"publisher": "brendanfh",
+ "license": "BSD-2-Clause",
"engines": {
"vscode": "^1.51.0"
},
"loop": true
}
}
+ ],
+ "breakpoints": [
+ {
+ "language": "onyx"
+ }
+ ],
+ "debuggers": [
+ {
+ "type": "onyx",
+ "languages": [
+ "onyx"
+ ],
+ "label": "Onyx Debug",
+ "program": "./out/debugAdapter.js",
+ "runtime": "node",
+ "configurationAttributes": {
+ "attach": {
+ "properties": {
+ "socketPath": {
+ "type": "string",
+ "description": "Path to UNIX socket for attaching to debugger",
+ "default": "/tmp/ovm-debug.0000"
+ },
+ "stopOnEntry": {
+ "type": "boolean",
+ "description": "Automatically stop after launch.",
+ "default": true
+ }
+ }
+ }
+ },
+ "initialConfigurations": [
+ {
+ "type": "onyx",
+ "request": "attach",
+ "stopOnEntry": true
+ }
+ ]
+ }
]
},
"files": [
"./textmate-configuration.json"
],
"dependencies": {
+ "@types/node": "^18.6.4",
+ "@vscode/debugadapter": "^1.57.0",
+ "await-notify": "^1.0.1",
"vscode-textmate-languageservice": "0.2.1"
},
"devDependencies": {
- "vscode": "^1.1.37"
+ "vscode": "^1.1.37",
+ "vscode-debugadapter": "^1.51.0"
}
}
"moduleResolution": "node",
"lib": ["es6", "es2016"],
"sourceMap": false,
- "rootDir": "."
+ "rootDir": ".",
+ "typeRoots": [
+ "node_modules/@types"
+ ]
},
"exclude": ["node_modules", ".vscode-test"]
}
\ No newline at end of file