UNPKG

uae-dap

Version:

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

338 lines 14.3 kB
"use strict"; 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