use rane::ffi::*;
use rane::mil;
use std::ffi::{c_char, c_void, CStr};
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("ANE deep introspection โ finding the bytecode\n");
introspect_compile()?;
Ok(())
}
fn introspect_compile() -> Result<(), Box<dyn std::error::Error>> {
for name in &["AppleNeuralEngine", "ANECompiler", "ANEServices"] {
let path = format!("/System/Library/PrivateFrameworks/{name}.framework/{name}");
let c = std::ffi::CString::new(path).unwrap();
unsafe {
dlopen(c.as_ptr(), RTLD_NOW);
}
}
let mil_text = mil::matmul(64, 64, 64).text;
let model_obj = unsafe { compile_and_get_model(&mil_text)? };
println!("\n=== Class hierarchy of _ANEInMemoryModel ===");
let cls = unsafe { object_getClass(model_obj) };
walk_class_hierarchy(cls);
println!("\n=== Enumerating ALL ivars (including superclasses) ===");
enumerate_ivars(model_obj);
println!("\n=== Methods on _ANEInMemoryModel ===");
enumerate_methods(cls);
println!("\n=== Following _model (_ANEModel) ivar ===");
let ane_model = unsafe { read_object_ivar(model_obj, "_model") };
if !ane_model.is_null() {
println!(" _ANEModel @ {ane_model:p}");
let ane_model_cls = unsafe { object_getClass(ane_model) };
println!(" Class hierarchy:");
walk_class_hierarchy(ane_model_cls);
println!(" ivars:");
enumerate_ivars(ane_model);
println!(" methods:");
enumerate_methods(ane_model_cls);
}
println!("\n=== _modelURL contents ===");
let url = unsafe { read_object_ivar(model_obj, "_modelURL") };
if !url.is_null() {
unsafe {
dump_nsurl(url);
}
}
println!("\n=== _sharedConnection (_ANEClient) ===");
let conn = unsafe { read_object_ivar(model_obj, "_sharedConnection") };
if !conn.is_null() {
let conn_cls = unsafe { object_getClass(conn) };
println!(" Methods:");
enumerate_methods(conn_cls);
}
println!("\n=== LOADING MODEL via loadWithQoS: ===");
let cls_dict = rane::ffi::cls("NSDictionary");
unsafe {
type DictFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
type LoadFn = unsafe extern "C" fn(ObjcId, ObjcSel, u32, ObjcId, *mut ObjcId) -> bool;
let dictf: DictFn = std::mem::transmute(objc_msgSend as *const c_void);
let loadf: LoadFn = std::mem::transmute(objc_msgSend as *const c_void);
let empty = dictf(cls_dict as ObjcId, sel("dictionary"));
let mut err: ObjcId = std::ptr::null_mut();
let ok = loadf(
model_obj,
sel("loadWithQoS:options:error:"),
21,
empty,
&mut err,
);
if !ok {
println!(" load FAILED");
} else {
println!(" loaded OK\n");
println!("=== Post-load ivar state ===");
enumerate_ivars(model_obj);
let am = read_object_ivar(model_obj, "_model");
if !am.is_null() {
let prog = read_object_ivar(am, "_program");
if !prog.is_null() {
println!("\n=== _ANEProgramForEvaluation @ {prog:p} ===");
let pc = object_getClass(prog);
println!(" Class hierarchy:");
walk_class_hierarchy(pc);
println!(" ivars:");
enumerate_ivars(prog);
println!(" methods:");
enumerate_methods(pc);
}
}
let prog2 = read_object_ivar(model_obj, "_program");
if !prog2.is_null() {
println!("\n=== Outer _program @ {prog2:p} ===");
let pc = object_getClass(prog2);
enumerate_ivars(prog2);
enumerate_methods(pc);
}
}
}
Ok(())
}
unsafe fn read_object_ivar(obj: ObjcId, name: &str) -> ObjcId {
let cls = object_getClass(obj);
let mut c = cls;
while !c.is_null() {
let mut count: u32 = 0;
let list = class_copyIvarList(c, &mut count);
if !list.is_null() {
for i in 0..count {
let ivar = *list.add(i as usize);
let n_ptr = ivar_getName(ivar);
if !n_ptr.is_null() {
let n = CStr::from_ptr(n_ptr).to_string_lossy();
if n == name {
libc_free(list as *mut c_void);
return object_getIvar(obj, ivar);
}
}
}
libc_free(list as *mut c_void);
}
c = class_getSuperclass(c);
}
std::ptr::null_mut()
}
unsafe fn dump_nsurl(url: ObjcId) {
type StrF = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
type Utf8F = unsafe extern "C" fn(ObjcId, ObjcSel) -> *const c_char;
let sf: StrF = std::mem::transmute(objc_msgSend as *const c_void);
let uf: Utf8F = std::mem::transmute(objc_msgSend as *const c_void);
let abs = sf(url, sel("absoluteString"));
if !abs.is_null() {
let cstr = uf(abs, sel("UTF8String"));
if !cstr.is_null() {
println!(
" absoluteString: {}",
CStr::from_ptr(cstr).to_string_lossy()
);
}
}
let p = sf(url, sel("path"));
if !p.is_null() {
let cstr = uf(p, sel("UTF8String"));
if !cstr.is_null() {
let path = CStr::from_ptr(cstr).to_string_lossy().into_owned();
println!(" path: {path}");
if let Ok(meta) = std::fs::metadata(&path) {
println!(" size: {} bytes, is_dir: {}", meta.len(), meta.is_dir());
if meta.is_dir() {
if let Ok(entries) = std::fs::read_dir(&path) {
for e in entries.flatten() {
let name = e.file_name();
let sz = e.metadata().map(|m| m.len()).unwrap_or(0);
println!(" {name:?}: {sz} bytes");
}
}
} else if meta.len() < 16 * 1024 * 1024 {
if let Ok(data) = std::fs::read(&path) {
let dst = format!("/tmp/ane_intro_modelURL.bin");
let _ = std::fs::write(&dst, &data);
println!(" saved {} bytes to {dst}", data.len());
let n = data.len().min(64);
print!(" head: ");
for &b in &data[..n] {
print!("{:02x} ", b);
}
println!();
}
}
}
}
}
}
unsafe fn compile_and_get_model(mil_text: &str) -> Result<ObjcId, Box<dyn std::error::Error>> {
type DataFn = unsafe extern "C" fn(ObjcId, ObjcSel, *const u8, u64) -> ObjcId;
type DictFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
type DescFn = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId, ObjcId, ObjcId) -> ObjcId;
type ModelFn = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId) -> ObjcId;
type CompileFn = unsafe extern "C" fn(ObjcId, ObjcSel, u32, ObjcId, *mut ObjcId) -> bool;
let cls_data = cls("NSData");
let cls_dict = cls("NSDictionary");
let cls_desc = cls("_ANEInMemoryModelDescriptor");
let cls_model = cls("_ANEInMemoryModel");
let dataf: DataFn = std::mem::transmute(objc_msgSend as *const c_void);
let dictf: DictFn = std::mem::transmute(objc_msgSend as *const c_void);
let descf: DescFn = std::mem::transmute(objc_msgSend as *const c_void);
let modelf: ModelFn = std::mem::transmute(objc_msgSend as *const c_void);
let compilef: CompileFn = std::mem::transmute(objc_msgSend as *const c_void);
let bytes = mil_text.as_bytes();
let nsdata = dataf(
cls_data as ObjcId,
sel("dataWithBytes:length:"),
bytes.as_ptr(),
bytes.len() as u64,
);
let empty = dictf(cls_dict as ObjcId, sel("dictionary"));
let descriptor = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
nsdata,
empty,
std::ptr::null_mut(),
);
let model = modelf(
cls_model as ObjcId,
sel("inMemoryModelWithDescriptor:"),
descriptor,
);
type StrF = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
type Utf8F = unsafe extern "C" fn(ObjcId, ObjcSel) -> *const c_char;
let sf: StrF = std::mem::transmute(objc_msgSend as *const c_void);
let uf: Utf8F = std::mem::transmute(objc_msgSend as *const c_void);
let hex_id = sf(model, sel("hexStringIdentifier"));
let hex_str = {
let cstr = uf(hex_id, sel("UTF8String"));
CStr::from_ptr(cstr).to_string_lossy().into_owned()
};
let tmp_dir = std::env::temp_dir().join(&hex_str);
let _ = std::fs::create_dir_all(tmp_dir.join("weights"));
let _ = std::fs::write(tmp_dir.join("model.mil"), mil_text);
let mut err: ObjcId = std::ptr::null_mut();
let ok = compilef(
model,
sel("compileWithQoS:options:error:"),
21,
empty,
&mut err,
);
if !ok {
return Err("compile failed".into());
}
Ok(model)
}
fn walk_class_hierarchy(cls: ObjcClass) {
let mut c = cls;
while !c.is_null() {
unsafe {
let name_ptr = class_getName(c);
let name = if !name_ptr.is_null() {
CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
} else {
"(null)".into()
};
println!(" {:?} โ {name}", c);
c = class_getSuperclass(c);
}
}
}
fn enumerate_ivars(obj: ObjcId) {
unsafe {
let mut c = object_getClass(obj);
while !c.is_null() {
let cls_name = if !class_getName(c).is_null() {
CStr::from_ptr(class_getName(c))
.to_string_lossy()
.into_owned()
} else {
"?".into()
};
let mut count: u32 = 0;
let list = class_copyIvarList(c, &mut count);
if !list.is_null() && count > 0 {
println!(" --- {cls_name} ({count} ivars) ---");
for i in 0..count {
let ivar = *list.add(i as usize);
let name_ptr = ivar_getName(ivar);
let type_ptr = ivar_getTypeEncoding(ivar);
let name = if !name_ptr.is_null() {
CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
} else {
"?".into()
};
let ty = if !type_ptr.is_null() {
CStr::from_ptr(type_ptr).to_string_lossy().into_owned()
} else {
"?".into()
};
let off = ivar_getOffset(ivar);
let val = object_getIvar(obj, ivar);
print!(" [{off:04x}] {name}: {ty} = {val:p}");
if ty.starts_with('@') && !val.is_null() {
let val_cls = object_getClass(val);
if !val_cls.is_null() {
let vc_name_p = class_getName(val_cls);
if !vc_name_p.is_null() {
let vc_name = CStr::from_ptr(vc_name_p).to_string_lossy();
print!(" [{vc_name}]");
if vc_name.contains("Data") || vc_name.contains("Buffer") {
dump_nsdata(val, &format!("{cls_name}_{name}"));
}
}
}
}
println!();
}
libc_free(list as *mut c_void);
}
c = class_getSuperclass(c);
}
}
}
fn enumerate_methods(cls: ObjcClass) {
unsafe {
let mut count: u32 = 0;
let list = class_copyMethodList(cls, &mut count);
if !list.is_null() && count > 0 {
println!(" Total methods on class: {count}");
let mut names: Vec<String> = Vec::new();
for i in 0..count {
let method = *list.add(i as usize);
let s = method_getName(method);
if !s.is_null() {
let n_ptr = sel_getName(s);
if !n_ptr.is_null() {
let n = CStr::from_ptr(n_ptr).to_string_lossy().into_owned();
names.push(n);
}
}
}
names.sort();
for n in &names {
let lower = n.to_lowercase();
if lower.contains("data")
|| lower.contains("byte")
|| lower.contains("compile")
|| lower.contains("kernel")
|| lower.contains("file")
|| lower.contains("url")
|| lower.contains("path")
|| lower.contains("binary")
|| lower.contains("blob")
|| lower.contains("size")
|| lower.contains("length")
{
println!(" {n}");
}
}
libc_free(list as *mut c_void);
}
}
}
fn dump_nsdata(data: ObjcId, label: &str) {
unsafe {
type LenFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> u64;
type BytesFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> *const u8;
let lf: LenFn = std::mem::transmute(objc_msgSend as *const c_void);
let bf: BytesFn = std::mem::transmute(objc_msgSend as *const c_void);
let len = lf(data, sel("length"));
let ptr = bf(data, sel("bytes"));
if ptr.is_null() {
println!(" bytes: <null>");
return;
}
println!(" NSData length={len}");
let slice = std::slice::from_raw_parts(ptr, len as usize);
let safe_label = label.replace(['/', ' ', ':'], "_");
let path = format!("/tmp/ane_intro_{safe_label}.bin");
let _ = std::fs::write(&path, slice);
println!(" saved to {path}");
let n = slice.len().min(64);
print!(" head: ");
for &b in &slice[..n] {
print!("{:02x} ", b);
}
println!();
}
}
extern "C" {
fn free(p: *mut c_void);
}
fn libc_free(p: *mut c_void) {
unsafe {
free(p);
}
}