uae-dap
Version:
Debug Adapter Protocol for Amiga development with FS-UAE or WinUAE
338 lines • 14.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DisassemblyManager = void 0;
const debugadapter_1 = require("@vscode/debugadapter");
const cpuDisassembler_1 = require("./cpuDisassembler");
const copperDisassembler_1 = require("./copperDisassembler");
const strings_1 = require("../utils/strings");
const disassembledFile_1 = require("./disassembledFile");
const path_1 = require("path");
const hardware_1 = require("../hardware");
class DisassemblyManager {
constructor(gdb, variables, sourceMap) {
this.gdb = gdb;
this.variables = variables;
this.sourceMap = sourceMap;
this.sourceHandles = new debugadapter_1.Handles();
this.lineCache = new Map();
}
/**
* Disassemble memory to CPU or Copper instructions
*/
async disassemble(args) {
let { memoryReference } = args;
let firstAddress;
const hasOffset = args.offset || args.instructionOffset;
if (memoryReference && hasOffset) {
// Apply offset to address
firstAddress = parseInt(args.memoryReference);
if (args.offset) {
firstAddress -= args.offset;
}
try {
// Set memoryReference to segment address if found
const location = this.sourceMap.lookupAddress(firstAddress);
const segment = this.sourceMap.getSegmentInfo(location.segmentIndex);
memoryReference = segment.address.toString();
}
catch (_) {
// Not found
}
}
if (args.segmentId === undefined &&
!memoryReference &&
!args.instructionCount) {
throw new Error(`Unable to disassemble; invalid parameters ${args}`);
}
// Check whether memoryReference points to previously disassembled copper lines if not specified.
const isCopper = args.copper ?? this.isCopperLine(parseInt(args.memoryReference));
let instructions = args.segmentId !== undefined
? await this.disassembleSegment(args.segmentId)
: await this.disassembleAddressExpression(memoryReference, args.instructionCount * 4, args.offset ?? 0, isCopper);
// Add source line data to instructions
for (const instruction of instructions) {
try {
const line = this.sourceMap.lookupAddress(parseInt(instruction.address));
const filename = line.path;
instruction.location = new debugadapter_1.Source((0, path_1.basename)(filename), filename);
instruction.line = line.line;
}
catch (_) {
// Not found
}
}
// Nothing left to do?
if (!firstAddress || !args.instructionOffset) {
return instructions;
}
// Find index of instruction matching first address
const instructionIndex = instructions.findIndex(({ address }) => parseInt(address) === firstAddress);
if (instructionIndex === -1) {
// Not found
return instructions;
}
// Apply instruction offset
const offsetIndex = instructionIndex + args.instructionOffset;
// Negative offset:
if (offsetIndex < 0) {
// Pad instructions array with dummy entries
const emptyArray = new Array(-offsetIndex);
const firstInstructionAddress = parseInt(instructions[0].address);
let currentAddress = firstInstructionAddress - 4;
for (let i = emptyArray.length - 1; i >= 0; i--) {
emptyArray[i] = {
address: (0, strings_1.formatHexadecimal)(currentAddress),
instruction: "-------",
};
currentAddress -= 4;
if (currentAddress < 0) {
currentAddress = 0;
}
}
instructions = emptyArray.concat(instructions);
}
// Positive offset within range:
if (offsetIndex > 0 && offsetIndex < instructions.length) {
// Splice up to start??
// TODO: check this
instructions = instructions.splice(0, offsetIndex);
}
// Ensure instructions length matches requested count:
if (instructions.length < args.instructionCount) {
// Too few instructions:
// Get address of last instruction
const lastInstruction = instructions[instructions.length - 1];
let lastAddress = parseInt(lastInstruction.address);
if (lastInstruction.instructionBytes) {
lastAddress += lastInstruction.instructionBytes.split(" ").length;
}
// Pad instructions array with dummy instructions at correct addresses
const padLength = args.instructionCount - instructions.length;
for (let i = 0; i < padLength; i++) {
instructions.push({
address: (0, strings_1.formatHexadecimal)(lastAddress + i * 4),
instruction: "-------",
});
}
}
else if (instructions.length > args.instructionCount) {
// Too many instructions - truncate
instructions = instructions.splice(0, args.instructionCount);
}
return instructions;
}
/**
* Get disassembled file contents by source reference
*/
async getDisassembledFileContentsByRef(ref) {
const dAsmFile = this.getSourceByReference(ref);
if (dAsmFile) {
return this.getDisassembledFileContents(dAsmFile);
}
}
/**
* Get disassembled content for a .dgasm file path
*
* The filename contains tokens for the disassemble options
*/
async getDisassembledFileContentsByPath(path) {
const dAsmFile = (0, disassembledFile_1.disassembledFileFromPath)(path);
return this.getDisassembledFileContents(dAsmFile);
}
/**
* Get text content for a disassembled source file
*/
async getDisassembledFileContents(dAsmFile) {
const instructions = await this.disassemble({
memoryReference: "",
instructionCount: 100,
...dAsmFile,
});
return instructions.map((v) => `${v.address}: ${v.instruction}`).join("\n");
}
async disassembleLine(pc, threadId) {
const cached = this.lineCache.get(pc);
if (cached) {
return cached;
}
let text = (0, strings_1.formatAddress)(pc) + ": ";
const isCopper = threadId === hardware_1.Threads.COPPER;
try {
const memory = await this.gdb.readMemory(pc, 10);
if (isCopper) {
// Copper thread
const lines = (0, copperDisassembler_1.disassembleCopper)(memory);
text += lines[0].toString().split(" ")[0];
}
else {
// CPU thread
const { code } = await (0, cpuDisassembler_1.disassemble)(memory);
const lines = (0, strings_1.splitLines)(code);
let selectedLine = lines.find((l) => l.trim().length) ?? lines[0];
const elms = selectedLine.split(" ");
if (elms.length > 2) {
selectedLine = elms[2];
}
text += selectedLine.trim().replace(/\s\s+/g, " ");
}
this.lineCache.set(pc, { text, isCopper });
}
catch (err) {
console.error("Error ignored: " + err.message);
}
return { text, isCopper };
}
isCopperLine(pc) {
const cached = this.lineCache.get(pc);
return cached?.isCopper === true;
}
async getStackFrame(stackPosition, threadId) {
const address = stackPosition.pc;
const { text, isCopper } = await this.disassembleLine(address, threadId);
const dAsmFile = {
copper: isCopper,
stackFrameIndex: stackPosition.index,
instructionCount: 500,
};
let label = text.replace(/\s+/g, " ");
let line = 1;
let segmentIndex = -1;
let segmentOffset = 0;
try {
const location = this.sourceMap.lookupAddress(address);
segmentIndex = location.segmentIndex;
segmentOffset = location.segmentOffset;
}
catch (_) {
// Not found
}
// is the pc on a opened segment ?
if (segmentIndex >= 0 && !isCopper) {
dAsmFile.segmentId = segmentIndex;
line = await this.getLineNumberInDisassembledSegment(segmentIndex, segmentOffset);
}
else {
dAsmFile.memoryReference = "$" + address.toString(16);
if (isCopper) {
// Search for selected copper list
const cop1Addr = await this.getCopperAddress(1);
const cop2Addr = await this.getCopperAddress(2);
const lineInCop1 = cop1Addr
? Math.floor((address - cop1Addr + 4) / 4)
: -1;
const lineInCop2 = cop2Addr
? Math.floor((address - cop2Addr + 4) / 4)
: -1;
if (lineInCop1 >= 0 &&
(lineInCop2 === -1 || lineInCop1 <= lineInCop2)) {
dAsmFile.memoryReference = "1";
line = lineInCop1;
label = "cop1";
}
else if (lineInCop2 >= 0) {
dAsmFile.memoryReference = "2";
line = lineInCop2;
label = "cop2";
}
dAsmFile.instructionCount = line + 499;
}
}
const sf = new debugadapter_1.StackFrame(stackPosition.index, label);
sf.instructionPointerReference = (0, strings_1.formatHexadecimal)(address);
const filename = (0, disassembledFile_1.disassembledFileToPath)(dAsmFile);
sf.source = new debugadapter_1.Source(filename, filename);
sf.source.sourceReference = this.sourceHandles.create(dAsmFile);
sf.line = line;
return sf;
}
getSourceByReference(ref) {
return this.sourceHandles.get(ref);
}
async disassembleSegment(segmentId) {
// ask for memory dump
const { address, size } = this.sourceMap.getSegmentInfo(segmentId);
const memory = await this.gdb.readMemory(address, size);
// disassemble the code
const { instructions } = await (0, cpuDisassembler_1.disassemble)(memory, address);
return instructions;
}
async disassembleAddressExpression(addressExpression, length, offset, isCopper) {
let address = await this.evaluateAddress(addressExpression, isCopper);
if (address === undefined) {
throw new Error("Unable to resolve address expression void returned");
}
if (offset) {
address += offset;
}
return this.disassembleAddress(address, length, isCopper);
}
async disassembleAddress(address, length, isCopper) {
const memory = await this.gdb.readMemory(address, length);
if (isCopper) {
return (0, copperDisassembler_1.disassembleCopper)(memory).map((inst, i) => ({
instructionBytes: inst.getInstructionBytes(),
address: (0, strings_1.formatHexadecimal)(address + i * 4),
instruction: inst.toString(),
}));
}
else {
// disassemble the code
const { instructions } = await (0, cpuDisassembler_1.disassemble)(memory, address);
return instructions;
}
}
async getAddressForFileEditorLine(filePath, lineNumber) {
let instructions = null;
if (lineNumber > 0) {
const dAsmFile = (0, disassembledFile_1.disassembledFileFromPath)(filePath);
if (dAsmFile.segmentId !== undefined) {
instructions = await this.disassembleSegment(dAsmFile.segmentId);
}
else {
// Path from outside segments
if (dAsmFile.memoryReference && dAsmFile.instructionCount) {
const address = await this.evaluateAddress(dAsmFile.memoryReference, dAsmFile.copper);
instructions = await this.disassembleAddress(address, dAsmFile.instructionCount, dAsmFile.copper);
}
}
if (instructions) {
const searchedLN = lineNumber - 1;
if (searchedLN < instructions.length) {
return parseInt(instructions[searchedLN].address, 16);
}
else {
throw new Error(`Searched line ${searchedLN} greater than file "${filePath}" length: ${instructions.length}`);
}
}
else {
throw new Error(`Searched line ${lineNumber} has no instructions`);
}
}
else {
throw new Error(`Invalid line number: '${lineNumber}'`);
}
}
async getLineNumberInDisassembledSegment(segmentId, offset) {
const { address, size } = this.sourceMap.getSegmentInfo(segmentId);
const memory = await this.gdb.readMemory(address, size);
const { instructions } = await (0, cpuDisassembler_1.disassemble)(memory);
const index = instructions.findIndex((instr) => parseInt(instr.address) === offset);
return index + 1;
}
evaluateAddress(addressExpression, isCopper) {
if (isCopper && (addressExpression === "1" || addressExpression === "2")) {
// Retrieve the copper address
return this.getCopperAddress(parseInt(addressExpression));
}
else {
return this.variables.evaluate(addressExpression);
}
}
async getCopperAddress(copperIndex) {
const copperHigh = copperIndex === 1 ? 0xdff080 : 0xdff084;
const memory = await this.gdb.readMemory(copperHigh, 4);
return parseInt(memory, 16);
}
}
exports.DisassemblyManager = DisassemblyManager;
//# sourceMappingURL=disassemblyManager.js.map