use crate::{objc::ObjcRuntime, util::read_u32_le_at};
const WORD_SHIFT_64: u8 = 3;
const IVAR_ENTSIZE: u32 = 32;
#[derive(Debug, Clone)]
pub struct Ivar<'a> {
name: &'a str,
type_encoding: &'a str,
offset: Option<u32>,
size: u32,
log2_alignment: u8,
}
impl<'a> Ivar<'a> {
pub fn name(&self) -> &'a str {
self.name
}
pub fn type_encoding(&self) -> &'a str {
self.type_encoding
}
pub fn offset(&self) -> Option<u32> {
self.offset
}
pub fn size(&self) -> u32 {
self.size
}
pub fn log2_alignment(&self) -> u8 {
self.log2_alignment
}
}
pub struct IvarIter<'a, 'p> {
rt: &'p ObjcRuntime<'a>,
layout: Option<IvarListLayout>,
cursor: u32,
}
#[derive(Debug, Clone, Copy)]
struct IvarListLayout {
base_va: u64,
entsize: u32,
count: u32,
}
impl<'a, 'p> IvarIter<'a, 'p> {
pub(crate) fn empty(rt: &'p ObjcRuntime<'a>) -> Self {
Self {
rt,
layout: None,
cursor: 0,
}
}
}
impl<'a, 'p> Iterator for IvarIter<'a, 'p> {
type Item = Ivar<'a>;
fn next(&mut self) -> Option<Self::Item> {
let layout = self.layout?;
loop {
if self.cursor >= layout.count {
return None;
}
let i = self.cursor;
self.cursor = self.cursor.checked_add(1)?;
let entry_off = u64::from(i).checked_mul(u64::from(layout.entsize))?;
let entry_va = layout.base_va.checked_add(entry_off)?;
if let Some(v) = decode_ivar(self.rt, entry_va) {
return Some(v);
}
#[cfg(feature = "tracing")]
tracing::debug!(
"darwinscope::objc: ivar row at 0x{:x} (idx={}) skipped - decode failed",
entry_va,
i
);
}
}
}
pub(crate) fn ivar_list_iter<'a, 'p>(rt: &'p ObjcRuntime<'a>, list_va: u64) -> IvarIter<'a, 'p> {
if list_va == 0 {
return IvarIter::empty(rt);
}
let Some(header) = rt.read_bytes(list_va, 8) else {
return IvarIter::empty(rt);
};
let Some(entsize_and_flags) = read_u32_le_at(header, 0) else {
return IvarIter::empty(rt);
};
let Some(count) = read_u32_le_at(header, 4) else {
return IvarIter::empty(rt);
};
let entsize = entsize_and_flags;
if entsize < IVAR_ENTSIZE {
return IvarIter::empty(rt);
}
let base_va = match list_va.checked_add(8) {
Some(v) => v,
None => return IvarIter::empty(rt),
};
IvarIter {
rt,
layout: Some(IvarListLayout {
base_va,
entsize,
count,
}),
cursor: 0,
}
}
fn decode_ivar<'a>(rt: &ObjcRuntime<'a>, entry_va: u64) -> Option<Ivar<'a>> {
let bytes = rt.read_bytes(entry_va, IVAR_ENTSIZE as usize)?;
let offset_slot_va = rt.resolve_pointer(entry_va).unwrap_or(0);
let name_va = rt.resolve_pointer(entry_va.checked_add(8)?)?;
let type_va = rt.resolve_pointer(entry_va.checked_add(16)?).unwrap_or(0);
let alignment_raw = read_u32_le_at(bytes, 24)?;
let size = read_u32_le_at(bytes, 28)?;
let name = rt.read_cstr(name_va)?;
let type_encoding = rt.read_cstr(type_va).unwrap_or("");
let offset = if offset_slot_va == 0 {
None
} else {
rt.read_u32(offset_slot_va)
};
let log2_alignment = if alignment_raw == u32::MAX {
WORD_SHIFT_64
} else {
(alignment_raw & 0xff) as u8
};
Some(Ivar {
name,
type_encoding,
offset,
size,
log2_alignment,
})
}