UNPKG

uae-dap

Version:

Debug Adapter Protocol for Amiga development with FS-UAE or WinUAE

346 lines 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GdbError = exports.GdbClient = exports.DEFAULT_FRAME_INDEX = exports.BreakpointCode = exports.HaltSignal = void 0; const net_1 = require("net"); const events_1 = require("events"); const debugadapter_1 = require("@vscode/debugadapter"); const async_mutex_1 = require("async-mutex"); const strings_1 = require("./utils/strings"); var HaltSignal; (function (HaltSignal) { HaltSignal[HaltSignal["INT"] = 2] = "INT"; HaltSignal[HaltSignal["ILL"] = 4] = "ILL"; HaltSignal[HaltSignal["TRAP"] = 5] = "TRAP"; HaltSignal[HaltSignal["EMT"] = 7] = "EMT"; HaltSignal[HaltSignal["FPE"] = 8] = "FPE"; HaltSignal[HaltSignal["BUS"] = 10] = "BUS"; HaltSignal[HaltSignal["SEGV"] = 11] = "SEGV"; })(HaltSignal = exports.HaltSignal || (exports.HaltSignal = {})); var BreakpointCode; (function (BreakpointCode) { BreakpointCode[BreakpointCode["SOFTWARE"] = 0] = "SOFTWARE"; BreakpointCode[BreakpointCode["HARDWARE"] = 1] = "HARDWARE"; BreakpointCode[BreakpointCode["WRITE"] = 2] = "WRITE"; BreakpointCode[BreakpointCode["READ"] = 3] = "READ"; BreakpointCode[BreakpointCode["ACCESS"] = 4] = "ACCESS"; })(BreakpointCode = exports.BreakpointCode || (exports.BreakpointCode = {})); exports.DEFAULT_FRAME_INDEX = -1; const TIMEOUT = 60000; const signalLabels = { [HaltSignal.INT]: "Interrupt", [HaltSignal.ILL]: "Illegal instruction", [HaltSignal.TRAP]: "Trace/breakpoint trap", [HaltSignal.EMT]: "Emulation trap", [HaltSignal.FPE]: "Arithmetic exception", [HaltSignal.BUS]: "Bus error", [HaltSignal.SEGV]: "Segmentation fault", }; const resp = { OK: /^OK$/, HEX: /^[0-9a-f]/, STOP: /^[ST]/, ANY: /./, }; class GdbClient { constructor(socket) { this.requestMutex = new async_mutex_1.Mutex(); this.frameMutex = new async_mutex_1.Mutex(); this.eventEmitter = new events_1.EventEmitter(); this.socket = socket || new net_1.Socket(); this.socket.on("data", this.handleData.bind(this)); } async connect(host, port) { return new Promise((resolve, reject) => { const cb = async () => { try { debugadapter_1.logger.log("Connected: initializing"); await this.request("QStartNoAckMode"); resolve(); } catch (error) { this.socket.destroy(); this.socket = new net_1.Socket(); reject(error); } }; this.socket.once("error", () => { this.socket.off("ready", cb); reject(); }); this.socket.once("ready", cb); this.socket.connect(port, host); }); } destroy() { this.socket.destroy(); } async getOffsets() { const res = await this.request("qOffsets", resp.HEX); return res.split(";").map((a) => parseInt(a, 16)); } // Breakpoints: async setBreakpoint(address, type = BreakpointCode.SOFTWARE, size) { let message = `Z${type},${hex(address)}`; if (size !== undefined) { message += `,${hex(size)}`; } await this.request(message); } async setExceptionBreakpoint(exceptionMask) { const expMskHex = hex(exceptionMask); const expMskHexSz = hex(expMskHex.length); await this.request("Z1,0,0;X" + expMskHexSz + "," + expMskHex); } async removeBreakpoint(address, type = BreakpointCode.SOFTWARE, size) { let message = `z${type},${hex(address)}`; if (size !== undefined) { message += `,${hex(size)}`; } await this.request(message); } // Navigation: async pause(threadId) { await this.request("vCont;t:" + threadId, resp.STOP); } async continueExecution(threadId) { await this.requestNoRes("vCont;c:" + threadId); } async stepIn(threadId) { await this.request("vCont;s:" + threadId, resp.STOP); } async stepToRange(threadId, startAddress, endAddress) { await this.request("vCont;r" + hex(startAddress) + "," + hex(endAddress) + ":" + threadId, resp.STOP); } async getHaltStatus() { if (this.haltStatus) { return this.haltStatus; } const response = await this.request("?", /^(OK|S|T)/); return response.indexOf("OK") < 0 ? this.parseHaltStatus(response) : null; } // Memory: async readMemory(address, length) { return this.request("m" + hex(address) + "," + hex(length), resp.HEX); } async writeMemory(address, dataToSend) { const size = Math.ceil(dataToSend.length / 2); await this.request("M" + hex(address) + "," + size + ":" + dataToSend); } // Registers: async getRegisters(threadId) { const message = await this.request(threadId ? "Hg" + threadId : "g", resp.HEX); const registers = []; const regCount = Math.floor(message.length / 8); for (let i = 0; i < regCount; i++) { const value = parseInt(message.substring(i * 8, (i + 1) * 8), 16); registers.push(value); } return registers; } async getRegister(regIdx) { const data = await this.request("p" + hex(regIdx), resp.HEX); return parseInt(data, 16); } async setRegister(regIdx, value) { await this.request("P" + regIdx.toString(16) + "=" + value.toString(16)); } // Stack Frames: async getFramesCount() { const data = await this.request("qTStatus", /^T/); const frameCountPosition = data.indexOf("tframes"); if (frameCountPosition > 0) { let endFrameCountPosition = data.indexOf(";", frameCountPosition); if (endFrameCountPosition <= 0) { endFrameCountPosition = data.length; } const v = data.substring(frameCountPosition + 8, endFrameCountPosition); return parseInt(v, 16); } return 1; } async selectFrame(frameIndex) { if (frameIndex < 0) { await this.request("QTFrame:ffffffff"); return exports.DEFAULT_FRAME_INDEX; } const data = await this.request("QTFrame:" + hex(frameIndex), /^F/); if (data === "F-1") { // No frame found return exports.DEFAULT_FRAME_INDEX; } let v = data.substring(1); const tPos = v.indexOf("T"); if (tPos >= 0) { v = v.substring(0, tPos); } return parseInt(v, 16); } async withFrame(requestedFrame, cb) { return this.frameMutex.runExclusive(async () => { if (requestedFrame === undefined) { requestedFrame = exports.DEFAULT_FRAME_INDEX; } const returnedFrame = await this.selectFrame(requestedFrame); return cb(returnedFrame); }); } // Commands: async monitor(command) { const response = await this.request("qRcmd," + stringToHex(command), resp.ANY); return response; } // Events: on(event, listener) { this.eventEmitter.on(event, listener); return this; } once(event, listener) { this.eventEmitter.once(event, listener); return this; } off(event, listener) { this.eventEmitter.off(event, listener); return this; } sendEvent(event, ...args) { setImmediate(() => { this.eventEmitter.emit(event, ...args); }); } // Socket IO: async request(text, expectedResponse = resp.OK) { return this.requestMutex.runExclusive(async () => { const req = `$${text}#${calculateChecksum(text)}`; debugadapter_1.logger.log(`[GDB] --> ${req}`); this.socket.write(req); return await new Promise((resolve, reject) => { const timeout = setTimeout(() => { debugadapter_1.logger.log(`[GDB] TIMEOUT: ${req}`); delete this.responseCallback; reject(new Error("Request timeout")); }, TIMEOUT); this.responseCallback = (message) => { debugadapter_1.logger.log(`[GDB] <-- ${message}`); if (message.startsWith("E")) { this.responseCallback = undefined; clearTimeout(timeout); reject(new GdbError(message)); } else if (message.match(expectedResponse)) { this.responseCallback = undefined; clearTimeout(timeout); resolve(message); } else { debugadapter_1.logger.log(`[GDB] ignored`); } }; }); }); } async requestNoRes(text) { return this.requestMutex.runExclusive(async () => { const req = `$${text}#${calculateChecksum(text)}`; debugadapter_1.logger.log(`[GDB] --> ${req}`); this.socket.write(req); }); } handleData(data) { const messages = [...data.toString().matchAll(/\$([^#]*)#[\da-f]{2}/g)].map((m) => m[1]); for (const message of messages) { if (this.responseCallback) { this.responseCallback(message); } else { switch (message[0]) { case "S": case "T": debugadapter_1.logger.log(`[GDB] STOP: ${message}`); this.haltStatus = this.parseHaltStatus(message); this.sendEvent("stop", this.haltStatus); break; case "W": debugadapter_1.logger.log(`[GDB] END`); this.sendEvent("end"); break; case "O": debugadapter_1.logger.log(`[GDB] OUTPUT: ${message}`); this.sendEvent("output", (0, strings_1.hexStringToASCII)(message.substring(1), 2)); break; default: debugadapter_1.logger.log(`[GDB] UNKNOWN: ${message}`); } } } } parseHaltStatus(message) { // Special case to treat S05 as exception: // Emulator currently returns this code for non-specified exceptions, but normally 05 is treated as a breakpoint. // We can differentiate because actual breakpoints use T05swbreak. if (message === "S05") { return { signal: HaltSignal.SEGV, label: "Exception", }; } const code = parseInt(message.substring(1, 3), 16); const details = signalLabels[code] || "Exception"; const threadMatch = message.match(/thread:(\d+)/); const threadId = threadMatch ? parseInt(threadMatch[1]) : undefined; return { signal: code, label: details, threadId }; } } exports.GdbClient = GdbClient; // Errors: const errorMessages = { E01: "General error during processing", E02: "Error during the packet parse", E03: "Unsupported / unknown command", E04: "Unknown register", E05: "Invalid Frame Id", E06: "Invalid memory location", E07: "Address not safe for a set memory command", E08: "Unknown breakpoint", E09: "The maximum of breakpoints have been reached", E0F: "Error during the packet parse for command send memory", E10: "Unknown register", E11: "Invalid Frame Id", E12: "Invalid memory location", E20: "Error during the packet parse for command set memory", E21: "Missing end packet for a set memory message", E22: "Address not safe for a set memory command", E25: "Error during the packet parse for command set register", E26: "Error during set register - unsupported register name", E30: "Error during the packet parse for command get register", E31: "Error during the vCont packet parse", E40: "Unable to load segments", E41: "Thread command parse error", }; class GdbError extends Error { constructor(errorType) { super(); this.errorType = errorType.toUpperCase(); this.name = "GdbError"; const msg = errorMessages[this.errorType]; this.message = msg || "Error code received: '" + this.errorType + "'"; } } exports.GdbError = GdbError; // Utils: function calculateChecksum(text) { let cs = 0; const buffer = Buffer.alloc(text.length, text); for (let i = 0; i < buffer.length; ++i) { cs += buffer[i]; } return (cs % 256).toString(16).padStart(2, "0"); } const hex = (n) => n.toString(16); function stringToHex(str) { let result = ""; for (let i = 0; i < str.length; ++i) { result += str.charCodeAt(i).toString(16).padStart(2, "0"); } return result; } //# sourceMappingURL=gdbClient.js.map