#![allow(clippy::manual_range_patterns)]
#![allow(clippy::unusual_byte_groupings)]
const COND_SUFFIX: [&str; 16] = [
"EQ", "NE", "CS", "CC", "MI", "PL", "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "", "NV",
];
pub fn disasm_arm(instr: u32, pc: u32) -> String {
let cond = ((instr >> 28) & 0xF) as usize;
let cond_str = COND_SUFFIX[cond];
if (instr & 0x0FFF_FFF0) == 0x012F_FF10 {
let rm = instr & 0xF;
return format!("BX{} R{}", cond_str, rm);
}
let bits_27_25 = (instr >> 25) & 0x7;
match bits_27_25 {
0b000 | 0b001 => disasm_arm_data_processing(instr, cond_str),
0b010 | 0b011 => disasm_arm_single_data_transfer(instr, cond_str),
0b101 => disasm_arm_branch(instr, pc, cond_str),
0b111 if (instr >> 24) & 0xF == 0xF => {
let comment = instr & 0x00FF_FFFF;
format!("SWI{} #0x{:06X}", cond_str, comment)
}
_ => "<undefined>".to_string(),
}
}
fn disasm_arm_data_processing(instr: u32, cond_str: &str) -> String {
let opcode = ((instr >> 21) & 0xF) as usize;
let s_bit = (instr >> 20) & 1 != 0;
let rn = (instr >> 16) & 0xF;
let rd = (instr >> 12) & 0xF;
let i_bit = (instr >> 25) & 1 != 0;
let mnemonic = [
"AND", "EOR", "SUB", "RSB", "ADD", "ADC", "SBC", "RSC", "TST", "TEQ", "CMP", "CMN", "ORR",
"MOV", "BIC", "MVN",
][opcode];
let is_test = matches!(opcode, 0x8 | 0x9 | 0xA | 0xB);
let s_suffix = if s_bit && !is_test { "S" } else { "" };
let op2 = format_arm_operand2(instr, i_bit);
match opcode {
0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0xC | 0xE => {
format!(
"{}{}{} R{}, R{}, {}",
mnemonic, cond_str, s_suffix, rd, rn, op2
)
}
0x8 | 0x9 | 0xA | 0xB => {
format!("{}{} R{}, {}", mnemonic, cond_str, rn, op2)
}
0xD | 0xF => {
format!("{}{}{} R{}, {}", mnemonic, cond_str, s_suffix, rd, op2)
}
_ => unreachable!(),
}
}
fn format_arm_operand2(instr: u32, i_bit: bool) -> String {
if i_bit {
let imm = instr & 0xFF;
let rotate = ((instr >> 8) & 0xF) * 2;
if rotate == 0 {
format!("#0x{:X}", imm)
} else {
format!("#0x{:X}", imm.rotate_right(rotate))
}
} else {
let rm = instr & 0xF;
let shift_imm = (instr >> 4) & 1 == 0;
let shift_type = (instr >> 5) & 0x3;
let shift_name = ["LSL", "LSR", "ASR", "ROR"][shift_type as usize];
if shift_imm {
let amount = (instr >> 7) & 0x1F;
format_arm_shifted_register(rm, shift_type, shift_name, amount)
} else {
let rs = (instr >> 8) & 0xF;
format!("R{}, {} R{}", rm, shift_name, rs)
}
}
}
fn format_arm_shifted_register(rm: u32, shift_type: u32, shift_name: &str, amount: u32) -> String {
if amount == 0 {
match shift_type {
0 => format!("R{}", rm), 1 | 2 => format!("R{}, {} #32", rm, shift_name), 3 => format!("R{}, RRX", rm), _ => unreachable!(),
}
} else {
format!("R{}, {} #{}", rm, shift_name, amount)
}
}
fn disasm_arm_single_data_transfer(instr: u32, cond_str: &str) -> String {
let i_bit = (instr >> 25) & 1 != 0;
let p_bit = (instr >> 24) & 1 != 0; let u_bit = (instr >> 23) & 1 != 0; let b_bit = (instr >> 22) & 1 != 0; let w_bit = (instr >> 21) & 1 != 0; let l_bit = (instr >> 20) & 1 != 0; let rn = (instr >> 16) & 0xF;
let rd = (instr >> 12) & 0xF;
let op = if l_bit { "LDR" } else { "STR" };
let b_suffix = if b_bit { "B" } else { "" };
let offset = if i_bit {
let rm = instr & 0xF;
let shift_type = (instr >> 5) & 0x3;
let amount = (instr >> 7) & 0x1F;
let shift_name = ["LSL", "LSR", "ASR", "ROR"][shift_type as usize];
let sign = if u_bit { "" } else { "-" };
let rm_part = format_arm_shifted_register(rm, shift_type, shift_name, amount);
format!("{}{}", sign, rm_part)
} else {
let imm = instr & 0xFFF;
let sign = if u_bit { "" } else { "-" };
format!("#{}0x{:X}", sign, imm)
};
if p_bit {
let writeback = if w_bit { "!" } else { "" };
format!(
"{}{}{} R{}, [R{}, {}]{}",
op, cond_str, b_suffix, rd, rn, offset, writeback
)
} else {
format!(
"{}{}{} R{}, [R{}], {}",
op, cond_str, b_suffix, rd, rn, offset
)
}
}
fn disasm_arm_branch(instr: u32, pc: u32, cond_str: &str) -> String {
let link = (instr >> 24) & 1 != 0;
let offset24 = (instr & 0x00FF_FFFF) as i32;
let signed = ((offset24 << 8) >> 8) << 2;
let target = pc.wrapping_add(8).wrapping_add(signed as u32);
let mnemonic = if link { "BL" } else { "B" };
format!("{}{} #0x{:08X}", mnemonic, cond_str, target)
}
pub fn disasm_thumb(instr: u16, pc: u32) -> String {
let top5 = instr >> 11;
match top5 {
0b00000 | 0b00001 | 0b00010 => disasm_thumb_format1(instr),
0b00011 => disasm_thumb_format2(instr),
0b00100 | 0b00101 | 0b00110 | 0b00111 => disasm_thumb_format3(instr),
0b01000 | 0b01001 => {
if instr & 0xFC00 == 0x4000 {
disasm_thumb_format4(instr)
} else if instr & 0xFC00 == 0x4400 {
disasm_thumb_format5(instr)
} else if instr & 0xF800 == 0x4800 {
disasm_thumb_format6(instr, pc)
} else {
"<undefined>".to_string()
}
}
0b10110 | 0b10111 if instr & 0xF600 == 0xB400 => disasm_thumb_format14(instr),
0b11010 | 0b11011 => disasm_thumb_format16(instr, pc),
0b11100 => disasm_thumb_format18(instr, pc),
_ => "<undefined>".to_string(),
}
}
fn disasm_thumb_format1(instr: u16) -> String {
let op = (instr >> 11) & 0x3;
let amount = (instr >> 6) & 0x1F;
let rs = (instr >> 3) & 0x7;
let rd = instr & 0x7;
let mnemonic = ["LSL", "LSR", "ASR"][op as usize];
let display_amount = if amount == 0 && (op == 0b01 || op == 0b10) {
32
} else {
amount
};
format!("{} R{}, R{}, #{}", mnemonic, rd, rs, display_amount)
}
fn disasm_thumb_format2(instr: u16) -> String {
let imm_flag = (instr >> 10) & 1 != 0;
let op_sub = (instr >> 9) & 1 != 0;
let rn_or_imm = (instr >> 6) & 0x7;
let rs = (instr >> 3) & 0x7;
let rd = instr & 0x7;
let mnemonic = if op_sub { "SUB" } else { "ADD" };
if imm_flag {
format!("{} R{}, R{}, #{}", mnemonic, rd, rs, rn_or_imm)
} else {
format!("{} R{}, R{}, R{}", mnemonic, rd, rs, rn_or_imm)
}
}
fn disasm_thumb_format3(instr: u16) -> String {
let op = (instr >> 11) & 0x3;
let rd = (instr >> 8) & 0x7;
let imm = instr & 0xFF;
let mnemonic = ["MOV", "CMP", "ADD", "SUB"][op as usize];
format!("{} R{}, #{}", mnemonic, rd, imm)
}
fn disasm_thumb_format4(instr: u16) -> String {
let op = (instr >> 6) & 0xF;
let rs = (instr >> 3) & 0x7;
let rd = instr & 0x7;
let mnemonic = [
"AND", "EOR", "LSL", "LSR", "ASR", "ADC", "SBC", "ROR", "TST", "NEG", "CMP", "CMN", "ORR",
"MUL", "BIC", "MVN",
][op as usize];
format!("{} R{}, R{}", mnemonic, rd, rs)
}
fn disasm_thumb_format5(instr: u16) -> String {
let op = (instr >> 8) & 0x3;
let h1 = (instr >> 7) & 1;
let h2 = (instr >> 6) & 1;
let rs = ((instr >> 3) & 0x7) | (h2 << 3);
let rd = (instr & 0x7) | (h1 << 3);
match op {
0b00 => format!("ADD R{}, R{}", rd, rs),
0b01 => format!("CMP R{}, R{}", rd, rs),
0b10 => format!("MOV R{}, R{}", rd, rs),
0b11 => format!("BX R{}", rs),
_ => unreachable!(),
}
}
fn disasm_thumb_format6(instr: u16, pc: u32) -> String {
let rd = (instr >> 8) & 0x7;
let imm = (instr & 0xFF) as u32;
let base = (pc.wrapping_add(4)) & !0x2;
let target = base.wrapping_add(imm << 2);
format!("LDR R{}, [PC, #0x{:X}] ; =0x{:08X}", rd, imm << 2, target)
}
fn disasm_thumb_format14(instr: u16) -> String {
let load = (instr >> 11) & 1 != 0;
let extra = (instr >> 8) & 1 != 0;
let reg_list = (instr & 0xFF) as u8;
let mnemonic = if load { "POP" } else { "PUSH" };
let extra_reg = if load { "PC" } else { "LR" };
let mut regs: Vec<String> = (0..8)
.filter(|i| reg_list & (1 << i) != 0)
.map(|i| format!("R{}", i))
.collect();
if extra {
regs.push(extra_reg.to_string());
}
format!("{} {{{}}}", mnemonic, regs.join(", "))
}
fn disasm_thumb_format16(instr: u16, pc: u32) -> String {
let cond = ((instr >> 8) & 0xF) as usize;
if cond == 0xF {
let comment = instr & 0xFF;
return format!("SWI #0x{:02X}", comment);
}
let offset = ((instr & 0xFF) as i8) as i32 * 2;
let target = (pc.wrapping_add(4) as i32).wrapping_add(offset) as u32;
format!("B{} #0x{:08X}", COND_SUFFIX[cond], target)
}
fn disasm_thumb_format18(instr: u16, pc: u32) -> String {
let offset11 = (instr & 0x7FF) as i32;
let signed = ((offset11 << 21) >> 21) << 1;
let target = (pc.wrapping_add(4) as i32).wrapping_add(signed) as u32;
format!("B #0x{:08X}", target)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arm_data_processing_add_immediate() {
let instr = 0xE282_1010;
assert_eq!(disasm_arm(instr, 0), "ADD R1, R2, #0x10");
}
#[test]
fn arm_data_processing_adds_uses_s_suffix() {
let instr = 0xE290_0001;
assert_eq!(disasm_arm(instr, 0), "ADDS R0, R0, #0x1");
}
#[test]
fn arm_data_processing_register_form() {
let instr = 0xE084_3005;
assert_eq!(disasm_arm(instr, 0), "ADD R3, R4, R5");
}
#[test]
fn arm_data_processing_register_with_shift() {
let instr = 0xE081_0202;
assert_eq!(disasm_arm(instr, 0), "ADD R0, R1, R2, LSL #4");
}
#[test]
fn arm_shifter_lsr_zero_renders_as_shift_by_32() {
let instr = 0xE081_0022;
assert_eq!(disasm_arm(instr, 0), "ADD R0, R1, R2, LSR #32");
}
#[test]
fn arm_shifter_asr_zero_renders_as_shift_by_32() {
let instr = 0xE081_0042;
assert_eq!(disasm_arm(instr, 0), "ADD R0, R1, R2, ASR #32");
}
#[test]
fn arm_shifter_ror_zero_renders_as_rrx() {
let instr = 0xE081_0062;
assert_eq!(disasm_arm(instr, 0), "ADD R0, R1, R2, RRX");
}
#[test]
fn arm_data_processing_mov_immediate() {
let instr = 0xE3A0_0012;
assert_eq!(disasm_arm(instr, 0), "MOV R0, #0x12");
}
#[test]
fn arm_data_processing_movs_register() {
let instr = 0xE1B0_0001;
assert_eq!(disasm_arm(instr, 0), "MOVS R0, R1");
}
#[test]
fn arm_data_processing_cmp_immediate_no_s_suffix() {
let instr = 0xE350_0005;
assert_eq!(disasm_arm(instr, 0), "CMP R0, #0x5");
}
#[test]
fn arm_data_processing_all_test_ops_render_without_s() {
assert_eq!(disasm_arm(0xE310_0001, 0), "TST R0, #0x1");
assert_eq!(disasm_arm(0xE330_0001, 0), "TEQ R0, #0x1");
assert_eq!(disasm_arm(0xE350_0001, 0), "CMP R0, #0x1");
assert_eq!(disasm_arm(0xE370_0001, 0), "CMN R0, #0x1");
}
#[test]
fn arm_data_processing_mvn_register() {
let instr = 0xE1E0_0001;
assert_eq!(disasm_arm(instr, 0), "MVN R0, R1");
}
#[test]
fn arm_branch_forward() {
let instr = 0xEA00_0000;
assert_eq!(disasm_arm(instr, 0), "B #0x00000008");
}
#[test]
fn arm_branch_with_link_and_negative_offset() {
let instr = 0xEBFF_FFFE;
assert_eq!(disasm_arm(instr, 0x100), "BL #0x00000100");
}
#[test]
fn arm_branch_uses_condition_code() {
let instr = 0x0A00_0000;
assert_eq!(disasm_arm(instr, 0), "BEQ #0x00000008");
}
#[test]
fn arm_bx_register() {
let instr = 0xE12F_FF1E;
assert_eq!(disasm_arm(instr, 0), "BX R14");
}
#[test]
fn arm_ldr_pre_indexed_immediate() {
let instr = 0xE591_0004;
assert_eq!(disasm_arm(instr, 0), "LDR R0, [R1, #0x4]");
}
#[test]
fn arm_ldrb_pre_indexed_immediate() {
let instr = 0xE5D1_0004;
assert_eq!(disasm_arm(instr, 0), "LDRB R0, [R1, #0x4]");
}
#[test]
fn arm_str_post_indexed_immediate_negative() {
let instr = 0xE401_0004;
assert_eq!(disasm_arm(instr, 0), "STR R0, [R1], #-0x4");
}
#[test]
fn arm_ldr_with_writeback() {
let instr = 0xE5B1_0004;
assert_eq!(disasm_arm(instr, 0), "LDR R0, [R1, #0x4]!");
}
#[test]
fn arm_ldr_register_offset_with_shift_special_cases() {
let instr = 0xE791_0002;
assert_eq!(disasm_arm(instr, 0), "LDR R0, [R1, R2]");
let instr = 0xE791_0022;
assert_eq!(disasm_arm(instr, 0), "LDR R0, [R1, R2, LSR #32]");
let instr = 0xE791_0042;
assert_eq!(disasm_arm(instr, 0), "LDR R0, [R1, R2, ASR #32]");
let instr = 0xE791_0062;
assert_eq!(disasm_arm(instr, 0), "LDR R0, [R1, R2, RRX]");
}
#[test]
fn arm_swi() {
let instr = 0xEF12_3456;
assert_eq!(disasm_arm(instr, 0), "SWI #0x123456");
}
#[test]
fn arm_undefined_opcode_class() {
let instr = 0xEC00_0000;
assert_eq!(disasm_arm(instr, 0), "<undefined>");
}
#[test]
fn thumb_format1_lsl_immediate() {
let instr = 0b00000_00010_001_000u16;
assert_eq!(disasm_thumb(instr, 0), "LSL R0, R1, #2");
}
#[test]
fn thumb_format1_asr_immediate() {
let instr = 0b00010_00101_011_010u16;
assert_eq!(disasm_thumb(instr, 0), "ASR R2, R3, #5");
}
#[test]
fn thumb_format1_lsr_zero_renders_as_shift_by_32() {
let instr = 0b00001_00000_001_000u16;
assert_eq!(disasm_thumb(instr, 0), "LSR R0, R1, #32");
}
#[test]
fn thumb_format1_asr_zero_renders_as_shift_by_32() {
let instr = 0b00010_00000_001_000u16;
assert_eq!(disasm_thumb(instr, 0), "ASR R0, R1, #32");
}
#[test]
fn thumb_format1_lsl_zero_stays_zero() {
let instr = 0b00000_00000_001_000u16;
assert_eq!(disasm_thumb(instr, 0), "LSL R0, R1, #0");
}
#[test]
fn thumb_format2_add_register() {
let instr = 0b0001100_001_000_010u16;
assert_eq!(disasm_thumb(instr, 0), "ADD R2, R0, R1");
}
#[test]
fn thumb_format2_sub_immediate3() {
let instr = 0b0001111_111_001_000u16;
assert_eq!(disasm_thumb(instr, 0), "SUB R0, R1, #7");
}
#[test]
fn thumb_format3_mov_immediate() {
let instr = 0b00100_000_00101010u16;
assert_eq!(disasm_thumb(instr, 0), "MOV R0, #42");
}
#[test]
fn thumb_format3_cmp_immediate() {
let instr = 0b00101_011_00001010u16;
assert_eq!(disasm_thumb(instr, 0), "CMP R3, #10");
}
#[test]
fn thumb_format4_and_register() {
let instr = 0b010000_0000_001_000u16;
assert_eq!(disasm_thumb(instr, 0), "AND R0, R1");
}
#[test]
fn thumb_format4_mul_register() {
let instr = 0b010000_1101_011_010u16;
assert_eq!(disasm_thumb(instr, 0), "MUL R2, R3");
}
#[test]
fn thumb_format5_mov_high_register() {
let instr = 0b010001_10_10_001_000u16;
assert_eq!(disasm_thumb(instr, 0), "MOV R8, R1");
}
#[test]
fn thumb_format5_bx_register() {
let instr = 0b010001_11_01_110_000u16;
assert_eq!(disasm_thumb(instr, 0), "BX R14");
}
#[test]
fn thumb_format6_pc_relative_load() {
let instr = 0b01001_000_00000100u16;
let s = disasm_thumb(instr, 0);
assert_eq!(s, "LDR R0, [PC, #0x10] ; =0x00000014");
}
#[test]
fn thumb_format14_push_with_lr() {
let instr = 0b1011_0_10_1_0000_0011u16;
assert_eq!(disasm_thumb(instr, 0), "PUSH {R0, R1, LR}");
}
#[test]
fn thumb_format14_pop_with_pc() {
let instr = 0b1011_1_10_1_0000_0001u16;
assert_eq!(disasm_thumb(instr, 0), "POP {R0, PC}");
}
#[test]
fn thumb_format14_push_no_extra() {
let instr = 0b1011_0_10_0_0001_1000u16;
assert_eq!(disasm_thumb(instr, 0), "PUSH {R3, R4}");
}
#[test]
fn thumb_format16_conditional_branch() {
let instr = 0b1101_0000_0000_0110u16;
assert_eq!(disasm_thumb(instr, 0), "BEQ #0x00000010");
}
#[test]
fn thumb_format16_negative_branch() {
let instr = 0b1101_0001_1111_1111u16;
assert_eq!(disasm_thumb(instr, 4), "BNE #0x00000006");
}
#[test]
fn thumb_format16_swi() {
let instr = 0b1101_1111_00010010u16;
assert_eq!(disasm_thumb(instr, 0), "SWI #0x12");
}
#[test]
fn thumb_format18_unconditional_branch() {
let instr = 0b11100_000_0000_0100u16;
assert_eq!(disasm_thumb(instr, 0), "B #0x0000000C");
}
#[test]
fn thumb_undefined_opcode() {
let instr = 0b11111_000_00000000u16;
assert_eq!(disasm_thumb(instr, 0), "<undefined>");
}
}