use crate::{
container::{Arch, Container, SectionKind},
rtti::v2::read_cstring_at_va,
util,
};
#[derive(Debug, Clone)]
pub struct RaiseSite {
pub call_addr: u64,
pub exception_type: Option<String>,
pub proc_name: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
pub enclosing_function: Option<String>,
}
pub fn scan(container: &Container<'_>) -> Vec<RaiseSite> {
let targets = find_raise_targets(container);
if targets.is_empty() {
return Vec::new();
}
let mut sites = match container.arch() {
Arch::Amd64 => scan_x86_64(container, &targets),
Arch::Aarch64 => scan_aarch64(container, &targets),
_ => return Vec::new(),
};
for site in &mut sites {
if let Some(func) = container.function_at_va(site.call_addr) {
site.enclosing_function = Some(func.name.to_string());
}
}
sites
}
fn find_raise_targets(container: &Container<'_>) -> Vec<u64> {
container
.symbols()
.iter()
.filter(|s| {
let name = s.name.as_ref();
name == "raiseExceptionEx" || name.starts_with("raiseExceptionEx.")
})
.map(|s| s.vm_addr)
.collect()
}
const X86_BACKWARD_WINDOW: usize = 80;
fn scan_x86_64(container: &Container<'_>, targets: &[u64]) -> Vec<RaiseSite> {
let mut sites = Vec::new();
let bytes = container.bytes();
for section in container.sections() {
if section.kind != SectionKind::Text {
continue;
}
if section.data.len() < 5 {
continue;
}
let sec_data = section.data;
let sec_va = section.vm_addr;
let mut i = 0;
while i + 5 <= sec_data.len() {
if sec_data[i] == 0xe8 {
let disp = i32::from_le_bytes([
sec_data[i + 1],
sec_data[i + 2],
sec_data[i + 3],
sec_data[i + 4],
]);
let call_va = sec_va + i as u64;
let target_va = (call_va as i64 + 5 + disp as i64) as u64;
if targets.contains(&target_va) {
let site = recover_args_x86_64(container, bytes, sec_data, i, call_va);
sites.push(site);
}
}
i += 1;
}
}
sites
}
fn recover_args_x86_64(
container: &Container<'_>,
bytes: &[u8],
sec_data: &[u8],
call_offset: usize,
call_va: u64,
) -> RaiseSite {
let start = call_offset.saturating_sub(X86_BACKWARD_WINDOW);
let window = &sec_data[start..call_offset];
let mut etype_va: Option<u64> = None;
let mut proc_va: Option<u64> = None;
let mut file_va: Option<u64> = None;
let mut line: Option<u32> = None;
let mut j = 0;
while j + 7 <= window.len() {
let abs_off = start + j;
if window[j] == 0x48 && window[j + 1] == 0x8d && j + 7 <= window.len() {
let modrm = window[j + 2];
let disp =
i32::from_le_bytes([window[j + 3], window[j + 4], window[j + 5], window[j + 6]]);
let insn_va = call_va.wrapping_sub(call_offset.wrapping_sub(abs_off) as u64);
let target = (insn_va as i64).wrapping_add(7).wrapping_add(disp as i64) as u64;
match modrm {
0x35 => etype_va = Some(target), 0x15 => proc_va = Some(target), 0x0d => file_va = Some(target), _ => {}
}
j += 7;
continue;
}
if window[j] == 0x41 && window[j + 1] == 0xb8 && j + 6 <= window.len() {
let imm =
u32::from_le_bytes([window[j + 2], window[j + 3], window[j + 4], window[j + 5]]);
if imm < 100_000 {
line = Some(imm);
}
j += 6;
continue;
}
j += 1;
}
RaiseSite {
call_addr: call_va,
exception_type: etype_va.and_then(|va| read_cstring_at_va(container, bytes, va)),
proc_name: proc_va.and_then(|va| read_cstring_at_va(container, bytes, va)),
file: file_va.and_then(|va| read_cstring_at_va(container, bytes, va)),
line,
enclosing_function: None, }
}
const AARCH64_BACKWARD_WINDOW: usize = 128;
fn scan_aarch64(container: &Container<'_>, targets: &[u64]) -> Vec<RaiseSite> {
let mut sites = Vec::new();
let bytes = container.bytes();
for section in container.sections() {
if section.kind != SectionKind::Text {
continue;
}
if section.data.len() < 4 {
continue;
}
let sec_data = section.data;
let sec_va = section.vm_addr;
let mut i = 0;
while i + 4 <= sec_data.len() {
let insn = util::read_u32_le(sec_data, i);
if insn & 0xFC00_0000 == 0x9400_0000 {
let imm26 = (insn & 0x03FF_FFFF) as i32;
let offset = if imm26 & (1 << 25) != 0 {
(imm26 | !0x03FF_FFFF) as i64 * 4
} else {
imm26 as i64 * 4
};
let call_va = sec_va + i as u64;
let target_va = (call_va as i64 + offset) as u64;
if targets.contains(&target_va) {
let site = recover_args_aarch64(container, bytes, sec_data, i, call_va);
sites.push(site);
}
}
i += 4; }
}
sites
}
fn recover_args_aarch64(
container: &Container<'_>,
bytes: &[u8],
sec_data: &[u8],
call_offset: usize,
call_va: u64,
) -> RaiseSite {
let start = call_offset.saturating_sub(AARCH64_BACKWARD_WINDOW);
let start = start & !3;
let mut adrp_pages: [Option<u64>; 32] = [None; 32];
let mut reg_vals: [Option<u64>; 32] = [None; 32];
let mut line: Option<u32> = None;
let mut j = start;
while j < call_offset {
let insn = util::read_u32_le(sec_data, j);
let insn_va = call_va - (call_offset - j) as u64;
if insn & 0x9F00_0000 == 0x9000_0000 {
let rd = (insn & 0x1F) as usize;
let immhi = ((insn >> 5) & 0x7FFFF) as i64;
let immlo = ((insn >> 29) & 0x3) as i64;
let imm = (immhi << 2) | immlo;
let imm = if imm & (1 << 20) != 0 {
imm | !0x1FFFFF
} else {
imm
};
let page = ((insn_va as i64) & !0xFFF) + (imm << 12);
adrp_pages[rd] = Some(page as u64);
}
if insn & 0xFFC0_0000 == 0x9100_0000 {
let rd = (insn & 0x1F) as usize;
let rn = ((insn >> 5) & 0x1F) as usize;
let imm12 = ((insn >> 10) & 0xFFF) as u64;
if let Some(page) = adrp_pages[rn] {
reg_vals[rd] = Some(page + imm12);
}
}
if insn & 0xFFE0_0000 == 0x5280_0000 {
let rd = (insn & 0x1F) as usize;
let imm16 = (insn >> 5) & 0xFFFF;
if rd == 4 && imm16 < 100_000 {
line = Some(imm16);
}
reg_vals[rd] = Some(imm16 as u64);
}
if insn & 0xFFE0_FFE0 == 0xAA00_03E0 {
let rd = (insn & 0x1F) as usize;
let rm = ((insn >> 16) & 0x1F) as usize;
if let Some(val) = reg_vals[rm] {
reg_vals[rd] = Some(val);
}
}
j += 4;
}
let etype_va = reg_vals[1];
let proc_va = reg_vals[2];
let file_va = reg_vals[3];
RaiseSite {
call_addr: call_va,
exception_type: etype_va.and_then(|va| read_cstring_at_va(container, bytes, va)),
proc_name: proc_va.and_then(|va| read_cstring_at_va(container, bytes, va)),
file: file_va.and_then(|va| read_cstring_at_va(container, bytes, va)),
line,
enclosing_function: None, }
}