use crate::{
swift::{
SwiftRuntime, classtrailers,
context::{ContextDescriptorFlags, ContextDescriptorKind, MetadataInitializationKind},
},
util::{read_i32_le_at, read_u32_le_at, relative_pointer},
};
const TYPE_DESCRIPTOR_BASE_SIZE: u64 = 20;
#[derive(Debug)]
pub struct TypeDescriptor<'a, 'p> {
pub(crate) rt: &'p SwiftRuntime<'a>,
pub(crate) address: u64,
pub(crate) flags: ContextDescriptorFlags,
pub(crate) parent_va: u64,
pub(crate) name: &'a str,
pub(crate) body: TypeKindBody<'a>,
}
#[derive(Debug, Clone)]
pub enum TypeKindBody<'a> {
Class(ClassBody<'a>),
Struct(StructBody),
Enum(EnumBody),
NonType,
}
#[derive(Debug, Clone)]
pub struct ClassBody<'a> {
pub superclass_mangled_name: Option<&'a str>,
pub metadata_negative_size_words: Option<u32>,
pub resilient_metadata_bounds_va: Option<u64>,
pub metadata_positive_size_words: Option<u32>,
pub extra_class_flags: Option<u32>,
pub num_immediate_members: u32,
pub num_fields: u32,
pub field_offset_vector_offset: u32,
pub generic_header_va: Option<u64>,
pub resilient_superclass_va: Option<u64>,
pub foreign_metadata_init_va: Option<u64>,
pub singleton_metadata_init_va: Option<u64>,
pub vtable_header: Option<VTableHeader>,
pub override_table_header: Option<OverrideTableHeader>,
pub objc_resilient_class_stub_va: Option<u64>,
pub prespecializations_count: Option<u32>,
pub prespecializations_base_va: Option<u64>,
pub invertible_protocol_set: Option<u16>,
pub singleton_metadata_pointer_va: Option<u64>,
pub default_override_table_header: Option<DefaultOverrideTableHeader>,
}
#[derive(Debug, Clone)]
pub struct StructBody {
pub num_fields: u32,
pub field_offset_vector_offset: u32,
pub generic_header_va: Option<u64>,
pub foreign_metadata_init_va: Option<u64>,
pub singleton_metadata_init_va: Option<u64>,
pub prespecializations_count: Option<u32>,
pub prespecializations_base_va: Option<u64>,
pub invertible_protocol_set: Option<u16>,
pub singleton_metadata_pointer_va: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct EnumBody {
pub num_payload_cases: u32,
pub payload_size_offset: u8,
pub num_empty_cases: u32,
pub generic_header_va: Option<u64>,
pub foreign_metadata_init_va: Option<u64>,
pub singleton_metadata_init_va: Option<u64>,
pub prespecializations_count: Option<u32>,
pub prespecializations_base_va: Option<u64>,
pub invertible_protocol_set: Option<u16>,
pub singleton_metadata_pointer_va: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VTableHeader {
pub vtable_offset: u32,
pub vtable_size: u32,
pub entries_va: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OverrideTableHeader {
pub num_entries: u32,
pub entries_va: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DefaultOverrideTableHeader {
pub num_entries: u32,
pub entries_va: u64,
}
impl<'a, 'p> TypeDescriptor<'a, 'p> {
pub fn runtime(&self) -> &'p SwiftRuntime<'a> {
self.rt
}
pub fn address(&self) -> u64 {
self.address
}
pub fn flags(&self) -> ContextDescriptorFlags {
self.flags
}
pub fn kind(&self) -> ContextDescriptorKind {
self.flags.kind()
}
pub fn type_flags(&self) -> crate::swift::TypeContextDescriptorFlags {
self.flags.type_flags()
}
pub fn parent_address(&self) -> u64 {
self.parent_va
}
pub fn name(&self) -> &'a str {
self.name
}
pub fn body(&self) -> &TypeKindBody<'a> {
&self.body
}
pub fn vtable(&self) -> Option<crate::swift::VTableIter<'a, 'p>> {
let class = match &self.body {
TypeKindBody::Class(c) => c,
_ => return None,
};
let header = class.vtable_header.as_ref()?;
Some(crate::swift::VTableIter {
rt: self.rt,
base_va: header.entries_va,
count: header.vtable_size,
cursor: 0,
})
}
pub fn override_table(&self) -> Option<crate::swift::OverrideEntryIter<'a, 'p>> {
let class = match &self.body {
TypeKindBody::Class(c) => c,
_ => return None,
};
let header = class.override_table_header.as_ref()?;
Some(crate::swift::OverrideEntryIter {
rt: self.rt,
base_va: header.entries_va,
count: header.num_entries,
cursor: 0,
})
}
pub fn default_override_table(&self) -> Option<crate::swift::DefaultOverrideEntryIter<'a, 'p>> {
let class = match &self.body {
TypeKindBody::Class(c) => c,
_ => return None,
};
let header = class.default_override_table_header.as_ref()?;
Some(crate::swift::DefaultOverrideEntryIter {
rt: self.rt,
base_va: header.entries_va,
count: header.num_entries,
cursor: 0,
})
}
pub fn resilient_superclass(&self) -> Option<crate::swift::ResilientSuperclass<'a>> {
let class = match &self.body {
TypeKindBody::Class(c) => c,
_ => return None,
};
let address = class.resilient_superclass_va?;
let bytes = self.rt.read_bytes(address, 4)?;
let rel = read_i32_le_at(bytes, 0)?;
let superclass_va = if rel == 0 {
0
} else {
relative_pointer(address, rel)
};
Some(crate::swift::ResilientSuperclass {
address,
superclass_va,
_marker: core::marker::PhantomData,
})
}
pub fn foreign_metadata_init(&self) -> Option<crate::swift::ForeignMetadataInit> {
let address = match &self.body {
TypeKindBody::Class(c) => c.foreign_metadata_init_va?,
TypeKindBody::Struct(s) => s.foreign_metadata_init_va?,
TypeKindBody::Enum(e) => e.foreign_metadata_init_va?,
TypeKindBody::NonType => return None,
};
let bytes = self.rt.read_bytes(address, 4)?;
let rel = read_i32_le_at(bytes, 0)?;
let completion_function_va = if rel == 0 {
0
} else {
relative_pointer(address, rel)
};
Some(crate::swift::ForeignMetadataInit {
address,
completion_function_va,
})
}
pub fn singleton_metadata_init(&self) -> Option<crate::swift::SingletonMetadataInit> {
let address = match &self.body {
TypeKindBody::Class(c) => c.singleton_metadata_init_va?,
TypeKindBody::Struct(s) => s.singleton_metadata_init_va?,
TypeKindBody::Enum(e) => e.singleton_metadata_init_va?,
TypeKindBody::NonType => return None,
};
let bytes = self.rt.read_bytes(address, 12)?;
let cache_rel = read_i32_le_at(bytes, 0)?;
let pattern_rel = read_i32_le_at(bytes, 4)?;
let completion_rel = read_i32_le_at(bytes, 8)?;
let resolve = |slot_off: u64, rel: i32| -> Option<u64> {
if rel == 0 {
return Some(0);
}
let slot = address.checked_add(slot_off)?;
Some(relative_pointer(slot, rel))
};
Some(crate::swift::SingletonMetadataInit {
address,
initialization_cache_va: resolve(0, cache_rel)?,
incomplete_metadata_va: resolve(4, pattern_rel)?,
completion_function_va: resolve(8, completion_rel)?,
})
}
pub fn prespecializations(&self) -> Option<crate::swift::PrespecializationIter<'a, 'p>> {
let (count, base_va) = match &self.body {
TypeKindBody::Class(c) => (c.prespecializations_count?, c.prespecializations_base_va?),
TypeKindBody::Struct(s) => (s.prespecializations_count?, s.prespecializations_base_va?),
TypeKindBody::Enum(e) => (e.prespecializations_count?, e.prespecializations_base_va?),
TypeKindBody::NonType => return None,
};
Some(crate::swift::PrespecializationIter {
rt: self.rt,
base_va,
count,
cursor: 0,
})
}
pub fn invertible_protocol_set(&self) -> Option<crate::swift::InvertibleProtocolSet> {
let (address, bits) = match &self.body {
TypeKindBody::Class(c) => (None, c.invertible_protocol_set?),
TypeKindBody::Struct(s) => (None, s.invertible_protocol_set?),
TypeKindBody::Enum(e) => (None, e.invertible_protocol_set?),
TypeKindBody::NonType => return None,
};
Some(crate::swift::InvertibleProtocolSet {
address: address.unwrap_or(0),
bits,
})
}
pub fn singleton_metadata_pointer(&self) -> Option<crate::swift::SingletonMetadataPointer> {
let address = match &self.body {
TypeKindBody::Class(c) => c.singleton_metadata_pointer_va?,
TypeKindBody::Struct(s) => s.singleton_metadata_pointer_va?,
TypeKindBody::Enum(e) => e.singleton_metadata_pointer_va?,
TypeKindBody::NonType => return None,
};
let bytes = self.rt.read_bytes(address, 4)?;
let rel = read_i32_le_at(bytes, 0)?;
let metadata_va = if rel == 0 {
0
} else {
relative_pointer(address, rel)
};
Some(crate::swift::SingletonMetadataPointer {
address,
metadata_va,
})
}
pub fn field_offsets(&self) -> Option<Vec<u32>> {
let TypeKindBody::Struct(s) = &self.body else {
return None;
};
let fovo = s.field_offset_vector_offset;
let num_fields = s.num_fields;
if fovo == 0 || num_fields == 0 {
return None;
}
let metadata_va = self.singleton_metadata_pointer()?.metadata_va;
if metadata_va == 0 {
return None;
}
const WORD: u64 = 8;
let base = metadata_va.checked_add(u64::from(fovo).checked_mul(WORD)?)?;
let byte_len = (num_fields as usize).checked_mul(4)?;
let bytes = self.rt.read_bytes(base, byte_len)?;
let mut offsets = Vec::with_capacity(num_fields as usize);
for i in 0..num_fields as usize {
offsets.push(read_u32_le_at(bytes, i.checked_mul(4)?)?);
}
Some(offsets)
}
pub fn objc_resilient_class_stub_info(
&self,
) -> Option<crate::swift::ObjcResilientClassStubInfo> {
let address = match &self.body {
TypeKindBody::Class(c) => c.objc_resilient_class_stub_va?,
_ => return None,
};
let bytes = self.rt.read_bytes(address, 4)?;
let rel = read_i32_le_at(bytes, 0)?;
let stub_va = if rel == 0 {
0
} else {
relative_pointer(address, rel)
};
Some(crate::swift::ObjcResilientClassStubInfo { address, stub_va })
}
pub fn generic_context_header_address(&self) -> Option<u64> {
match &self.body {
TypeKindBody::Class(c) => c.generic_header_va,
TypeKindBody::Struct(s) => s.generic_header_va,
TypeKindBody::Enum(e) => e.generic_header_va,
TypeKindBody::NonType => None,
}
}
}
pub struct TypeIter<'a, 'p> {
rt: &'p SwiftRuntime<'a>,
cursor: usize,
}
impl<'a, 'p> TypeIter<'a, 'p> {
pub(crate) fn new(rt: &'p SwiftRuntime<'a>) -> Self {
Self { rt, cursor: 0 }
}
}
impl<'a, 'p> Iterator for TypeIter<'a, 'p> {
type Item = TypeDescriptor<'a, 'p>;
fn next(&mut self) -> Option<Self::Item> {
let section = self.rt.types.as_ref()?;
loop {
let slot_off = self.cursor;
let slot_end = slot_off.checked_add(4)?;
if slot_end > section.body.len() {
return None;
}
self.cursor = slot_end;
let Some(rel) = read_i32_le_at(section.body, slot_off) else {
continue;
};
if rel == 0 {
continue;
}
let slot_va = section.vmaddr.wrapping_add(slot_off as u64);
let descriptor_va = relative_pointer(slot_va, rel);
if let Some(descriptor) = decode_type_descriptor(self.rt, descriptor_va) {
return Some(descriptor);
}
#[cfg(feature = "tracing")]
tracing::debug!(
"darwinscope::swift: type descriptor at 0x{:x} (slot 0x{:x}) skipped - decode failed",
descriptor_va,
slot_va,
);
}
}
}
pub(crate) fn decode_type_descriptor<'a, 'p>(
rt: &'p SwiftRuntime<'a>,
descriptor_va: u64,
) -> Option<TypeDescriptor<'a, 'p>> {
let header = rt.read_bytes(descriptor_va, 20)?;
let flags_raw = read_u32_le_at(header, 0)?;
let parent_rel = read_i32_le_at(header, 4)?;
let name_rel = read_i32_le_at(header, 8)?;
let flags = ContextDescriptorFlags(flags_raw);
let parent_slot_va = descriptor_va.checked_add(4)?;
let parent_va = if parent_rel == 0 {
0
} else {
relative_pointer(parent_slot_va, parent_rel)
};
let name_slot_va = descriptor_va.checked_add(8)?;
let name_va = relative_pointer(name_slot_va, name_rel);
let name = rt.read_cstr(name_va)?;
let body = match flags.kind() {
ContextDescriptorKind::Class => decode_class_body(rt, descriptor_va, flags)
.map(TypeKindBody::Class)
.unwrap_or(TypeKindBody::NonType),
ContextDescriptorKind::Struct => decode_struct_body(rt, descriptor_va, flags)
.map(TypeKindBody::Struct)
.unwrap_or(TypeKindBody::NonType),
ContextDescriptorKind::Enum => decode_enum_body(rt, descriptor_va, flags)
.map(TypeKindBody::Enum)
.unwrap_or(TypeKindBody::NonType),
_ => TypeKindBody::NonType,
};
Some(TypeDescriptor {
rt,
address: descriptor_va,
flags,
parent_va,
name,
body,
})
}
fn decode_class_body<'a>(
rt: &SwiftRuntime<'a>,
descriptor_va: u64,
flags: ContextDescriptorFlags,
) -> Option<ClassBody<'a>> {
let class_off = descriptor_va.checked_add(TYPE_DESCRIPTOR_BASE_SIZE)?;
let header = rt.read_bytes(class_off, 24)?;
let superclass_rel = read_i32_le_at(header, 0)?;
let union_24 = read_u32_le_at(header, 4)?;
let union_28 = read_u32_le_at(header, 8)?;
let num_immediate_members = read_u32_le_at(header, 12)?;
let num_fields = read_u32_le_at(header, 16)?;
let field_offset_vector_offset = read_u32_le_at(header, 20)?;
let type_flags = flags.type_flags();
let resilient = type_flags.class_has_resilient_superclass();
let superclass_slot_va = class_off;
let superclass_mangled_name = if superclass_rel == 0 {
None
} else {
let target = relative_pointer(superclass_slot_va, superclass_rel);
rt.read_cstr(target)
};
let (metadata_negative_size_words, resilient_metadata_bounds_va) = if resilient {
let off = i32::from_le_bytes(union_24.to_le_bytes());
let slot = class_off.checked_add(4)?;
let target = if off == 0 {
0
} else {
relative_pointer(slot, off)
};
(None, Some(target))
} else {
(Some(union_24), None)
};
let (metadata_positive_size_words, extra_class_flags) = if resilient {
(None, Some(union_28))
} else {
(Some(union_28), None)
};
let mut body = ClassBody {
superclass_mangled_name,
metadata_negative_size_words,
resilient_metadata_bounds_va,
metadata_positive_size_words,
extra_class_flags,
num_immediate_members,
num_fields,
field_offset_vector_offset,
generic_header_va: None,
resilient_superclass_va: None,
foreign_metadata_init_va: None,
singleton_metadata_init_va: None,
vtable_header: None,
override_table_header: None,
objc_resilient_class_stub_va: None,
prespecializations_count: None,
prespecializations_base_va: None,
invertible_protocol_set: None,
singleton_metadata_pointer_va: None,
default_override_table_header: None,
};
let trailing_start = class_off.checked_add(24)?;
classtrailers::decode_class_trailers(rt, &flags, trailing_start, &mut body);
Some(body)
}
fn decode_struct_body(
rt: &SwiftRuntime<'_>,
descriptor_va: u64,
flags: ContextDescriptorFlags,
) -> Option<StructBody> {
let struct_off = descriptor_va.checked_add(TYPE_DESCRIPTOR_BASE_SIZE)?;
let header = rt.read_bytes(struct_off, 8)?;
let num_fields = read_u32_le_at(header, 0)?;
let field_offset_vector_offset = read_u32_le_at(header, 4)?;
let mut body = StructBody {
num_fields,
field_offset_vector_offset,
generic_header_va: None,
foreign_metadata_init_va: None,
singleton_metadata_init_va: None,
prespecializations_count: None,
prespecializations_base_va: None,
invertible_protocol_set: None,
singleton_metadata_pointer_va: None,
};
let trailing_start = struct_off.checked_add(8)?;
classtrailers::decode_value_type_trailers(
rt,
&flags,
trailing_start,
|va| body.generic_header_va = Some(va),
|va| body.foreign_metadata_init_va = Some(va),
|va| body.singleton_metadata_init_va = Some(va),
|count, base| {
body.prespecializations_count = Some(count);
body.prespecializations_base_va = Some(base);
},
|bits| body.invertible_protocol_set = Some(bits),
|va| body.singleton_metadata_pointer_va = Some(va),
);
Some(body)
}
fn decode_enum_body(
rt: &SwiftRuntime<'_>,
descriptor_va: u64,
flags: ContextDescriptorFlags,
) -> Option<EnumBody> {
let enum_off = descriptor_va.checked_add(TYPE_DESCRIPTOR_BASE_SIZE)?;
let header = rt.read_bytes(enum_off, 8)?;
let packed = read_u32_le_at(header, 0)?;
let num_empty_cases = read_u32_le_at(header, 4)?;
let num_payload_cases = packed & 0x00FF_FFFF;
let payload_size_offset = ((packed >> 24) & 0xFF) as u8;
let mut body = EnumBody {
num_payload_cases,
payload_size_offset,
num_empty_cases,
generic_header_va: None,
foreign_metadata_init_va: None,
singleton_metadata_init_va: None,
prespecializations_count: None,
prespecializations_base_va: None,
invertible_protocol_set: None,
singleton_metadata_pointer_va: None,
};
let trailing_start = enum_off.checked_add(8)?;
classtrailers::decode_value_type_trailers(
rt,
&flags,
trailing_start,
|va| body.generic_header_va = Some(va),
|va| body.foreign_metadata_init_va = Some(va),
|va| body.singleton_metadata_init_va = Some(va),
|count, base| {
body.prespecializations_count = Some(count);
body.prespecializations_base_va = Some(base);
},
|bits| body.invertible_protocol_set = Some(bits),
|va| body.singleton_metadata_pointer_va = Some(va),
);
Some(body)
}
#[allow(dead_code)]
pub(crate) fn metadata_init_kind(flags: ContextDescriptorFlags) -> MetadataInitializationKind {
flags.type_flags().metadata_initialization()
}