mod operand;
pub(crate) use operand::decode;
use crate::dbgeng::Flow;
pub(crate) const INSTRUCTION_BYTES: usize = 4;
pub(crate) fn flow(word: u32, address: u64) -> Flow {
if word & 0x7c00_0000 == 0x1400_0000 {
let target = relative(address, word & 0x03ff_ffff, 26);
return match word & 0x8000_0000 == 0 {
true => Flow::Jmp(Some(target)),
false => Flow::Call(Some(target)),
};
}
if word & 0xfe00_0000 == 0x5400_0000 {
if word & 0x0100_0000 != 0 {
return Flow::Trap;
}
let target = relative(address, (word >> 5) & 0x0007_ffff, 19);
return match word & 0xe == 0xe {
true => Flow::Jmp(Some(target)),
false => Flow::Branch(Some(target)),
};
}
if word & 0x7e00_0000 == 0x3400_0000 {
return Flow::Branch(Some(relative(address, (word >> 5) & 0x0007_ffff, 19)));
}
if word & 0x7e00_0000 == 0x3600_0000 {
return Flow::Branch(Some(relative(address, (word >> 5) & 0x0000_3fff, 14)));
}
if word & 0xfe00_0000 == 0xd600_0000 {
let (op2, op3) = ((word >> 16) & 0x1f, (word >> 10) & 0x3f);
if op2 != 0b11111 || !matches!(op3, 0b000000 | 0b000010 | 0b000011) {
return Flow::Trap;
}
return match (word >> 21) & 0xf {
0b0000 | 0b1000 => Flow::Jmp(None),
0b0001 | 0b1001 => Flow::Call(None),
0b0010 | 0b0100 | 0b0101 => Flow::Return,
_ => Flow::Trap,
};
}
if word & 0xff00_0000 == 0xd400_0000 {
let (opc, op2, ll) = ((word >> 21) & 0x7, (word >> 2) & 0x7, word & 0x3);
return match (opc, op2, ll) {
(0b000, 0b000, 0b01 | 0b10 | 0b11) => Flow::Fallthrough,
_ => Flow::Trap,
};
}
if (word >> 25) & 0xf == 0 {
return Flow::Trap;
}
Flow::Fallthrough
}
fn relative(address: u64, immediate: u32, bits: u32) -> u64 {
let shift = u32::BITS - bits;
let displacement = ((immediate << shift) as i32 >> shift) as i64;
address.wrapping_add((displacement * INSTRUCTION_BYTES as i64) as u64)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum UnwindRecord {
PackedLength(u64),
XdataAt(u32),
Reserved,
}
pub(crate) fn unwind_record(second_word: u32) -> UnwindRecord {
match second_word & 0x3 {
0 => UnwindRecord::XdataAt(second_word),
3 => UnwindRecord::Reserved,
_ => UnwindRecord::PackedLength((((second_word >> 2) & 0x7ff) as u64) * 4),
}
}
pub(crate) fn xdata_length(header: u32) -> Option<u64> {
((header >> 18) & 0x3 == 0).then(|| ((header & 0x0003_ffff) as u64) * 4)
}
#[cfg(test)]
mod tests {
use super::*;
const KE_BUG_CHECK_EX: u64 = 0xfffff802_e9e5df60;
#[test]
fn test_a_direct_call_carries_its_destination() {
assert_eq!(
flow(0x97ff_fcc4, KE_BUG_CHECK_EX + 0x10),
Flow::Call(Some(0xfffff802_e9e5d280))
);
}
#[test]
fn test_a_direct_branch_is_a_jump_and_not_a_call() {
assert_eq!(flow(0x1400_0004, 0x1000), Flow::Jmp(Some(0x1010)));
assert_eq!(flow(0x9400_0004, 0x1000), Flow::Call(Some(0x1010)));
}
#[test]
fn test_a_conditional_branch_takes_both_edges() {
assert_eq!(
flow(0x5400_02c1, 0xfffff802_ea69dc1c),
Flow::Branch(Some(0xfffff802_ea69dc74))
);
}
#[test]
fn test_an_always_condition_does_not_fall_through() {
assert_eq!(flow(0x5400_002e, 0x1000), Flow::Jmp(Some(0x1004)));
assert_eq!(flow(0x5400_002f, 0x1000), Flow::Jmp(Some(0x1004)));
assert_eq!(flow(0x5400_0020, 0x1000), Flow::Branch(Some(0x1004)));
}
#[test]
fn test_compare_and_branch_is_conditional() {
assert_eq!(
flow(0xb400_0293, 0xfffff802_ea3773f0),
Flow::Branch(Some(0xfffff802_ea377440))
);
assert_eq!(
flow(0x3500_0128, 0xfffff802_ea69dc80),
Flow::Branch(Some(0xfffff802_ea69dca4))
);
}
#[test]
fn test_test_and_branch_reads_a_shorter_displacement() {
assert_eq!(
flow(0x3680_0208, 0xfffff802_e9ef919c),
Flow::Branch(Some(0xfffff802_e9ef91dc))
);
assert_eq!(
flow(0x3610_0168, 0xfffff802_e9e493ac),
Flow::Branch(Some(0xfffff802_e9e493d8))
);
}
#[test]
fn test_an_indirect_transfer_names_no_destination() {
assert_eq!(flow(0xd63f_01e0, 0x1000), Flow::Call(None));
assert_eq!(flow(0xd65f_03c0, 0x1000), Flow::Return);
assert_eq!(flow(0xd61f_0200, 0x1000), Flow::Jmp(None));
assert_eq!(flow(0xd65f_0fff, 0x1000), Flow::Return);
assert_eq!(flow(0xd69f_03e0, 0x1000), Flow::Return);
}
#[test]
fn test_a_trap_stops_a_walk_and_a_system_call_does_not() {
assert_eq!(flow(0xd43e_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0xd440_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0xd400_0001, 0x1000), Flow::Fallthrough);
assert_eq!(flow(0xd400_0002, 0x1000), Flow::Fallthrough);
assert_eq!(flow(0xd400_0003, 0x1000), Flow::Fallthrough);
}
#[test]
fn test_an_unallocated_encoding_in_an_allocated_class_stops() {
assert_eq!(flow(0xd620_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0xd63f_0400, 0x1000), Flow::Trap);
assert_eq!(flow(0xd63f_0000, 0x1000), Flow::Call(None));
assert_eq!(flow(0xd61f_0000, 0x1000), Flow::Jmp(None));
assert_eq!(flow(0xd65f_0bff, 0x1000), Flow::Return);
assert_eq!(flow(0xd63f_0800, 0x1000), Flow::Call(None));
assert_eq!(flow(0xd67f_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0x5500_0020, 0x1000), Flow::Trap);
assert_eq!(flow(0x5400_0020, 0x1000), Flow::Branch(Some(0x1004)));
assert_eq!(flow(0xd400_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0xd400_0004, 0x1000), Flow::Trap);
assert_eq!(flow(0xd400_0005, 0x1000), Flow::Trap);
}
#[test]
fn test_nothing_else_in_the_exception_class_falls_through() {
assert_eq!(flow(0xd460_0020, 0x1000), Flow::Trap);
assert_eq!(flow(0xd4a0_0001, 0x1000), Flow::Trap);
assert_eq!(flow(0xd4a0_0002, 0x1000), Flow::Trap);
assert_eq!(flow(0xd4a0_0003, 0x1000), Flow::Trap);
assert_eq!(flow(0xd480_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0xd65f_03c0, 0x1000), Flow::Return);
}
#[test]
fn test_the_reserved_top_level_space_is_the_undefined_instruction() {
assert_eq!(flow(0x0000_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0x0000_ffff, 0x1000), Flow::Trap);
assert_eq!(flow(0x0001_0000, 0x1000), Flow::Trap);
assert_eq!(flow(0x01ff_ffff, 0x1000), Flow::Trap);
assert_eq!(flow(0xe1ff_ffff, 0x1000), Flow::Trap);
assert_eq!(flow(0x0200_0000, 0x1000), Flow::Fallthrough);
assert_eq!(flow(0x0600_0000, 0x1000), Flow::Fallthrough);
}
#[test]
fn test_a_branch_class_this_does_not_decode_reads_as_a_fall_through() {
assert_eq!(flow(0x74e2_8041, 0x1000), Flow::Fallthrough);
}
#[test]
fn test_an_ordinary_instruction_falls_through() {
for word in [
0xd503_237f_u32,
0xa9bf_7bfd,
0x9100_03fd,
0x9000_1b68,
0xf942_1e93,
0x7100_011f,
] {
assert_eq!(flow(word, 0x1000), Flow::Fallthrough, "{word:#010x}");
}
}
#[test]
fn test_a_branch_target_keeps_the_instruction_s_address_space() {
assert_eq!(
flow(0x1400_0001, 0xffff_ffff_ffff_fffc),
Flow::Jmp(Some(0x0000_0000_0000_0000))
);
assert_eq!(
flow(0x1400_0001, 0x0000_0000_ffff_fffc),
Flow::Jmp(Some(0x0000_0001_0000_0000))
);
assert_eq!(
flow(0x1600_0000, 0x0000_0001_0000_0000),
Flow::Jmp(Some(0x0000_0000_f800_0000))
);
}
#[test]
fn test_the_packed_unwind_record_carries_a_length() {
assert_eq!(unwind_record(0x0142_0079), UnwindRecord::PackedLength(0x78));
assert_eq!(unwind_record(0x01c3_00d5), UnwindRecord::PackedLength(0xd4));
assert_eq!(unwind_record(0x0000_000a), UnwindRecord::PackedLength(8));
assert_eq!(unwind_record(0xffff_fffd), UnwindRecord::PackedLength(8188));
}
#[test]
fn test_a_zero_flag_is_an_xdata_pointer_and_not_a_length() {
assert_eq!(
unwind_record(0x0005_f218),
UnwindRecord::XdataAt(0x0005_f218)
);
assert_eq!(unwind_record(0x0000_0003), UnwindRecord::Reserved);
}
#[test]
fn test_the_xdata_header_carries_a_length_and_a_version() {
assert_eq!(xdata_length(0x0800_0006), Some(24));
assert_eq!(xdata_length(0x1800_007f), Some(0x1fc));
assert_eq!(xdata_length(0x1840_00cc), Some(0x330));
assert_eq!(xdata_length(0x0004_0006), None);
}
}