go6502/asm/disasm.go

169 lines
4.6 KiB
Go

/*
Package asm provides routines for decompiling 6502 assembly language.
*/
package asm
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strconv"
"github.com/zellyn/go6502/asm/opcodes"
)
// Symbols are symbol tables used to convert addresses to names
type Symbols map[int]string
// name returns a symbol for an address. If lookback is 1 or greater,
// it can return strings like "FOO+1" for the addresses succeeding
// defined symbols.
func (s Symbols) name(addr int, lookback int) string {
if n, ok := s[addr]; ok {
return n
}
for i := 1; i <= lookback; i++ {
if n, ok := s[addr-i]; ok {
return fmt.Sprintf("%s+%d", n, i)
}
}
return ""
}
// bytesString takes three bytes and a length, returning the formatted
// hex bytes for an instrction of the given length.
func bytesString(byte0, byte1, byte2 byte, length int) string {
switch length {
case 1:
return fmt.Sprintf("%02X", byte0)
case 2:
return fmt.Sprintf("%02X %02X", byte0, byte1)
case 3:
return fmt.Sprintf("%02X %02X %02X", byte0, byte1, byte2)
}
panic("Length must be 1, 2, or 3")
}
func (s *Symbols) addr4(addr uint16, lookback int) string {
if n := s.name(int(addr), lookback); n != "" {
return n
}
return fmt.Sprintf("$%04X", addr)
}
func (s *Symbols) addr2(addr byte, lookback int) string {
if n := s.name(int(addr), lookback); n != "" {
return n
}
return fmt.Sprintf("$%02X", addr)
}
// addrString returns the address part of a 6502 assembly language
// instruction.
func addrString(pc uint16, byte1, byte2 byte, length int, mode opcodes.AddressingMode, s Symbols, lookback int) string {
addr16 := uint16(byte1) + uint16(byte2)<<8
addrRel := uint16(int32(pc+2) + int32(int8(byte1)))
switch mode {
case opcodes.MODE_IMPLIED, opcodes.MODE_A:
return ""
case opcodes.MODE_ABSOLUTE:
return fmt.Sprintf("%s", s.addr4(addr16, lookback))
case opcodes.MODE_INDIRECT:
return fmt.Sprintf("(%s)", s.addr4(addr16, lookback))
case opcodes.MODE_RELATIVE:
return fmt.Sprintf("%s", s.addr4(addrRel, lookback))
case opcodes.MODE_IMMEDIATE:
return fmt.Sprintf("#$%02X", byte1)
case opcodes.MODE_ABS_X:
return fmt.Sprintf("%s,X", s.addr4(addr16, lookback))
case opcodes.MODE_ABS_Y:
return fmt.Sprintf("%s,Y", s.addr4(addr16, lookback))
case opcodes.MODE_ZP:
return fmt.Sprintf("%s", s.addr2(byte1, lookback))
case opcodes.MODE_ZP_X:
return fmt.Sprintf("%s,X", s.addr2(byte1, lookback))
case opcodes.MODE_ZP_Y:
return fmt.Sprintf("%s,Y", s.addr2(byte1, lookback))
case opcodes.MODE_INDIRECT_Y:
return fmt.Sprintf("(%s),Y", s.addr2(byte1, lookback))
case opcodes.MODE_INDIRECT_X:
return fmt.Sprintf("(%s,X)", s.addr2(byte1, lookback))
}
panic(fmt.Sprintf("Unknown op mode: %d", mode))
}
// Disasm disassembles a single (up to three byte) 6502
// instruction. It returns the formatted bytes, the formatted
// instruction and address, and the length. If it cannot find the
// instruction, it returns a 1-byte "???" instruction.
func Disasm(pc uint16, byte0, byte1, byte2 byte, s Symbols, lookback int) (string, string, int) {
op, ok := opcodes.Opcodes[byte0]
if !ok {
op = opcodes.NoOp
}
length := opcodes.ModeLengths[op.Mode]
bytes := bytesString(byte0, byte1, byte2, length)
addr := addrString(pc, byte1, byte2, length, op.Mode, s, lookback)
return bytes, op.Name + " " + addr, length
}
// DisasmBlock disassembles an entire block, writing out the disassembly.
func DisasmBlock(block []byte, startAddr uint16, w io.Writer, s Symbols, lookback int, printLabels bool) {
l := len(block)
for i := 0; i < l; i++ {
byte0 := block[i]
byte1 := byte(0xFF)
byte2 := byte(0xFF)
if i+1 < l {
byte1 = block[i+1]
}
if i+2 < l {
byte2 = block[i+2]
}
addr := uint16(i) + startAddr
bytes, op, length := Disasm(addr, byte0, byte1, byte2, s, lookback)
if printLabels {
label, ok := s[int(addr)]
if ok {
fmt.Fprintf(w, "$%04X: %s:\n", addr, label)
}
}
fmt.Fprintf(w, "$%04X: %-8s %s\n", addr, bytes, op)
i += length - 1
}
}
var symRe = regexp.MustCompile(`(?i)^([0-9a-z_]+) *(?:.eq|equ|epz|=) *\$([0-9a-f]+)\b`)
func ReadSymbols(filename string) (Symbols, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(file)
s := make(Symbols)
lineNum := 0
for scanner.Scan() {
line := scanner.Text()
lineNum++
groups := symRe.FindStringSubmatch(line)
if groups == nil {
continue
}
i, err := strconv.ParseInt(groups[2], 16, 32)
if err != nil {
return nil, fmt.Errorf("%d: %s: %s", lineNum, line, err)
}
if i > 0xffff {
return nil, fmt.Errorf("%d: %s: Value %d out of range", lineNum, line, i)
}
s[int(i)] = groups[1]
}
if err := scanner.Err(); err != nil {
return nil, err
}
return s, nil
}