use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Copy)]
pub struct MovwMovtPair {
pub addr: u64,
pub rd: u8,
pub value: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct PcLiteralLoad {
pub addr: u64,
pub rt: u8,
pub literal_addr: u64,
pub value: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct AdrSite {
pub addr: u64,
pub rd: u8,
pub target: u64,
}
pub fn discover_functions(data: &[u8], base: u64) -> BTreeSet<u64> {
let mut found = BTreeSet::new();
found.insert(base);
let code_end = base + data.len() as u64;
if data.len() >= 8 {
let sp_word = u32::from_le_bytes(data[0..4].try_into().unwrap_or([0; 4]));
if (sp_word & 0xFF00_0000) == 0x2000_0000 {
let scan_end = 0x400.min(data.len() & !3);
for i in (4..scan_end).step_by(4) {
let entry = u32::from_le_bytes(data[i..i + 4].try_into().unwrap_or([0; 4]));
if (entry & 1) == 0 {
continue;
}
let canon = (entry as u64) & !1;
if canon >= base && canon < code_end {
found.insert(entry as u64);
}
}
}
}
for i in (0..data.len().saturating_sub(3)).step_by(4) {
let word = u32::from_le_bytes(data[i..i + 4].try_into().unwrap_or([0; 4]));
if (word & 0x0F000000) == 0x0B000000 {
let imm24 = word & 0x00FFFFFF;
let offset = if imm24 & 0x800000 != 0 {
((imm24 | 0xFF000000) as i32) << 2
} else {
(imm24 as i32) << 2
};
let target = (base as i64 + i as i64 + 8 + offset as i64) as u64;
if target >= base && target < code_end {
found.insert(target);
}
}
}
for i in (0..data.len().saturating_sub(3)).step_by(2) {
let hw1 = u16::from_le_bytes([data[i], data[i + 1]]) as u32;
let hw2 = u16::from_le_bytes([data[i + 2], data[i + 3]]) as u32;
if (hw1 & 0xF800) != 0xF000 {
continue;
}
let is_bl = (hw2 & 0xD000) == 0xD000;
let is_blx = (hw2 & 0xD000) == 0xC000;
if !is_bl && !is_blx {
continue;
}
if let Some(target) = decode_thumb2_bl_target(base + i as u64, hw1, hw2, is_blx) {
let canon = target & !1;
if canon >= base && canon < code_end {
found.insert(target);
}
}
}
found
}
fn decode_thumb2_bl_target(pc: u64, hw1: u32, hw2: u32, is_blx: bool) -> Option<u64> {
let s = (hw1 >> 10) & 1;
let j1 = (hw2 >> 13) & 1;
let j2 = (hw2 >> 11) & 1;
let i1 = (!(j1 ^ s)) & 1;
let i2 = (!(j2 ^ s)) & 1;
let imm10 = hw1 & 0x3FF;
let imm11 = hw2 & 0x7FF;
let mut offset: i32 = ((i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1)) as i32;
if s != 0 {
offset |= 0xFF00_0000u32 as i32;
}
let raw = (pc as i64 + 4 + offset as i64) as u64;
Some(if is_blx { raw & !0x3 } else { raw | 1 })
}
pub fn find_call_sites(data: &[u8], base: u64, query: u64) -> Vec<u64> {
let mut sites = Vec::new();
let code_end = base + data.len() as u64;
let want = query & !1;
let mut i = 0;
while i + 4 <= data.len() {
let hw1 = u16::from_le_bytes([data[i], data[i + 1]]) as u32;
let hw2 = u16::from_le_bytes([data[i + 2], data[i + 3]]) as u32;
if (hw1 & 0xF800) == 0xF000 {
let is_bl = (hw2 & 0xD000) == 0xD000;
let is_blx = (hw2 & 0xD000) == 0xC000;
if is_bl || is_blx {
if let Some(t) = decode_thumb2_bl_target(base + i as u64, hw1, hw2, is_blx) {
if (t & !1) == want && (t & !1) >= base && (t & !1) < code_end {
sites.push(base + i as u64);
}
}
}
}
i += 2;
}
let mut j = 0;
while j + 4 <= data.len() {
let word = u32::from_le_bytes(data[j..j + 4].try_into().unwrap_or([0; 4]));
if (word & 0x0F000000) == 0x0B000000 {
let imm24 = word & 0x00FFFFFF;
let offset = if imm24 & 0x800000 != 0 {
((imm24 | 0xFF000000) as i32) << 2
} else {
(imm24 as i32) << 2
};
let target = (base as i64 + j as i64 + 8 + offset as i64) as u64;
if (target & !1) == want && (target & !1) >= base && (target & !1) < code_end {
sites.push(base + j as u64);
}
}
j += 4;
}
sites.sort();
sites.dedup();
sites
}
fn decode_movw_movt(hw1: u32, hw2: u32, is_movt: bool) -> Option<(u8, u16)> {
let want = if is_movt { 0xF2C0 } else { 0xF240 };
if (hw1 & 0xFBF0) != want {
return None;
}
if (hw2 & 0x8000) != 0 {
return None;
}
let imm4 = hw1 & 0x000F;
let i = (hw1 >> 10) & 1;
let imm3 = (hw2 >> 12) & 0x7;
let imm8 = hw2 & 0xFF;
let imm16 = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
let rd = ((hw2 >> 8) & 0xF) as u8;
Some((rd, imm16 as u16))
}
pub fn find_movw_movt_pairs(data: &[u8], base: u64) -> Vec<MovwMovtPair> {
let mut pairs = Vec::new();
let mut i = 0;
while i + 8 <= data.len() {
let hw1 = u16::from_le_bytes([data[i], data[i + 1]]) as u32;
let hw2 = u16::from_le_bytes([data[i + 2], data[i + 3]]) as u32;
let Some((rd_lo, lo)) = decode_movw_movt(hw1, hw2, false) else {
i += 2;
continue;
};
let hw3 = u16::from_le_bytes([data[i + 4], data[i + 5]]) as u32;
let hw4 = u16::from_le_bytes([data[i + 6], data[i + 7]]) as u32;
let Some((rd_hi, hi)) = decode_movw_movt(hw3, hw4, true) else {
i += 2;
continue;
};
if rd_lo != rd_hi {
i += 2;
continue;
}
pairs.push(MovwMovtPair {
addr: base + i as u64,
rd: rd_lo,
value: ((hi as u32) << 16) | (lo as u32),
});
i += 8;
}
pairs
}
pub fn find_pc_literal_loads(data: &[u8], base: u64) -> Vec<PcLiteralLoad> {
let mut loads = Vec::new();
let mut i = 0;
while i + 2 <= data.len() {
let hw1 = u16::from_le_bytes([data[i], data[i + 1]]) as u32;
if (hw1 & 0xF800) == 0x4800 {
let rt = ((hw1 >> 8) & 0x7) as u8;
let imm8 = hw1 & 0xFF;
let pc_aligned = (base + i as u64 + 4) & !0x3;
let lit_addr = pc_aligned + (imm8 as u64) * 4;
if let Some(value) = read_u32_at(data, base, lit_addr) {
loads.push(PcLiteralLoad {
addr: base + i as u64,
rt,
literal_addr: lit_addr,
value,
});
}
i += 2;
continue;
}
if i + 4 > data.len() {
i += 2;
continue;
}
let is_u_pos = hw1 == 0xF8DF;
let is_u_neg = hw1 == 0xF85F;
if is_u_pos || is_u_neg {
let hw2 = u16::from_le_bytes([data[i + 2], data[i + 3]]) as u32;
let rt = ((hw2 >> 12) & 0xF) as u8;
let imm12 = hw2 & 0xFFF;
let pc_aligned = (base + i as u64 + 4) & !0x3;
let lit_addr = if is_u_pos {
pc_aligned + imm12 as u64
} else {
pc_aligned.wrapping_sub(imm12 as u64)
};
if let Some(value) = read_u32_at(data, base, lit_addr) {
loads.push(PcLiteralLoad {
addr: base + i as u64,
rt,
literal_addr: lit_addr,
value,
});
}
i += 4;
continue;
}
i += 2;
}
loads
}
pub fn find_adr_sites(data: &[u8], base: u64) -> Vec<AdrSite> {
let mut sites = Vec::new();
let mut i = 0;
while i + 2 <= data.len() {
let hw1 = u16::from_le_bytes([data[i], data[i + 1]]) as u32;
if (hw1 & 0xF800) == 0xA000 {
let rd = ((hw1 >> 8) & 0x7) as u8;
let imm8 = hw1 & 0xFF;
let pc_aligned = (base + i as u64 + 4) & !0x3;
let target = pc_aligned + (imm8 as u64) * 4;
sites.push(AdrSite {
addr: base + i as u64,
rd,
target,
});
i += 2;
continue;
}
if i + 4 <= data.len() {
let hw2 = u16::from_le_bytes([data[i + 2], data[i + 3]]) as u32;
let i_bit = (hw1 >> 10) & 0x1;
let op = (hw1 >> 4) & 0x1F; let rn = hw1 & 0xF;
let top5 = hw1 & 0xFBF0;
let is_add_imm12 = top5 == 0xF20F; let is_sub_imm12 = top5 == 0xF2AF; let _ = (op, rn);
if is_add_imm12 || is_sub_imm12 {
if (hw2 & 0x8000) == 0 {
let imm3 = (hw2 >> 12) & 0x7;
let rd = ((hw2 >> 8) & 0xF) as u8;
let imm8 = hw2 & 0xFF;
let imm12 = (i_bit << 11) | (imm3 << 8) | imm8;
let pc_aligned = (base + i as u64 + 4) & !0x3;
let target = if is_add_imm12 {
pc_aligned + imm12 as u64
} else {
pc_aligned.wrapping_sub(imm12 as u64)
};
sites.push(AdrSite {
addr: base + i as u64,
rd,
target,
});
i += 4;
continue;
}
}
}
i += 2;
}
sites
}
fn read_u32_at(data: &[u8], base: u64, va: u64) -> Option<u32> {
if va < base {
return None;
}
let off = (va - base) as usize;
if off + 4 > data.len() {
return None;
}
Some(u32::from_le_bytes(data[off..off + 4].try_into().ok()?))
}
pub fn build_constant_load_map(data: &[u8], base: u64) -> BTreeMap<u64, u32> {
let mut map = BTreeMap::new();
for p in find_movw_movt_pairs(data, base) {
map.insert(p.addr, p.value);
}
for l in find_pc_literal_loads(data, base) {
map.insert(l.addr, l.value);
}
map
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn movw_movt_pair_basic() {
let bytes: [u8; 8] = [0x41, 0xF2, 0x34, 0x20, 0xC5, 0xF2, 0x78, 0x60];
let pairs = find_movw_movt_pairs(&bytes, 0x08000000);
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].rd, 0);
assert_eq!(pairs[0].value, 0x5678_1234);
}
#[test]
fn discover_finds_vector_seed() {
let mut data = vec![0u8; 0x500];
data[0..4].copy_from_slice(&0x2002_0000u32.to_le_bytes());
data[4..8].copy_from_slice(&0x0800_0401u32.to_le_bytes());
let found = discover_functions(&data, 0x0800_0000);
assert!(found.contains(&0x0800_0401));
}
#[test]
fn thumb2_bl_target_decode_canon() {
let hw1 = 0xF000u32;
let hw2 = 0xF820u32;
let target = decode_thumb2_bl_target(0x0800_0100, hw1, hw2, false).unwrap();
assert_eq!(target, 0x0800_0145);
}
}