Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

add a small disassembler

  • Loading branch information...
commit 5f726f53262068639601c0a0383b37ce1c1a56d7 1 parent 05ae11f
@robey authored
View
1  Cakefile
@@ -38,6 +38,7 @@ assemblerFiles = [
"assembler",
"builtins",
"dcpu",
+ "disassembler",
"errors",
"expression",
"line",
View
3  src/d16bunny.coffee
@@ -16,5 +16,8 @@ assembler = require './d16bunny/assembler'
exports.DataLine = assembler.DataLine
exports.Assembler = assembler.Assembler
+disassembler = require './d16bunny/disassembler'
+exports.Disassembler = disassembler.Disassembler
+
prettyprint = require "./d16bunny/prettyprint"
exports.pp = prettyprint.pp
View
2  src/d16bunny/assembler.coffee
@@ -2,7 +2,7 @@
Dcpu = require("./dcpu").Dcpu
Expression = require('./expression').Expression
Parser = require('./parser').Parser
-Operand = require('./parser').Operand
+Operand = require('./operand').Operand
Macro = require('./parser').Macro
AssemblerError = require('./errors').AssemblerError
AssemblerOutput = require('./output').AssemblerOutput
View
5 src/d16bunny/dcpu.coffee
@@ -58,4 +58,9 @@ Dcpu.RegisterRegex = new RegExp("^(" + Dcpu.Reserved.join("|") + ")\\b", "i")
Dcpu.ReservedOp = (x for x of Dcpu.BinaryOp).concat(x for x of Dcpu.SpecialOp).concat(
[ "jmp", "hlt", "ret", "bra", "dat", "org", "equ" ])
+Dcpu.BinaryOpNames = {}
+for k, v of Dcpu.BinaryOp then Dcpu.BinaryOpNames[v] = k
+Dcpu.SpecialOpNames = {}
+for k, v of Dcpu.SpecialOp then Dcpu.SpecialOpNames[v] = k
+
exports.Dcpu = Dcpu
View
164 src/d16bunny/disassembler.coffee
@@ -0,0 +1,164 @@
+
+Dcpu = require("./dcpu").Dcpu
+Operand = require('./operand').Operand
+pp = require('./prettyprint').pp
+
+class Instruction
+ constructor: (@pc, @words, @opname, @a, @b, @aArgument, @bArgument) ->
+
+ # if any of the arguments are labels, use the label name.
+ resolve: (labels) ->
+ if @aArgument? and labels[@aArgument] then @aArgument = labels[@aArgument]
+ if @bArgument? and labels[@bArgument] then @bArgument = labels[@bArgument]
+ this
+
+ stringify: (x) ->
+ if typeof x == "number"
+ if x < 32 then x.toString() else "0x#{x.toString(16)}"
+ else
+ x.toString()
+
+ decodeOperand: (op, argument, dest=false) ->
+ if op >= Operand.Register and op < Operand.Register + 8
+ Dcpu.RegisterNames[op]
+ else if op >= Operand.RegisterDereference and op < Operand.RegisterDereference + 8
+ "[#{Dcpu.RegisterNames[op - Operand.RegisterDereference]}]"
+ else if op >= Operand.RegisterIndex and op < Operand.RegisterIndex + 8
+ "[#{Dcpu.RegisterNames[op - Operand.RegisterIndex]} + #{@stringify(argument)}]"
+ else if op == Dcpu.Specials["pop"]
+ if dest then "PUSH" else "POP"
+ else if op == Dcpu.Specials["peek"]
+ "PEEK"
+ else if op == Dcpu.Specials["pick"]
+ "PICK #{@stringify(argument)}"
+ else if op == Dcpu.Specials["sp"]
+ "SP"
+ else if op == Dcpu.Specials["pc"]
+ "PC"
+ else if op == Dcpu.Specials["ex"]
+ "EX"
+ else if op == Operand.ImmediateDereference
+ "[#{@stringify(argument)}]"
+ else if op == Operand.Immediate
+ @stringify(argument)
+ else
+ "wut"
+
+ toString: (labels) ->
+ bString = if @b? then "#{@decodeOperand(@b, @bArgument, true)}, " else ""
+ aString = @decodeOperand(@a, @aArgument)
+ target = @target()
+ if target
+ target = if labels? and labels[target]? then labels[target] else @stringify(target)
+ comment = if target? and target != aString then " ; #{target}" else ""
+ "#{@opname.toUpperCase()} #{bString}#{aString}#{comment}"
+
+ # if this instruction has an easily-understood target, return it
+ target: ->
+ if @opname in [ "jsr", "ias" ] and @a == Operand.Immediate
+ @aArgument
+ else if @opname in [ "set", "add", "sub", "xor" ] and @b == Dcpu.Specials["pc"] and @a == Operand.Immediate
+ switch @opname
+ when "set" then @aArgument
+ when "add" then ((@pc + @words) + @aArgument) & 0xffff
+ when "sub" then ((@pc + @words) - @aArgument) & 0xffff
+ when "xor" then ((@pc + @words) ^ @aArgument) & 0xffff
+ else null
+ else
+ null
+
+ # will this instruction change PC?
+ terminal: ->
+ @opname == "rfi" or (@opname in [ "set", "add", "sub", "xor", "adx", "sbx", "sti", "std" ] and @b == Dcpu.Specials["pc"])
+
+ conditional: ->
+ @opname[0..1] == "if"
+
+
+class Disassembler
+ hasImmediate:
+ 0x10: true
+ 0x11: true
+ 0x12: true
+ 0x13: true
+ 0x14: true
+ 0x15: true
+ 0x16: true
+ 0x17: true
+ 0x1a: true
+ 0x1e: true
+ 0x1f: true
+
+ constructor: (@memory) ->
+ @address = 0
+
+ nextWord: ->
+ word = @memory[@address]
+ @address = (@address + 1) % 0x10000
+ word
+
+ nextInstruction: ->
+ pc = @address
+ word = @nextWord()
+ opcode = word & 0x1f
+ a = (word >> 10) & 0x3f
+ b = (word >> 5) & 0x1f
+ aArgument = null
+ bArgument = null
+ if opcode == 0
+ # special
+ opcode = b
+ b = null
+ opname = Dcpu.SpecialOpNames[opcode]
+ else
+ # binary
+ opname = Dcpu.BinaryOpNames[opcode]
+ if opname? then bArgument = if @hasImmediate[b] then @nextWord() else null
+ if opname?
+ aArgument = if @hasImmediate[a] then @nextWord() else null
+ # go ahead and decode embedded immediates
+ if a >= 0x20
+ aArgument = (a - 0x21) & 0xffff
+ a = Operand.Immediate
+ else
+ opname = "DAT"
+ aArgument = word
+ a = Operand.Immediate
+ b = null
+ instruction = new Instruction(pc, @address - pc, opname, a, b, aArgument, bArgument)
+
+ disassemble: ->
+ out = []
+ instructions = []
+ # first, skip all zeros.
+ while (@memory[@address] or 0) == 0 and @address < 0x10000 then @address += 1
+ if @address >= 0x10000 then return []
+ if @address > 0
+ out.push(".ORG 0x#{@address.toString(16)}")
+ # also, ignore zeros from the end.
+ end = 0x10000
+ while (@memory[end - 1] or 0) == 0 then end -= 1
+ # now, process all instructions.
+ while @address < end
+ instructions.push(@nextInstruction())
+ # build up labels
+ targets = []
+ labels = {}
+ for x in instructions
+ target = x.target()
+ if target? then targets.push(target)
+ targets.sort()
+ for t, i in targets
+ labels[t] = "t#{i + 1}"
+ # fill in labels
+ for x in instructions then x.resolve(labels)
+ # flush
+ indent = " "
+ for x in instructions
+ if labels[x.pc]? then out.push(":#{labels[x.pc]}")
+ out.push(indent + x.toString(labels))
+ indent = if x.conditional() then (indent + " ") else " "
+ out
+
+
+exports.Disassembler = Disassembler
View
138 test/test_disassembler.coffee
@@ -0,0 +1,138 @@
+should = require 'should'
+d16bunny = require '../src/d16bunny'
+
+pp = require('../src/d16bunny/prettyprint').prettyPrinter
+
+describe "Disassembler", ->
+ dis1 = (bytes...) ->
+ d = new d16bunny.Disassembler(bytes)
+ d.nextInstruction()
+
+ dis1at = (address, bytes...) ->
+ memory = []
+ for word, i in bytes then memory[address + i] = word
+ d = new d16bunny.Disassembler(memory)
+ d.address = address
+ d.nextInstruction()
+
+ disat = (address, bytes...) ->
+ memory = []
+ for word, i in bytes then memory[address + i] = word
+ d = new d16bunny.Disassembler(memory)
+ d.disassemble()
+
+
+ it "decodes a register SET", ->
+ dis1(0x0401).toString().should.eql("SET A, B")
+
+ it "decodes a dereferenced SET", ->
+ dis1(0x0561).toString().should.eql("SET [X], B")
+
+ it "decodes an indexed SET", ->
+ dis1(0x0661, 0x0004).toString().should.eql("SET [X + 4], B")
+ dis1(0x0661, 0x0104).toString().should.eql("SET [X + 0x104], B")
+ dis1(0x0661, 0x0104).resolve(0x104: "house").toString().should.eql("SET [X + house], B")
+
+ it "decodes an immediate", ->
+ dis1(0x7c21, 0x0384).toString().should.eql("SET B, 0x384")
+
+ it "decodes an immediate dereference", ->
+ dis1(0x7821, 0x0384).toString().should.eql("SET B, [0x384]")
+
+ it "decodes a packed immediate", ->
+ dis1(0xa821).toString().should.eql("SET B, 9")
+ dis1(0x8021).toString().should.eql("SET B, 0xffff")
+
+ it "decodes POP", ->
+ dis1(0x60e1).toString().should.eql("SET J, POP")
+
+ it "decodes PUSH", ->
+ dis1(0x1b01).toString().should.eql("SET PUSH, I")
+
+ it "decodes PEEK", ->
+ dis1(0x6461).toString().should.eql("SET X, PEEK")
+
+ it "decodes PICK", ->
+ dis1(0x6861, 0x0002).toString().should.eql("SET X, PICK 2")
+
+ it "decodes SP, PC, EX", ->
+ dis1(0x6c61).toString().should.eql("SET X, SP")
+ dis1(0x7061).toString().should.eql("SET X, PC")
+ dis1(0x7461).toString().should.eql("SET X, EX")
+
+ it "gives up for DAT", ->
+ dis1(0).toString().should.eql("DAT 0")
+ dis1(0xfffd).toString().should.eql("DAT 0xfffd")
+
+ describe "finds targets", ->
+ it "in JSR", ->
+ x = dis1(0x7c20, 0xfded)
+ x.toString().should.eql("JSR 0xfded")
+ x.target().should.eql(0xfded)
+
+ it "in IAS", ->
+ x = dis1(0x7d40, 0xfded)
+ x.toString().should.eql("IAS 0xfded")
+ x.target().should.eql(0xfded)
+
+ it "in BRA", ->
+ x = dis1at(0x2000, 0x9782)
+ x.toString().should.eql("ADD PC, 4 ; 0x2005")
+ x.target().should.eql(0x2005)
+
+ it "in JMP", ->
+ x = dis1(0x7f81, 0x9000)
+ x.toString().should.eql("SET PC, 0x9000")
+ x.target().should.eql(0x9000)
+
+ describe "finds terminals", ->
+ it "in RET", ->
+ x = dis1(0x6381)
+ x.toString().should.eql("SET PC, POP")
+ x.terminal().should.eql(true)
+
+ it "in JMP", ->
+ x = dis1(0x7f81, 0x9000)
+ x.toString().should.eql("SET PC, 0x9000")
+ x.terminal().should.eql(true)
+
+ it "in RFI", ->
+ x = dis1(0x8560)
+ x.toString().should.eql("RFI 0")
+ x.terminal().should.eql(true)
+
+ it "nowhere else", ->
+ x = dis1(0xa821)
+ x.toString().should.eql("SET B, 9")
+ x.terminal().should.eql(false)
+
+ it "processes a loop", ->
+ x = disat(0x200, 0x8862, 0xd476, 0x9383, 0x6381)
+ x.should.eql([
+ ".ORG 0x200"
+ ":t1"
+ " ADD X, 1"
+ " IFL X, 20"
+ " SUB PC, 3 ; t1"
+ " SET PC, POP"
+ ])
+
+ it "processes nested conditionals", ->
+ x = disat(0, 0x9816, 0x9014, 0x8033, 0x14ac, 0x6381)
+ x.should.eql([
+ " IFL A, 5"
+ " IFG A, 3"
+ " IFN B, 0xffff"
+ " XOR Z, Z"
+ " SET PC, POP"
+ ])
+
+ it "processes jumps", ->
+ x = disat(0x2000, 0x7f81, 0x2002, 0x7f81, 0x2000)
+ x.should.eql([
+ ".ORG 0x2000"
+ ":t1"
+ " SET PC, t2"
+ ":t2"
+ " SET PC, t1"
+ ])
Please sign in to comment.
Something went wrong with that request. Please try again.