use std::marker::PhantomData;
use crate::{
objc::{
ObjcRuntime,
method::{MethodIter, method_list_iter},
property::{PropertyIter, property_list_iter},
strip_objc_symbol_prefix,
},
util::{read_u32_le_at, read_u64_le_at},
};
const PROTOCOL_BASE_SIZE: usize = 72;
const PROTOCOL_FIELD_EXTENDED_METHOD_TYPES_OFFSET: u32 = 72;
const PROTOCOL_FIELD_DEMANGLED_NAME_OFFSET: u32 = 80;
const PROTOCOL_FIELD_CLASS_PROPERTIES_OFFSET: u32 = 88;
#[derive(Debug, Clone)]
pub struct ObjcProtocol<'a, 'p> {
pub(crate) rt: &'p ObjcRuntime<'a>,
pub(crate) address: u64,
pub(crate) name: &'a str,
pub(crate) instance_methods_va: u64,
pub(crate) class_methods_va: u64,
pub(crate) optional_instance_methods_va: u64,
pub(crate) optional_class_methods_va: u64,
pub(crate) instance_properties_va: u64,
pub(crate) class_properties_va: Option<u64>,
pub(crate) protocols_va: u64,
pub(crate) size: u32,
pub(crate) flags: u32,
}
impl<'a, 'p> ObjcProtocol<'a, 'p> {
pub fn address(&self) -> u64 {
self.address
}
pub fn name(&self) -> &'a str {
self.name
}
pub fn size(&self) -> u32 {
self.size
}
pub fn flags(&self) -> u32 {
self.flags
}
pub fn instance_methods(&self) -> MethodIter<'a, 'p> {
method_list_iter(self.rt, self.instance_methods_va)
}
pub fn class_methods(&self) -> MethodIter<'a, 'p> {
method_list_iter(self.rt, self.class_methods_va)
}
pub fn optional_instance_methods(&self) -> MethodIter<'a, 'p> {
method_list_iter(self.rt, self.optional_instance_methods_va)
}
pub fn optional_class_methods(&self) -> MethodIter<'a, 'p> {
method_list_iter(self.rt, self.optional_class_methods_va)
}
pub fn instance_properties(&self) -> PropertyIter<'a, 'p> {
property_list_iter(self.rt, self.instance_properties_va)
}
pub fn class_properties(&self) -> PropertyIter<'a, 'p> {
match self.class_properties_va {
Some(va) => property_list_iter(self.rt, va),
None => PropertyIter::empty(self.rt),
}
}
pub fn protocols(&self) -> ProtocolNameIter<'a, 'p> {
protocol_name_iter(self.rt, self.protocols_va)
}
}
pub struct ProtocolIter<'a, 'p> {
rt: &'p ObjcRuntime<'a>,
cursor: u64,
end: u64,
}
impl<'a, 'p> ProtocolIter<'a, 'p> {
pub(crate) fn new(rt: &'p ObjcRuntime<'a>) -> Self {
let (cursor, end) = match rt.proto_list {
Some(s) => (0u64, s.body.len() as u64),
None => (0u64, 0u64),
};
Self { rt, cursor, end }
}
}
impl<'a, 'p> Iterator for ProtocolIter<'a, 'p> {
type Item = ObjcProtocol<'a, 'p>;
fn next(&mut self) -> Option<Self::Item> {
let proto_list = self.rt.proto_list?;
loop {
if self.cursor.checked_add(8)? > self.end {
return None;
}
let slot_va = proto_list.vmaddr.wrapping_add(self.cursor);
let slot_idx = self.cursor / 8;
self.cursor = self.cursor.checked_add(8)?;
let proto_va = self.rt.resolve_pointer(slot_va).unwrap_or(0);
if proto_va == 0 {
continue;
}
if let Some(p) = decode_protocol(self.rt, proto_va) {
return Some(p);
}
#[cfg(feature = "tracing")]
tracing::debug!(
"darwinscope::objc: protocol at 0x{:x} (slot idx={}) skipped - decode failed",
proto_va,
slot_idx,
);
#[cfg(not(feature = "tracing"))]
let _ = slot_idx;
}
}
}
pub(crate) fn decode_protocol<'a, 'p>(
rt: &'p ObjcRuntime<'a>,
proto_va: u64,
) -> Option<ObjcProtocol<'a, 'p>> {
let bytes = rt.read_bytes(proto_va, PROTOCOL_BASE_SIZE)?;
let name_va = rt.resolve_pointer(proto_va.checked_add(8)?)?;
let protocols_va = rt.resolve_pointer(proto_va.checked_add(16)?).unwrap_or(0);
let instance_methods_va = rt.resolve_pointer(proto_va.checked_add(24)?).unwrap_or(0);
let class_methods_va = rt.resolve_pointer(proto_va.checked_add(32)?).unwrap_or(0);
let optional_instance_methods_va = rt.resolve_pointer(proto_va.checked_add(40)?).unwrap_or(0);
let optional_class_methods_va = rt.resolve_pointer(proto_va.checked_add(48)?).unwrap_or(0);
let instance_properties_va = rt.resolve_pointer(proto_va.checked_add(56)?).unwrap_or(0);
let size = read_u32_le_at(bytes, 64)?;
let flags = read_u32_le_at(bytes, 68)?;
let name = rt.read_cstr(name_va)?;
let class_properties_va = if size as usize
>= (PROTOCOL_FIELD_CLASS_PROPERTIES_OFFSET as usize).saturating_add(8)
{
let slot_va = proto_va.checked_add(u64::from(PROTOCOL_FIELD_CLASS_PROPERTIES_OFFSET))?;
rt.read_bytes(slot_va, 8)?;
let resolved = rt.resolve_pointer(slot_va).unwrap_or(0);
if resolved == 0 { None } else { Some(resolved) }
} else {
None
};
let _ = PROTOCOL_FIELD_EXTENDED_METHOD_TYPES_OFFSET;
let _ = PROTOCOL_FIELD_DEMANGLED_NAME_OFFSET;
Some(ObjcProtocol {
rt,
address: proto_va,
name,
instance_methods_va,
class_methods_va,
optional_instance_methods_va,
optional_class_methods_va,
instance_properties_va,
class_properties_va,
protocols_va,
size,
flags,
})
}
pub struct ProtocolNameIter<'a, 'p> {
rt: &'p ObjcRuntime<'a>,
list_base: Option<u64>,
count: u64,
cursor: u64,
_phantom: PhantomData<&'a ()>,
}
impl<'a, 'p> ProtocolNameIter<'a, 'p> {
pub(crate) fn empty(rt: &'p ObjcRuntime<'a>) -> Self {
Self {
rt,
list_base: None,
count: 0,
cursor: 0,
_phantom: PhantomData,
}
}
}
pub(crate) fn protocol_name_iter<'a, 'p>(
rt: &'p ObjcRuntime<'a>,
list_va: u64,
) -> ProtocolNameIter<'a, 'p> {
if list_va == 0 || (list_va & 0x1) != 0 {
return ProtocolNameIter::empty(rt);
}
let Some(header) = rt.read_bytes(list_va, 8) else {
return ProtocolNameIter::empty(rt);
};
let Some(count) = read_u64_le_at(header, 0) else {
return ProtocolNameIter::empty(rt);
};
let count = count.min(1 << 20);
let Some(base) = list_va.checked_add(8) else {
return ProtocolNameIter::empty(rt);
};
ProtocolNameIter {
rt,
list_base: Some(base),
count,
cursor: 0,
_phantom: PhantomData,
}
}
impl<'a, 'p> Iterator for ProtocolNameIter<'a, 'p> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let base = self.list_base?;
loop {
if self.cursor >= self.count {
return None;
}
let i = self.cursor;
self.cursor = self.cursor.checked_add(1)?;
let slot_va = base.checked_add(i.checked_mul(8)?)?;
let proto_va = self.rt.resolve_pointer(slot_va).unwrap_or(0);
if proto_va != 0
&& let Some(p) = decode_protocol(self.rt, proto_va)
{
return Some(p.name());
}
if let Some((sym, _)) = self.rt.binds_by_va.get(&slot_va) {
return Some(strip_objc_symbol_prefix(sym));
}
#[cfg(feature = "tracing")]
tracing::debug!(
"darwinscope::objc: protocol-list slot at 0x{:x} unresolved",
slot_va
);
}
}
}