Skip to content

Commit 1fb9828

Browse files
committed
[Swift] Add support for applying parameter and return types during demangling
This is disabled by default due to a current limitation where core is not able to represent parameters that are small structs being passed across multiple registers. `analysis.swift.extractTypesFromMangledNames` can be enabled to test this.
1 parent 7aa5bd4 commit 1fb9828

File tree

5 files changed

+602
-7
lines changed

5 files changed

+602
-7
lines changed
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
use binaryninja::architecture::{ArchitectureExt, CoreArchitecture, Register};
2+
use binaryninja::confidence::Conf;
3+
use binaryninja::rc::Ref;
4+
use binaryninja::types::{FunctionParameter, Type};
5+
use binaryninja::variable::{Variable, VariableSourceType};
6+
use swift_demangler::{
7+
Accessor, AccessorKind, ConstructorKind, HasFunctionSignature, HasModule, Metadata,
8+
MetadataKind, Symbol,
9+
};
10+
11+
use super::type_reconstruction::{make_named_type_ref, TypeRefExt};
12+
13+
/// Swift calling convention builder.
14+
///
15+
/// Tracks which implicit parameters (self, error, async context) are present,
16+
/// constructs the corresponding `FunctionParameter`s, and resolves the correct
17+
/// architecture-specific calling convention when building the final function type.
18+
struct CallingConvention {
19+
arch: CoreArchitecture,
20+
flags: u8,
21+
leading_params: Vec<FunctionParameter>,
22+
trailing_params: Vec<FunctionParameter>,
23+
}
24+
25+
impl CallingConvention {
26+
const SELF: u8 = 1;
27+
const THROWS: u8 = 2;
28+
const ASYNC: u8 = 4;
29+
30+
fn for_arch(arch: &CoreArchitecture) -> Self {
31+
Self {
32+
arch: *arch,
33+
flags: 0,
34+
leading_params: Vec::new(),
35+
trailing_params: Vec::new(),
36+
}
37+
}
38+
39+
/// Mark this as a concrete instance method with self in x20.
40+
fn set_self(&mut self, module: Option<&str>, containing_type: &str) {
41+
self.flags |= Self::SELF;
42+
let named_ty = make_named_type_ref(module, containing_type);
43+
let self_ty = Type::pointer(&self.arch, &named_ty);
44+
self.leading_params.push(FunctionParameter {
45+
ty: self_ty.into(),
46+
name: "self".to_string(),
47+
location: None,
48+
});
49+
}
50+
51+
/// Optionally mark as a concrete instance method if `containing_type` is `Some`.
52+
fn set_self_if(&mut self, module: Option<&str>, containing_type: Option<&str>) {
53+
if let Some(ct) = containing_type {
54+
self.set_self(module, ct);
55+
}
56+
}
57+
58+
/// Mark this as a protocol witness method. Self is in x20 (swift-self)
59+
/// like concrete methods. The self type metadata and witness table are
60+
/// appended as trailing arguments after the explicit params.
61+
fn set_protocol_self(&mut self, module: Option<&str>, containing_type: &str) {
62+
self.set_self(module, containing_type);
63+
let void_ptr = Type::pointer(&self.arch, &Type::void());
64+
self.trailing_params.push(FunctionParameter {
65+
ty: void_ptr.clone().into(),
66+
name: "selfMetadata".to_string(),
67+
location: None,
68+
});
69+
self.trailing_params.push(FunctionParameter {
70+
ty: void_ptr.into(),
71+
name: "selfWitnessTable".to_string(),
72+
location: None,
73+
});
74+
}
75+
76+
fn set_protocol_self_if(&mut self, module: Option<&str>, containing_type: Option<&str>) {
77+
if let Some(ct) = containing_type {
78+
self.set_protocol_self(module, ct);
79+
}
80+
}
81+
82+
/// Mark this as a throwing function.
83+
fn set_throws(&mut self) {
84+
self.flags |= Self::THROWS;
85+
}
86+
87+
/// Mark this as an async function.
88+
fn set_async(&mut self) {
89+
self.flags |= Self::ASYNC;
90+
}
91+
92+
/// Build the final function type.
93+
///
94+
/// Prepends `self` before `params` and appends error/async context after,
95+
/// then looks up the appropriate calling convention by name on the architecture.
96+
fn build_type(self, ret_type: &Type, params: Vec<FunctionParameter>) -> Ref<Type> {
97+
let mut all_params = self.leading_params;
98+
all_params.extend(params);
99+
all_params.extend(self.trailing_params);
100+
101+
if self.flags & Self::THROWS != 0 {
102+
let error_ty = Type::pointer(&self.arch, &make_named_type_ref(Some("Swift"), "Error"));
103+
all_params.push(FunctionParameter {
104+
ty: error_ty.into(),
105+
name: "error".to_string(),
106+
location: None,
107+
});
108+
}
109+
110+
if self.flags & Self::ASYNC != 0 {
111+
let ptr_ty = Type::pointer(&self.arch, &Type::void());
112+
all_params.push(FunctionParameter {
113+
ty: ptr_ty.into(),
114+
name: "asyncContext".to_string(),
115+
location: None,
116+
});
117+
}
118+
119+
Type::function(ret_type, all_params, false)
120+
}
121+
}
122+
123+
/// Build a Binary Ninja function type from a parsed Swift symbol.
124+
///
125+
/// Returns `None` if the symbol does not have a function signature (e.g. metadata, variables).
126+
/// Thunks are intentionally excluded for now.
127+
pub fn build_function_type(symbol: &Symbol, arch: &CoreArchitecture) -> Option<Ref<Type>> {
128+
match symbol {
129+
Symbol::Accessor(a) => return build_accessor_type(a, arch),
130+
Symbol::Metadata(m) => return build_metadata_function_type(m, arch),
131+
Symbol::Attributed(a) => return build_function_type(&a.inner, arch),
132+
Symbol::Specialization(s) => return build_function_type(&s.inner, arch),
133+
Symbol::Suffixed(s) => return build_function_type(&s.inner, arch),
134+
_ => {}
135+
}
136+
137+
let mut cc = CallingConvention::for_arch(arch);
138+
139+
// Determine implicit `self` parameter for instance methods/constructors.
140+
match symbol {
141+
Symbol::Function(f) if f.is_method() && !f.is_static() => {
142+
if f.containing_type_is_protocol() {
143+
cc.set_protocol_self_if(f.module(), f.containing_type());
144+
} else {
145+
cc.set_self_if(f.module(), f.containing_type());
146+
}
147+
}
148+
Symbol::Constructor(c) if c.kind() != ConstructorKind::Allocating => {
149+
cc.set_self_if(c.module(), c.containing_type());
150+
}
151+
Symbol::Destructor(d) => {
152+
cc.set_self_if(d.module(), d.containing_type());
153+
return Some(cc.build_type(&Type::void(), vec![]));
154+
}
155+
_ => {}
156+
};
157+
158+
let (sig, labels) = match symbol {
159+
Symbol::Function(f) => (f.signature(), f.labels()),
160+
Symbol::Constructor(c) => (c.signature(), c.labels()),
161+
Symbol::Closure(c) => (c.signature(), vec![]),
162+
_ => (None, vec![]),
163+
};
164+
let sig = sig?;
165+
166+
if sig.is_throwing() {
167+
cc.set_throws();
168+
}
169+
if sig.is_async() {
170+
cc.set_async();
171+
}
172+
173+
let params: Vec<FunctionParameter> = sig
174+
.parameters()
175+
.iter()
176+
.enumerate()
177+
.filter_map(|(i, p)| {
178+
let ty = p.type_ref.to_bn_type(arch)?;
179+
let name = labels
180+
.get(i)
181+
.copied()
182+
.flatten()
183+
.or(p.label)
184+
.unwrap_or("")
185+
.to_string();
186+
Some(FunctionParameter {
187+
ty: ty.into(),
188+
name,
189+
location: None,
190+
})
191+
})
192+
.collect();
193+
194+
let ret_type = sig
195+
.return_type()
196+
.and_then(|rt| rt.to_bn_type(arch))
197+
.unwrap_or_else(Type::void);
198+
199+
Some(cc.build_type(&ret_type, params))
200+
}
201+
202+
/// Build a function type for a property accessor from its property type.
203+
fn build_accessor_type(accessor: &Accessor, arch: &CoreArchitecture) -> Option<Ref<Type>> {
204+
let prop_ty = accessor
205+
.property_type()
206+
.and_then(|pt| pt.to_bn_type(arch))?;
207+
208+
let mut cc = CallingConvention::for_arch(arch);
209+
210+
// Non-static instance accessors take `self` as a parameter.
211+
if !accessor.is_static() {
212+
if let Some(ct) = accessor.containing_type() {
213+
cc.set_self(accessor.module(), &ct);
214+
}
215+
}
216+
217+
match accessor.kind() {
218+
// Getter-like: (self) -> PropertyType
219+
AccessorKind::Getter | AccessorKind::GlobalGetter | AccessorKind::Read => {
220+
Some(cc.build_type(&prop_ty, vec![]))
221+
}
222+
223+
// Setter-like: (self, newValue: PropertyType) -> Void
224+
AccessorKind::Setter
225+
| AccessorKind::WillSet
226+
| AccessorKind::DidSet
227+
| AccessorKind::Modify
228+
| AccessorKind::Init => {
229+
let params = vec![FunctionParameter {
230+
ty: prop_ty.into(),
231+
name: "newValue".to_string(),
232+
location: None,
233+
}];
234+
Some(cc.build_type(&Type::void(), params))
235+
}
236+
237+
_ => None,
238+
}
239+
}
240+
241+
/// Build a function type for Swift runtime metadata functions.
242+
fn build_metadata_function_type(metadata: &Metadata, arch: &CoreArchitecture) -> Option<Ref<Type>> {
243+
let void_ptr = Type::pointer(arch, &Type::void());
244+
245+
match metadata.kind() {
246+
// Type metadata accessor: void* fn()
247+
// Returns a pointer to the type metadata singleton.
248+
MetadataKind::AccessFunction | MetadataKind::CanonicalSpecializedGenericAccessFunction => {
249+
Some(Type::function(&void_ptr, vec![], false))
250+
}
251+
252+
// Method lookup function: void* fn(void* metadata, void* method)
253+
MetadataKind::MethodLookupFunction => {
254+
let params = vec![
255+
FunctionParameter {
256+
ty: void_ptr.clone().into(),
257+
name: "metadata".to_string(),
258+
location: None,
259+
},
260+
FunctionParameter {
261+
ty: void_ptr.clone().into(),
262+
name: "method".to_string(),
263+
location: None,
264+
},
265+
];
266+
Some(Type::function(&void_ptr, params, false))
267+
}
268+
269+
_ => None,
270+
}
271+
}

