diff options
27 files changed, 1752 insertions, 272 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b98a238..61db9ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,35 @@ This is the changelog of Pollex. See `"README.md"` for more information. +## 0.5.0 + +* Document `assert_or_err` +* Add docs logo +* Restructure tests +* Bump minor +* Update lints +* Add new errors: `IllegalFlag`, `IllegalShifter`, `InvalidOpcode`, `UnknownMnemonic` +* Update readme +* Update documentation +* Improve diagnostics + +### Arm32 + +* Encode more instructions for Thumb +* Make `InstructionCodec::encode_thumb` return tuple +* Add new instructions: `Test`, `TestEquivalence`, `UnsignedSaturate`, `PreloadData`, `BranchLinkExchange`, `Swap`, `Load`, `Store` +* Rework `Flag` +* Add `Address` type +* Add `as_register` method to `Shifter` +* Add `skip_words`, `skip_halfwords`, and `skib_bytes` methods to `InstructionCodec` +* Add `from_register` method to `Shifter` +* Add Thumb decoding to `InstructionCodec` +* Add `from_u8` method to `Predicate` +* Add `from_u8` method to `Register` +* Implement `FromStr` for `Instruction` +* Add new `Sflag`, `Bflag`, and `Tflag` flags +* Add `seek_to` method to `InstructionCodec` + ## 0.4.0 * Bump minor version @@ -1,6 +1,6 @@ [package] name = "pollex" -version = "0.4.0" +version = "0.5.0" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Arm instruction manipulator." @@ -18,7 +18,6 @@ bool_to_int_with_if = "warn" borrow_as_ptr = "forbid" branches_sharing_code = "warn" cast_lossless = "warn" -cast_possible_wrap = "warn" cast_ptr_alignment = "forbid" checked_conversions = "warn" clear_with_drain = "warn" @@ -101,7 +100,6 @@ option_as_ref_cloned = "warn" option_if_let_else = "warn" option_option = "deny" or_fun_call = "deny" -panic_in_result_fn = "deny" path_buf_push_overwrite = "deny" pattern_type_mismatch = "deny" ptr_as_ptr = "forbid" @@ -1,9 +1,82 @@ -# About +# Pollex [Pollex](https://crates.io/crates/pollex/) is a Rust-written library for manipulating instructions of Arm ISAs. See [Docs.rs](https://docs.rs/pollex/) for documentation. +## Support + +Pollex supports encoding of instructions to both Arm and Thumb on Arm32 targets. +Arm64 support is planned. + +## Usage + +Instructions can be created directly using the `Instruction` type: + +```rs +use pollex::arm32::{ + Instruction, + Predicate, + Register, + Sflag, + Shifter, +}; + +// MOVS r0, r1 +let instr = Instruction::Move { + predicate: Predicate::Always, + destination: Register::R0, + source: Shifter::from_register(Register::R1), + s: Sflag::On, +}; +``` + +Instructions can also be parsed from strings: + +```rs +use pollex::arm32::{ + Instruction, + Predicate, + Register, + Sflag, + Shifter, +}; + +let instr: Instruction = "CPY r0, r1".parse()?; + +// Is equivalent to: + +let instr = Instruction::Move { + predicate: Predicate::Always, + destination: Register::R0, + source: Shifter::from_register(Register::R1), + s: Sflag::Off, +}; + +# Ok::<(), Box<dyn std::error::Error>>(()) +``` + +But do note that the latter is currently **not** implemented. + +Instructions can be encoded to both Arm and Thumb using the `InstructionCodec` type: + +```rs +use pollex::arm32::{Instruction, InstructionCodec}; + +let instr: Instruction = "BX lr".parse()?; + +let mut codec = InstructionCodec::new(); + +let arm_opcode = codec.encode_arm(instr)?; +let thumb_opcode = codec.encode_thumb(instr)?; + +assert_eq!(arm_opcode, 0b11100001_00101111_11111111_00011110); +assert_eq!(thumb_opcode.0, 0b01000111_01110000); +assert_eq!(thumb_opcode.1, None); + +# Ok::<(), Box<dyn std::error::Error>>(()) +``` + ## Copyright & Licensing Copyright 2024 Gabriel Bjørnager Jensen. diff --git a/pollex-monochrome.svg b/pollex-monochrome.svg new file mode 100644 index 0000000..26cd119 --- /dev/null +++ b/pollex-monochrome.svg @@ -0,0 +1,34 @@ +<!-- Copyright 2024 Gabriel Bjørnager Jensen. --> + +<!-- + This work is licensed under Creative Commons At- + tribution-NoDerivatives 4.0 International. To + view a copy of this license, visit: + + <https://creativecommons.org/licenses/by-nd/4.0/> +--> + +<svg height="96" width="96" xmlns="http://www.w3.org/2000/svg"> + <!-- gradients: --> + + <!-- clips: --> + + <!-- masks: --> + + <mask id="z"> + <rect fill="white" height="96" rx="16" width="96" x="0" y="0" /> + + <polygon fill="black" points="16,16 71,16 71,34 32,34 32,16 32,40 16,40" /> + <circle cx="71" cy="25" fill="black" r="9" /> + + <polygon fill="white" points="32,22 61,22 61,28 32,28" /> + <circle cx="61" cy="25" fill="white" r="3" /> + + <polygon fill="black" points="16,56 32,56 80,80 64,80" /> + <polygon fill="black" points="64,56 80,56 32,80 16,80" /> + </mask> + + <!-- fills: --> + + <rect fill="#FFFFFF" mask="url(#z)" height="96" width="96" x="0" y="0" /> +</svg> diff --git a/src/arm32/address/mod.rs b/src/arm32/address/mod.rs new file mode 100644 index 0000000..5990d56 --- /dev/null +++ b/src/arm32/address/mod.rs @@ -0,0 +1,52 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. + +use crate::arm32::{Register, Shifter}; + +use core::fmt::{Display, Formatter}; + +/// An address operand. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Address { + ImmediateOffset { base: Register, source: i32 }, + + RegisterOffset { base: Register, source: Register }, + + ScaledRegisterOffset { base: Register, source: Register, shift: Shifter }, + +} + +impl Display for Address { + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { + use Address::*; + + match *self { + ImmediateOffset { base, source } + => write!(f, "[{base}, #{source}]"), + + RegisterOffset { base, source } + => write!(f, "[{base}, {source}]"), + + ScaledRegisterOffset { base, source, shift } + => write!(f, "[{base}, {source}, {shift}]"), + } + } +} diff --git a/src/arm32/flag/mod.rs b/src/arm32/flag/mod.rs index fe564e4..ad9c453 100644 --- a/src/arm32/flag/mod.rs +++ b/src/arm32/flag/mod.rs @@ -26,78 +26,108 @@ use core::fmt::Display; -/// A flag. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[repr(u8)] -pub enum Flag<const C: char> { - Off = 0x0, - On = 0x1, -} +macro_rules! define_flag { + { + vis: $vis:vis, + name: $name:ident, + symbol: $symbol:literal, + doc: $doc:literal $(,)? + } => { + #[doc = $doc] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(u8)] + $vis enum $name { + Off = 0x0, + On = 0x1, + } -impl<const C: char> Flag<C> { - /// Checks if the flag is off. - #[inline(always)] - #[must_use] - pub const fn is_off(self) -> bool { self as u8 == Self::Off as u8 } + impl $name { + /// Checks if the flag is on. + #[inline(always)] + #[must_use] + pub const fn is_on(self) -> bool { self as u8 == Self::On as u8 } - /// Checks if the flag is on. - #[inline(always)] - #[must_use] - pub const fn is_on(self) -> bool { self as u8 == Self::On as u8 } -} + /// Checks if the flag is off. + #[inline(always)] + #[must_use] + pub const fn is_off(self) -> bool { self as u8 == Self::Off as u8 } + } + + impl Display for $name { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + const SYMBOL: &str = $symbol; -impl<const C: char> Display for Flag<C> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - if self.is_on() { - write!(f, "{C}")?; + if self.is_on() { write!(f, "{SYMBOL}")? }; + + Ok(()) + } } - Ok(()) - } -} + impl From<bool> for $name { + #[inline(always)] + fn from(value: bool) -> Self { + if value { + Self::On + } else { + Self::Off + } + } + } -impl<const C: char> From<bool> for Flag<C> { - #[inline(always)] - fn from(value: bool) -> Self { - if value { - Self::On - } else { - Self::Off + impl From<$name> for bool { + #[inline(always)] + fn from(value: $name) -> Self { value.is_on() } } - } -} -impl<const C: char> From<Flag<C>> for bool { - #[inline(always)] - fn from(value: Flag<C>) -> Self { value.is_on() } -} + impl From<$name> for u128 { + #[inline(always)] + fn from(value: $name) -> Self { Self::from(value.is_on()) } + } -impl<const C: char> From<Flag<C>> for u128 { - #[inline(always)] - fn from(value: Flag<C>) -> Self { Self::from(value.is_on()) } -} + impl From<$name> for u16 { + #[inline(always)] + fn from(value: $name) -> Self { Self::from(value.is_on()) } + } -impl<const C: char> From<Flag<C>> for u16 { - #[inline(always)] - fn from(value: Flag<C>) -> Self { Self::from(value.is_on()) } -} + impl From<$name> for u32 { + #[inline(always)] + fn from(value: $name) -> Self { Self::from(value.is_on()) } + } -impl<const C: char> From<Flag<C>> for u32 { - #[inline(always)] - fn from(value: Flag<C>) -> Self { Self::from(value.is_on()) } + impl From<$name> for u64 { + #[inline(always)] + fn from(value: $name) -> Self { Self::from(value.is_on()) } + } + + impl From<$name> for u8 { + #[inline(always)] + fn from(value: $name) -> Self { Self::from(value.is_on()) } + } + + impl From<$name> for usize { + #[inline(always)] + fn from(value: $name) -> Self { Self::from(value.is_on()) } + } + }; } -impl<const C: char> From<Flag<C>> for u64 { - #[inline(always)] - fn from(value: Flag<C>) -> Self { Self::from(value.is_on()) } +define_flag! { + vis: pub, + name: Bflag, + symbol: "B", + doc: "A B flag.\n\nThis indicates binary operations on some memory instructions.\n" } -impl<const C: char> From<Flag<C>> for u8 { - #[inline(always)] - fn from(value: Flag<C>) -> Self { Self::from(value.is_on()) } +define_flag! { + vis: pub, + name: Sflag, + symbol: "S", + doc: "An S flag.\n\nThis indicates setting the status register on some arithmetic instructions.\n" } -impl<const C: char> From<Flag<C>> for usize { - #[inline(always)] - fn from(value: Flag<C>) -> Self { Self::from(value.is_on()) } +define_flag! { + vis: pub, + name: Tflag, + symbol: "T", + doc: "An T flag.\n\nThis indicates translation on some memory instructions.\n" } diff --git a/src/arm32/instruction/display.rs b/src/arm32/instruction/display.rs index f610782..b98294e 100644 --- a/src/arm32/instruction/display.rs +++ b/src/arm32/instruction/display.rs @@ -19,7 +19,7 @@ // fero General Public License along with Pollex. // If not, see <https://www.gnu.org/licenses/>. -use crate::arm32::{Flag, Instruction, Shifter}; +use crate::arm32::{Sflag, Instruction, Shifter}; use core::fmt::Display; @@ -63,17 +63,22 @@ impl Display for Instruction { Branch { predicate, immediate, - } => write!(f, "B{predicate} <#{immediate}>"), + } => write!(f, "B{predicate} #{immediate}"), BranchExchange { predicate, - register, - } => write!(f, "BX{predicate} {register}"), + source, + } => write!(f, "BX{predicate} {source}"), BranchLink { predicate, - immediate, - } => write!(f, "BL{predicate} <#{immediate}>"), + source, + } => write!(f, "BL{predicate} #{source}"), + + BranchLinkExchange { + predicate, + source, + } => write!(f, "BLX{predicate} {source}"), Breakpoint { immediate } => write!(f, "BKPT #{immediate}"), @@ -112,16 +117,80 @@ impl Display for Instruction { s, } => write!(f, "ORR{predicate}{s} {destination}, {base}, {source}"), + Load { + predicate, + register, + address, + b, + t, + } => write!(f, "LDR{predicate}{b}{t} {register}, {address}"), + Move { predicate, destination, source: Shifter::LogicalShiftLeftImmediate { source, shift: 0x0 }, - s: Flag::Off, + s: Sflag::Off, } => write!(f, "CPY{predicate} {destination}, {source}"), Move { predicate, destination, + source: Shifter::ArithmeticShiftRightImmediate { source, shift }, + s, + } => write!(f, "ASR{predicate}{s} {destination}, {source}, #{shift}"), + + Move { + predicate, + destination, + source: Shifter::ArithmeticShiftRightRegister { source, shift }, + s, + } => write!(f, "ASR{predicate}{s} {destination}, {source}, {shift}"), + + Move { + predicate, + destination, + source: Shifter::LogicalShiftLeftImmediate { source, shift }, + s, + } if shift != 0x0 => write!(f, "LSL{predicate}{s} {destination}, {source}, #{shift}"), + + Move { + predicate, + destination, + source: Shifter::LogicalShiftLeftRegister { source, shift }, + s, + } => write!(f, "LSL{predicate}{s} {destination}, {source}, {shift}"), + + Move { + predicate, + destination, + source: Shifter::LogicalShiftRightImmediate { source, shift }, + s, + } => write!(f, "LSR{predicate}{s} {destination}, {source}, #{shift}"), + + Move { + predicate, + destination, + source: Shifter::LogicalShiftRightRegister { source, shift }, + s, + } => write!(f, "LSR{predicate}{s} {destination}, {source}, {shift}"), + + Move { + predicate, + destination, + source: Shifter::RotateRightImmediate { source, shift }, + s, + } => write!(f, "ROR{predicate}{s} {destination}, {source}, #{shift}"), + + Move { + predicate, + destination, + source: Shifter::RotateRightRegister { source, shift }, + s, + } => write!(f, "ROR{predicate}{s} {destination}, {source}, {shift}"), + + Move { + predicate, + destination, source, s, } => write!(f, "MOV{predicate}{s} {destination}, {source}"), @@ -160,6 +229,14 @@ impl Display for Instruction { predicate, destination, base, + source: Shifter::Immediate(0x0), + s, + } => write!(f, "NEG{predicate}{s} {destination}, {base}"), + + ReverseSubtract { + predicate, + destination, + base, source, s, } => write!(f, "RSB{predicate}{s} {destination}, {base}, {source}"), @@ -191,6 +268,14 @@ impl Display for Instruction { immediate, } => write!(f, "SWI{predicate} #{immediate}"), + Store { + predicate, + register, + address, + b, + t, + } => write!(f, "STR{predicate}{b}{t} {register}, {address}"), + Subtract { predicate, destination, @@ -206,6 +291,32 @@ impl Display for Instruction { source, s, } => write!(f, "SBC{predicate}{s} {destination}, {base}, {source}"), + + Swap { + predicate, + register, + address, + b, + } => write!(f, "SWP{predicate}{b} {register}, {address}"), + + UnsignedSaturate { + predicate, + destination, + immediate, + source, + } => write!(f, "USAT{predicate} {destination}, #{immediate}, {source}"), + + Test { + predicate, + lhs, + rhs, + } => write!(f, "TST{predicate} {lhs}, {rhs}"), + + TestEquivalence { + predicate, + lhs, + rhs, + } => write!(f, "TEQ{predicate} {lhs}, {rhs}"), } } } diff --git a/src/arm32/instruction/from_str.rs b/src/arm32/instruction/from_str.rs new file mode 100644 index 0000000..cbb0716 --- /dev/null +++ b/src/arm32/instruction/from_str.rs @@ -0,0 +1,56 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. + +use crate::Error; +use crate::arm32::{ + Instruction, + Predicate, + Register, + Shifter, + Sflag, +}; + +use core::str::FromStr; + +impl FromStr for Instruction { + type Err = Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + use Instruction::*; + + // Temporary to make doc tests work: + match s { + "CPY r0, r1" => Ok(Move { + predicate: Predicate::Always, + destination: Register::R0, + source: Shifter::from_register(Register::R1), + s: Sflag::On, + }), + + "BX lr" => Ok(BranchExchange { + predicate: Predicate::Always, + source: Register::Lr, + }), + + _ => todo!(), + } + } +}
\ No newline at end of file diff --git a/src/arm32/instruction/mod.rs b/src/arm32/instruction/mod.rs index 7cc3718..2a08ff3 100644 --- a/src/arm32/instruction/mod.rs +++ b/src/arm32/instruction/mod.rs @@ -19,16 +19,17 @@ // fero General Public License along with Pollex. // If not, see <https://www.gnu.org/licenses/>. -#[cfg(test)] -mod test; - mod display; +mod from_str; use crate::arm32::{ + Address, + Bflag, Predicate, - Flag, + Sflag, Register, Shifter, + Tflag }; /// An Arm32 instruction. @@ -49,7 +50,7 @@ use crate::arm32::{ /// See [`Shifter`] for more information. /// /// Also note that not all operands can be encoded in Arm instruction sets. -/// Even the largest immediates usually have a limit at (24) significant figures. +/// Even the largest immediates usually have a limit at `24` significant figures. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Instruction { Add { @@ -57,7 +58,7 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, AddCarry { @@ -65,7 +66,7 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, And { @@ -73,7 +74,7 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, BitClear { @@ -81,22 +82,27 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, Branch { predicate: Predicate, - immediate: i32, + immediate: u32, }, BranchExchange { predicate: Predicate, - register: Register, + source: Register, }, BranchLink { predicate: Predicate, - immediate: i32, + source: u32, + }, + + BranchLinkExchange { + predicate: Predicate, + source: Shifter, }, Breakpoint { @@ -126,7 +132,7 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, InclusiveOr { @@ -134,21 +140,29 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, + }, + + Load { + predicate: Predicate, + register: Register, + address: Address, + b: Bflag, + t: Tflag, }, Move { predicate: Predicate, destination: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, MoveNot { predicate: Predicate, destination: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, Multiply { @@ -156,7 +170,7 @@ pub enum Instruction { destination: Register, base: Register, source: Register, - s: Flag<'S'>, + s: Sflag, }, MultiplyAccumulate { @@ -165,7 +179,7 @@ pub enum Instruction { base: Register, source: Register, shift: Register, - s: Flag<'S'>, + s: Sflag, }, Reverse { @@ -179,7 +193,7 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, ReverseSubtractCarry { @@ -187,7 +201,7 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, SaturatingAdd { @@ -209,12 +223,20 @@ pub enum Instruction { immediate: u32, }, + Store { + predicate: Predicate, + register: Register, + address: Address, + b: Bflag, + t: Tflag, + }, + Subtract { predicate: Predicate, destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, }, SubtractCarry { @@ -222,6 +244,32 @@ pub enum Instruction { destination: Register, base: Register, source: Shifter, - s: Flag<'S'>, + s: Sflag, + }, + + Swap { + predicate: Predicate, + register: Register, + address: Address, + b: Bflag, + }, + + UnsignedSaturate { + predicate: Predicate, + destination: Register, + immediate: u32, + source: Shifter, + }, + + Test { + predicate: Predicate, + lhs: Register, + rhs: Shifter, + }, + + TestEquivalence { + predicate: Predicate, + lhs: Register, + rhs: Shifter, }, } diff --git a/src/arm32/instruction_codec/decode_thumb.rs b/src/arm32/instruction_codec/decode_thumb.rs new file mode 100644 index 0000000..fb1e8ee --- /dev/null +++ b/src/arm32/instruction_codec/decode_thumb.rs @@ -0,0 +1,146 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. + +use crate::Result; +use crate::arm32::{ + Instruction, + InstructionCodec, + Predicate, + Register, + Sflag, + Shifter, + ThumbOpcode, +}; + +use core::num::Wrapping; + +impl InstructionCodec { + /// Decodes the given Thumb opcode. + /// + /// # Errors + /// + /// If the provided opcode is invalid (i.e. does not match any known pattern), an [InvalidOpcode](crate::Error::InvalidOpcode) error is returned. + pub fn decode_thumb(&mut self, opcode: ThumbOpcode) -> Result<Instruction> { + use Instruction::*; + + let opcode = opcode.to_u16(); + + macro_rules! match_bits { + ($mask:expr) => {{ + opcode & $mask == $mask + }}; + } + + let instruction = if match_bits!(0b11010000_00000000) { + let predicate = (opcode & 0b00001111_00000000).wrapping_shr(0x8) as u8; + let immediate = u32::from(opcode & 0b00000000_11111111); + + if predicate == 0b1111 { + SoftwareInterrupt { + predicate: Predicate::Always, + immediate, + } + } else { + let predicate = Predicate::from_u8(predicate).unwrap(); + + let mut immediate = (immediate.wrapping_shl(0x18) as i32).wrapping_shl(0x17) as u32; + immediate = immediate.wrapping_add(self.address.0).wrapping_add(0x4); + + Branch { + predicate, + immediate, + } + } + } else if match_bits!(0b00011000_00000000) { + let destination = Register::from_u8((opcode & 0b00000000_00000111) as u8).unwrap(); + let base = Register::from_u8((opcode & 0b00000000_00111000).wrapping_shr(0x3) as u8).unwrap(); + + let subtract = opcode & 0b00000010_00000000 != 0x0; + + let immediate_source = opcode & 0b00000100_00000000 != 0x0; + if immediate_source { + let source = u32::from((opcode & 0b00000001_11000000).wrapping_shr(0x6)); + + if subtract { + Subtract { + predicate: Predicate::Always, + destination, + base, + source: Shifter::Immediate(source), + s: Sflag::On, + } + } else { + Add { + predicate: Predicate::Always, + destination, + base, + source: Shifter::Immediate(source), + s: Sflag::On, + } + } + } else { + // register source + + let source = Register::from_u8((opcode & 0b00000001_11000000).wrapping_shr(0x6) as u8).unwrap(); + + if subtract { + Subtract { + predicate: Predicate::Always, + destination, + base, + source: Shifter::from_register(source), + s: Sflag::On, + } + } else { + Add { + predicate: Predicate::Always, + destination, + base, + source: Shifter::from_register(source), + s: Sflag::On, + } + } + } + } else if match_bits!(0b01000111_00000000) { + let source = Register::from_u8((opcode & 0b00000000_01111000).wrapping_shr(0x3) as u8).unwrap(); + let l = opcode & 0b00000000_10000000 != 0x0; + + if l { + BranchLinkExchange { + predicate: Predicate::Always, + source: Shifter::from_register(source), + } + } else { + BranchExchange { + predicate: Predicate::Always, + source, + } + } + } else { + let _code = (opcode & 0b00011000_00000000).wrapping_shr(0xB) as u8; + + todo!(); + }; + + self.address += Wrapping(ThumbOpcode::SIZE); + Ok(instruction) + } +} diff --git a/src/arm32/instruction_codec/encode_arm.rs b/src/arm32/instruction_codec/encode_arm.rs index 9c63f81..a24d2bd 100644 --- a/src/arm32/instruction_codec/encode_arm.rs +++ b/src/arm32/instruction_codec/encode_arm.rs @@ -27,6 +27,71 @@ use crate::arm32::{ Shifter, }; +use core::num::Wrapping; + +fn add_shifter(mut opcode: u32, shifter: Shifter) -> Result<u32> { + use Shifter::*; + + let get_shift_code = |shifter: Shifter| match shifter { + LogicalShiftLeftImmediate { .. } => 0b00, + LogicalShiftRightImmediate { .. } => 0b01, + ArithmeticShiftRightImmediate { .. } => 0b10, + RotateRightImmediate { .. } => 0b11, + + _ => panic!("cannot get shifter code of `{shifter:?}`"), + }; + + match shifter { + LogicalShiftLeftImmediate { source, shift: 0x0 } + => { + opcode |= source as u32; + } + + | ArithmeticShiftRightImmediate { source, shift } + | LogicalShiftLeftImmediate { source, shift } + | LogicalShiftRightImmediate { source, shift } + | RotateRightImmediate { source, shift } + => { + assert_or_err!(shift != 0x0, Error::IllegalImmediate { reason: "immediate shift cannot be null on arm" }); + + let code = get_shift_code(shifter); + + opcode |= source as u32; + opcode |= code << 0x5; + opcode |= shift << 0x7; + } + + RotateRightExtend { .. } => { + todo!() + } + + | ArithmeticShiftRightRegister { source, .. } + | LogicalShiftLeftRegister { source, .. } + | LogicalShiftRightRegister { source, .. } + | RotateRightRegister { source, .. } + => { + let _code = get_shift_code(shifter); + + opcode |= 0b00000000_00000000_00000000_00010000; + opcode |= source as u32; + } + + Immediate(source) => { + let (source, rotate) = if source <= 0xFF { + (source, 0x00) + } else { + todo!() + }; + + opcode |= 0b00000010_00000000_00000000_00000000; + opcode |= source; + opcode |= rotate << 0x8; + } + } + + Ok(opcode) +} + impl InstructionCodec { /// Encodes the given Arm instruction. /// @@ -45,7 +110,16 @@ impl InstructionCodec { } => { opcode |= 0b00001011_00000000_00000000_00000000; opcode |= (predicate as u32) << 0x1C; - }, + } + + BranchExchange { + predicate, + source, + } => { + opcode |= 0b00000001_00101111_11111111_00010000; + opcode |= source as u32; + opcode |= (predicate as u32) << 0x1C; + } BranchLink { predicate, @@ -53,7 +127,7 @@ impl InstructionCodec { } => { opcode |= 0b00001010_00000000_00000000_00000000; opcode |= (predicate as u32) << 0x1C; - }, + } Breakpoint { immediate, @@ -61,7 +135,7 @@ impl InstructionCodec { opcode |= 0b11100001_00100000_00000000_01110000; opcode |= immediate & 0b00000000_00000000_00000000_00001111; opcode |= (immediate & 0b00000000_00000000_11111111_11110000) << 0x4; - }, + } Move { predicate, @@ -75,7 +149,7 @@ impl InstructionCodec { opcode |= (predicate as u32) << 0x1C; opcode = add_shifter(opcode, source)?; - }, + } SoftwareInterrupt { predicate, @@ -84,80 +158,13 @@ impl InstructionCodec { opcode |= 0b00001111_00000000_00000000_00000000; opcode |= immediate & 0b00000000_11111111_11111111_11111111; opcode |= (predicate as u32) << 0x1C; - }, + } _ => todo!(), } - self.address += ArmOpcode::SIZE; - Ok(opcode.into()) - } -} - -fn add_shifter(mut opcode: u32, shifter: Shifter) -> Result<u32> { - use Shifter::*; - - match shifter { - LogicalShiftLeftImmediate { source, shift: 0x0 } - => { - opcode |= source as u32; - }, - - | ArithmeticShiftRightImmediate { source, shift } - | LogicalShiftLeftImmediate { source, shift } - | LogicalShiftRightImmediate { source, shift } - | RotateRightImmediate { source, shift } - => { - let code = match shifter { - LogicalShiftLeftImmediate { .. } => 0b00, - LogicalShiftRightImmediate { .. } => 0b01, - ArithmeticShiftRightImmediate { .. } => 0b10, - RotateRightImmediate { .. } => 0b11, - - _ => unreachable!(), - }; - - assert_or_err!(shift != 0x0, Error::IllegalImmediate); - - opcode |= source as u32; - opcode |= code << 0x5; - opcode |= shift << 0x7; - }, - - RotateRightExtend { .. } => { - todo!() - }, - - | ArithmeticShiftRightRegister { source, .. } - | LogicalShiftLeftRegister { source, .. } - | LogicalShiftRightRegister { source, .. } - | RotateRightRegister { source, .. } - => { - let _code = match shifter { - LogicalShiftLeftRegister { .. } => 0b00, - LogicalShiftRightRegister { .. } => 0b01, - ArithmeticShiftRightRegister { .. } => 0b10, - RotateRightRegister { .. } => 0b11, - - _ => unreachable!(), - }; + self.skip_words(0x1); - opcode |= 0b00000000_00000000_00000000_00010000; - opcode |= source as u32; - }, - - Immediate { source } => { - let (source, rotate) = if source <= 0xFF { - (source, 0x00) - } else { - todo!() - }; - - opcode |= 0b00000010_00000000_00000000_00000000; - opcode |= source; - opcode |= rotate << 0x8; - }, + Ok(opcode.into()) } - - Ok(opcode) } diff --git a/src/arm32/instruction_codec/encode_thumb.rs b/src/arm32/instruction_codec/encode_thumb.rs index f2fa0b8..b825628 100644 --- a/src/arm32/instruction_codec/encode_thumb.rs +++ b/src/arm32/instruction_codec/encode_thumb.rs @@ -24,69 +24,500 @@ use crate::arm32::{ Instruction, InstructionCodec, Predicate, + Sflag, Shifter, ThumbOpcode, }; +use core::num::Wrapping; + +macro_rules! quick_assert { + (base_equals_destination: $base:expr, $destination:expr) => {{ + assert_or_err!($base == $destination, Error::IllegalRegister { reason: "base must also be destination on thumb" }); + }}; + + (low_register: $register:expr) => {{ + assert_or_err!($register.is_low(), Error::IllegalRegister { reason: "cannot encode low register on thumb" }); + }}; + + (predicate_always: $predicate:expr) => {{ + assert_or_err!($predicate == Predicate::Always, Error::IllegalPredicate { reason: "must be `AL` on thumb" }); + }}; + + (s_flag_on: $flag:expr) => {{ + assert_or_err!($flag.is_on(), Error::IllegalFlag { reason: "s flag must be on on thumb" }); + }}; + + (source_equals_destination: $source:expr, $destination:expr) => {{ + assert_or_err!($source == $destination, Error::IllegalRegister { reason: "source must also be destination on thumb" }); + }}; +} + +#[inline] +const fn encode_shift_immediate(shift: u32) -> Result<u32> { + match shift { + 0x0 => Err(Error::IllegalImmediate { reason: "immediate shifts cannot be null on thumb" }), + + 0x1..=0x1F => Ok(shift), + + 0x20 => Ok(0x0), + + _ => Err(Error::IllegalImmediate { reason: "immediate shifts cannot be greater than (32) on thumb" }), + } +} + impl InstructionCodec { /// Encodes the given Thumb instruction. /// /// # Errors /// /// If the given instruction cannot be encoded for Thumb, an error is returned. - pub fn encode_thumb(&mut self, instruction: Instruction) -> Result<ThumbOpcode> { + pub fn encode_thumb(&mut self, instruction: Instruction) -> Result<(ThumbOpcode, Option<ThumbOpcode>)> { use Instruction::*; - let mut opcode = 0b00000000_00000000_u16; + let mut opcode = (0b00000000_00000000_u16, 0b00000000_00000000_u16); + let mut has_opcode1 = false; match instruction { - Move { +// Add { +// predicate, +// destination, +// base, +// source, +// s, +// } => { +// +// } + + AddCarry { + predicate, + destination, + base, + source, + s, + } => { + let source = source.as_register()?; + + quick_assert!(predicate_always: predicate); + quick_assert!(base_equals_destination: base, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000001_01000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + And { + predicate, + destination, + base, + source, + s, + } => { + let source = source.as_register()?; + + quick_assert!(predicate_always: predicate); + quick_assert!(base_equals_destination: base, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000000_00000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + BitClear { + predicate, + destination, + base, + source, + s, + } => { + let source = source.as_register()?; + + quick_assert!(predicate_always: predicate); + quick_assert!(base_equals_destination: base, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000011_10000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + Branch { + predicate, + immediate, + } => { + let offset = (Wrapping(immediate) - self.address - Wrapping(0x4)).0 as i32; + + if predicate == Predicate::Always { + assert_or_err!(offset >= -0x800, Error::IllegalImmediate { reason: "cannot enocde offset larger than (-2048) on thumb" }); + assert_or_err!(offset <= 0x7FE, Error::IllegalImmediate { reason: "cannot enocde offset larger than (2046) on thumb" }); + + let offset = offset as u32; + + opcode.0 |= 0b11100000_00000000; + opcode.0 |= ((offset & 0b00001111_11111111).wrapping_shr(0x1)) as u16; + } else { + opcode.0 |= 0b11010000_00000000; + opcode.0 |= (predicate as u16).wrapping_shl(0x8); + opcode.0 |= ((offset & 0b00000001_11111111).wrapping_shr(0x1)) as u16; + } + } + + BranchExchange { + predicate, + source, + } => { + quick_assert!(predicate_always: predicate); + + opcode.0 |= 0b01000111_00000000; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + BranchLink { + predicate, + source, + } => { + quick_assert!(predicate_always: predicate); + + let offset = (Wrapping(source) - self.address - Wrapping(0x4)).0 as i32; + + assert_or_err!(offset >= -0x00400000, Error::IllegalImmediate { reason: "cannot encode offset larger than (-4194304) on thumb" }); + assert_or_err!(offset <= 0x003FFFFE, Error::IllegalImmediate { reason: "cannot encode offset larger than (4194302) on thumb" }); + assert_or_err!(offset % 0x2 == 0x0, Error::IllegalImmediate { reason: "cannot encode uneven offset on thumb" }); + + let offset = offset as u32; + + opcode.0 |= 0b11110000_00000000; + opcode.0 |= ((offset & 0b00000000_00111111_11110000_00000000).wrapping_shr(0xC)) as u16; + + opcode.1 |= 0b11111000_00000000; + opcode.1 |= ((offset & 0b00000000_00000000_00001111_11111110).wrapping_shr(0x1)) as u16; + + has_opcode1 = true; + } + + BranchLinkExchange { + predicate, + source, + } => { + quick_assert!(predicate_always: predicate); + + let source = source.as_register() + .map_err(|_| Error::IllegalShifter { reason: "can only encode registers on thumb" })?; + + opcode.0 |= 0b01000111_10000000; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + Breakpoint { + immediate, + } => { + assert_or_err!(immediate <= 0xFF, Error::IllegalImmediate { reason: "cannot encode larger than (255) on thumb" }); + + opcode.0 |= 0b10111110_00000000; + opcode.0 |= immediate as u16; + } + + ExclusiveOr { predicate, destination, + base, source, s, } => { - assert_or_err!(predicate == Predicate::Always, Error::IllegalPredicate); + let source = source.as_register()?; + + quick_assert!(predicate_always: predicate); + quick_assert!(base_equals_destination: base, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000000_01000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + InclusiveOr { + predicate, + destination, + base, + source, + s, + } => { + let source = source.as_register()?; + + quick_assert!(predicate_always: predicate); + quick_assert!(base_equals_destination: base, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000011_00000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + Move { + predicate, + destination, + source: Shifter::ArithmeticShiftRightImmediate { source, shift }, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(source_equals_destination: source, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + let shift = encode_shift_immediate(shift)?; + + opcode.0 |= 0b00010000_00000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + opcode.0 |= (shift as u16).wrapping_shl(0x6); + } + + Move { + predicate, + destination, + source: Shifter::ArithmeticShiftRightRegister { source, shift }, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(source_equals_destination: source, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000001_00000000; + opcode.0 |= destination as u16; + opcode.0 |= (shift as u16).wrapping_shl(0x3); + } + + Move { + predicate, + destination, + source: Shifter::LogicalShiftLeftImmediate { source, shift }, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(source_equals_destination: source, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + // Remember that LSL does takes null shifts. + assert_or_err!(shift <= 0x1F, Error::IllegalImmediate { reason: "shift must be at most (31)" }); + + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + opcode.0 |= (shift as u16).wrapping_shl(0x6); + } + + Move { + predicate, + destination, + source: Shifter::LogicalShiftLeftRegister { source, shift }, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(source_equals_destination: source, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000000_10000000; + opcode.0 |= destination as u16; + opcode.0 |= (shift as u16).wrapping_shl(0x3); + } + + Move { + predicate, + destination, + source: Shifter::LogicalShiftRightImmediate { source, shift }, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(source_equals_destination: source, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); - if s.is_on() { - if let Shifter::Immediate { source } = source { - assert_or_err!(destination.is_low(), Error::IllegalRegister); - assert_or_err!(source <= 0xFF, Error::IllegalRegister); + let shift = encode_shift_immediate(shift)?; - opcode |= 0b00100000_00000000; - opcode |= source as u16; - opcode |= (destination as u16) << 0x8; + opcode.0 |= 0b00001000_00000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + opcode.0 |= (shift as u16).wrapping_shl(0x6); + } - } else if let Shifter::LogicalShiftLeftImmediate { source, shift: 0x0 } = source { - assert_or_err!(destination.is_low(), Error::IllegalRegister); - assert_or_err!(source.is_low(), Error::IllegalRegister); + Move { + predicate, + destination, + source: Shifter::LogicalShiftRightRegister { source, shift }, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(source_equals_destination: source, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000000_11000000; + opcode.0 |= destination as u16; + opcode.0 |= (shift as u16).wrapping_shl(0x3); + } + + Move { + predicate, + destination, + source: Shifter::RotateRightRegister { source, shift }, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(source_equals_destination: source, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000001_11000000; + opcode.0 |= destination as u16; + opcode.0 |= (shift as u16).wrapping_shl(0x3); + } + + Move { + predicate, + destination, + source, + s: Sflag::Off, + } => { + quick_assert!(predicate_always: predicate); - opcode |= 0b00100000_00000000; - opcode |= destination as u16; - opcode |= (source as u16) << 0x3; - } else { - return Err(Error::IllegalInstruction); - } - } else if let Shifter::LogicalShiftLeftImmediate { source, shift: 0x0 } = source { - opcode |= 0b01000110_00000000; + if let Ok(source) = source.as_register() { + opcode.0 |= 0b01000110_00000000; let h0 = destination.is_high(); let h1 = source.is_high(); - opcode |= destination as u16 & 0b00000000_00000111; - opcode |= (source as u16 & 0b00000000_00000111) << 0x3; - opcode |= u16::from(h0) << 0x6; - opcode |= u16::from(h1) << 0x7; + opcode.0 |= destination as u16 & 0b00000000_00000111; + opcode.0 |= (source as u16 & 0b00000000_00000111).wrapping_shl(0x3); + opcode.0 |= u16::from(h0).wrapping_shl(0x6); + opcode.0 |= u16::from(h1).wrapping_shl(0x7); } else { - // TODO: Shifters &c. - todo!(); + return Err(Error::IllegalShifter { reason: "can only encode registers with s flag off" }); } } - _ => return Err(Error::IllegalInstruction), + Move { + predicate, + destination, + source, + s: Sflag::On, + } => { + quick_assert!(predicate_always: predicate); + + if let Shifter::Immediate(source) = source { + quick_assert!(low_register: destination); + + assert_or_err!(source <= 0xFF, Error::IllegalImmediate { reason: "cannot encode larger than (255) on thumb" }); + + opcode.0 |= 0b00100000_00000000; + opcode.0 |= source as u16; + opcode.0 |= (destination as u16).wrapping_shl(0x8); + } else if let Ok(source) = source.as_register() { + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + + opcode.0 |= 0b00100000_00000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } else { + return Err(Error::IllegalShifter { reason: "not encodable on thumb" }); + } + } + + MoveNot { + predicate, + destination, + source, + s, + } => { + let source = source.as_register()?; + + quick_assert!(predicate_always: predicate); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000011_11000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + Multiply { + predicate, + destination, + base, + source, + s, + } => { + quick_assert!(predicate_always: predicate); + quick_assert!(base_equals_destination: base, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000011_01000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + SoftwareInterrupt { + predicate, + immediate, + } => { + quick_assert!(predicate_always: predicate); + + assert_or_err!(immediate <= 0xFF, Error::IllegalImmediate { reason: "cannot encode larger than (255) on thumb" }); + + opcode.0 |= 0b11011111_00000000; + opcode.0 |= immediate as u16; + } + + SubtractCarry { + predicate, + destination, + base, + source, + s, + } => { + let source = source.as_register()?; + + quick_assert!(predicate_always: predicate); + quick_assert!(base_equals_destination: base, destination); + quick_assert!(low_register: destination); + quick_assert!(low_register: source); + quick_assert!(s_flag_on: s); + + opcode.0 |= 0b01000000_11000000; + opcode.0 |= destination as u16; + opcode.0 |= (source as u16).wrapping_shl(0x3); + } + + _ => return Err(Error::IllegalInstruction { reason: "not supported on thumb" } ), } - self.address += ThumbOpcode::SIZE; - Ok(opcode.into()) + let opcode_count = 0x1 + u32::from(has_opcode1); + self.skip_halfwords(opcode_count); + + Ok((opcode.0.into(), has_opcode1.then_some(opcode.1.into()))) } } diff --git a/src/arm32/instruction_codec/mod.rs b/src/arm32/instruction_codec/mod.rs index 841d335..1780c49 100644 --- a/src/arm32/instruction_codec/mod.rs +++ b/src/arm32/instruction_codec/mod.rs @@ -19,9 +19,7 @@ // fero General Public License along with Pollex. // If not, see <https://www.gnu.org/licenses/>. -#[cfg(test)] -mod test; - +mod decode_thumb; mod encode_arm; mod encode_thumb; @@ -29,8 +27,8 @@ use core::num::Wrapping; /// Codec for encoding and decoding instruction. /// -/// Arm instructions can be encoded/decoded using the [`encode_arm`](InstructionCodec::encode_arm) and `decode_arm` (soon). -/// Thumb instruction will similarly be manipulated using [`encode_thumb`](InstructionCodec::encode_thumb) and `decode_thumb`. +/// Arm instructions can be encoded/decoded using the [`encode_arm`](InstructionCodec::encode_arm) and (soon) `decode_arm`. +/// Thumb instruction will similarly be manipulated using [`encode_thumb`](InstructionCodec::encode_thumb) and [`decode_thumb`](InstructionCodec::decode_thumb). /// /// This structure keeps track of the adress at which instructions are to be placed (see *Rationale*). /// If encoding causes this internal address to go past `0xFFFFFFFF`, the value is safely wrapped to the origin (i.e. `0x00000000`). @@ -47,15 +45,36 @@ pub struct InstructionCodec { } impl InstructionCodec { + /// Constructs a new codec at the origin. + #[inline(always)] + #[must_use] + pub const fn new() -> Self { Self::new_at(0x00000000) } + /// Constructs a new codec with a given starting address. #[inline(always)] #[must_use] pub const fn new_at(address: u32) -> Self { Self { address: Wrapping(address) } } + + /// Sets the internal address to the provided one. + #[inline(always)] + pub fn seek_to(&mut self, address: u32) { self.address = Wrapping(address) } + + /// Skips the given ammount of bytes. + #[inline(always)] + pub fn skip_bytes(&mut self, count: u32) { self.address += Wrapping(count) } + + /// Skips the given ammount of halfwords. + #[inline(always)] + pub fn skip_halfwords(&mut self, count: u32) { self.address += Wrapping(count) * Wrapping(0x2) } + + /// Skips the given ammount of words. + #[inline(always)] + pub fn skip_words(&mut self, count: u32) { self.address += Wrapping(count) * Wrapping(0x4) } } impl Default for InstructionCodec { #[inline(always)] - fn default() -> Self { Self::new_at(0x00000000) } + fn default() -> Self { Self::new() } } diff --git a/src/arm32/mod.rs b/src/arm32/mod.rs index 3451841..cab2e4c 100644 --- a/src/arm32/mod.rs +++ b/src/arm32/mod.rs @@ -24,6 +24,7 @@ //! This includes T variants of Arm32. use crate::use_mod; +use_mod!(pub address); use_mod!(pub arm_opcode); use_mod!(pub flag); use_mod!(pub instruction); diff --git a/src/arm32/predicate/mod.rs b/src/arm32/predicate/mod.rs index 0b761d9..66b6534 100644 --- a/src/arm32/predicate/mod.rs +++ b/src/arm32/predicate/mod.rs @@ -20,6 +20,7 @@ // If not, see <https://www.gnu.org/licenses/>. use core::fmt::Display; +use core::mem::transmute; /// An instruction predicate (condition code). /// @@ -48,6 +49,22 @@ pub enum Predicate { //Never = 0b1111, } +impl Predicate { + /// Converts the provided byte into a register identifier. + /// If the byte's value is not a valid predicate, [`None`] is returned. + /// + /// This conversion is valid for all values less than or equal (14). + #[inline] + #[must_use] + pub const fn from_u8(value: u8) -> Option<Self> { + if value <= 0b1110 { + Some(unsafe { transmute::<u8, Self>(value) }) + } else { + None + } + } +} + impl Display for Predicate { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use Predicate::*; diff --git a/src/arm32/register/mod.rs b/src/arm32/register/mod.rs index 0013c46..0c86093 100644 --- a/src/arm32/register/mod.rs +++ b/src/arm32/register/mod.rs @@ -19,16 +19,31 @@ // fero General Public License along with Pollex. // If not, see <https://www.gnu.org/licenses/>. +use alloc::borrow::ToOwned; + use crate::Error; use core::fmt::Display; +use core::mem::transmute; use core::str::FromStr; /// An Arm register. /// /// Some opcodes can only encode specific registers. -/// For example, many Thumb instructions only accept registers from `r0` to `r7` (the so-called *low* registers). -/// Other opcodes only encode a single register (which is then inferred from the opcode in question). +/// And yet other opcodes only encode a single register (which is then inferred from the opcode in question). +/// +/// # Low registers +/// +/// The registers whose identifiers can fit into `3` bits (i.e. registers from `r0` to `r7`, inclusive) are called *low* registers. +/// +/// Some instructions (primarily on Thumb) can only encode low registers. +/// In the case of Thumb, a few instructions can be used to move values to and from low registers. +/// +/// # High registers +/// +/// In contrast to low registers, registers that can only be addressed using `4` bíts are called *high registers*. +/// +/// Some instructions are pedantic about combining both low and high registers. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] #[repr(u8)] pub enum Register { @@ -51,6 +66,20 @@ pub enum Register { } impl Register { + /// Converts the provided byte into a register identifier. + /// If the byte's value is not a valid identifier, [`None`] is returned. + /// + /// This conversion is valid for all `4`-bit values. + #[inline] + #[must_use] + pub const fn from_u8(value: u8) -> Option<Self> { + if value <= 0b1111 { + Some(unsafe { transmute::<u8, Self>(value) }) + } else { + None + } + } + /// Checks if the register is a low register. /// /// That is, it is any of `r0`, `r1`, `r2`, `r3`, `r4`, `r5`, `r6`, or `r7` -- or any alias herof. @@ -166,7 +195,7 @@ impl FromStr for Register { | "r15" => Ok(Pc), - _ => Err(Error::UnknownRegister) + _ => Err(Error::UnknownRegister(s.to_owned())) } } } diff --git a/src/arm32/shifter/mod.rs b/src/arm32/shifter/mod.rs index ae0b1f9..d802e95 100644 --- a/src/arm32/shifter/mod.rs +++ b/src/arm32/shifter/mod.rs @@ -19,9 +19,10 @@ // fero General Public License along with Pollex. // If not, see <https://www.gnu.org/licenses/>. +use crate::{Error, Result}; use crate::arm32::Register; -use core::fmt::Display; +use core::fmt::{Display, Formatter}; /// A shifter operand. /// @@ -39,18 +40,65 @@ use core::fmt::Display; /// ADD r0, r1, LSL #2 /// ``` /// -/// In fact, the first example will encode identically to the following: +/// In fact, the mentioned `LSL` instruction will encode identically to the following: /// /// ```as /// MOV r1, r1, LSL #2 /// ``` +/// +/// # Immediates +/// +/// Shifters can store any immediate value with the [`Immediate`](Shifter::Immediate) variant. +/// +/// In theory, these values are limited to *at most* `24` bits on Arm, and to `11` bits on Thumb. +/// In practice, however, these are even further limited -- in some cases down to `3` bits. +/// +/// # Shifts +/// +/// As mentioned, shifters can also incorporate the functionality of other instructions. +/// This is done as *shifts* (hence the name *shifter*), and can use one of the following functions: +/// +/// * Logical shift left --- `LSL` +/// * Logical shift right --- `LSR` +/// * Arithmetic shift right --- `ASR` +/// * Rotate right --- `ROR` +/// * Rotate right extend --- `RRX` +/// +/// With the exception of `RRX`, all of these functions can take a *shift value* -- which sets the ammount of digits to shifts -- from either an immediate or a register. +/// For immediates, this is limited to the range `1` to `32`, inclusive, except for `LSL`, which uses the range `0` to `31` (again inclusive; see also *Registers*). +/// +/// Therefore, the following two examples are somewhat equivalent: +/// +/// ```as +/// ; First example: +/// ADD r0, r0, r1, LSL #1 +/// +/// ; Second example: +/// MOV r2, #1 +/// ADD r0, r0, r1, LSL r2 +/// ``` +/// +/// Of course, the first example has a smaller memory footprint and a (in theory) shorter runtime. +/// +/// ## Registers +/// +/// Whilst lone registers are accepted as shifter operands, they aren't encoded specially. +/// Instead, a single, unshifted register is encoded as itself shifted to the left by zero figures: +/// +/// ```as +/// ; These are identical: +/// RSB r0, r0, r7 +/// RSB r0, r0, r7, LSL #0 +/// ``` +/// +/// This instead makes `LSL` a special case in that it cannot encode shift ammounts of `32` figures -- in contrast to the other shift functions. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Shifter { ArithmeticShiftRightImmediate { source: Register, shift: u32 }, ArithmeticShiftRightRegister { source: Register, shift: Register }, - Immediate { source: u32 }, + Immediate(u32), LogicalShiftLeftImmediate { source: Register, shift: u32 }, @@ -67,54 +115,68 @@ pub enum Shifter { RotateRightRegister { source: Register, shift: Register }, } +impl Shifter { + /// Creates a new shifter from a register. + /// + /// This is identical to creating a [`LogicalShiftLeftImmediate`](Shifter::LogicalShiftLeftImmediate) instance with `source: register` and `shift: 0x0`. + #[inline(always)] + #[must_use] + pub const fn from_register(register: Register) -> Self { + Self::LogicalShiftLeftImmediate { source: register, shift: 0x0 } + } + + /// Collapses the shifter into a single register. + /// + /// # Errors + /// + /// If this shifter does **not** fit the pattern `LogicalShiftLeft { shift: 0x0, .. }`, an [`IllegalShifter`](Error::IllegalShifter) error is returned. + #[inline] + pub const fn as_register(self) -> Result<Register> { + if let Self::LogicalShiftLeftImmediate { source: register, shift: 0x0 } = self { + Ok(register) + } else { + Err(Error::IllegalShifter { reason: "cannot collapse to register" }) + } + } +} + impl Display for Shifter { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { use Shifter::*; match *self { - ArithmeticShiftRightImmediate { source, shift } => { - write!(f, "{source}, ASR #{shift}") - }, + ArithmeticShiftRightImmediate { source, shift } + => write!(f, "{source}, ASR #{shift}"), - ArithmeticShiftRightRegister { source, shift } => { - write!(f, "{source}, ASR {shift}") - }, + ArithmeticShiftRightRegister { source, shift } + => write!(f, "{source}, ASR {shift}"), - Immediate { source } => { - write!(f, "#{source}") - }, + Immediate(source) + => write!(f, "#{source}"), - LogicalShiftLeftImmediate { source, shift: 0x0 } => { - write!(f, "{source}") - }, + LogicalShiftLeftImmediate { source, shift: 0x0 } + => write!(f, "{source}"), - LogicalShiftLeftImmediate { source, shift } => { - write!(f, "{source}, LSL #{shift}") - }, + LogicalShiftLeftImmediate { source, shift } + => write!(f, "{source}, LSL #{shift}"), - LogicalShiftLeftRegister { source, shift } => { - write!(f, "{source}, LSL {shift}") - }, + LogicalShiftLeftRegister { source, shift } + => write!(f, "{source}, LSL {shift}"), - LogicalShiftRightImmediate { source, shift } => { - write!(f, "{source}, LSR #{shift}") - }, + LogicalShiftRightImmediate { source, shift } + => write!(f, "{source}, LSR #{shift}"), - LogicalShiftRightRegister { source, shift } => { - write!(f, "{source}, LSR {shift}") - }, + LogicalShiftRightRegister { source, shift } + => write!(f, "{source}, LSR {shift}"), - RotateRightExtend { source } => { - write!(f, "{source}, RRX") - }, + RotateRightExtend { source } + => write!(f, "{source}, RRX"), - RotateRightImmediate { source, shift } => { - write!(f, "{source}, ROR #{shift}") - }, + RotateRightImmediate { source, shift } + => write!(f, "{source}, ROR #{shift}"), - RotateRightRegister { source, shift } => { - write!(f, "{source}, ROR {shift}") - }, + RotateRightRegister { source, shift } + => write!(f, "{source}, ROR {shift}"), } } } diff --git a/src/arm32/thumb_opcode/mod.rs b/src/arm32/thumb_opcode/mod.rs index c327696..06a73b5 100644 --- a/src/arm32/thumb_opcode/mod.rs +++ b/src/arm32/thumb_opcode/mod.rs @@ -29,7 +29,7 @@ pub struct ThumbOpcode(u16); impl ThumbOpcode { /// The size of any Thumb opcode, in bytes. /// - /// This value is not including `BL` prefixes and suffixes, which count as two opcodes in total. + /// This is **not** including `BL` prefixes and suffixes, which instead count as *two* opcodes. pub const SIZE: u32 = 0x2; /// Creates a new opcode from a primitive. diff --git a/src/error/mod.rs b/src/error/mod.rs index b6c1e24..e561072 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -19,6 +19,7 @@ // fero General Public License along with Pollex. // If not, see <https://www.gnu.org/licenses/>. +use alloc::string::String; use core::fmt::{Display, Formatter}; /// Wrapping of [`core::result::Result`]. @@ -27,42 +28,63 @@ pub type Result<T> = core::result::Result<T, Error>; /// A crate error. #[derive(Clone, Debug)] pub enum Error { + /// The given flag is currently not support. + /// + /// Some Thumb instructions require the S flag to be on, for example. + /// These yield this error if encoded with the S flag is off. + IllegalFlag { reason: &'static str }, + /// The given immediate cannot be encoded. - IllegalImmediate, + IllegalImmediate { reason: &'static str }, /// The given instruction cannot be encoded. /// /// Even valid instructions are not valid in *every* case. /// For example, most shifter operands and instruction predicates are **not** allowed in a Thumb context (with exceptions). - IllegalInstruction, + IllegalInstruction { reason: &'static str }, /// The given instruction predicate cannot be encoded. - IllegalPredicate, + IllegalPredicate { reason: &'static str }, /// The given register can currently not be used. - IllegalRegister, + IllegalRegister { reason: &'static str }, + + /// The given shifter operand cannot be encoded. + IllegalShifter { reason: &'static str }, + + /// The provided opcode could not be decoded. + InvalidOpcode, + + /// Mnemonic is not known. + UnknownMnemonic(String), /// Register name is not known. - UnknownRegister, + UnknownRegister(String), } impl Display for Error { fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { use Error::*; - let message = match *self { - IllegalImmediate => "illegal immediate value", + match *self { + IllegalFlag { reason } => write!(f, "illegal flag value: {reason}"), + + IllegalImmediate { reason } => write!(f, "illegal immediate value: {reason}"), + + IllegalInstruction { reason } => write!(f, "illegal instruction: {reason}"), + + IllegalPredicate { reason } => write!(f, "illegal instruction predicate: {reason}"), - IllegalInstruction => "illegal instruction", + IllegalRegister { reason } => write!(f, "register not permitted here: {reason}"), - IllegalPredicate => "illegal instruction predicate", + IllegalShifter { reason } => write!(f, "shifter operand not permitted here: {reason}"), - IllegalRegister => "register not permitted here", + InvalidOpcode => write!(f, "invalid opcode"), - UnknownRegister => "unknown register", - }; + UnknownMnemonic(ref s) => write!(f, "unknown mnemonic `{s}`"), - f.write_str(message) + UnknownRegister(ref s) => write!(f, "unknown register `{s}`"), + } } } @@ -19,17 +19,105 @@ // fero General Public License along with Pollex. // If not, see <https://www.gnu.org/licenses/>. -//! Arm instruction manipulator. +//! [Pollex](https://crates.io/crates/pollex/) is a Rust-written library for manipulating instructions of Arm ISAs. //! -//! This library is meant to be used by assemblers and disassembler, emulators, etc. -//! That is to leverage encoding and decoding of instructions. +//! # Support //! -//! The goal of this project is to fully implement all Arm32 instruction set architectures, and, hopefully, Arm64 architectures as well. +//! Pollex supports encoding of instructions to both Arm and Thumb on Arm32 targets. +//! Arm64 support is planned. +//! +//! # Usage +//! +//! Instructions can be created directly using the [`Instruction`](arm32::Instruction) type: +//! +//! ``` +//! use pollex::arm32::{ +//! Instruction, +//! Predicate, +//! Register, +//! Sflag, +//! Shifter, +//! }; +//! +//! // MOVS r0, r1 +//! let instr = Instruction::Move { +//! predicate: Predicate::Always, +//! destination: Register::R0, +//! source: Shifter::from_register(Register::R1), +//! s: Sflag::On, +//! }; +//! ``` +//! +//! Instructions can also be parsed from strings: +//! +//! ``` +//! use pollex::arm32::{ +//! Instruction, +//! Predicate, +//! Register, +//! Sflag, +//! Shifter, +//! }; +//! +//! let instr: Instruction = "CPY r0, r1".parse()?; +//! +//! // Is equivalent to: +//! +//! let instr = Instruction::Move { +//! predicate: Predicate::Always, +//! destination: Register::R0, +//! source: Shifter::from_register(Register::R1), +//! s: Sflag::Off, +//! }; +//! +//! # Ok::<(), Box<dyn std::error::Error>>(()) +//! ``` +//! +//! But do note that the latter is currently **not** implemented. +//! +//! Instructions can be encoded to both Arm and Thumb using the [`InstructionCodec`](arm32::InstructionCodec) type: +//! +//! ``` +//! use pollex::arm32::{Instruction, InstructionCodec}; +//! +//! let instr: Instruction = "BX lr".parse()?; +//! +//! let mut codec = InstructionCodec::new(); +//! +//! let arm_opcode = codec.encode_arm(instr)?; +//! let thumb_opcode = codec.encode_thumb(instr)?; +//! +//! assert_eq!(arm_opcode, 0b11100001_00101111_11111111_00011110); +//! assert_eq!(thumb_opcode.0, 0b01000111_01110000); +//! assert_eq!(thumb_opcode.1, None); +//! +//! # Ok::<(), Box<dyn std::error::Error>>(()) +//! ``` +//! +//! # Copyright & Licensing +//! +//! Copyright 2024 Gabriel Bjørnager Jensen. +//! +//! This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +//! +//! This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. +//! +//! You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. +//! +//! # Trademarks +//! +//! Arm and Thumb are registered trademarks or trademarks of Arm Limited (or its subsidiaries or affiliates). + +#![doc(html_logo_url = "https://gitlab.com/bjoernager/pollex/-/raw/master/pollex-monochrome.svg?ref_type=heads")] #![no_std] extern crate alloc; +#[cfg(test)] +#[doc(hidden)] +mod test; + pub mod arm32; pub mod arm64; @@ -43,6 +131,9 @@ pub(in crate) use use_mod; use_mod!(pub error); +/// Asserts the given predicate. +/// +/// In contrast to [`assert`], this macro makes the caller return an error instead of panicking. #[macro_export] macro_rules! assert_or_err { ($predicate:expr, $error:expr) => {{ diff --git a/src/arm32/instruction_codec/test.rs b/src/test/arm32/arm_encode.rs index 0194a33..c16a93f 100644 --- a/src/arm32/instruction_codec/test.rs +++ b/src/test/arm32/arm_encode.rs @@ -20,7 +20,7 @@ // If not, see <https://www.gnu.org/licenses/>. use crate::arm32::{ - Flag, + Sflag, Instruction, InstructionCodec, Predicate, @@ -31,11 +31,11 @@ use crate::arm32::{ use alloc::vec::Vec; #[test] -fn test_arm32_encoder() { +fn test_arm_encode() { let tree = [ Instruction::BranchLink { predicate: Predicate::HigherOrSame, - immediate: 0x1F, + source: 0x1F, }, Instruction::Breakpoint { @@ -51,7 +51,7 @@ fn test_arm32_encoder() { predicate: Predicate::Plus, destination: Register::Pc, source: Shifter::ArithmeticShiftRightImmediate { source: Register::R3, shift: 0x20 }, - s: Flag::On, + s: Sflag::On, }, ]; diff --git a/src/arm32/instruction/test.rs b/src/test/arm32/instruction_display.rs index d9067e9..4f834ef 100644 --- a/src/arm32/instruction/test.rs +++ b/src/test/arm32/instruction_display.rs @@ -21,24 +21,24 @@ use crate::arm32::{ Predicate, - Flag, + Sflag, Instruction, Register, Shifter, }; -use alloc::format; +use alloc::string::ToString; use alloc::vec::Vec; #[test] -fn test_arm32_instruction() { +fn test_instruction_display() { let tree = [ Instruction::Add { predicate: Predicate::GreaterThanOrEqual, destination: Register::R1, base: Register::R2, source: Shifter::RotateRightImmediate { source: Register::R3, shift: 0x2 }, - s: Flag::Off, + s: Sflag::Off, }, Instruction::SaturatingSubtract { @@ -53,7 +53,7 @@ fn test_arm32_instruction() { destination: Register::R7, base: Register::R8, source: Shifter::LogicalShiftLeftImmediate { source: Register::R9, shift: 0x0 }, - s: Flag::On, + s: Sflag::On, }, Instruction::MultiplyAccumulate { @@ -62,19 +62,41 @@ fn test_arm32_instruction() { base: Register::Pc, source: Register::Pc, shift: Register::Lr, - s: Flag::Off, + s: Sflag::Off, }, Instruction::Move { predicate: Predicate::NotEqual, destination: Register::R0, source: Shifter::LogicalShiftLeftImmediate { source: Register::Pc, shift: 0x0 }, - s: Flag::Off, + s: Sflag::Off, + }, + + Instruction::ReverseSubtract { + predicate: Predicate::Always, + destination: Register::R0, + base: Register::R0, + source: Shifter::Immediate(0x0), + s: Sflag::On, + }, + + Instruction::Move { + predicate: Predicate::GreaterThan, + destination: Register::R0, + source: Shifter::LogicalShiftRightImmediate { source: Register::R7, shift: 0x20 }, + s: Sflag::On, + }, + + Instruction::Move { + predicate: Predicate::Always, + destination: Register::R0, + source: Shifter::LogicalShiftLeftImmediate { source: Register::R0, shift: 0x0 }, + s: Sflag::On, }, ]; let mut displays = Vec::with_capacity(tree.len()); - for instruction in tree { displays.push(format!("{instruction}")) } + for instruction in tree { displays.push(instruction.to_string()) } assert_eq!( displays, @@ -83,7 +105,10 @@ fn test_arm32_instruction() { "QSUBLT r4, r5, r6", "ORRS r7, r8, r9", "MLAEQ r0, pc, pc, lr", - "CPYNE r0, pc" + "CPYNE r0, pc", + "NEGS r0, r0", + "LSRGTS r0, r7, #32", + "MOVS r0, r0", ], ); } diff --git a/src/test/arm32/mod.rs b/src/test/arm32/mod.rs new file mode 100644 index 0000000..f7270a9 --- /dev/null +++ b/src/test/arm32/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. + +mod arm_encode; +mod instruction_display; +mod thumb_decode; +mod thumb_encode;
\ No newline at end of file diff --git a/src/test/arm32/thumb_decode.rs b/src/test/arm32/thumb_decode.rs new file mode 100644 index 0000000..eff61d2 --- /dev/null +++ b/src/test/arm32/thumb_decode.rs @@ -0,0 +1,57 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. + +use crate::arm32::{ + Instruction, + InstructionCodec, + Predicate, + Register, +}; + +use alloc::vec::Vec; + +#[test] +fn test_thumb_decode() { + let binary = [ + 0b11011111_10101010, + 0b01000111_01110000, + ]; + + let mut codec = InstructionCodec::new(); + + let mut programme = Vec::new(); + for opcode in binary { programme.push(codec.decode_thumb(opcode.into()).unwrap()) } + + assert_eq!( + programme, + [ + Instruction::SoftwareInterrupt { + predicate: Predicate::Always, + immediate: 0b10101010, + }, + + Instruction::BranchExchange { + predicate: Predicate::Always, + source: Register::Lr, + }, + ], + ) +} diff --git a/src/test/arm32/thumb_encode.rs b/src/test/arm32/thumb_encode.rs new file mode 100644 index 0000000..178b938 --- /dev/null +++ b/src/test/arm32/thumb_encode.rs @@ -0,0 +1,74 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. + +use crate::arm32::{ + Instruction, + InstructionCodec, + Predicate, + Register, + Sflag, + Shifter, +}; + +use alloc::vec::Vec; + +#[test] +fn test_thumb_encode() { + let programme = [ + Instruction::BranchLink { + predicate: Predicate::Always, + source: 0x08000044, + }, + + Instruction::And { + predicate: Predicate::Always, + destination: Register::R0, + base: Register::R0, + source: Shifter::LogicalShiftLeftImmediate { source: Register::R7, shift: 0x0 }, + s: Sflag::On, + }, + + Instruction::Branch { + predicate: Predicate::Always, + immediate: 0x08000008, + }, + ]; + + let mut codec = InstructionCodec::new_at(0x08000000); + + let mut opcodes = Vec::new(); + for instruction in programme { + let opcode = codec.encode_thumb(instruction).unwrap(); + + opcodes.push(opcode.0); + if let Some(opcode) = opcode.1 { opcodes.push(opcode) }; + } + + assert_eq!( + opcodes, + [ + 0b11110000_00000000, + 0b11111000_00100000, + 0b01000000_00111000, + 0b11100000_00000000, + ], + ) +} diff --git a/src/test/arm64/mod.rs b/src/test/arm64/mod.rs new file mode 100644 index 0000000..8336d6c --- /dev/null +++ b/src/test/arm64/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 0000000..d2b26ce --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Pollex. +// +// Pollex is free software: you can redistribute it +// and/or modify it under the terms of the GNU Af- +// fero General Public License as published by the +// Free Software Foundation, either version 3 of +// the License, or (at your option) any later ver- +// sion. +// +// Pollex is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Af- +// fero General Public License along with Pollex. +// If not, see <https://www.gnu.org/licenses/>. + +mod arm32; +mod arm64;
\ No newline at end of file |