disassemble
Convert 6502 machine code from a Commodore 64 memory address into readable assembly instructions with labels and branch targets for debugging.
Instructions
Disassemble 6502 machine code at a memory address.
Returns human-readable assembly instructions with:
Address and raw bytes
Mnemonic and operand
Branch target addresses (for branch instructions)
Known KERNAL/BASIC labels
Options:
address: Start address (default: current PC)
count: Number of instructions (default: 10)
Related tools: readMemory, getRegisters, step
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| address | No | Start address (default: current PC) | |
| count | No | Number of instructions to disassemble (default: 10) |
Implementation Reference
- src/index.ts:874-940 (handler)The main handler function for the 'disassemble' tool. It optionally gets current PC, reads memory from VICE, calls the disassemble helper, adds labels, formats the assembly listing, and returns structured response.async (args) => { try { // Get PC if no address specified let startAddress = args.address; if (startAddress === undefined) { const regResponse = await client.getRegisters(); const count = regResponse.body.readUInt16LE(0); let offset = 2; for (let i = 0; i < count && offset < regResponse.body.length; i++) { const id = regResponse.body[offset]; const size = regResponse.body[offset + 1]; offset += 2; if (id === 3 && size === 2) { startAddress = regResponse.body.readUInt16LE(offset); break; } offset += size; } startAddress = startAddress ?? 0; } const instructionCount = args.count || 10; // Read enough bytes (max 3 bytes per instruction) const bytesToRead = Math.min(instructionCount * 3, 0x10000 - startAddress); const endAddress = Math.min(startAddress + bytesToRead - 1, 0xffff); const memData = await client.readMemory(startAddress, endAddress); // Disassemble const instructions = disassemble(memData, startAddress, instructionCount); // Add labels for known addresses const instructionsWithLabels = instructions.map((instr) => { const label = getLabelForAddress(instr.address); const targetLabel = instr.branchTarget ? getLabelForAddress(instr.branchTarget) : undefined; return { ...instr, label, targetLabel, }; }); // Format for output const lines = instructionsWithLabels.map((instr) => { let line = `${instr.addressHex}: ${instr.bytesHex.padEnd(8)} ${instr.fullInstruction}`; if (instr.label) line += ` ; ${instr.label}`; if (instr.targetLabel) line += ` ; -> ${instr.targetLabel}`; return line; }); return formatResponse({ startAddress: { value: startAddress, hex: `$${startAddress.toString(16).padStart(4, "0")}`, }, instructionCount: instructions.length, instructions: instructionsWithLabels, listing: lines.join("\n"), hint: instructions.length > 0 && instructions[0].mnemonic === "BRK" ? "First instruction is BRK - this might be uninitialized memory or data" : `Disassembled ${instructions.length} instruction(s) from $${startAddress.toString(16).padStart(4, "0")}`, }); } catch (error) { return formatError(error as ViceError); } }
- src/index.ts:864-873 (schema)Zod input schema defining optional 'address' (0-FFFF, defaults to PC) and 'count' (1-100, default 10) parameters for disassembly.inputSchema: z.object({ address: z .number() .min(0) .max(0xffff) .optional() .describe("Start address (default: current PC)"), count: z.number().min(1).max(100).optional().describe("Number of instructions to disassemble (default: 10)"), }), },
- src/index.ts:848-941 (registration)MCP tool registration call for 'disassemble', specifying name, description, input schema, and handler function.server.registerTool( "disassemble", { description: `Disassemble 6502 machine code at a memory address. Returns human-readable assembly instructions with: - Address and raw bytes - Mnemonic and operand - Branch target addresses (for branch instructions) - Known KERNAL/BASIC labels Options: - address: Start address (default: current PC) - count: Number of instructions (default: 10) Related tools: readMemory, getRegisters, step`, inputSchema: z.object({ address: z .number() .min(0) .max(0xffff) .optional() .describe("Start address (default: current PC)"), count: z.number().min(1).max(100).optional().describe("Number of instructions to disassemble (default: 10)"), }), }, async (args) => { try { // Get PC if no address specified let startAddress = args.address; if (startAddress === undefined) { const regResponse = await client.getRegisters(); const count = regResponse.body.readUInt16LE(0); let offset = 2; for (let i = 0; i < count && offset < regResponse.body.length; i++) { const id = regResponse.body[offset]; const size = regResponse.body[offset + 1]; offset += 2; if (id === 3 && size === 2) { startAddress = regResponse.body.readUInt16LE(offset); break; } offset += size; } startAddress = startAddress ?? 0; } const instructionCount = args.count || 10; // Read enough bytes (max 3 bytes per instruction) const bytesToRead = Math.min(instructionCount * 3, 0x10000 - startAddress); const endAddress = Math.min(startAddress + bytesToRead - 1, 0xffff); const memData = await client.readMemory(startAddress, endAddress); // Disassemble const instructions = disassemble(memData, startAddress, instructionCount); // Add labels for known addresses const instructionsWithLabels = instructions.map((instr) => { const label = getLabelForAddress(instr.address); const targetLabel = instr.branchTarget ? getLabelForAddress(instr.branchTarget) : undefined; return { ...instr, label, targetLabel, }; }); // Format for output const lines = instructionsWithLabels.map((instr) => { let line = `${instr.addressHex}: ${instr.bytesHex.padEnd(8)} ${instr.fullInstruction}`; if (instr.label) line += ` ; ${instr.label}`; if (instr.targetLabel) line += ` ; -> ${instr.targetLabel}`; return line; }); return formatResponse({ startAddress: { value: startAddress, hex: `$${startAddress.toString(16).padStart(4, "0")}`, }, instructionCount: instructions.length, instructions: instructionsWithLabels, listing: lines.join("\n"), hint: instructions.length > 0 && instructions[0].mnemonic === "BRK" ? "First instruction is BRK - this might be uninitialized memory or data" : `Disassembled ${instructions.length} instruction(s) from $${startAddress.toString(16).padStart(4, "0")}`, }); } catch (error) { return formatError(error as ViceError); } } );
- src/utils/disasm.ts:255-373 (helper)Core disassembly logic: parses 6502 opcodes from memory buffer starting at given address, handles all addressing modes, computes branch targets, formats assembly syntax, returns array of structured instructions.export function disassemble( data: Buffer | number[], startAddress: number, count?: number ): DisassembledInstruction[] { const bytes = Buffer.isBuffer(data) ? data : Buffer.from(data); const result: DisassembledInstruction[] = []; let offset = 0; let instructionCount = 0; while (offset < bytes.length) { if (count !== undefined && instructionCount >= count) break; const address = startAddress + offset; const opcode = bytes[offset]; const entry = OPCODES[opcode]; if (!entry) { // Unknown opcode - treat as single byte data result.push({ address, addressHex: `$${address.toString(16).padStart(4, "0")}`, bytes: [opcode], bytesHex: opcode.toString(16).padStart(2, "0"), mnemonic: "???", operand: `$${opcode.toString(16).padStart(2, "0")}`, fullInstruction: `??? $${opcode.toString(16).padStart(2, "0")}`, size: 1, }); offset++; instructionCount++; continue; } const [mnemonic, mode, size] = entry; // Check if we have enough bytes if (offset + size > bytes.length) break; const instrBytes: number[] = []; for (let i = 0; i < size; i++) { instrBytes.push(bytes[offset + i]); } const bytesHex = instrBytes.map((b) => b.toString(16).padStart(2, "0")).join(" "); let operand = ""; let branchTarget: number | undefined; let branchTargetHex: string | undefined; switch (mode) { case "impl": operand = ""; break; case "acc": operand = "A"; break; case "imm": operand = `#$${bytes[offset + 1].toString(16).padStart(2, "0")}`; break; case "zp": operand = `$${bytes[offset + 1].toString(16).padStart(2, "0")}`; break; case "zpx": operand = `$${bytes[offset + 1].toString(16).padStart(2, "0")},X`; break; case "zpy": operand = `$${bytes[offset + 1].toString(16).padStart(2, "0")},Y`; break; case "abs": operand = `$${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")}`; break; case "abx": operand = `$${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")},X`; break; case "aby": operand = `$${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")},Y`; break; case "ind": operand = `($${(bytes[offset + 1] | (bytes[offset + 2] << 8)).toString(16).padStart(4, "0")})`; break; case "izx": operand = `($${bytes[offset + 1].toString(16).padStart(2, "0")},X)`; break; case "izy": operand = `($${bytes[offset + 1].toString(16).padStart(2, "0")}),Y`; break; case "rel": { // Relative branch - calculate target address const displacement = bytes[offset + 1]; // Convert to signed const signed = displacement > 127 ? displacement - 256 : displacement; branchTarget = (address + 2 + signed) & 0xffff; branchTargetHex = `$${branchTarget.toString(16).padStart(4, "0")}`; operand = branchTargetHex; break; } } const fullInstruction = operand ? `${mnemonic} ${operand}` : mnemonic; result.push({ address, addressHex: `$${address.toString(16).padStart(4, "0")}`, bytes: instrBytes, bytesHex, mnemonic, operand, fullInstruction, size, branchTarget, branchTargetHex, }); offset += size; instructionCount++; } return result; }
- src/utils/disasm.ts:241-253 (schema)TypeScript interface defining the output structure for each disassembled instruction, used by the disassemble function and handler.export interface DisassembledInstruction { address: number; addressHex: string; bytes: number[]; bytesHex: string; mnemonic: string; operand: string; fullInstruction: string; size: number; // For branches: the target address branchTarget?: number; branchTargetHex?: string; }