fn analyze_arm64(bytes: &[u8], va: u64, arch: Arch) -> AnalysisResult {
if bytes.len() < 4 {
return (
CppBodyKind::Unknown,
CppReturnChannel::Unknown,
false,
None,
None,
None,
"function body too small".into(),
);
}
let cfg = FunctionCfg::build(bytes, va, arch);
if cfg.insns.is_empty() {
return (
CppBodyKind::Unknown,
CppReturnChannel::Unknown,
false,
None,
None,
None,
"no decodable instructions".into(),
);
}
match &cfg.insns[0].kind {
InsnKind::Return => {
return (
CppBodyKind::Stub,
CppReturnChannel::Unknown,
true,
None,
Some(ParamCounts { gpr: 0, fp: 0 }),
None,
"immediate RET".into(),
);
}
InsnKind::Branch(_) | InsnKind::Call(_) => {
return (
CppBodyKind::Thunk,
CppReturnChannel::Unknown,
true,
None,
None,
None,
"immediate branch/call".into(),
);
}
_ => {}
}
let mut detail_parts = Vec::new();
let mut has_sret = false;
let mut max_gpr_arg_saved = -1i32;
let mut max_fpr_arg_saved = -1i32;
for insn in cfg.insns.iter().take(PROLOGUE_WINDOW) {
if matches!(
insn.kind,
InsnKind::Return | InsnKind::Branch(_) | InsnKind::Call(_)
) {
break;
}
let ops = insn.operands();
let is_store = ops.iter().any(|op| matches!(op, Operand::Mem { .. }));
if is_store {
for op in ops {
match op {
Operand::Reg(r) if r.class == RegClass::Gpr && r.num <= 7 => {
max_gpr_arg_saved = max_gpr_arg_saved.max(r.num as i32);
}
Operand::Reg(r) if r.class == RegClass::Gpr && r.num == 8 => {
has_sret = true;
}
Operand::Reg(r) if r.class == RegClass::Fp && r.num <= 7 => {
max_fpr_arg_saved = max_fpr_arg_saved.max(r.num as i32);
}
_ => {}
}
}
}
}
let ret_positions = cfg.ret_positions();
let mut wrote_d0 = false;
let mut wrote_x0 = false;
for &rp in &ret_positions {
let window_start = rp.saturating_sub(EPILOGUE_WINDOW);
for insn in &cfg.insns[window_start..rp] {
let ops = insn.operands();
if let [Operand::Reg(dst), Operand::Reg(src)] = ops {
if dst.class == RegClass::Fp && dst.num == 0 && src.class == RegClass::Fp {
wrote_d0 = true;
}
}
if let Some(Operand::Reg(r)) = ops.first() {
if r.class == RegClass::Gpr && r.num == 0 {
let is_restore = ops.iter().any(|op| {
matches!(
op,
Operand::Mem { base, .. } if base.class == RegClass::Gpr && base.num == 31
)
});
if !is_restore {
wrote_x0 = true;
}
}
}
}
}
let mut return_channel = CppReturnChannel::Unknown;
if has_sret {
return_channel = CppReturnChannel::AggregateIndirect;
detail_parts.push("x8 saved (sret)");
} else if wrote_d0 {
return_channel = CppReturnChannel::FloatingPoint;
detail_parts.push("FP return detected");
} else if wrote_x0 {
return_channel = CppReturnChannel::GeneralPurpose;
detail_parts.push("x0 set before RET");
} else if !ret_positions.is_empty() {
return_channel = CppReturnChannel::Void;
detail_parts.push("no return register written");
}
let param_counts = if max_gpr_arg_saved >= 0 || max_fpr_arg_saved >= 0 {
let gpr = if max_gpr_arg_saved >= 0 {
((max_gpr_arg_saved + 1) as u32).min(ARM64_MAX_GPR_ARGS)
} else {
0
};
let fp = if max_fpr_arg_saved >= 0 {
((max_fpr_arg_saved + 1) as u32).min(ARM64_MAX_FP_ARGS)
} else {
0
};
detail_parts.push("param count from register saves");
Some(ParamCounts { gpr, fp })
} else {
None
};
let detail = if detail_parts.is_empty() {
"standard body, no strong heuristics".to_string()
} else {
detail_parts.join("; ")
};
(
CppBodyKind::Standard,
return_channel,
false,
None,
param_counts,
Some(cfg),
detail,
)
}
const X86_RDI: u8 = 7;
const X86_RSI: u8 = 6;
const X86_RDX: u8 = 2;
const X86_RCX: u8 = 1;
const X86_R8: u8 = 8;
const X86_R9: u8 = 9;
fn x86_arg_position(gpr_num: u8) -> Option<i32> {
match gpr_num {
X86_RDI => Some(0),
X86_RSI => Some(1),
X86_RDX => Some(2),
X86_RCX => Some(3),
X86_R8 => Some(4),
X86_R9 => Some(5),
_ => None,
}
}