uae-dap
Version:
Debug Adapter Protocol for Amiga development with FS-UAE or WinUAE
346 lines • 13.2 kB
JavaScript
;
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