fn analyze_x86_64(bytes: &[u8], va: u64) -> AnalysisResult {
if bytes.is_empty() {
return (
CppBodyKind::Unknown,
CppReturnChannel::Unknown,
false,
None,
None,
None,
"empty body".into(),
);
}
if let Ok(first) = macho_insn::decode_one(bytes, va, Arch::X86_64) {
if matches!(first.kind, InsnKind::Other) {
let ops = first.operands();
if let (Some(Operand::Reg(dst)), Some(&Operand::Imm(imm))) = (ops.first(), ops.get(1)) {
if dst.class == RegClass::Gpr && dst.num == X86_RDI {
if let Ok(next) = macho_insn::decode_one(
&bytes[first.len..],
va + first.len as u64,
Arch::X86_64,
) {
if matches!(next.kind, InsnKind::Branch(_)) {
let adj = if bytes.len() > 2 && (bytes[2] >> 3) & 7 == 5 {
-imm
} else {
imm
};
return (
CppBodyKind::Thunk,
CppReturnChannel::Unknown,
true,
Some(adj),
None,
None,
"this-adjusting thunk".into(),
);
}
}
}
}
}
match &first.kind {
InsnKind::Branch(_) => {
return (
CppBodyKind::Thunk,
CppReturnChannel::Unknown,
true,
None,
None,
None,
"jump thunk".into(),
);
}
InsnKind::Return => {
return (
CppBodyKind::Stub,
CppReturnChannel::Unknown,
true,
None,
Some(ParamCounts { gpr: 0, fp: 0 }),
None,
"immediate RET".into(),
);
}
_ => {}
}
}
let cfg = FunctionCfg::build(bytes, va, Arch::X86_64);
let mut detail_parts = Vec::new();
let mut max_gpr_arg_touched = -1i32;
let mut max_fp_arg_touched = -1i32;
let mut rdi_was_saved = false;
for insn in cfg.insns.iter().take(PROLOGUE_WINDOW) {
if matches!(
insn.kind,
InsnKind::Return | InsnKind::Branch(_) | InsnKind::Call(_)
) {
break;
}
let ops = insn.operands();
match ops {
[Operand::Reg(r)] if r.class == RegClass::Gpr => {
if r.num == X86_RDI {
rdi_was_saved = true;
}
if let Some(pos) = x86_arg_position(r.num) {
max_gpr_arg_touched = max_gpr_arg_touched.max(pos);
}
}
[Operand::Mem { .. }, Operand::Reg(r)] if r.class == RegClass::Gpr => {
if r.num == X86_RDI {
rdi_was_saved = true;
}
if let Some(pos) = x86_arg_position(r.num) {
max_gpr_arg_touched = max_gpr_arg_touched.max(pos);
}
}
[Operand::Mem { .. }, Operand::Reg(r)] if r.class == RegClass::Fp && r.num <= 7 => {
max_fp_arg_touched = max_fp_arg_touched.max(r.num as i32);
}
_ => {}
}
}
let ret_positions = cfg.ret_positions();
let mut wrote_xmm0 = false;
let mut wrote_rax = false;
let mut rax_from_stack = false;
for &rp in &ret_positions {
let window_start = rp.saturating_sub(EPILOGUE_WINDOW);
for insn in &cfg.insns[window_start..rp] {
if insn.writes_implicit_gpr0 {
continue;
}
match insn.operands() {
[Operand::Reg(r), ..] if r.class == RegClass::Fp && r.num == 0 => {
wrote_xmm0 = true;
}
[Operand::Reg(r), Operand::Mem { .. }]
if r.class == RegClass::Gpr && r.num == 0 =>
{
rax_from_stack = true;
wrote_rax = true;
}
[Operand::Reg(r), ..] if r.class == RegClass::Gpr && r.num == 0 => {
wrote_rax = true;
}
_ => {}
}
}
}
let mut has_sret = false;
if rdi_was_saved && rax_from_stack {
has_sret = true;
}
let mut return_channel = CppReturnChannel::Unknown;
if has_sret {
return_channel = CppReturnChannel::AggregateIndirect;
detail_parts.push("sret (rax loaded from stack, rdi saved)");
if max_gpr_arg_touched == 0 {
max_gpr_arg_touched = -1;
} else if max_gpr_arg_touched > 0 {
max_gpr_arg_touched -= 1;
}
} else if wrote_xmm0 {
return_channel = CppReturnChannel::FloatingPoint;
detail_parts.push("xmm0 set before RET");
} else if wrote_rax {
return_channel = CppReturnChannel::GeneralPurpose;
detail_parts.push("rax set before RET");
} else if !ret_positions.is_empty() {
return_channel = CppReturnChannel::Void;
detail_parts.push("no return register written");
}
let gpr = if max_gpr_arg_touched >= 0 {
((max_gpr_arg_touched + 1) as u32).min(X86_64_MAX_GPR_ARGS)
} else {
0
};
let fp = if max_fp_arg_touched >= 0 {
((max_fp_arg_touched + 1) as u32).min(X86_64_MAX_FP_ARGS)
} else {
0
};
let param_counts = if gpr > 0 || fp > 0 {
detail_parts.push("param count from register spills");
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 STRING_FUNCTIONS: &[&str] = &[
"strlen",
"strcmp",
"strncmp",
"strcpy",
"strncpy",
"strcat",
"strncat",
"strdup",
"strndup",
"strchr",
"strrchr",
"strstr",
"strtol",
"strtoul",
"strtod",
"strtof",
"atoi",
"atol",
"atof",
"printf",
"fprintf",
"snprintf",
"sprintf",
"vprintf",
"vfprintf",
"vsnprintf",
"vsprintf",
"puts",
"fputs",
"fgets",
"sscanf",
"fscanf",
"fopen",
"freopen",
"NSLog",
"CFStringCreateWithCString",
];
const OBJC_FUNCTIONS: &[&str] = &[
"objc_msgSend",
"objc_msgSendSuper",
"objc_msgSendSuper2",
"objc_msgSend_stret",
"objc_retain",
"objc_release",
"objc_autorelease",
"objc_alloc",
"objc_alloc_init",
"objc_opt_new",
"objc_storeStrong",
"objc_retainAutoreleasedReturnValue",
];