working on disasm

This commit is contained in:
Zellyn Hunter 2014-08-05 22:18:03 -07:00
parent a9f4d9b8ff
commit d504143800
3 changed files with 123 additions and 25 deletions

View File

@ -1,61 +1,95 @@
/*
/*
Package asm provides routines for decompiling 6502 assembly language.
*/
package asm
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strconv"
"github.com/zellyn/go6502/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)
return fmt.Sprintf("%02X", byte0)
case 2:
return fmt.Sprintf("%02X %02X ", byte0, byte1)
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) string {
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:
return " "
case opcodes.MODE_IMPLIED, opcodes.MODE_A:
return ""
case opcodes.MODE_ABSOLUTE:
return fmt.Sprintf("$%04X ", addr16)
return fmt.Sprintf("%s", s.addr4(addr16, lookback))
case opcodes.MODE_INDIRECT:
return fmt.Sprintf("($%04X)", addr16)
return fmt.Sprintf("(%s)", s.addr4(addr16, lookback))
case opcodes.MODE_RELATIVE:
return fmt.Sprintf("$%04X ", addrRel)
return fmt.Sprintf("%s", s.addr4(addrRel, lookback))
case opcodes.MODE_IMMEDIATE:
return fmt.Sprintf("#$%02X ", byte1)
return fmt.Sprintf("#$%02X", byte1)
case opcodes.MODE_ABS_X:
return fmt.Sprintf("$%04X,X", addr16)
return fmt.Sprintf("%s,X", s.addr4(addr16, lookback))
case opcodes.MODE_ABS_Y:
return fmt.Sprintf("$%04X,Y", addr16)
return fmt.Sprintf("%s,Y", s.addr4(addr16, lookback))
case opcodes.MODE_ZP:
return fmt.Sprintf("$%02X ", byte1)
return fmt.Sprintf("%s", s.addr2(byte1, lookback))
case opcodes.MODE_ZP_X:
return fmt.Sprintf("$%02X,X ", byte1)
return fmt.Sprintf("%s,X", s.addr2(byte1, lookback))
case opcodes.MODE_ZP_Y:
return fmt.Sprintf("$%02X,Y ", byte1)
return fmt.Sprintf("%s,Y", s.addr2(byte1, lookback))
case opcodes.MODE_INDIRECT_Y:
return fmt.Sprintf("($%02X),Y", byte1)
return fmt.Sprintf("(%s),Y", s.addr2(byte1, lookback))
case opcodes.MODE_INDIRECT_X:
return fmt.Sprintf("($%02X,X)", byte1)
case opcodes.MODE_A:
return " "
return fmt.Sprintf("(%s,X)", s.addr2(byte1, lookback))
}
panic(fmt.Sprintf("Unknown op mode: %d", mode))
}
@ -64,19 +98,19 @@ func addrString(pc uint16, byte1, byte2 byte, length int, mode opcodes.Addressin
// 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) (string, string, int) {
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)
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) {
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]
@ -89,8 +123,46 @@ func DisasmBlock(block []byte, startAddr uint16, w io.Writer) {
byte2 = block[i+2]
}
addr := uint16(i) + startAddr
bytes, op, length := Disasm(addr, byte0, byte1, byte2)
fmt.Fprintf(w, "$%04X: %s %s\n", addr, bytes, op)
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
}

26
asm/disasm_test.go Normal file
View File

@ -0,0 +1,26 @@
package asm
import "testing"
func TestSymbolRE(t *testing.T) {
tests := []struct {
line string
label string
address string
}{
{"SHAPEL EQU $1A POINTER TO", "SHAPEL", "1A"},
}
for i, tt := range tests {
groups := symRe.FindStringSubmatch(tt.line)
if groups == nil {
t.Errorf(`%d. Unable to parse '%s'`, i, tt.line)
}
if groups[1] != tt.label {
t.Errorf(`%d. want label='%s'; got '%s' for line: '%s'`, i, tt.label, groups[1], tt.line)
}
if groups[2] != tt.address {
t.Errorf(`%d. want address='%s'; got '%s' for line: '%s'`, i, tt.address, groups[2], tt.line)
}
}
}

View File

@ -132,7 +132,7 @@ func (c *cpu) Reset() {
// status prints out the current CPU instruction and register status.
func status(c *cpu, m Memory) string {
bytes, text, _ := asm.Disasm(c.PC(), m.Read(c.PC()), m.Read(c.PC()+1), m.Read(c.PC()+2))
return fmt.Sprintf("$%04X: %s %s A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%08b",
return fmt.Sprintf("$%04X: %-8s %-11s A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%08b",
c.PC(), bytes, text, c.A(), c.X(), c.Y(), c.SP(), c.P())
}