use std::collections::HashMap;
use crate::{
binary::MachoBinary,
util::{read_cstr_at, read_u32_le_at, read_u64_le_at, vm_to_file_offset_in},
};
mod capture;
mod classtrailers;
mod conformance;
mod context;
mod field;
mod parent;
mod protocol;
mod reflstr;
mod replacement;
mod section;
mod typedescriptor;
mod vtable;
pub use capture::{CaptureDescriptor, CaptureIter};
pub use classtrailers::{
ForeignMetadataInit, GenericContextHeader, InvertibleProtocolSet, ObjcResilientClassStubInfo,
PrespecializationIter, ResilientSuperclass, SingletonMetadataInit, SingletonMetadataPointer,
};
pub use conformance::{Conformance, ConformanceIter, TypeReference};
pub use context::{
ConformanceFlags, ContextDescriptorFlags, ContextDescriptorKind, FieldDescriptorKind,
FieldRecordFlags, MetadataInitializationKind, MethodDescriptorFlags, SwiftMethodKind,
TypeContextDescriptorFlags, TypeReferenceKind,
};
pub use field::{FieldDescriptor, FieldIter, FieldRecord, FieldRecordIter};
pub use parent::{ParentChain, ParentContext};
pub use protocol::{ProtocolIter, SwiftProtocol};
pub use replacement::{DynamicReplacementScope, ReplacementIter};
pub use typedescriptor::{
DefaultOverrideTableHeader, OverrideTableHeader, TypeDescriptor, TypeIter, TypeKindBody,
VTableHeader,
};
pub use vtable::{
DefaultOverrideEntry, DefaultOverrideEntryIter, OverrideEntry, OverrideEntryIter, VTableEntry,
VTableIter,
};
pub(crate) use section::{SwiftSection, find_swift_section};
#[derive(Debug)]
pub struct SwiftRuntime<'a> {
pub(crate) data: &'a [u8],
pub(crate) segments: Vec<(u64, u64, u64, u64)>,
pub(crate) types: Option<SwiftSection<'a>>,
pub(crate) protos: Option<SwiftSection<'a>>,
pub(crate) proto: Option<SwiftSection<'a>>,
pub(crate) fieldmd: Option<SwiftSection<'a>>,
pub(crate) replac: Option<SwiftSection<'a>>,
pub(crate) replac2: Option<SwiftSection<'a>>,
pub(crate) capture: Option<SwiftSection<'a>>,
pub(crate) reflstr: Option<SwiftSection<'a>>,
#[allow(dead_code)]
pub(crate) typeref: Option<SwiftSection<'a>>,
pub(crate) builtin: Option<SwiftSection<'a>>,
pub(crate) entry: Option<SwiftSection<'a>>,
pub(crate) mpenum: Option<SwiftSection<'a>>,
pub(crate) acfuncs: Option<SwiftSection<'a>>,
pub(crate) assocty: Option<SwiftSection<'a>>,
#[allow(dead_code)]
pub(crate) binds_by_va: HashMap<u64, (&'a str, &'a str)>,
#[allow(dead_code)]
pub(crate) rebases_by_va: HashMap<u64, u64>,
}
impl<'a> SwiftRuntime<'a> {
pub(crate) fn build(bin: &MachoBinary<'a>) -> Option<Self> {
if !bin.header().is_64() {
#[cfg(feature = "tracing")]
tracing::debug!("darwinscope::swift: 32-bit Mach-O - Swift walker is 64-bit only");
return None;
}
let types = find_swift_section(bin, "__swift5_types");
let protos = find_swift_section(bin, "__swift5_protos");
let proto = find_swift_section(bin, "__swift5_proto");
let fieldmd = find_swift_section(bin, "__swift5_fieldmd");
if types.is_none() && protos.is_none() && proto.is_none() && fieldmd.is_none() {
return None;
}
let mut segments: Vec<(u64, u64, u64, u64)> = Vec::new();
for s in bin.segments() {
segments.push((s.vmaddr(), s.vmsize(), s.fileoff(), s.filesize()));
}
let mut binds_by_va: HashMap<u64, (&'a str, &'a str)> = HashMap::new();
let data = bin.raw();
for imp in bin.imports() {
let Some(name) = reborrow_into_data(data, imp.name) else {
continue;
};
let Some(dylib) = reborrow_into_data(data, imp.dylib) else {
continue;
};
binds_by_va.insert(imp.address, (name, dylib));
}
let mut rebases_by_va: HashMap<u64, u64> = HashMap::new();
for r in bin.chained_rebases() {
rebases_by_va.insert(r.vm_address(), r.target_vmaddr());
}
Some(Self {
data,
segments,
types,
protos,
proto,
fieldmd,
replac: find_swift_section(bin, "__swift5_replac"),
replac2: find_swift_section(bin, "__swift5_replac2"),
capture: find_swift_section(bin, "__swift5_capture"),
reflstr: find_swift_section(bin, "__swift5_reflstr"),
typeref: find_swift_section(bin, "__swift5_typeref"),
builtin: find_swift_section(bin, "__swift5_builtin"),
entry: find_swift_section(bin, "__swift5_entry"),
mpenum: find_swift_section(bin, "__swift5_mpenum"),
acfuncs: find_swift_section(bin, "__swift5_acfuncs"),
assocty: find_swift_section(bin, "__swift5_assocty"),
binds_by_va,
rebases_by_va,
})
}
pub fn types(&self) -> TypeIter<'a, '_> {
TypeIter::new(self)
}
pub fn protocols(&self) -> ProtocolIter<'a, '_> {
ProtocolIter::new(self)
}
pub fn conformances(&self) -> ConformanceIter<'a, '_> {
ConformanceIter::new(self)
}
pub fn field_descriptors(&self) -> FieldIter<'a, '_> {
FieldIter::new(self)
}
pub fn dynamic_replacements(&self) -> ReplacementIter<'a, '_> {
ReplacementIter::new(self)
}
pub fn captures(&self) -> CaptureIter<'a, '_> {
CaptureIter::new(self)
}
pub fn has_entry_point(&self) -> bool {
self.entry.is_some()
}
pub fn has_builtin_descriptors(&self) -> bool {
self.builtin.is_some()
}
pub fn has_multi_payload_enum_descriptors(&self) -> bool {
self.mpenum.is_some()
}
pub fn has_accessible_functions(&self) -> bool {
self.acfuncs.is_some()
}
pub fn has_associated_type_descriptors(&self) -> bool {
self.assocty.is_some()
}
pub fn has_replacement_chain(&self) -> bool {
self.replac2.is_some()
}
pub(crate) fn vm_to_file_offset(&self, vmaddr: u64) -> Option<u64> {
vm_to_file_offset_in(self.segments.iter().copied(), vmaddr)
}
pub(crate) fn read_cstr(&self, vmaddr: u64) -> Option<&'a str> {
if vmaddr == 0 {
return None;
}
let off = self.vm_to_file_offset(vmaddr)? as usize;
read_cstr_at(self.data, off)
}
#[allow(dead_code)]
pub(crate) fn read_u32(&self, vmaddr: u64) -> Option<u32> {
let off = self.vm_to_file_offset(vmaddr)? as usize;
read_u32_le_at(self.data, off)
}
#[allow(dead_code)]
pub(crate) fn read_u64(&self, vmaddr: u64) -> Option<u64> {
let off = self.vm_to_file_offset(vmaddr)? as usize;
read_u64_le_at(self.data, off)
}
pub(crate) fn read_bytes(&self, vmaddr: u64, len: usize) -> Option<&'a [u8]> {
let off = self.vm_to_file_offset(vmaddr)? as usize;
let end = off.checked_add(len)?;
self.data.get(off..end)
}
#[allow(dead_code)]
pub(crate) fn resolve_absolute_pointer(&self, slot_va: u64) -> Option<u64> {
if let Some(&target) = self.rebases_by_va.get(&slot_va) {
return Some(target);
}
let raw = self.read_u64(slot_va)?;
Some(crate::ptrauth::strip_signature(raw))
}
}
fn reborrow_into_data<'a>(data: &'a [u8], borrowed: &str) -> Option<&'a str> {
if borrowed.is_empty() {
return Some("");
}
let data_start = data.as_ptr() as usize;
let data_end = data_start.checked_add(data.len())?;
let s_start = borrowed.as_ptr() as usize;
let s_end = s_start.checked_add(borrowed.len())?;
if s_start < data_start || s_end > data_end {
return None;
}
let off = s_start.checked_sub(data_start)?;
let end = off.checked_add(borrowed.len())?;
let slice = data.get(off..end)?;
core::str::from_utf8(slice).ok()
}