2014-08-06 05:18:03 +00:00
|
|
|
/*
|
2013-02-23 22:25:46 +00:00
|
|
|
Package asm provides routines for decompiling 6502 assembly language.
|
2013-02-23 22:11:02 +00:00
|
|
|
*/
|
|
|
|
package asm
|
|
|
|
|
|
|
|
import (
|
2014-08-06 05:18:03 +00:00
|
|
|
"bufio"
|
2013-02-23 22:11:02 +00:00
|
|
|
"fmt"
|
2013-04-21 23:49:28 +00:00
|
|
|
"io"
|
2014-08-06 05:18:03 +00:00
|
|
|
"os"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
2013-02-28 17:01:31 +00:00
|
|
|
|
|
|
|
"github.com/zellyn/go6502/opcodes"
|
2013-02-23 22:11:02 +00:00
|
|
|
)
|
|
|
|
|
2014-08-06 05:18:03 +00:00
|
|
|
// 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 ""
|
|
|
|
}
|
|
|
|
|
2013-02-23 22:11:02 +00:00
|
|
|
// 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:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%02X", byte0)
|
2013-02-23 22:11:02 +00:00
|
|
|
case 2:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%02X %02X", byte0, byte1)
|
2013-02-23 22:11:02 +00:00
|
|
|
case 3:
|
|
|
|
return fmt.Sprintf("%02X %02X %02X", byte0, byte1, byte2)
|
|
|
|
}
|
|
|
|
panic("Length must be 1, 2, or 3")
|
|
|
|
}
|
|
|
|
|
2014-08-06 05:18:03 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2013-02-23 22:11:02 +00:00
|
|
|
// addrString returns the address part of a 6502 assembly language
|
|
|
|
// instruction.
|
2014-08-06 05:18:03 +00:00
|
|
|
func addrString(pc uint16, byte1, byte2 byte, length int, mode opcodes.AddressingMode, s Symbols, lookback int) string {
|
2013-02-23 22:11:02 +00:00
|
|
|
addr16 := uint16(byte1) + uint16(byte2)<<8
|
|
|
|
addrRel := uint16(int32(pc+2) + int32(int8(byte1)))
|
|
|
|
switch mode {
|
2014-08-06 05:18:03 +00:00
|
|
|
case opcodes.MODE_IMPLIED, opcodes.MODE_A:
|
|
|
|
return ""
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_ABSOLUTE:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%s", s.addr4(addr16, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_INDIRECT:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("(%s)", s.addr4(addr16, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_RELATIVE:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%s", s.addr4(addrRel, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_IMMEDIATE:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("#$%02X", byte1)
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_ABS_X:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%s,X", s.addr4(addr16, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_ABS_Y:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%s,Y", s.addr4(addr16, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_ZP:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%s", s.addr2(byte1, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_ZP_X:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%s,X", s.addr2(byte1, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_ZP_Y:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("%s,Y", s.addr2(byte1, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_INDIRECT_Y:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("(%s),Y", s.addr2(byte1, lookback))
|
2013-02-28 17:01:31 +00:00
|
|
|
case opcodes.MODE_INDIRECT_X:
|
2014-08-06 05:18:03 +00:00
|
|
|
return fmt.Sprintf("(%s,X)", s.addr2(byte1, lookback))
|
2013-02-23 22:11:02 +00:00
|
|
|
}
|
|
|
|
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.
|
2014-08-06 05:18:03 +00:00
|
|
|
func Disasm(pc uint16, byte0, byte1, byte2 byte, s Symbols, lookback int) (string, string, int) {
|
2013-02-28 17:01:31 +00:00
|
|
|
op, ok := opcodes.Opcodes[byte0]
|
2013-02-23 22:11:02 +00:00
|
|
|
if !ok {
|
2013-02-28 17:01:31 +00:00
|
|
|
op = opcodes.NoOp
|
2013-02-23 22:11:02 +00:00
|
|
|
}
|
2013-02-28 17:01:31 +00:00
|
|
|
length := opcodes.ModeLengths[op.Mode]
|
2013-02-23 22:11:02 +00:00
|
|
|
bytes := bytesString(byte0, byte1, byte2, length)
|
2014-08-06 05:18:03 +00:00
|
|
|
addr := addrString(pc, byte1, byte2, length, op.Mode, s, lookback)
|
2013-02-23 22:11:02 +00:00
|
|
|
return bytes, op.Name + " " + addr, length
|
|
|
|
}
|
2013-04-21 23:49:28 +00:00
|
|
|
|
|
|
|
// DisasmBlock disassembles an entire block, writing out the disassembly.
|
2014-08-06 05:18:03 +00:00
|
|
|
func DisasmBlock(block []byte, startAddr uint16, w io.Writer, s Symbols, lookback int, printLabels bool) {
|
2013-04-21 23:49:28 +00:00
|
|
|
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
|
2014-08-06 05:18:03 +00:00
|
|
|
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)
|
2013-04-21 23:49:28 +00:00
|
|
|
i += length - 1
|
|
|
|
}
|
|
|
|
}
|
2014-08-06 05:18:03 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|