plugins/workflow_swift/src/demangler/mod.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1+
mod function_type;
2+
mod name;
3+
mod type_reconstruction;
4+
15
use binaryninja::architecture::CoreArchitecture;
26
use binaryninja::binary_view::BinaryView;
37
use binaryninja::demangle::CustomDemangler;
48
use binaryninja::rc::Ref;
9+
use binaryninja::settings::{QueryOptions, Settings};
510
use binaryninja::types::{QualifiedName, Type};
611

12+
fn should_extract_types(view: Option<&BinaryView>) -> bool {
13+
let mut opts = match view {
14+
Some(v) => QueryOptions::new_with_view(v),
15+
None => QueryOptions::new(),
16+
};
17+
Settings::new().get_bool_with_opts(crate::SETTING_EXTRACT_TYPES, &mut opts)
18+
}
19+
720
pub struct SwiftDemangler;
821

922
impl CustomDemangler for SwiftDemangler {
@@ -19,16 +32,25 @@ impl CustomDemangler for SwiftDemangler {
1932

2033
fn demangle(
2134
&self,
22-
_arch: &CoreArchitecture,
35+
arch: &CoreArchitecture,
2336
name: &str,
24-
_view: Option<Ref<BinaryView>>,
37+
view: Option<Ref<BinaryView>>,
2538
) -> Option<(QualifiedName, Option<Ref<Type>>)> {
2639
let ctx = swift_demangler::Context::new();
2740
let symbol = swift_demangler::Symbol::parse(&ctx, name)?;
28-
// Use the canonical demangled form from the parsed node tree.
29-
// This matches what `xcrun swift-demangle` produces.
30-
// TODO: Use the structured Symbol API to also reconstruct BN Types.
31-
let demangled = symbol.display();
32-
Some((QualifiedName::from(demangled), None))
41+
42+
if should_extract_types(view.as_deref()) {
43+
let ty = function_type::build_function_type(&symbol, arch);
44+
let qname = if ty.is_some() {
45+
name::build_short_name(&symbol)
46+
} else {
47+
None
48+
}
49+
.unwrap_or_else(|| QualifiedName::from(symbol.display()));
50+
Some((qname, ty))
51+
} else {
52+
let qname = QualifiedName::from(symbol.display());
53+
Some((qname, None))
54+
}
3355
}
3456
}

0 commit comments

Comments
 (0)