diff options
author | iximeow <me@iximeow.net> | 2020-12-06 22:16:43 -0800 |
---|---|---|
committer | iximeow <me@iximeow.net> | 2020-12-06 22:19:55 -0800 |
commit | 589e11b0e1ff53f4d3e8edff2e77c72df38036f8 (patch) | |
tree | 7811ae1ba1e61718bca23d658db3b1f12a3995f8 |
initial commit
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CHANGELOG | 2 | ||||
-rw-r--r-- | Cargo.toml | 24 | ||||
-rw-r--r-- | README.md | 22 | ||||
-rw-r--r-- | src/lib.rs | 804 | ||||
-rw-r--r-- | test/test.rs | 137 |
6 files changed, 991 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..d63ba77 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,2 @@ +## 0.1.0 +* wrote a decoder diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5b12b52 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] + +name = "yaxpeax-sm83" +version = "0.1.0" +authors = [ "iximeow <me@iximeow.net>" ] +license = "0BSD" +repository = "http://git.iximeow.net/yaxpeax-sm83/" +description = "sm83 decoder for the yaxpeax project" +keywords = ["diassembler", "gameboy", "gbc", "sm83", "lr35902"] + +[dependencies] +yaxpeax-arch = { version = "0.0.4", default-features = false, features = [] } +"serde" = { version = "1.0", optional = true } +"serde_derive" = { version = "1.0", optional = true } +"num_enum" = { version = "0.2", default-features = false } + +[[test]] +name = "test" +path = "test/test.rs" + +[features] +default = [] + +use-serde = [] diff --git a/README.md b/README.md new file mode 100644 index 0000000..a696fd2 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +## yaxpeax-sm83 + +decoder for the Sharp SM83 cpu core, which was famously used in the Nintendo +Game Boy and Game Boy Color. + +some documentation refers to the processor in those devices as the `Sharp +LR35902` - this is partially correct: the SoC powering the Game Boy and Game +Boy Color is branded `LR35902`, but the cpu contained therein appears to be +very much like an `SM83` core. gekkio has done significantly more Game Boy +reverse engineering than i plan to do in my life, and has a more compelling +argument with citations in [this nesdev +post](https://forums.nesdev.com/viewtopic.php?f=20&t=18335) + +this decoder is heavily derived from the opcode tables at +[pastraiser](https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html) and in [gekkio's technical reference](https://gekkio.fi/files/gb-docs/gbctr.pdf) + +## stability +the sm83 microcomputer, being over two decades old, is not changing much. the initial release of `yaxpeax-sm83` will likely be 0.1. a 1.0 release has a short but important worklist: + +### 1.0 checklist +- [ ] compare the opcode table from pastraiser with gekkio's documentation. if there are disagreements, figure out what is correct and add appropriate tests +- [ ] confirm acceptable disassembly of real sm83 programs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6c0f79c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,804 @@ +#[cfg(feature="use-serde")] +#[macro_use] extern crate serde_derive; +#[cfg(feature="use-serde")] +extern crate serde; + +extern crate yaxpeax_arch; + +extern crate core; + +use core::fmt; + +use yaxpeax_arch::AddressDiff; +use yaxpeax_arch::Arch; +use yaxpeax_arch::Decoder; +use yaxpeax_arch::LengthedInstruction; + +#[cfg(feature="use-serde")] +#[derive(Debug, Serialize, Deserialize)] +pub struct SM83; + +#[cfg(not(feature="use-serde"))] +#[derive(Debug)] +pub struct SM83; + +impl Arch for SM83 { + type Address = u16; + type Instruction = Instruction; + type DecodeError = DecodeError; + type Decoder = InstDecoder; + type Operand = Operand; +} + +#[derive(Debug, Copy, Clone)] +pub struct Instruction { + opcode: Opcode, + operands: [Operand; 2], + length: u8, +} + +impl Instruction { + pub fn opcode(&self) -> Opcode { + self.opcode + } + + pub fn operands(&self) -> &[Operand; 2] { + &self.operands + } + + pub fn length(&self) -> u8 { + self.length + } +} + +impl Default for Instruction { + fn default() -> Instruction { + Instruction { + opcode: Opcode::NOP, + operands: [Operand::Nothing, Operand::Nothing], + length: 1, + } + } +} + +impl fmt::Display for Instruction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.opcode())?; + let ops = self.operands(); + if ops[0] != Operand::Nothing { + write!(f, " {}", ops[0])?; + } + if ops[1] != Operand::Nothing { + write!(f, ", {}", ops[1])?; + } + Ok(()) + } +} + +impl LengthedInstruction for Instruction { + type Unit = AddressDiff<<SM83 as Arch>::Address>; + fn min_size() -> Self::Unit { + AddressDiff::from_const(1) + } + fn len(&self) -> Self::Unit { + AddressDiff::from_const(self.length as u16) + } +} + +#[derive(Debug, PartialEq)] +pub enum DecodeError { + ExhaustedInput, + InvalidOpcode, + Incomplete, +} + +impl yaxpeax_arch::DecodeError for DecodeError { + fn data_exhausted(&self) -> bool { self == &DecodeError::ExhaustedInput } + fn bad_opcode(&self) -> bool { self == &DecodeError::InvalidOpcode } + fn bad_operand(&self) -> bool { false } +} + +impl fmt::Display for DecodeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DecodeError::ExhaustedInput => { write!(f, "exhausted input") } + DecodeError::InvalidOpcode => { write!(f, "invalid opcode") } + DecodeError::Incomplete => { write!(f, "incomplete") } + } + } +} + +impl yaxpeax_arch::Instruction for Instruction { + // only decode well-formed instructions (for now???) + fn well_defined(&self) -> bool { true } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Operand { + A, + B, + C, + D, + E, + H, + L, + AF, + BC, + DE, + HL, + SP, + DerefHL, + DerefBC, + DerefDE, + DerefDecHL, + DerefIncHL, + DerefHighC, + DerefHighD8(u8), + SPWithOffset(i8), + Bit(u8), + D8(u8), + I8(i8), + R8(i8), + A16(u16), + D16(u16), + + CondC, + CondNC, + CondZ, + CondNZ, + + Nothing, +} + +impl fmt::Display for Operand { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Operand::A => write!(f, "a"), + Operand::B => write!(f, "b"), + Operand::C => write!(f, "c"), + Operand::D => write!(f, "d"), + Operand::E => write!(f, "e"), + Operand::H => write!(f, "h"), + Operand::L => write!(f, "l"), + Operand::AF => write!(f, "af"), + Operand::BC => write!(f, "bc"), + Operand::DE => write!(f, "de"), + Operand::HL => write!(f, "hl"), + Operand::SP => write!(f, "sp"), + Operand::DerefHL => write!(f, "[hl]"), + Operand::DerefBC => write!(f, "[bc]"), + Operand::DerefDE => write!(f, "[de]"), + Operand::DerefDecHL => write!(f, "[hl-]"), + Operand::DerefIncHL => write!(f, "[hl+]"), + Operand::DerefHighC => write!(f, "[0xff00 + c]"), + Operand::DerefHighD8(imm) => write!(f, "[$ff00 + ${:02x}]", imm), + Operand::SPWithOffset(imm) => { + if *imm == -128 { + write!(f, "[sp - $80]") + } else if *imm >= 0 { + write!(f, "[sp + ${:02x}]", imm) + } else { + write!(f, "[sp - ${:02x}]", -imm) + } + } + Operand::Bit(imm) => write!(f, "{}", imm), + Operand::D8(imm) => write!(f, "${:02x}", imm), + Operand::D16(imm) => write!(f, "${:04x}", imm), + Operand::I8(imm) => { + if *imm == -128 { + write!(f, "-0x80") + } else if *imm >= 0 { + write!(f, "${:02x}", imm) + } else { + write!(f, "-${:02x}", -imm) + } + } + Operand::R8(imm) => { + if *imm == -128 { + write!(f, "$-0x80") + } else if *imm >= 0 { + write!(f, "$+${:02x}", imm) + } else { + write!(f, "$-${:02x}", -imm) + } + } + Operand::A16(addr) => write!(f, "[${:4x}]", addr), + + Operand::CondC => write!(f, "C"), + Operand::CondNC => write!(f, "nC"), + Operand::CondZ => write!(f, "Z"), + Operand::CondNZ => write!(f, "nZ"), + + Operand::Nothing => write!(f, "nothing (BUG: should not be shown)"), + } + } +} + +#[derive(Debug, Copy, Clone)] +enum OperandSpec { + A, + B, + C, + D, + E, + H, + L, + AF, + BC, + DE, + HL, + SP, + DerefHL, + DerefBC, + DerefDE, + DerefDecHL, + DerefIncHL, + DerefHighC, + DerefHighD8, + SPWithOffset, + D8, + R8, + I8, + A16, + D16, + + CondC, + CondNC, + CondZ, + CondNZ, + + Bit(u8), + Imm(u8), + + Nothing, +} + +#[derive(Debug, Copy, Clone)] +pub enum Opcode { + NOP, + + LD, + + DEC, + INC, + + ADC, + ADD, + SBC, + SUB, + + AND, + XOR, + OR, + CP, + + POP, + PUSH, + + JP, + JR, + CALL, + RET, + RETI, + HALT, + RST, + STOP, + + RLCA, + RRCA, + RLA, + RRA, + + DAA, + CPL, + SCF, + CCF, + + DI, + EI, + + LDH, + + RLC, + RRC, + RL, + RR, + SLA, + SRA, + SWAP, + SRL, + BIT, + RES, + SET, +} + +impl fmt::Display for Opcode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Opcode::NOP => write!(f, "nop"), + + Opcode::LD => write!(f, "ld"), + + Opcode::DEC => write!(f, "dec"), + Opcode::INC => write!(f, "inc"), + + Opcode::ADC => write!(f, "adc"), + Opcode::ADD => write!(f, "add"), + Opcode::SBC => write!(f, "sbc"), + Opcode::SUB => write!(f, "sub"), + + Opcode::AND => write!(f, "and"), + Opcode::XOR => write!(f, "xor"), + Opcode::OR => write!(f, "or"), + Opcode::CP => write!(f, "cp"), + + Opcode::POP => write!(f, "pop"), + Opcode::PUSH => write!(f, "push"), + + Opcode::JP => write!(f, "jp"), + Opcode::JR => write!(f, "jr"), + Opcode::CALL => write!(f, "call"), + Opcode::RET => write!(f, "ret"), + Opcode::RETI => write!(f, "reti"), + Opcode::HALT => write!(f, "halt"), + Opcode::RST => write!(f, "rst"), + Opcode::STOP => write!(f, "stop"), + + Opcode::RLCA => write!(f, "rlca"), + Opcode::RRCA => write!(f, "rrca"), + Opcode::RLA => write!(f, "rla"), + Opcode::RRA => write!(f, "rra"), + + Opcode::DAA => write!(f, "daa"), + Opcode::CPL => write!(f, "cpl"), + Opcode::SCF => write!(f, "scf"), + Opcode::CCF => write!(f, "ccf"), + + Opcode::DI => write!(f, "di"), + Opcode::EI => write!(f, "ei"), + + Opcode::LDH => write!(f, "ldh"), + + Opcode::RLC => write!(f, "rlc"), + Opcode::RRC => write!(f, "rrc"), + Opcode::RL => write!(f, "rl"), + Opcode::RR => write!(f, "rr"), + Opcode::SLA => write!(f, "sla"), + Opcode::SRA => write!(f, "sra"), + Opcode::SWAP => write!(f, "swap"), + Opcode::SRL => write!(f, "srl"), + Opcode::BIT => write!(f, "bit"), + Opcode::RES => write!(f, "res"), + Opcode::SET => write!(f, "set"), + } + } +} + +#[derive(Debug)] +pub struct InstDecoder { } + +impl Default for InstDecoder { + fn default() -> Self { + InstDecoder { } + } +} + +// main operand map is used for ld, arithmetic, and $CB-prefixed instructions +const OPMAP: [OperandSpec; 8] = [ + OperandSpec::B, + OperandSpec::C, + OperandSpec::D, + OperandSpec::E, + OperandSpec::H, + OperandSpec::L, + OperandSpec::DerefHL, + OperandSpec::A, +]; + +const UPPER_INSTRUCTIONS: [(Option<Opcode>, [OperandSpec; 2]); 64] = [ + // 0xc0 + (Some(Opcode::RET), [OperandSpec::CondNZ, OperandSpec::Nothing]), + (Some(Opcode::POP), [OperandSpec::BC, OperandSpec::Nothing]), + (Some(Opcode::JP), [OperandSpec::CondNZ, OperandSpec::D16]), + (Some(Opcode::JP), [OperandSpec::D16, OperandSpec::Nothing]), + (Some(Opcode::CALL), [OperandSpec::CondNZ, OperandSpec::D16]), + (Some(Opcode::PUSH), [OperandSpec::BC, OperandSpec::Nothing]), + (Some(Opcode::ADD), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x00), OperandSpec::Nothing]), + // 0xc8 + (Some(Opcode::RET), [OperandSpec::CondZ, OperandSpec::Nothing]), + (Some(Opcode::RET), [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::JP), [OperandSpec::CondNC, OperandSpec::D16]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::CALL), [OperandSpec::CondZ, OperandSpec::D16]), + (Some(Opcode::CALL), [OperandSpec::D16, OperandSpec::Nothing]), + (Some(Opcode::ADC), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x08), OperandSpec::Nothing]), + // 0xd0 + (Some(Opcode::RET), [OperandSpec::CondNC, OperandSpec::Nothing]), + (Some(Opcode::POP), [OperandSpec::DE, OperandSpec::Nothing]), + (Some(Opcode::JP), [OperandSpec::CondNC, OperandSpec::D16]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::CALL), [OperandSpec::CondNC, OperandSpec::D16]), + (Some(Opcode::PUSH), [OperandSpec::DE, OperandSpec::Nothing]), + (Some(Opcode::SUB), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x10), OperandSpec::Nothing]), + // 0xd8 + (Some(Opcode::RET), [OperandSpec::CondC, OperandSpec::Nothing]), + (Some(Opcode::RETI), [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::JP), [OperandSpec::CondC, OperandSpec::D16]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::CALL), [OperandSpec::CondC, OperandSpec::D16]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::SBC), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x18), OperandSpec::Nothing]), + // 0xe0 + (Some(Opcode::LDH), [OperandSpec::DerefHighD8, OperandSpec::A]), + (Some(Opcode::POP), [OperandSpec::HL, OperandSpec::Nothing]), + (Some(Opcode::LDH), [OperandSpec::DerefHighC, OperandSpec::A]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::PUSH), [OperandSpec::HL, OperandSpec::Nothing]), + (Some(Opcode::AND), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x20), OperandSpec::Nothing]), + // 0xe8 + (Some(Opcode::ADD), [OperandSpec::SP, OperandSpec::I8]), + (Some(Opcode::JP), [OperandSpec::HL, OperandSpec::Nothing]), + (Some(Opcode::LD), [OperandSpec::A16, OperandSpec::A]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::XOR), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x28), OperandSpec::Nothing]), + // 0xf0 + (Some(Opcode::LDH), [OperandSpec::A, OperandSpec::DerefHighD8]), + (Some(Opcode::POP), [OperandSpec::AF, OperandSpec::Nothing]), + (Some(Opcode::LDH), [OperandSpec::A, OperandSpec::DerefHighC]), + (Some(Opcode::DI), [OperandSpec::Nothing, OperandSpec::Nothing]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::PUSH), [OperandSpec::AF, OperandSpec::Nothing]), + (Some(Opcode::OR), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x30), OperandSpec::Nothing]), + // 0xf8 + (Some(Opcode::LD), [OperandSpec::HL, OperandSpec::SPWithOffset]), + (Some(Opcode::LD), [OperandSpec::SP, OperandSpec::HL]), + (Some(Opcode::LD), [OperandSpec::A, OperandSpec::A16]), + (Some(Opcode::EI), [OperandSpec::Nothing, OperandSpec::Nothing]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (None, [OperandSpec::Nothing, OperandSpec::Nothing]), + (Some(Opcode::CP), [OperandSpec::D8, OperandSpec::Nothing]), + (Some(Opcode::RST), [OperandSpec::Imm(0x38), OperandSpec::Nothing]), +]; + +impl Decoder<Instruction> for InstDecoder { + type Error = DecodeError; + + fn decode_into<T: IntoIterator<Item=u8>>(&self, inst: &mut Instruction, bytes: T) -> Result<(), Self::Error> { + let mut iter = bytes.into_iter(); + + let opc: u8 = iter.next().ok_or(DecodeError::ExhaustedInput)?; + inst.length = 1; + + let high = ((opc >> 3) & 0b111) as usize; + let low = (opc & 0b111) as usize; + + // early part of the table is more varied, more special cases + if opc < 0x40 { + let (opcode, operands) = match low { + 0 => { + // special case on high + match high { + 0 => { (Opcode::NOP, [OperandSpec::Nothing, OperandSpec::Nothing]) }, + 1 => { (Opcode::LD, [OperandSpec::A16, OperandSpec::SP]) }, + 2 => { (Opcode::STOP, [OperandSpec::Nothing, OperandSpec::Nothing]) }, + 3 => { (Opcode::JR, [OperandSpec::R8, OperandSpec::Nothing]) }, + 4 => { (Opcode::JR, [OperandSpec::CondNZ, OperandSpec::R8]) }, + 5 => { (Opcode::JR, [OperandSpec::CondZ, OperandSpec::R8]) }, + 6 => { (Opcode::JR, [OperandSpec::CondNC, OperandSpec::R8]) }, + 7 => { (Opcode::JR, [OperandSpec::CondC, OperandSpec::R8]) }, + _ => { unreachable!("impossible bit pattern"); } + } + } + 1 => { + // also special case, but fewer + if high & 1 == 0 { + let opcode = Opcode::LD; + let ops = [[ + OperandSpec::BC, + OperandSpec::DE, + OperandSpec::HL, + OperandSpec::SP, + ][high as usize >> 1], OperandSpec::D16]; + (opcode, ops) + } else { + let opcode = Opcode::ADD; + let ops = [OperandSpec::HL, [ + OperandSpec::BC, + OperandSpec::DE, + OperandSpec::HL, + OperandSpec::SP, + ][high as usize >> 1]]; + (opcode, ops) + } + } + 2 => { + let op = Opcode::LD; + if high & 1 == 0 { + let op0 = [ + OperandSpec::DerefBC, + OperandSpec::DerefDE, + OperandSpec::DerefIncHL, + OperandSpec::DerefDecHL, + ][high as usize >> 1]; + (op, [op0, OperandSpec::A]) + } else { + let op1 = [ + OperandSpec::DerefBC, + OperandSpec::DerefDE, + OperandSpec::DerefIncHL, + OperandSpec::DerefDecHL, + ][high as usize >> 1]; + (op, [OperandSpec::A, op1]) + } + } + 3 => { + if high & 1 == 0 { + let op0 = [ + OperandSpec::BC, + OperandSpec::DE, + OperandSpec::HL, + OperandSpec::SP, + ][high as usize >> 1]; + (Opcode::INC, [op0, OperandSpec::Nothing]) + } else { + let op0 = [ + OperandSpec::BC, + OperandSpec::DE, + OperandSpec::HL, + OperandSpec::SP, + ][high as usize >> 1]; + (Opcode::DEC, [op0, OperandSpec::Nothing]) + } + } + 4 => { + let op = Opcode::INC; + let op0 = [ + OperandSpec::B, + OperandSpec::C, + OperandSpec::D, + OperandSpec::E, + OperandSpec::H, + OperandSpec::L, + OperandSpec::DerefHL, + OperandSpec::A, + ][high as usize]; + (op, [op0, OperandSpec::Nothing]) + } + 5 => { + let op = Opcode::DEC; + let op0 = [ + OperandSpec::B, + OperandSpec::C, + OperandSpec::D, + OperandSpec::E, + OperandSpec::H, + OperandSpec::L, + OperandSpec::DerefHL, + OperandSpec::A, + ][high as usize]; + (op, [op0, OperandSpec::Nothing]) + } + 6 => { + let op = Opcode::LD; + let op0 = [ + OperandSpec::B, + OperandSpec::C, + OperandSpec::D, + OperandSpec::E, + OperandSpec::H, + OperandSpec::L, + OperandSpec::DerefHL, + OperandSpec::A, + ][high as usize]; + (op, [op0, OperandSpec::D8]) + } + 7 => { + // special cases here too + let op = [ + Opcode::RLCA, + Opcode::RRCA, + Opcode::RLA, + Opcode::RRA, + Opcode::DAA, + Opcode::CPL, + Opcode::SCF, + Opcode::CCF, + ][high as usize]; + (op, [OperandSpec::Nothing, OperandSpec::Nothing]) + } + _ => { + unreachable!("impossible bit pattern"); + } + }; + inst.opcode = opcode; + interpret_operands(iter, inst, operands)?; + return Ok(()); + } else if opc < 0x80 { + if opc == 0x76 { + inst.opcode = Opcode::HALT; + inst.operands = [Operand::Nothing, Operand::Nothing]; + return Ok(()); + } else { + inst.opcode = Opcode::LD; + interpret_operands(iter, inst, [OPMAP[high], OPMAP[low]])?; + return Ok(()); + } + } else if opc < 0xc0 { + const OPCODES: [Opcode; 8] = [ + Opcode::ADD, + Opcode::ADC, + Opcode::SUB, + Opcode::SBC, + Opcode::AND, + Opcode::XOR, + Opcode::OR, + Opcode::CP, + ]; + inst.opcode = OPCODES[high]; + let operands = [OPMAP[low], OperandSpec::Nothing]; + interpret_operands(iter, inst, operands)?; + return Ok(()); + } else { + if opc == 0xcb { + // sm83 special CB-prefixed instructions + let opc: u8 = iter.next().ok_or(DecodeError::ExhaustedInput)?; + inst.length += 1; + if opc < 0x40 { + let high = (opc >> 3) & 0b111; + let low = opc & 0b111; + const LOW_OP: [OperandSpec; 8] = [ + OperandSpec::B, + OperandSpec::C, + OperandSpec::D, + OperandSpec::E, + OperandSpec::H, + OperandSpec::L, + OperandSpec::DerefHL, + OperandSpec::A, + ]; + let operands = [LOW_OP[low as usize], OperandSpec::Nothing]; + const OPCODES: [Opcode; 8] = [ + Opcode::RLC, + Opcode::RRC, + Opcode::RL, + Opcode::RR, + Opcode::SLA, + Opcode::SRA, + Opcode::SWAP, + Opcode::SRL, + ]; + inst.opcode = OPCODES[high as usize]; + interpret_operands(iter, inst, operands)?; + return Ok(()); + } else { + let bit = (opc >> 3) & 0b111; + let low = opc & 0b111; + const LOW_OP: [OperandSpec; 8] = [ + OperandSpec::B, + OperandSpec::C, + OperandSpec::D, + OperandSpec::E, + OperandSpec::H, + OperandSpec::L, + OperandSpec::DerefHL, + OperandSpec::A, + ]; + let operands = [OperandSpec::Bit(bit), LOW_OP[low as usize]]; + const OPCODES: [Opcode; 3] = [ + Opcode::BIT, + Opcode::RES, + Opcode::SET, + ]; + inst.opcode = OPCODES[(opc >> 6) as usize - 1]; + interpret_operands(iter, inst, operands)?; + return Ok(()); + } + } else { + // there is no special thing we can do here. do a table lookup. + let (maybe_opcode, operands) = UPPER_INSTRUCTIONS[opc as usize - 0xc0]; + if let Some(opcode) = maybe_opcode { + inst.opcode = opcode; + interpret_operands(iter, inst, operands)?; + return Ok(()); + } else { + return Err(DecodeError::InvalidOpcode); + } + } + } + } +} + +fn interpret_operands<I: Iterator<Item=u8>>(mut iter: I, inst: &mut Instruction, operands: [OperandSpec; 2]) -> Result<(), DecodeError> { + inst.operands[0] = interpret_operand(&mut iter, inst, operands[0])?; + inst.operands[1] = interpret_operand(&mut iter, inst, operands[1])?; + Ok(()) +} + +fn interpret_operand<I: Iterator<Item=u8>>(iter: &mut I, inst: &mut Instruction, operand: OperandSpec) -> Result<Operand, DecodeError> { + let operand = match operand { + OperandSpec::A => Operand::A, + OperandSpec::B => Operand::B, + OperandSpec::C => Operand::C, + OperandSpec::D => Operand::D, + OperandSpec::E => Operand::E, + OperandSpec::H => Operand::H, + OperandSpec::L => Operand::L, + OperandSpec::AF => Operand::AF, + OperandSpec::BC => Operand::BC, + OperandSpec::DE => Operand::DE, + OperandSpec::HL => Operand::HL, + OperandSpec::SP => Operand::SP, + OperandSpec::DerefHL => Operand::DerefHL, + OperandSpec::DerefBC => Operand::DerefBC, + OperandSpec::DerefDE => Operand::DerefDE, + OperandSpec::DerefDecHL => Operand::DerefDecHL, + OperandSpec::DerefIncHL => Operand::DerefIncHL, + OperandSpec::D8 => { + let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?; + inst.length += 1; + Operand::D8(imm) + } + OperandSpec::DerefHighC => { + Operand::DerefHighC + } + OperandSpec::DerefHighD8 => { + let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?; + inst.length += 1; + Operand::DerefHighD8(imm) + } + OperandSpec::R8 => { + let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?; + inst.length += 1; + Operand::R8(imm as i8) + } + OperandSpec::I8 => { + let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?; + inst.length += 1; + Operand::I8(imm as i8) + } + OperandSpec::A16 => { + let imm = + (iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) | + ((iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) << 8); + inst.length += 2; + Operand::A16(imm as u16) + } + OperandSpec::D16 => { + let imm = + (iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) | + ((iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) << 8); + inst.length += 2; + Operand::D16(imm as u16) + } + OperandSpec::SPWithOffset => { + let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?; + inst.length += 1; + Operand::SPWithOffset(imm as i8) + } + + OperandSpec::Imm(u) => { + Operand::D8(u) + } + OperandSpec::Bit(u) => { + Operand::Bit(u) + } + + OperandSpec::CondC => Operand::CondC, + OperandSpec::CondNC => Operand::CondNC, + OperandSpec::CondZ => Operand::CondZ, + OperandSpec::CondNZ => Operand::CondNZ, + + OperandSpec::Nothing => Operand::Nothing, + }; + Ok(operand) +} diff --git a/test/test.rs b/test/test.rs new file mode 100644 index 0000000..856db45 --- /dev/null +++ b/test/test.rs @@ -0,0 +1,137 @@ +use std::fmt::Write; + +extern crate yaxpeax_arch; +extern crate yaxpeax_sm83; + +use yaxpeax_arch::{AddressBase, Decoder, LengthedInstruction}; +use yaxpeax_sm83::InstDecoder; + +fn test_invalid(data: &[u8]) { + test_invalid_under(&InstDecoder::default(), data); +} + +fn test_invalid_under(decoder: &InstDecoder, data: &[u8]) { + if let Ok(inst) = decoder.decode(data.into_iter().cloned()) { + panic!("decoded {:?} from {:02x?}", inst.opcode(), data); + } else { + // this is fine + } +} + +fn test_display(data: &[u8], expected: &'static str) { + test_display_under(&InstDecoder::default(), data, expected); +} + +fn test_display_under(decoder: &InstDecoder, data: &[u8], expected: &'static str) { + let mut hex = String::new(); + for b in data { + write!(hex, "{:02x}", b).unwrap(); + } + match decoder.decode(data.into_iter().map(|x| *x)) { + Ok(instr) => { + let text = format!("{}", instr); + assert!( + text == expected, + "display error for {}:\n decoded: {:?}\n displayed: {}\n expected: {}\n", + hex, + instr, + text, + expected + ); + // while we're at it, test that the instruction is as long, and no longer, than its + // input + assert_eq!((0u16.wrapping_offset(instr.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected); + }, + Err(e) => { + assert!(false, "decode error ({}) for {}:\n expected: {}\n", e, hex, expected); + } + } +} + +#[test] +fn test_invalid_ops() { + test_invalid(&[0xd3]); + test_invalid(&[0xdb]); + test_invalid(&[0xdd]); + test_invalid(&[0xe3]); + test_invalid(&[0xe4]); + test_invalid(&[0xeb]); + test_invalid(&[0xec]); + test_invalid(&[0xed]); + test_invalid(&[0xf4]); + test_invalid(&[0xfc]); + test_invalid(&[0xfd]); +} + +#[test] +fn test_ld_decode() { + test_display(&[0x6f], "ld l, a"); + test_display(&[0x6e], "ld l, [hl]"); + test_display(&[0x32], "ld [hl-], a"); + test_display(&[0x3a], "ld a, [hl-]"); + test_display(&[0x73], "ld [hl], e"); + test_display(&[0x74], "ld [hl], h"); + test_display(&[0x67], "ld h, a"); + test_display(&[0x79], "ld a, c"); + test_display(&[0x52], "ld d, d"); + test_display(&[0x64], "ld h, h"); + test_display(&[0x0e, 0x75], "ld c, $75"); + test_display(&[0x3e, 0x75], "ld a, $75"); + test_display(&[0x01, 0x99, 0x88], "ld bc, $8899"); + test_display(&[0x11, 0x99, 0x88], "ld de, $8899"); + test_display(&[0x21, 0x99, 0x88], "ld hl, $8899"); + test_display(&[0x31, 0x99, 0x88], "ld sp, $8899"); + test_display(&[0xea, 0x34, 0x12], "ld [$1234], a"); +} + +#[test] +fn test_push_pop() { + test_display(&[0xf1], "pop af"); + test_display(&[0xe1], "pop hl"); + test_display(&[0xe5], "push hl"); +} + +#[test] +fn test_control_flow() { + test_display(&[0xc3, 0x10, 0x20], "jp $2010"); // should this be relative? + test_display(&[0xda, 0x10, 0x20], "jp C, $2010"); // should this be relative? + test_display(&[0x20, 0x33], "jr nZ, $+$33"); + test_display(&[0xd9], "reti"); + test_display(&[0xc9], "ret"); + test_display(&[0x00], "nop"); + test_display(&[0xcd, 0x44, 0x66], "call $6644"); // not relative, right + test_display(&[0xfe, 0x10], "cp $10"); + test_display(&[0xb8], "cp b"); +} + +#[test] +fn test_arithmetic() { + test_display(&[0xce, 0x40], "adc $40"); + test_display(&[0x19], "add hl, de"); + test_display(&[0x81], "add c"); + test_display(&[0x83], "add e"); + test_display(&[0x88], "adc b"); + test_display(&[0x89], "adc c"); + test_display(&[0x99], "sbc c"); + test_display(&[0x9d], "sbc l"); + test_display(&[0x91], "sub c"); + test_display(&[0x0c], "inc c"); + test_display(&[0x0d], "dec c"); + test_display(&[0x2d], "dec l"); +} + +#[test] +fn test_bitwise() { + test_display(&[0xb5], "or l"); + test_display(&[0xf6, 0x10], "or $10"); + test_display(&[0xcb, 0x71], "bit 6, c"); + test_display(&[0xaf], "xor a"); + test_display(&[0x2b], "dec hl"); + test_display(&[0x34], "inc [hl]"); +} + +#[test] +fn test_misc() { + test_display(&[0x37], "scf"); + test_display(&[0x10], "stop"); +} |