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: usize = 0;
while i.saturating_add(5) <= sec_data.len() {
if sec_data.get(i).copied() == Some(0xe8) {
let disp = i32::from_le_bytes([
sec_data.get(i.saturating_add(1)).copied().unwrap_or(0),
sec_data.get(i.saturating_add(2)).copied().unwrap_or(0),
sec_data.get(i.saturating_add(3)).copied().unwrap_or(0),
sec_data.get(i.saturating_add(4)).copied().unwrap_or(0),
]);
let call_va = sec_va.wrapping_add(i as u64);
let target_va = (call_va as i64).wrapping_add(5).wrapping_add(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 = i.saturating_add(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.get(start..call_offset).unwrap_or(&[]);
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: usize = 0;
while j.saturating_add(7) <= window.len() {
let abs_off = start.saturating_add(j);
let b0 = window.get(j).copied();
let b1 = window.get(j.saturating_add(1)).copied();
if b0 == Some(0x48) && b1 == Some(0x8d) {
let modrm = window.get(j.saturating_add(2)).copied().unwrap_or(0);
let disp = i32::from_le_bytes([
window.get(j.saturating_add(3)).copied().unwrap_or(0),
window.get(j.saturating_add(4)).copied().unwrap_or(0),
window.get(j.saturating_add(5)).copied().unwrap_or(0),
window.get(j.saturating_add(6)).copied().unwrap_or(0),
]);
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 = j.saturating_add(7);
continue;
}
if b0 == Some(0x41) && b1 == Some(0xb8) && j.saturating_add(6) <= window.len() {
let imm = u32::from_le_bytes([
window.get(j.saturating_add(2)).copied().unwrap_or(0),
window.get(j.saturating_add(3)).copied().unwrap_or(0),
window.get(j.saturating_add(4)).copied().unwrap_or(0),
window.get(j.saturating_add(5)).copied().unwrap_or(0),
]);
if imm < 100_000 {
line = Some(imm);
}
j = j.saturating_add(6);
continue;
}
j = j.saturating_add(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: usize = 0;
while i.saturating_add(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).wrapping_mul(4)
} else {
(imm26 as i64).wrapping_mul(4)
};
let call_va = sec_va.wrapping_add(i as u64);
let target_va = (call_va as i64).wrapping_add(offset) as u64;
if targets.contains(&target_va) {
let site = recover_args_aarch64(container, bytes, sec_data, i, call_va);
sites.push(site);
}
}
i = i.saturating_add(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.wrapping_sub(call_offset.wrapping_sub(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).wrapping_add(imm << 12);
if let Some(slot) = adrp_pages.get_mut(rd) {
*slot = 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(Some(page)) = adrp_pages.get(rn).copied()
&& let Some(slot) = reg_vals.get_mut(rd)
{
*slot = Some(page.wrapping_add(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);
}
if let Some(slot) = reg_vals.get_mut(rd) {
*slot = 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(Some(val)) = reg_vals.get(rm).copied()
&& let Some(slot) = reg_vals.get_mut(rd)
{
*slot = Some(val);
}
}
j = j.saturating_add(4);
}
let etype_va = reg_vals.get(1).copied().flatten();
let proc_va = reg_vals.get(2).copied().flatten();
let file_va = reg_vals.get(3).copied().flatten();
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, }
}