summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.project1
-rw-r--r--build.sbt9
-rw-r--r--notes75
-rw-r--r--src/ByteUtils.scala114
-rw-r--r--src/ConversionUtils.scala87
-rw-r--r--src/FunctionUtils.scala19
-rw-r--r--src/Instruction.scala306
-rw-r--r--src/external/Base64.scala69
-rw-r--r--src/main.scala27
-rw-r--r--src/msp430_instruction77
-rw-r--r--src/msp430_instruction.2100
12 files changed, 886 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b309029
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+project/*
+target/*
diff --git a/.project b/.project
new file mode 100644
index 0000000..a2f49b5
--- /dev/null
+++ b/.project
@@ -0,0 +1 @@
+project_name="msp430-disassembler"
diff --git a/build.sbt b/build.sbt
new file mode 100644
index 0000000..2c50d75
--- /dev/null
+++ b/build.sbt
@@ -0,0 +1,9 @@
+name := "MPS430 disassembler"
+
+scalaVersion := "2.11.5"
+
+scalaSource in Compile := baseDirectory.value / "src"
+
+initialCommands in console := """
+import ixee.re.disasm._
+"""
diff --git a/notes b/notes
new file mode 100644
index 0000000..294e217
--- /dev/null
+++ b/notes
@@ -0,0 +1,75 @@
+ops:
+15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+0 | 0 | 0 | 1 | 0 | 0 | opcode | bw | ad | dest |
+0 | 0 | 1 | cond | pc-offset |
+opcode | source | ad | bw | as | dest |
+
+bw = 1 for byte ops, 0 for word ops
+
+oneop
+000 RRC 9-bit rotate through carry
+001 SWPB - swap 8-bit halves. no byte form
+010 RRA(.B) - arithmetic right shift
+011 SXT sign extend 8 bits to 16 bits. no byte form
+100 PUSH(.B) - push byte still decrements sp by 2
+101 CALL - get operand. push PC, PC = operand. no byte form. hard to do PC-relative call
+110 RETI - pop SP then pop PC. opeand field unused.
+111 not used
+
+relative jumps
+all PC-relative
+000 JNE/JNZ
+001 JEQ/JZ
+010 JNC/JLO
+011 JC/JHS
+100 JN
+101 JGE
+110 JL
+111 JMP
+
+twoop
+generally dest = src op dest
+mov doesnt fetch dest, cmp/bit dont write dest. all valid in 8 bit and 16 bit
+
+0100 MOV - dest = src
+0101 ADD - dest += src
+0110 ADDC - dest += src + C
+0111 SUBC - dest = dest - src + c (dest += ~src + C)
+1000 SUB - dest -= src
+1001 CMP - dest - src (set status only, dest not written)
+1010 DADD - dest += src + C, in BCD
+1011 BIT - dest & src (set status only)
+1100 BIC - dest &= ~src (status not set)
+1101 BIS - dest |= src (status not set)
+1110 XOR - dest ^= src
+1111 AND - dest &= src
+
+NOP implemented as virtual function, among others -
+NOP == MOV r3, r3
+POP dst == MOV @SP+, dst
+BR dst == MOV dst, PC
+RET == MOV @SP+, PC
+CLRC 1
+SETC 1
+CLRZ 2
+SETZ 2
+CLRN 4
+SETN 4
+DINT 8
+EINT 8
+
+RLA dst == ADD dst, dst
+RLC dst = ADDC dst, dst
+
+INV dst == XOR #-1, dst
+CLR dst == MOV #0, dst
+TST dst == CMP #0, dst
+
+DEC == SUB #1, dst
+DECD == SUB #2, dst
+INC == ADD #1, dst
+INCD == ADD #2, dst
+
+ADC dst == ADDC #0, dst
+DADC dst == DADD #0, dst
+SBC dst == SUBC #0, dst
diff --git a/src/ByteUtils.scala b/src/ByteUtils.scala
new file mode 100644
index 0000000..723164d
--- /dev/null
+++ b/src/ByteUtils.scala
@@ -0,0 +1,114 @@
+package ixee.cryptopals.utils
+
+import FunctionUtils.tup
+import ConversionUtils._
+
+object ByteUtils {
+ trait SizedNumeric[T] {
+ def manifest: Manifest[T]
+ def byteSize: Int
+ def bitSize: Int
+ def fromLong(l: Long): T
+ def toLong(l: T): Long
+ def buildFrom[U : SizedNumeric : BitOps](from: U): T = fromLong(from.toLong)
+ def truncate[U : SizedNumeric : BitOps](from: U): T = buildFrom(from)
+ override def toString = s"SizedNumeric[${manifest}]"
+ }
+
+ trait BitOps[T] {
+ def @>>>(t: T, x: Int): T
+ def @<<(t: T, x: Int): T
+ def @|(a: T, b: T): T
+ def @&(a: T, b: T): T
+ def @^(a: T, b: T): T
+ def @+(a: T, b: T): T
+ def @-(a: T, b: T): T
+ }
+
+ implicit val byteOps: BitOps[Byte] = new BitOps[Byte] {
+ def @>>>(t: Byte, x: Int): Byte = (t >>> x).toByte
+ def @<<(t: Byte, x: Int): Byte = (t << x).toByte
+ def @|(a: Byte, b: Byte): Byte = (a | b).toByte
+ def @&(a: Byte, b: Byte): Byte = (a & b).toByte
+ def @^(a: Byte, b: Byte): Byte = (a ^ b).toByte
+ def @+(a: Byte, b: Byte): Byte = (a + b).toByte
+ def @-(a: Byte, b: Byte): Byte = (a - b).toByte
+ }
+
+ implicit val intOps: BitOps[Int] = new BitOps[Int] {
+ def @>>>(t: Int, x: Int): Int = t >>> x
+ def @<<(t: Int, x: Int): Int = (t << x)
+ def @|(a: Int, b: Int): Int = a | b
+ def @&(a: Int, b: Int): Int = a & b
+ def @^(a: Int, b: Int): Int = a ^ b
+ def @+(a: Int, b: Int): Int = a + b
+ def @-(a: Int, b: Int): Int = a - b
+ }
+
+ implicit class WithBitOpts[T : BitOps](x: T) {
+ def @>>>(b: Int): T = implicitly[BitOps[T]].@>>>(x, b)
+ def @<<(b: Int): T = implicitly[BitOps[T]].@<<(x, b)
+ def @|(b: T): T = implicitly[BitOps[T]].@|(x, b)
+ def @&(b: T): T = implicitly[BitOps[T]].@&(x, b)
+ def @^(b: T): T = implicitly[BitOps[T]].@^(x, b)
+ def @+(b: T): T = implicitly[BitOps[T]].@+(x, b)
+ def @-(b: T): T = implicitly[BitOps[T]].@-(x, b)
+ }
+
+ implicit class SizedWithByteInfo[T : SizedNumeric : BitOps](x: T) {
+ def byteSize = implicitly[SizedNumeric[T]].byteSize
+ def octetSize = byteSize * 2
+ def bitSize = implicitly[SizedNumeric[T]].bitSize
+ def toLong = implicitly[SizedNumeric[T]].toLong(x)
+ def liftedTo[U : SizedNumeric]: U = {
+ val uSize = implicitly[SizedNumeric[U]]
+ val tSize = implicitly[SizedNumeric[T]]
+ if (uSize.byteSize < tSize.byteSize) {
+ throw new RuntimeException(s"Target size ($uSize) is smaller than the source size ($tSize)")
+ }
+
+ uSize.buildFrom(x)
+ }
+ def hex: String = s"%0${octetSize}x" format x
+ def bitsSet: Int = {
+ var bits = 0
+ var num = x
+
+ while (num.toLong > 0) {
+ bits = bits + 1
+ num = num @& (num @- 1.truncatedTo[T])
+ }
+
+ bits
+ }
+ def truncatedTo[U : SizedNumeric]: U = {
+ implicitly[SizedNumeric[U]].truncate(x)
+ }
+ }
+
+ def sizedNumeric[T : Manifest](bytes: Int)(from: Long => T)(to: T => Long) = new SizedNumeric[T] {
+ def manifest = implicitly[Manifest[T]]
+ def byteSize = bytes
+ def bitSize = bytes * 8
+ def toLong(t: T) = to(t)
+ def fromLong(l: Long) = from(l)
+ }
+
+ implicit val intSized = sizedNumeric(4) { x: Long => x.toInt } { _.toLong }
+ implicit val shortized = sizedNumeric(2) { x: Long => x.toShort } { _.toLong }
+ implicit val byteSized = sizedNumeric(1) { x: Long => x.toByte } { _.toLong }
+
+ implicit class SeqByteOps[T : SizedNumeric : BitOps](seq: Seq[T]) {
+ def xor(other: Seq[T]): Seq[T] =
+ seq.zip(other).map(tup(_ @^ _))
+ }
+
+ def hammingDistance(a: String, b: String): Int =
+ hammingDistance(a.asBytes, b.asBytes)
+
+ def hammingDistance[T : BitOps : SizedNumeric](a: Seq[T], b: Seq[T]): Int =
+ (a xor b).map(_.bitsSet).reduce(_ @+ _)
+
+ def avgHammingDistance[T : BitOps : SizedNumeric](xs: Seq[Seq[T]]): Double =
+ xs.sliding(2).map({ case Stream(a: Seq[T], b: Seq[T]) => hammingDistance(a, b) }).reduce(_ @+ _) / xs.length.toDouble
+}
diff --git a/src/ConversionUtils.scala b/src/ConversionUtils.scala
new file mode 100644
index 0000000..32689fc
--- /dev/null
+++ b/src/ConversionUtils.scala
@@ -0,0 +1,87 @@
+package ixee.cryptopals.utils
+
+import ByteUtils._
+import FunctionUtils._
+
+object ConversionUtils {
+ def hexStr2Bytes(s: String): Seq[Byte] =
+ padToByte(s).grouped(2).map(byteStr2Byte).toStream.force
+
+ def byteStr2Byte(str: String): Byte =
+ charSeq2Byte(str.toSeq)
+
+ def charSeq2Byte(seq: Seq[Char]): Byte =
+ seq.map(octet2nibble).reduce(joinNibbles)
+
+ def joinNibbles(a: Byte, b: Byte): Byte =
+ ((a @<< 4) @+ b)
+
+ def padToByte(s: String): String =
+ if (s.length % 2 != 0) "0" + s else s
+
+ def octet2nibble(c: Char): Byte = {
+ (c.toLower match {
+ case c if c >= 'a' && c <= 'f' =>
+ (c - 'a') + 10.toByte
+ case c if c >= '0' && c <= '9' =>
+ c - '0'
+ case _ =>
+ throw new IllegalArgumentException(s"Invalid hexadecimal character: $c")
+ }).toByte
+ }
+
+ def hexStr2Base64String(s: String): String = {
+ import io.github.marklister.base64.Base64._
+
+ hexStr2Bytes(s).toArray.toBase64
+ }
+
+ implicit class RichSeqByte(seq: Seq[Byte]) {
+ def to[T : SizedNumeric : BitOps]: T = {
+ val numeric = implicitly[SizedNumeric[T]]
+ if(seq.length < numeric.byteSize) {
+ throw new RuntimeException("Byte input is not long enough")
+ }
+
+ var out: Long = seq(0)
+ for(i <- 1 until numeric.byteSize) {
+ out << 8
+ out = seq(i) | out
+ }
+ numeric.fromLong(out)
+ }
+
+ def hex: String =
+ seq.map(_.hex).reduceLeft(_ + _)
+
+ def asAscii: String =
+ new String(seq.toArray)
+ }
+
+ implicit class RichStringBytes(s: String) {
+ def asBytes = s.toSeq.map(_.toByte)
+ def asByteStream = s.asBytes.toStream
+ def asRepeatedBytes(count: Int) =
+ Stream.continually(s.asBytes).flatten.take(count)
+ }
+
+ // TODO: tailrec this, get rid of for
+ def toByteSeq[T : SizedNumeric : BitOps](x: T): Seq[Byte] = {
+ val buf = new Array[Byte](x.byteSize)
+ for(i <- 0 until x.byteSize) {
+ val shiftAmount = (x.byteSize - i - 1) << 3
+ buf(i) = (x @>>> shiftAmount).truncatedTo[Byte]
+ }
+ buf.toSeq
+ }
+
+ def toBinaryString[T : SizedNumeric](x: T)(implicit a: BitOps[T]): String = {
+ val buf = new StringBuilder(x.bitSize)
+ for(i <- 0 until x.bitSize) {
+ val shiftAmount: Int = x.bitSize - i - 1
+ buf.append((x @>>> shiftAmount) @& 0x01.liftedTo[T])
+ }
+ buf.toString()
+ }
+}
+
diff --git a/src/FunctionUtils.scala b/src/FunctionUtils.scala
new file mode 100644
index 0000000..3d6610c
--- /dev/null
+++ b/src/FunctionUtils.scala
@@ -0,0 +1,19 @@
+package ixee.cryptopals.utils
+
+object FunctionUtils {
+ // Because doing (_ f _).tupled confuses the inferencer...
+ def tup[A, B, C](f: (A, B) => C): ((A, B)) => C = f.tupled
+
+ def ident[A]: A => A = { x => x }
+
+ implicit class Compositor[A, B](f: A => B) {
+ def :|[C](g: B => C): A => C = f.andThen(g)
+ }
+
+ implicit class Tap[A](x: A) {
+ def tap(f: A => Any): A = {
+ f(x)
+ x
+ }
+ }
+}
diff --git a/src/Instruction.scala b/src/Instruction.scala
new file mode 100644
index 0000000..7177b89
--- /dev/null
+++ b/src/Instruction.scala
@@ -0,0 +1,306 @@
+package ixee.re.disasm
+
+import ixee.cryptopals.utils.ByteUtils._
+import ixee.cryptopals.utils.FunctionUtils._
+
+import scala.collection.mutable.Map
+
+case class Source(value: Int)
+case class Destination(value: Int)
+
+sealed trait AddressingMode {
+ def modeName: String
+ override def toString: String = modeName
+ def nameifyRegister(reg: Int): String = reg match {
+ case 0 => "pc"
+ case 1 => "sp"
+ case 2 => "sr"
+ case 3 => "cg"
+ case num @ _ => s"r$num"
+ }
+ def stringifyWith(reg: Int): String
+}
+
+sealed trait OneOpAddressing extends AddressingMode
+sealed trait TwoOpAddressing extends AddressingMode
+sealed trait TwoOpSourceAddressing extends TwoOpAddressing
+sealed trait TwoOpDestAddressing extends TwoOpAddressing
+
+object TwoOpSourceAddressingModes {
+ // just alias the apply over to make names look right at call sites.
+ // think hard about refactoring this
+ def apply(register: Int, mode: Int): Option[TwoOpSourceAddressing] = OneOpAddressingModes(register, mode)
+}
+
+object OneOpAddressingModes {
+ case class AddressingMode(as: Int, name: String, stringifier: String => String) extends OneOpAddressing with TwoOpSourceAddressing {
+ def modeName = name
+ def stringifyWith(reg: Int): String = stringifier(nameifyRegister(reg))
+ }
+
+ def apply(register: Int, mode: Int): Option[AddressingMode] = register match {
+ case 0 => PCModes(mode) orElse General(mode)
+ case 2 => SRModes(mode) orElse General(mode)
+ case 3 => CGModes(mode) orElse General(mode)
+ case _ => General(mode)
+ }
+
+ trait Modes {
+ private val modeMap: Map[Int, AddressingMode] = Map()
+ def Mode(as: Int, name: String, stringifier: String => String) = AddressingMode(as, name, stringifier).tap(modeMap += as -> _)
+
+ def apply(x: Int): Option[AddressingMode] = modeMap.get(x)
+ }
+
+ object PCModes extends Modes {
+ Mode(3, "NextWord", reg => s"@$reg+ (next word)")
+ }
+ object SRModes extends Modes {
+ Mode(2, "Const4", reg => "#4")
+ Mode(3, "Const8", reg => "#8")
+ }
+ object CGModes extends Modes {
+ Mode(0, "Const0", reg => "#0")
+ Mode(1, "Const1", reg => "#1")
+ Mode(2, "Const2", reg => "#2")
+ Mode(3, "ConstNeg1", reg => "#-1")
+ }
+ object General extends Modes {
+ Mode(0, "RegisterDirect", ident)
+ Mode(1, "Indexed", reg => s"@PC+($reg)")
+ Mode(2, "RegisterIndirect", reg => s"@$reg")
+ Mode(3, "IndirectAutoInc", reg => s"@$reg+")
+ }
+
+ PCModes
+ SRModes
+ CGModes
+ General
+}
+
+
+object TwoOpDestAddressingModes {
+ case class AddressingMode(as: Int, name: String, stringifier: String => String) extends TwoOpDestAddressing {
+ def modeName = name
+ def stringifyWith(reg: Int): String = stringifier(nameifyRegister(reg))
+ }
+
+ def apply(register: Int, mode: Int): Option[AddressingMode] = register match {
+ case 0 => PCModes(mode) orElse General(mode)
+ case 2 => SRModes(mode) orElse General(mode)
+ case _ => General(mode)
+ }
+
+ trait Modes {
+ private val modeMap: Map[Int, AddressingMode] = Map()
+ def Mode(as: Int, name: String, stringifier: String => String) = AddressingMode(as, name, stringifier).tap(modeMap += as -> _)
+
+ def apply(x: Int): Option[AddressingMode] = modeMap.get(x)
+ }
+
+ object PCModes extends Modes {
+ Mode(1, "Symbolic", reg => s"<??> (symbolic? x(PC)?")
+ }
+ object SRModes extends Modes {
+ Mode(1, "Absolute", reg => s"&@PC+ (value at nextWord)")
+ }
+ object General extends Modes {
+ Mode(0, "RegisterDirect", reg => s"$reg")
+ Mode(1, "Indexed", reg => s"@PC+($reg)")
+ }
+
+ PCModes
+ SRModes
+ General
+}
+
+sealed trait OpCode {
+ def instrName: String
+ override def toString: String = instrName
+}
+
+sealed trait NoOpCode extends OpCode
+sealed trait OneOpCode extends OpCode
+sealed trait TwoOpCode extends OpCode
+
+object Opcodes {
+ def opcodes: Map[Int, OpCode] = noOpCodes ++ oneOpCodes ++ twoOpCodes
+ val noOpCodes: Map[Int, NoOpCode] = Map()
+ val oneOpCodes: Map[Int, OneOpCode] = Map()
+ val twoOpCodes: Map[Int, TwoOpCode] = Map()
+
+ def apply(value: Int) = opcodes.get(value)
+ def noOp(value: Int) = noOpCodes.get(value)
+ def oneOp(value: Int) = oneOpCodes.get(value)
+ def twoOp(value: Int) = twoOpCodes.get(value)
+
+ object NoOpCodes {
+ val Prefix = 0x01
+ case class Code(value: Byte, name: String) extends NoOpCode { def instrName = name }
+ object Code {
+ def apply(value: Int, name: String) = new Code(value.toByte, name).tap(code => noOpCodes += ((((Prefix << 3) + value) << 10) -> code))
+ }
+
+ Code(0x00, "JNE")
+ Code(0x01, "JEQ")
+ Code(0x02, "JNC")
+ Code(0x03, "JC ")
+ Code(0x04, "JN ")
+ Code(0x05, "JGE")
+ Code(0x06, "JL ")
+ Code(0x07, "JMP")
+ }
+
+ object OneOpCodes {
+ val Prefix = 0x04
+ case class Code(value: Byte, name: String) extends OneOpCode { def instrName = name }
+ object Code {
+ def apply(value: Int, name: String) = new Code(value.toByte, name).tap(code => oneOpCodes += ((((Prefix << 3) + value) << 7) -> code))
+ }
+
+ Code(0x00, "RRC")
+ Code(0x01, "SWPB")
+ Code(0x02, "RRA")
+ Code(0x03, "SXT")
+ Code(0x04, "PUSH")
+ Code(0x05, "CALL")
+ Code(0x06, "RETI")
+// Code(0x07, "NotImplemented")
+ }
+
+ object TwoOpCodes {
+ val Prefix = 0x1
+ case class Code(value: Byte, name: String) extends TwoOpCode { def instrName = name }
+ object Code {
+ def apply(value: Int, name: String) = new Code(value.toByte, name).tap(code => twoOpCodes += ((value @<< 12) -> code))
+ }
+
+ Code(0x04, "MOV")
+ Code(0x05, "ADD")
+ Code(0x06, "ADDC")
+ Code(0x07, "SUBC")
+ Code(0x08, "SUB")
+ Code(0x09, "CMP")
+ Code(0x0a, "DADD")
+ Code(0x0b, "BIT")
+ Code(0x0c, "BIC")
+ Code(0x0d, "BIS")
+ Code(0x0e, "XOR")
+ Code(0x0f, "AND")
+ }
+
+ // forces the various opcode bodies to be evaluated. kinda weird, but it works so welp.
+ NoOpCodes
+ OneOpCodes
+ TwoOpCodes
+}
+
+sealed trait Instruction
+
+case class NoOpInst(code: NoOpCode, offset: Int) extends Instruction {
+ override def toString: String = {
+ s"$code -> $offset"
+ }
+}
+case class OneOpInst(code: OneOpCode, byte: Boolean, dest: Int, addressMode: OneOpAddressing) extends Instruction {
+ override def toString: String = {
+ val byteStr = if (byte) ".B" else " "
+ s"$code$byteStr ${addressMode.stringifyWith(dest)}"
+ }
+}
+case class TwoOpInst(code: TwoOpCode, byte: Boolean, src: Int, srcMode: TwoOpSourceAddressing, dest: Int, dstMode: TwoOpDestAddressing) extends Instruction {
+ override def toString: String = {
+ val byteStr = if (byte) ".B" else " "
+ s"$code$byteStr ${srcMode.stringifyWith(src)}, ${dstMode.stringifyWith(dest)}"
+ }
+}
+
+object NoOp {
+ def info(bytes: (Byte, Byte)): (Int, Int) = {
+ val op = (bytes._1 & 0xfc) >> 2
+ val offset = ((bytes._1 & 0x03) << 8) | bytes._2
+ (op, offset)
+ }
+
+ def apply(bytes: (Byte, Byte)): Option[NoOpInst] = {
+ val (opVal, offset) = info(bytes)
+ for {
+ opcode <- Opcodes.noOp(opVal << 10)
+ } yield NoOpInst(opcode, offset)
+ }
+}
+
+object OneOp {
+ def info(bytes: (Byte, Byte)): (Int, Int, Int, Int) = {
+ val dest = (bytes._2 & 0x0f)
+ val destMode = ((bytes._2 & 0x30) >> 4)
+ val byteBit = ((bytes._2 & 0x40) >> 6)
+ val opcode = ((bytes._1 & 0xff) << 1) | ((bytes._2 & 0x80) >> 7)
+ (opcode, byteBit, dest, destMode)
+ }
+
+ def apply(bytes: (Byte, Byte)): Option[OneOpInst] = {
+ val (opVal, byteInstr, dest, rawDestMode) = info(bytes)
+ for {
+ opcode <- Opcodes.oneOp(opVal << 7)
+ isByteOp = if (byteInstr == 1) true else false
+ destMode <- OneOpAddressingModes(dest, rawDestMode)
+ } yield OneOpInst(opcode, isByteOp, dest, destMode)
+ }
+}
+
+object TwoOp {
+ def info(bytes: (Byte, Byte)): (Int, Int, Int, Int, Int, Int) = {
+ val dest = (bytes._2 & 0x0f)
+ val sourceMode = (bytes._2 & 0x30) >> 4
+ val byteBit = (bytes._2 & 0x40) >> 6
+ val destMode = (bytes._2 & 0x80) >> 7
+ val source = (bytes._1 & 0x0f)
+ val opcode = (bytes._1 & 0xf0) >> 4
+ (opcode, byteBit, source, destMode, dest, sourceMode)
+ }
+
+ def apply(bytes: (Byte, Byte)): Option[TwoOpInst] = {
+ val (opVal, byteInstr, source, rawDestMode, dest, rawSourceMode) = info(bytes)
+
+ for {
+ opcode <- Opcodes.twoOp(opVal << 12)
+ isByteOp = if (byteInstr == 1) true else false
+ destMode <- TwoOpDestAddressingModes(dest, rawDestMode)
+ sourceMode <- TwoOpSourceAddressingModes(source, rawSourceMode)
+ } yield TwoOpInst(opcode, isByteOp, source, sourceMode, dest, destMode)
+ }
+}
+
+object Instruction {
+ def apply(instruction: (Byte, Byte)): Option[Instruction] = NoOp(instruction) orElse OneOp(instruction) orElse TwoOp(instruction)
+ def littleEndian(instruction: String): Option[Instruction] = {
+ val instr = Integer.parseInt(instruction, 16)
+ val hi = (instr & 0xff00) >> 8
+ val lo = instr & 0x00ff
+ apply((hi.toByte, lo.toByte))
+ }
+ def apply(instruction: String): Option[Instruction] = {
+ val instr = Integer.parseInt(instruction, 16)
+ val hi = (instr & 0xff00) >> 8
+ val lo = instr & 0x00ff
+ apply((lo.toByte, hi.toByte))
+ }
+
+ def noOp(instruction: (Byte, Byte)): Option[NoOpInst] = NoOp.apply(instruction)
+
+ def oneOp(instruction: (Byte, Byte)): Option[OneOpInst] = OneOp.apply(instruction)
+
+ def twoOp(instruction: (Byte, Byte)): Option[TwoOpInst] = TwoOp.apply(instruction)
+
+ def isNoOp(instruction: (Byte ,Byte)) = noOp(instruction).isDefined
+
+ def isOneOp(instruction: (Byte, Byte)) = oneOp(instruction).isDefined
+
+ def isTwoOp(instruction: (Byte, Byte)) = twoOp(instruction).isDefined
+}
+
+sealed trait ByteOp {
+ override def toString = s"(Single Byte: ${super.toString})"
+}
+
diff --git a/src/external/Base64.scala b/src/external/Base64.scala
new file mode 100644
index 0000000..692abb8
--- /dev/null
+++ b/src/external/Base64.scala
@@ -0,0 +1,69 @@
+package io.github.marklister.base64
+
+/**
+ * Base64 encoder
+ * @author Mark Lister
+ * (c) Mark Lister 2014
+ *
+ * I, the copyright holder of this work, release this work into the public domain.
+ * This applies worldwide. In some countries this may not be legally possible;
+ * if so: I grant anyone the right to use this work for any purpose, without any
+ * conditions, unless such conditions are required by law.
+ *
+ * The repo for this Base64 encoder lives at https://github.com/marklister/base64
+ * Please send your issues, suggestions and pull requests there.
+ *
+ */
+
+object Base64 {
+
+ class B64Scheme(val encodeTable: IndexedSeq[Char]) {
+ lazy val decodeTable = collection.immutable.TreeMap(encodeTable.zipWithIndex: _*)
+ }
+
+ lazy val base64 = new B64Scheme(('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ Seq('+', '/'))
+ lazy val base64Url = new B64Scheme(base64.encodeTable.dropRight(2) ++ Seq('-', '_'))
+
+ implicit class Encoder(b: Array[Byte]) {
+ private[this] val zero = Array(0, 0).map(_.toByte)
+ lazy val pad = (3 - b.length % 3) % 3
+
+ def toBase64(implicit scheme: B64Scheme = base64): String = {
+ def sixBits(x: Array[Byte]): Seq[Int] = {
+ val a = (x(0) & 0xfc) >> 2
+ val b = ((x(0) & 0x3) << 4) + ((x(1) & 0xf0) >> 4)
+ val c = ((x(1) & 0xf) << 2) + ((x(2) & 0xc0) >> 6)
+ val d = (x(2)) & 0x3f
+ Seq(a, b, c, d)
+ }
+ ((b ++ zero.take(pad)).grouped(3)
+ .flatMap(sixBits(_))
+ .map(x => scheme.encodeTable(x))
+ .toSeq
+ .dropRight(pad) :+ "=" * pad)
+ .mkString
+ }
+ }
+
+ implicit class Decoder(s: String) {
+ lazy val cleanS = s.reverse.dropWhile(_ == '=').reverse
+ lazy val pad = s.length - cleanS.length
+
+ def toByteArray(implicit scheme: B64Scheme = base64): Array[Byte] = {
+ def threeBytes(s: Seq[Char]): Array[Byte] = {
+ val r = s.map(scheme.decodeTable(_)).foldLeft(0)((a,b)=>(a << 6) +b)
+ java.nio.ByteBuffer.allocate(8).putLong(r).array().takeRight(3)
+ }
+ if (pad > 2 || s.length % 4 != 0) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s)
+ if (!cleanS.forall(scheme.encodeTable.contains(_))) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s)
+
+ (cleanS + "A" * pad)
+ .grouped(4)
+ .map(threeBytes(_))
+ .flatten
+ .toArray
+ .dropRight(pad)
+ }
+ }
+
+}
diff --git a/src/main.scala b/src/main.scala
new file mode 100644
index 0000000..9dc5d42
--- /dev/null
+++ b/src/main.scala
@@ -0,0 +1,27 @@
+package ixee.re.disasm
+
+object main extends App {
+ def apply: Unit = {
+ def disassemble(bytes: Seq[String]): Seq[Option[Instruction]] = bytes.map(Instruction(_))
+
+ val bytes = """
+3140 0044 1542 5c01
+75f3 35d0 085a 3f40
+0000 0f93 0724 8245
+5c01 2f83 9f4f 0c46
+0024 f923 3f40 2200
+0f93 0624 8245 5c01
+1f83 cf43 0024 fa23
+b012 2045 0f43 32d0
+f000 fd3f 3040 0a46
+3012 7f00 b012 7a45
+2153 3041 0412 0441
+2453 2183 c443 fcff
+3e40 fcff 0e54 0e12
+0f12 3012 7d00 b012
+7a45 5f44 fcff 8f11
+""".replace("\n", " ").split(" ").filter(_ != "")
+
+ println(disassemble(bytes).flatten.mkString("\n"))
+ }
+}
diff --git a/src/msp430_instruction b/src/msp430_instruction
new file mode 100644
index 0000000..dfd297d
--- /dev/null
+++ b/src/msp430_instruction
@@ -0,0 +1,77 @@
+byteOpFlag: bit{1}
+
+RRC := b000
+SWPB:= b001
+RRA := b010
+SXT := b011
+PUSH:= b100
+CALL:= b101
+RETI:= b110
+
+oneOpCode: bit{3} :=
+ RRC | SWPB | RRA | SXT | PUSH | CALL | RETI
+
+JNE := b000
+JEQ := b001
+JNC := b010
+JC := b011
+JN := b100
+JGE := b101
+JL := b110
+JMP := b111
+
+jumpCondition: bit{3} :=
+ JNE | JEQ | JNC | JC | JN | JGE | JL | JMP
+
+MOV := b0100
+ADD := b0101
+ADDC:= b0110
+SUBC:= b0111
+SUB := b1000
+CMP := b1001
+DADD:= b1010
+BIT := b1011
+BIC := b1100
+BIS := b1101
+XOR := b1110
+AND := b1111
+
+twoOpCode: bit{4} :=
+ MOV | ADD | ADDC | SUBC | SUB | CMP | DADD | BIT | BIC | BIS | XOR | AND
+
+Symbolic := b1
+Absolute := b1
+RegisterDirect := b0
+Indexed := b1
+
+twoOpDestMode: bit{1} :=
+ RegisterDirect | Indexed | Absolute | Symbolic
+
+NextWord := b11
+Const0 := b00
+Const1 := b01
+Const2 := b10
+Const4 := b10
+Const8 := b11
+ConstNeg1 := b11
+RegisterDirect := b00
+Indexed := b01
+RegisterIndirect := b10
+IndirectAutoInc := b11
+
+twoOpSourceMode: bit{2} :=
+ NextWord | Const0 | Const1 |
+ Const2 | Const4 | Const8 |
+ ConstNeg1 | RegisterDirect | Indexed |
+ RegisterIndirect | IndirectAutoInc
+
+oneOpDestMode: bit{2} := twoOpSourceMode
+
+sourceReg: bit{4}
+destReg: bit{4} := sourceReg
+
+noOp := b001:jumpCondition:pcOffset
+oneOp := b000100:oneOpCode:byteOpFlag:oneOpDestMode:destReg
+twoOp := twoOpCode:sourceReg:twoOpDestMode:byteOpFlag:twoOpSourceMode:destReg
+
+root := oneOp | twoOp | noOp
diff --git a/src/msp430_instruction.2 b/src/msp430_instruction.2
new file mode 100644
index 0000000..ccf1554
--- /dev/null
+++ b/src/msp430_instruction.2
@@ -0,0 +1,100 @@
+byteOpFlag: bit{1}
+
+RRC := b000
+SWPB:= b001
+RRA := b010
+SXT := b011
+PUSH:= b100
+CALL:= b101
+RETI:= b110
+
+oneOpCode: bit{3} :=
+ RRC | SWPB | RRA | SXT | PUSH | CALL | RETI
+
+JNE := b000
+JEQ := b001
+JNC := b010
+JC := b011
+JN := b100
+JGE := b101
+JL := b110
+JMP := b111
+
+jumpCondition: bit{3} :=
+ JNE | JEQ | JNC | JC | JN | JGE | JL | JMP
+
+MOV := b0100
+ADD := b0101
+ADDC:= b0110
+SUBC:= b0111
+SUB := b1000
+CMP := b1001
+DADD:= b1010
+BIT := b1011
+BIC := b1100
+BIS := b1101
+XOR := b1110
+AND := b1111
+
+twoOpCode: bit{4} :=
+ MOV | ADD | ADDC | SUBC | SUB | CMP | DADD | BIT | BIC | BIS | XOR | AND
+
+Symbolic := b1
+Absolute := b1
+RegisterDirect := b0
+Indexed := b1
+
+twoOpDestMode: bit{1} :=
+ RegisterDirect | Indexed | Absolute | Symbolic
+
+NextWord := b11
+Const0 := b00
+Const1 := b01
+Const2 := b10
+Const4 := b10
+Const8 := b11
+ConstNeg1 := b11
+RegisterDirect := b00
+Indexed := b01
+RegisterIndirect := b10
+IndirectAutoInc := b11
+
+twoOpSourceMode: bit{2} :=
+ NextWord | Const0 | Const1 |
+ Const2 | Const4 | Const8 |
+ ConstNeg1 | RegisterDirect | Indexed |
+ RegisterIndirect | IndirectAutoInc
+
+oneOpDestMode: bit{2} := twoOpSourceMode
+
+twoOpDestModePrime :=
+ Symbolic:b0000 |
+ Absolute:b0010 |
+ RegisterDirect:register |
+ Indexed:register
+
+# replace with register names!
+twoOpSourceModePrime :=
+ NextWord:b0000
+ Const4:b0010
+ Const8:b0010
+ Const0:b0011
+ Const1:b0011
+ Const2:b0011
+ ConstNeg1:b0011
+ RegisterDirect:register
+ Indexed:register
+ RegisterIndirect:register
+ IndirectAutoInc:register
+
+sourceReg: bit{4}
+destReg: bit{4} := sourceReg
+
+noOp := b001:jumpCondition:pcOffset
+oneOp:= b000100:oneOpCode:byteOpFlag:oneOpDestMode:destReg
+twoOp := twoOpCode:sourceReg:twoOpDestMode:byteOpFlag:twoOpSourceMode:destReg
+twoOpRewrite $= twoOp =>
+ twoOpCode:byteOpFlag:twoOpSourceMode:sourceReg:sourceReg:twoOpDestMode:destReg:destReg
+twoOpPrime := twoOpCode:byteOpFlag:twoOpSourceModePrime:sourceReg:twoOpDestModePrime:destReg
+
+root := oneOp | twoOp | noOp