From a49c29b7aea694e41fb92b100325ce34affb413f Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Thu, 4 Sep 2025 14:40:53 -0700 Subject: [PATCH 1/4] [Swift] Initial skeleton of a plug-in for Swift support --- Cargo.lock | 20 +++ Cargo.toml | 2 + plugins/workflow_swift/CMakeLists.txt | 171 +++++++++++++++++++++++++ plugins/workflow_swift/Cargo.toml | 18 +++ plugins/workflow_swift/build.rs | 25 ++++ plugins/workflow_swift/demo/Cargo.toml | 19 +++ plugins/workflow_swift/demo/build.rs | 15 +++ plugins/workflow_swift/src/lib.rs | 16 +++ 8 files changed, 286 insertions(+) create mode 100644 plugins/workflow_swift/CMakeLists.txt create mode 100644 plugins/workflow_swift/Cargo.toml create mode 100644 plugins/workflow_swift/build.rs create mode 100644 plugins/workflow_swift/demo/Cargo.toml create mode 100644 plugins/workflow_swift/demo/build.rs create mode 100644 plugins/workflow_swift/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ffbf65631f..f98edff74f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3454,6 +3454,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "workflow_swift" +version = "0.1.0" +dependencies = [ + "binaryninja", + "binaryninjacore-sys", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "workflow_swift-static" +version = "0.1.0" +dependencies = [ + "binaryninja", + "binaryninjacore-sys", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "writeable" version = "0.6.2" diff --git a/Cargo.toml b/Cargo.toml index ca260b9dba..5cf7ac75e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,8 @@ members = [ "plugins/workflow_objc/demo", "plugins/bntl_utils", "plugins/bntl_utils/cli", + "plugins/workflow_swift", + "plugins/workflow_swift/demo", ] [workspace.dependencies] diff --git a/plugins/workflow_swift/CMakeLists.txt b/plugins/workflow_swift/CMakeLists.txt new file mode 100644 index 0000000000..d9b027f325 --- /dev/null +++ b/plugins/workflow_swift/CMakeLists.txt @@ -0,0 +1,171 @@ +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) + +project(workflow_swift) + +if(NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD) + if(NOT BN_API_PATH) + # If we have not already defined the API source directory try and find it. + find_path( + BN_API_PATH + NAMES binaryninjaapi.h + # List of paths to search for the clone of the api + HINTS ../../.. ../../binaryninja/api/ binaryninjaapi binaryninja-api $ENV{BN_API_PATH} + REQUIRED + ) + endif() + set(CARGO_STABLE_VERSION 1.91.1) + add_subdirectory(${BN_API_PATH} binaryninjaapi) +endif() + +file(GLOB_RECURSE PLUGIN_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + if(DEMO) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/dev-demo) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --profile=dev-demo) + else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) + endif() +else() + if(DEMO) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release-demo) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --profile=release-demo) + else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) + endif() +endif() + +if(FORCE_COLORED_OUTPUT) + set(CARGO_OPTS ${CARGO_OPTS} --color always) +endif() + +if(DEMO) + set(CARGO_FEATURES --features demo --manifest-path ${PROJECT_SOURCE_DIR}/demo/Cargo.toml) + + set(OUTPUT_FILE_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}${PROJECT_NAME}_static${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(OUTPUT_PDB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}${PROJECT_NAME}.pdb) + set(OUTPUT_FILE_PATH ${CMAKE_BINARY_DIR}/${OUTPUT_FILE_NAME}) + set(OUTPUT_PDB_PATH ${CMAKE_BINARY_DIR}/${OUTPUT_PDB_NAME}) + + set(BINJA_LIB_DIR $) +else() + # NOTE: --no-default-features is set to disable building artifacts used for testing + # NOTE: the linker is looking in the target dir and linking on it apparently. + set(CARGO_FEATURES "--no-default-features") + + set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb) + set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_FILE_NAME}) + set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${OUTPUT_PDB_NAME}) + + set(BINJA_LIB_DIR ${BN_INSTALL_BIN_DIR}) +endif() + + +add_custom_target(${PROJECT_NAME} ALL DEPENDS ${OUTPUT_FILE_PATH}) +add_dependencies(${PROJECT_NAME} binaryninjaapi) +get_target_property(BN_API_SOURCE_DIR binaryninjaapi SOURCE_DIR) +list(APPEND CMAKE_MODULE_PATH "${BN_API_SOURCE_DIR}/cmake") +find_package(BinaryNinjaCore REQUIRED) + +set_property(TARGET ${PROJECT_NAME} PROPERTY OUTPUT_FILE_PATH ${OUTPUT_FILE_PATH}) + +# Add the whole api to the depends too +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/build.rs + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/src/*.rs + ${PROJECT_SOURCE_DIR}/../../rust/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../rust/src/*/*.rs) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo) + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + if(DEMO) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/dev-demo/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/dev-demo/${OUTPUT_FILE_NAME}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE_NAME}) + endif() + else() + if(DEMO) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release-demo/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release-demo/${OUTPUT_FILE_NAME}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE_NAME}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE_NAME}) + endif() + endif() + + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=aarch64-apple-darwin ${CARGO_OPTS} --package binaryninjacore-sys + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean --target=x86_64-apple-darwin ${CARGO_OPTS} --package binaryninjacore-sys + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=aarch64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build --target=x86_64-apple-darwin ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + else() + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BINJA_LIB_DIR} + ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + endif() +elseif(WIN32) + if(DEMO) + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + else() + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${OUTPUT_PDB_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) + endif() +else() + add_custom_command( + OUTPUT ${OUTPUT_FILE_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} + ) +endif() diff --git a/plugins/workflow_swift/Cargo.toml b/plugins/workflow_swift/Cargo.toml new file mode 100644 index 0000000000..4307371803 --- /dev/null +++ b/plugins/workflow_swift/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "workflow_swift" +version = "0.1.0" +edition = "2021" +license = "BSD-3-Clause" +publish = false + +[lib] +crate-type = ["staticlib", "cdylib"] + +[features] +demo = [] + +[dependencies] +binaryninja = { workspace = true } +binaryninjacore-sys.workspace = true +tracing = "0.1" +thiserror = "2.0" diff --git a/plugins/workflow_swift/build.rs b/plugins/workflow_swift/build.rs new file mode 100644 index 0000000000..9006f16a69 --- /dev/null +++ b/plugins/workflow_swift/build.rs @@ -0,0 +1,25 @@ +fn main() { + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); + + println!("cargo::rustc-link-lib=dylib=binaryninjacore"); + println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); + + #[cfg(target_os = "linux")] + { + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}", + link_path.to_string_lossy() + ); + } + + #[cfg(target_os = "macos")] + { + let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set"); + let lib_name = crate_name.replace('-', "_"); + println!( + "cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib", + lib_name + ); + } +} diff --git a/plugins/workflow_swift/demo/Cargo.toml b/plugins/workflow_swift/demo/Cargo.toml new file mode 100644 index 0000000000..38fb633d03 --- /dev/null +++ b/plugins/workflow_swift/demo/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "workflow_swift-static" +version = "0.1.0" +edition = "2021" +license = "BSD-3-Clause" +publish = false + +[lib] +crate-type = ["staticlib"] +path = "../src/lib.rs" + +[features] +demo = [] + +[dependencies] +binaryninja = { workspace = true, features = ["demo"] } +binaryninjacore-sys.workspace = true +tracing = "0.1" +thiserror = "2.0" diff --git a/plugins/workflow_swift/demo/build.rs b/plugins/workflow_swift/demo/build.rs new file mode 100644 index 0000000000..ed6cec7d27 --- /dev/null +++ b/plugins/workflow_swift/demo/build.rs @@ -0,0 +1,15 @@ +fn main() { + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); + + println!("cargo::rustc-link-lib=dylib=binaryninjacore"); + println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); + + #[cfg(not(target_os = "windows"))] + { + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}", + link_path.to_string_lossy() + ); + } +} diff --git a/plugins/workflow_swift/src/lib.rs b/plugins/workflow_swift/src/lib.rs new file mode 100644 index 0000000000..b8ddb7a464 --- /dev/null +++ b/plugins/workflow_swift/src/lib.rs @@ -0,0 +1,16 @@ +use binaryninja::add_optional_plugin_dependency; + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginDependencies() { + add_optional_plugin_dependency("arch_x86"); + add_optional_plugin_dependency("arch_arm64"); +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "C" fn CorePluginInit() -> bool { + binaryninja::tracing_init!("Plugin.Swift"); + + true +} From 7aa5bd46fc2343458dff6a24ec2c67169fcac34c Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Wed, 18 Feb 2026 19:43:43 -0800 Subject: [PATCH 2/4] [Swift] Initial demangling support --- Cargo.lock | 10 ++++++ plugins/workflow_swift/Cargo.toml | 1 + plugins/workflow_swift/demo/Cargo.toml | 1 + plugins/workflow_swift/src/demangler/mod.rs | 34 +++++++++++++++++++++ plugins/workflow_swift/src/lib.rs | 6 ++++ 5 files changed, 52 insertions(+) create mode 100644 plugins/workflow_swift/src/demangler/mod.rs diff --git a/Cargo.lock b/Cargo.lock index f98edff74f..16a2fe2a98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2598,6 +2598,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "swift-demangler" +version = "0.1.0" +source = "git+https://github.com/Vector35/swift-demangler.git#b0b98bfa446284632eafe23eddcc93e5098534e2" +dependencies = [ + "cmake", +] + [[package]] name = "syn" version = "2.0.104" @@ -3460,6 +3468,7 @@ version = "0.1.0" dependencies = [ "binaryninja", "binaryninjacore-sys", + "swift-demangler", "thiserror 2.0.18", "tracing", ] @@ -3470,6 +3479,7 @@ version = "0.1.0" dependencies = [ "binaryninja", "binaryninjacore-sys", + "swift-demangler", "thiserror 2.0.18", "tracing", ] diff --git a/plugins/workflow_swift/Cargo.toml b/plugins/workflow_swift/Cargo.toml index 4307371803..8ed4f8c46b 100644 --- a/plugins/workflow_swift/Cargo.toml +++ b/plugins/workflow_swift/Cargo.toml @@ -16,3 +16,4 @@ binaryninja = { workspace = true } binaryninjacore-sys.workspace = true tracing = "0.1" thiserror = "2.0" +swift-demangler = { git = "https://github.com/Vector35/swift-demangler.git" } diff --git a/plugins/workflow_swift/demo/Cargo.toml b/plugins/workflow_swift/demo/Cargo.toml index 38fb633d03..a1e46891f1 100644 --- a/plugins/workflow_swift/demo/Cargo.toml +++ b/plugins/workflow_swift/demo/Cargo.toml @@ -17,3 +17,4 @@ binaryninja = { workspace = true, features = ["demo"] } binaryninjacore-sys.workspace = true tracing = "0.1" thiserror = "2.0" +swift-demangler = { git = "https://github.com/Vector35/swift-demangler.git" } diff --git a/plugins/workflow_swift/src/demangler/mod.rs b/plugins/workflow_swift/src/demangler/mod.rs new file mode 100644 index 0000000000..e1f22893fe --- /dev/null +++ b/plugins/workflow_swift/src/demangler/mod.rs @@ -0,0 +1,34 @@ +use binaryninja::architecture::CoreArchitecture; +use binaryninja::binary_view::BinaryView; +use binaryninja::demangle::CustomDemangler; +use binaryninja::rc::Ref; +use binaryninja::types::{QualifiedName, Type}; + +pub struct SwiftDemangler; + +impl CustomDemangler for SwiftDemangler { + fn is_mangled_string(&self, name: &str) -> bool { + name.starts_with("$s") + || name.starts_with("_$s") + || name.starts_with("$S") + || name.starts_with("_$S") + || name.starts_with("$e") + || name.starts_with("_$e") + || name.starts_with("_T") + } + + fn demangle( + &self, + _arch: &CoreArchitecture, + name: &str, + _view: Option>, + ) -> Option<(QualifiedName, Option>)> { + let ctx = swift_demangler::Context::new(); + let symbol = swift_demangler::Symbol::parse(&ctx, name)?; + // Use the canonical demangled form from the parsed node tree. + // This matches what `xcrun swift-demangle` produces. + // TODO: Use the structured Symbol API to also reconstruct BN Types. + let demangled = symbol.display(); + Some((QualifiedName::from(demangled), None)) + } +} diff --git a/plugins/workflow_swift/src/lib.rs b/plugins/workflow_swift/src/lib.rs index b8ddb7a464..2e97a43bdd 100644 --- a/plugins/workflow_swift/src/lib.rs +++ b/plugins/workflow_swift/src/lib.rs @@ -1,4 +1,8 @@ +mod demangler; + use binaryninja::add_optional_plugin_dependency; +use binaryninja::demangle::Demangler; +use demangler::SwiftDemangler; #[no_mangle] #[allow(non_snake_case)] @@ -12,5 +16,7 @@ pub extern "C" fn CorePluginDependencies() { pub extern "C" fn CorePluginInit() -> bool { binaryninja::tracing_init!("Plugin.Swift"); + Demangler::register("Swift", SwiftDemangler); + true } From 1fb9828d1e12f242fa6a53aa43603ae5e556b69d Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Wed, 18 Feb 2026 22:03:29 -0800 Subject: [PATCH 3/4] [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. --- .../src/demangler/function_type.rs | 271 ++++++++++++++++++ plugins/workflow_swift/src/demangler/mod.rs | 36 ++- plugins/workflow_swift/src/demangler/name.rs | 84 ++++++ .../src/demangler/type_reconstruction.rs | 204 +++++++++++++ plugins/workflow_swift/src/lib.rs | 14 + 5 files changed, 602 insertions(+), 7 deletions(-) create mode 100644 plugins/workflow_swift/src/demangler/function_type.rs create mode 100644 plugins/workflow_swift/src/demangler/name.rs create mode 100644 plugins/workflow_swift/src/demangler/type_reconstruction.rs diff --git a/plugins/workflow_swift/src/demangler/function_type.rs b/plugins/workflow_swift/src/demangler/function_type.rs new file mode 100644 index 0000000000..3313143e97 --- /dev/null +++ b/plugins/workflow_swift/src/demangler/function_type.rs @@ -0,0 +1,271 @@ +use binaryninja::architecture::{ArchitectureExt, CoreArchitecture, Register}; +use binaryninja::confidence::Conf; +use binaryninja::rc::Ref; +use binaryninja::types::{FunctionParameter, Type}; +use binaryninja::variable::{Variable, VariableSourceType}; +use swift_demangler::{ + Accessor, AccessorKind, ConstructorKind, HasFunctionSignature, HasModule, Metadata, + MetadataKind, Symbol, +}; + +use super::type_reconstruction::{make_named_type_ref, TypeRefExt}; + +/// Swift calling convention builder. +/// +/// Tracks which implicit parameters (self, error, async context) are present, +/// constructs the corresponding `FunctionParameter`s, and resolves the correct +/// architecture-specific calling convention when building the final function type. +struct CallingConvention { + arch: CoreArchitecture, + flags: u8, + leading_params: Vec, + trailing_params: Vec, +} + +impl CallingConvention { + const SELF: u8 = 1; + const THROWS: u8 = 2; + const ASYNC: u8 = 4; + + fn for_arch(arch: &CoreArchitecture) -> Self { + Self { + arch: *arch, + flags: 0, + leading_params: Vec::new(), + trailing_params: Vec::new(), + } + } + + /// Mark this as a concrete instance method with self in x20. + fn set_self(&mut self, module: Option<&str>, containing_type: &str) { + self.flags |= Self::SELF; + let named_ty = make_named_type_ref(module, containing_type); + let self_ty = Type::pointer(&self.arch, &named_ty); + self.leading_params.push(FunctionParameter { + ty: self_ty.into(), + name: "self".to_string(), + location: None, + }); + } + + /// Optionally mark as a concrete instance method if `containing_type` is `Some`. + fn set_self_if(&mut self, module: Option<&str>, containing_type: Option<&str>) { + if let Some(ct) = containing_type { + self.set_self(module, ct); + } + } + + /// Mark this as a protocol witness method. Self is in x20 (swift-self) + /// like concrete methods. The self type metadata and witness table are + /// appended as trailing arguments after the explicit params. + fn set_protocol_self(&mut self, module: Option<&str>, containing_type: &str) { + self.set_self(module, containing_type); + let void_ptr = Type::pointer(&self.arch, &Type::void()); + self.trailing_params.push(FunctionParameter { + ty: void_ptr.clone().into(), + name: "selfMetadata".to_string(), + location: None, + }); + self.trailing_params.push(FunctionParameter { + ty: void_ptr.into(), + name: "selfWitnessTable".to_string(), + location: None, + }); + } + + fn set_protocol_self_if(&mut self, module: Option<&str>, containing_type: Option<&str>) { + if let Some(ct) = containing_type { + self.set_protocol_self(module, ct); + } + } + + /// Mark this as a throwing function. + fn set_throws(&mut self) { + self.flags |= Self::THROWS; + } + + /// Mark this as an async function. + fn set_async(&mut self) { + self.flags |= Self::ASYNC; + } + + /// Build the final function type. + /// + /// Prepends `self` before `params` and appends error/async context after, + /// then looks up the appropriate calling convention by name on the architecture. + fn build_type(self, ret_type: &Type, params: Vec) -> Ref { + let mut all_params = self.leading_params; + all_params.extend(params); + all_params.extend(self.trailing_params); + + if self.flags & Self::THROWS != 0 { + let error_ty = Type::pointer(&self.arch, &make_named_type_ref(Some("Swift"), "Error")); + all_params.push(FunctionParameter { + ty: error_ty.into(), + name: "error".to_string(), + location: None, + }); + } + + if self.flags & Self::ASYNC != 0 { + let ptr_ty = Type::pointer(&self.arch, &Type::void()); + all_params.push(FunctionParameter { + ty: ptr_ty.into(), + name: "asyncContext".to_string(), + location: None, + }); + } + + Type::function(ret_type, all_params, false) + } +} + +/// Build a Binary Ninja function type from a parsed Swift symbol. +/// +/// Returns `None` if the symbol does not have a function signature (e.g. metadata, variables). +/// Thunks are intentionally excluded for now. +pub fn build_function_type(symbol: &Symbol, arch: &CoreArchitecture) -> Option> { + match symbol { + Symbol::Accessor(a) => return build_accessor_type(a, arch), + Symbol::Metadata(m) => return build_metadata_function_type(m, arch), + Symbol::Attributed(a) => return build_function_type(&a.inner, arch), + Symbol::Specialization(s) => return build_function_type(&s.inner, arch), + Symbol::Suffixed(s) => return build_function_type(&s.inner, arch), + _ => {} + } + + let mut cc = CallingConvention::for_arch(arch); + + // Determine implicit `self` parameter for instance methods/constructors. + match symbol { + Symbol::Function(f) if f.is_method() && !f.is_static() => { + if f.containing_type_is_protocol() { + cc.set_protocol_self_if(f.module(), f.containing_type()); + } else { + cc.set_self_if(f.module(), f.containing_type()); + } + } + Symbol::Constructor(c) if c.kind() != ConstructorKind::Allocating => { + cc.set_self_if(c.module(), c.containing_type()); + } + Symbol::Destructor(d) => { + cc.set_self_if(d.module(), d.containing_type()); + return Some(cc.build_type(&Type::void(), vec![])); + } + _ => {} + }; + + let (sig, labels) = match symbol { + Symbol::Function(f) => (f.signature(), f.labels()), + Symbol::Constructor(c) => (c.signature(), c.labels()), + Symbol::Closure(c) => (c.signature(), vec![]), + _ => (None, vec![]), + }; + let sig = sig?; + + if sig.is_throwing() { + cc.set_throws(); + } + if sig.is_async() { + cc.set_async(); + } + + let params: Vec = sig + .parameters() + .iter() + .enumerate() + .filter_map(|(i, p)| { + let ty = p.type_ref.to_bn_type(arch)?; + let name = labels + .get(i) + .copied() + .flatten() + .or(p.label) + .unwrap_or("") + .to_string(); + Some(FunctionParameter { + ty: ty.into(), + name, + location: None, + }) + }) + .collect(); + + let ret_type = sig + .return_type() + .and_then(|rt| rt.to_bn_type(arch)) + .unwrap_or_else(Type::void); + + Some(cc.build_type(&ret_type, params)) +} + +/// Build a function type for a property accessor from its property type. +fn build_accessor_type(accessor: &Accessor, arch: &CoreArchitecture) -> Option> { + let prop_ty = accessor + .property_type() + .and_then(|pt| pt.to_bn_type(arch))?; + + let mut cc = CallingConvention::for_arch(arch); + + // Non-static instance accessors take `self` as a parameter. + if !accessor.is_static() { + if let Some(ct) = accessor.containing_type() { + cc.set_self(accessor.module(), &ct); + } + } + + match accessor.kind() { + // Getter-like: (self) -> PropertyType + AccessorKind::Getter | AccessorKind::GlobalGetter | AccessorKind::Read => { + Some(cc.build_type(&prop_ty, vec![])) + } + + // Setter-like: (self, newValue: PropertyType) -> Void + AccessorKind::Setter + | AccessorKind::WillSet + | AccessorKind::DidSet + | AccessorKind::Modify + | AccessorKind::Init => { + let params = vec![FunctionParameter { + ty: prop_ty.into(), + name: "newValue".to_string(), + location: None, + }]; + Some(cc.build_type(&Type::void(), params)) + } + + _ => None, + } +} + +/// Build a function type for Swift runtime metadata functions. +fn build_metadata_function_type(metadata: &Metadata, arch: &CoreArchitecture) -> Option> { + let void_ptr = Type::pointer(arch, &Type::void()); + + match metadata.kind() { + // Type metadata accessor: void* fn() + // Returns a pointer to the type metadata singleton. + MetadataKind::AccessFunction | MetadataKind::CanonicalSpecializedGenericAccessFunction => { + Some(Type::function(&void_ptr, vec![], false)) + } + + // Method lookup function: void* fn(void* metadata, void* method) + MetadataKind::MethodLookupFunction => { + let params = vec![ + FunctionParameter { + ty: void_ptr.clone().into(), + name: "metadata".to_string(), + location: None, + }, + FunctionParameter { + ty: void_ptr.clone().into(), + name: "method".to_string(), + location: None, + }, + ]; + Some(Type::function(&void_ptr, params, false)) + } + + _ => None, + } +} diff --git a/plugins/workflow_swift/src/demangler/mod.rs b/plugins/workflow_swift/src/demangler/mod.rs index e1f22893fe..657c62c34b 100644 --- a/plugins/workflow_swift/src/demangler/mod.rs +++ b/plugins/workflow_swift/src/demangler/mod.rs @@ -1,9 +1,22 @@ +mod function_type; +mod name; +mod type_reconstruction; + use binaryninja::architecture::CoreArchitecture; use binaryninja::binary_view::BinaryView; use binaryninja::demangle::CustomDemangler; use binaryninja::rc::Ref; +use binaryninja::settings::{QueryOptions, Settings}; use binaryninja::types::{QualifiedName, Type}; +fn should_extract_types(view: Option<&BinaryView>) -> bool { + let mut opts = match view { + Some(v) => QueryOptions::new_with_view(v), + None => QueryOptions::new(), + }; + Settings::new().get_bool_with_opts(crate::SETTING_EXTRACT_TYPES, &mut opts) +} + pub struct SwiftDemangler; impl CustomDemangler for SwiftDemangler { @@ -19,16 +32,25 @@ impl CustomDemangler for SwiftDemangler { fn demangle( &self, - _arch: &CoreArchitecture, + arch: &CoreArchitecture, name: &str, - _view: Option>, + view: Option>, ) -> Option<(QualifiedName, Option>)> { let ctx = swift_demangler::Context::new(); let symbol = swift_demangler::Symbol::parse(&ctx, name)?; - // Use the canonical demangled form from the parsed node tree. - // This matches what `xcrun swift-demangle` produces. - // TODO: Use the structured Symbol API to also reconstruct BN Types. - let demangled = symbol.display(); - Some((QualifiedName::from(demangled), None)) + + if should_extract_types(view.as_deref()) { + let ty = function_type::build_function_type(&symbol, arch); + let qname = if ty.is_some() { + name::build_short_name(&symbol) + } else { + None + } + .unwrap_or_else(|| QualifiedName::from(symbol.display())); + Some((qname, ty)) + } else { + let qname = QualifiedName::from(symbol.display()); + Some((qname, None)) + } } } diff --git a/plugins/workflow_swift/src/demangler/name.rs b/plugins/workflow_swift/src/demangler/name.rs new file mode 100644 index 0000000000..fd39d8b7c5 --- /dev/null +++ b/plugins/workflow_swift/src/demangler/name.rs @@ -0,0 +1,84 @@ +use binaryninja::types::QualifiedName; +use swift_demangler::{ConstructorKind, DestructorKind, HasModule, Symbol}; + +/// Push a module name into the name parts, skipping `__C` (Swift's internal +/// module for C/Objective-C imports). +fn push_module(parts: &mut Vec, module: Option<&str>) { + if let Some(m) = module { + if m != "__C" { + parts.push(m.to_string()); + } + } +} + +/// Build a qualified name from a symbol's components, omitting parameter types +/// and return type since those are represented directly in the function's type +/// +/// Returns `None` for symbol kinds that don't benefit from shortening (e.g. +/// variables, thunks), in which case the caller should fall back to `symbol.display()`. +pub fn build_short_name(symbol: &swift_demangler::Symbol) -> Option { + let mut parts: Vec = Vec::new(); + + match symbol { + Symbol::Function(f) => { + push_module(&mut parts, f.module()); + if let Some(ct) = f.containing_type() { + parts.push(ct.to_string()); + } + parts.push(f.full_name()); + } + Symbol::Constructor(c) => { + push_module(&mut parts, c.module()); + if let Some(ct) = c.containing_type() { + parts.push(ct.to_string()); + } + let init_name = match c.kind() { + ConstructorKind::Allocating => "__allocating_init", + ConstructorKind::Regular => "init", + }; + let labels: Vec = c + .labels() + .iter() + .map(|l| { + l.map(|s| format!("{s}:")) + .unwrap_or_else(|| "_:".to_string()) + }) + .collect(); + parts.push(format!("{init_name}({})", labels.join(""))); + } + Symbol::Destructor(d) => { + push_module(&mut parts, d.module()); + if let Some(ct) = d.containing_type() { + parts.push(ct.to_string()); + } + let deinit_name = match d.kind() { + DestructorKind::Deallocating => "__deallocating_deinit", + DestructorKind::IsolatedDeallocating => "__isolated_deallocating_deinit", + DestructorKind::Regular => "deinit", + }; + parts.push(deinit_name.to_string()); + } + // Wrappers: combine the wrapper's display with the inner function's short name. + Symbol::Attributed(_) | Symbol::Specialization(_) => { + let inner = match symbol { + Symbol::Attributed(a) => &*a.inner, + Symbol::Specialization(s) => &*s.inner, + _ => unreachable!(), + }; + return build_short_name(inner).map(|inner_name| { + QualifiedName::from(format!("{}{}", symbol.display(), inner_name)) + }); + } + Symbol::Suffixed(s) => { + return build_short_name(&s.inner) + .map(|inner_name| QualifiedName::from(format!("{} {}", inner_name, s.suffix))); + } + _ => return None, + } + + if parts.is_empty() { + return None; + } + + Some(QualifiedName::from(parts.join("."))) +} diff --git a/plugins/workflow_swift/src/demangler/type_reconstruction.rs b/plugins/workflow_swift/src/demangler/type_reconstruction.rs new file mode 100644 index 0000000000..3a3ba3e741 --- /dev/null +++ b/plugins/workflow_swift/src/demangler/type_reconstruction.rs @@ -0,0 +1,204 @@ +use binaryninja::architecture::{Architecture, CoreArchitecture}; +use binaryninja::rc::Ref; +use binaryninja::types::{NamedTypeReference, NamedTypeReferenceClass, QualifiedName, Type}; +use swift_demangler::{TypeKind, TypeRef}; + +pub(crate) trait TypeRefExt { + fn to_bn_type(&self, arch: &CoreArchitecture) -> Option>; +} + +impl TypeRefExt for TypeRef<'_> { + fn to_bn_type(&self, arch: &CoreArchitecture) -> Option> { + match self.kind() { + TypeKind::Named(named) => { + let name = named.name()?; + let module = named.module(); + + // __C is Swift's internal module for C/Objective-C imports; drop it. + let module = match module { + Some("__C") => None, + other => other, + }; + + // Map Swift standard library primitive types to BN primitives. + // Bound generic types (e.g., Optional) are never primitives. + if module == Some("Swift") && !named.is_generic() { + if let Some(ty) = swift_primitive(name, arch) { + return Some(ty); + } + } + + let ntr = if named.is_generic() { + // Include generic arguments in the type name. + let args: Vec = + named.generic_args().iter().map(|a| a.display()).collect(); + let full_name = format!("{}<{}>", name, args.join(", ")); + make_named_type_ref(module, &full_name) + } else { + make_named_type_ref(module, name) + }; + + // Class types (including ObjC classes) are reference types — + // always a pointer at the ABI level. + if named.is_class() { + Some(Type::pointer(arch, &ntr)) + } else { + Some(ntr) + } + } + + TypeKind::Function(func_type) => { + let params: Vec<_> = func_type + .parameters() + .iter() + .filter_map(|p| { + let ty = p.type_ref.to_bn_type(arch)?; + let name = p.label.unwrap_or("").to_string(); + Some(binaryninja::types::FunctionParameter { + ty: ty.into(), + name, + location: None, + }) + }) + .collect(); + + let ret_type = func_type + .return_type() + .and_then(|rt| rt.to_bn_type(arch)) + .unwrap_or_else(Type::void); + + Some(Type::function(&ret_type, params, false)) + } + + TypeKind::Tuple(elements) => { + if elements.is_empty() { + // () is Swift.Void + Some(Type::void()) + } else { + let display = self.display(); + Some(make_named_type_ref(Some("Swift"), &display)) + } + } + + TypeKind::Optional(inner) => { + let display = inner.display(); + Some(make_named_type_ref(Some("Swift"), &display)) + } + + TypeKind::Array(inner) => { + let display = inner.display(); + let label = format!("[{display}]"); + Some(make_named_type_ref(Some("Swift"), &label)) + } + + TypeKind::Dictionary { key, value } => { + let key_display = key.display(); + let value_display = value.display(); + let label = format!("[{key_display} : {value_display}]"); + Some(make_named_type_ref(Some("Swift"), &label)) + } + + TypeKind::InOut(inner) => { + let inner_ty = inner.to_bn_type(arch)?; + Some(Type::pointer(arch, &inner_ty)) + } + + TypeKind::Metatype(_) => { + // Metatype is an opaque pointer-sized value at runtime. + Some(Type::pointer_of_width( + &Type::void(), + arch.address_size(), + false, + false, + None, + )) + } + + TypeKind::GenericParam { .. } => { + // Generic parameters are opaque pointer-sized values at runtime. + Some(Type::pointer_of_width( + &Type::int(1, false), + arch.address_size(), + false, + false, + None, + )) + } + + // Ownership wrappers: unwrap and recurse. + TypeKind::Shared(inner) + | TypeKind::Owned(inner) + | TypeKind::Sending(inner) + | TypeKind::Isolated(inner) + | TypeKind::NoDerivative(inner) => inner.to_bn_type(arch), + + TypeKind::Weak(inner) | TypeKind::Unowned(inner) => inner.to_bn_type(arch), + + TypeKind::DynamicSelf(inner) => inner.to_bn_type(arch), + + TypeKind::ConstrainedExistential(inner) => inner.to_bn_type(arch), + + TypeKind::Any => { + // Swift.Any is an existential container (pointer-sized at the ABI level). + Some(make_named_type_ref(Some("Swift"), "Any")) + } + + TypeKind::Existential(protocols) => { + if protocols.len() == 1 { + protocols[0].to_bn_type(arch) + } else { + None + } + } + + TypeKind::Generic { inner, .. } => inner.to_bn_type(arch), + + // Types we can't meaningfully represent. + TypeKind::Error + | TypeKind::Builtin(_) + | TypeKind::BuiltinFixedArray { .. } + | TypeKind::ImplFunction(_) + | TypeKind::Pack(_) + | TypeKind::ValueGeneric(_) + | TypeKind::CompileTimeLiteral(_) + | TypeKind::AssociatedType { .. } + | TypeKind::Opaque { .. } + | TypeKind::SILBox { .. } + | TypeKind::Other(_) => None, + } + } +} + +/// Map a Swift standard library type name to a primitive type. +fn swift_primitive(name: &str, arch: &CoreArchitecture) -> Option> { + match name { + "Int" => Some(Type::int(arch.address_size(), true)), + "UInt" => Some(Type::int(arch.address_size(), false)), + "Int8" => Some(Type::int(1, true)), + "Int16" => Some(Type::int(2, true)), + "Int32" => Some(Type::int(4, true)), + "Int64" => Some(Type::int(8, true)), + "UInt8" => Some(Type::int(1, false)), + "UInt16" => Some(Type::int(2, false)), + "UInt32" => Some(Type::int(4, false)), + "UInt64" => Some(Type::int(8, false)), + "Float" => Some(Type::float(4)), + "Double" => Some(Type::float(8)), + "Float80" => Some(Type::float(10)), + "Bool" => Some(Type::bool()), + _ => None, + } +} + +/// Create a named type reference from an optional module and a type name. +/// +/// `__C` (Swift's internal module for C/Objective-C imports) is dropped since +/// it is not meaningful to users. +pub(crate) fn make_named_type_ref(module: Option<&str>, name: &str) -> Ref { + let qname = match module { + Some("__C") | None => QualifiedName::from(name), + Some(module) => QualifiedName::from(format!("{module}.{name}")), + }; + let ntr = NamedTypeReference::new(NamedTypeReferenceClass::UnknownNamedTypeClass, qname); + Type::named_type(&ntr) +} diff --git a/plugins/workflow_swift/src/lib.rs b/plugins/workflow_swift/src/lib.rs index 2e97a43bdd..a04f365998 100644 --- a/plugins/workflow_swift/src/lib.rs +++ b/plugins/workflow_swift/src/lib.rs @@ -2,8 +2,11 @@ mod demangler; use binaryninja::add_optional_plugin_dependency; use binaryninja::demangle::Demangler; +use binaryninja::settings::Settings; use demangler::SwiftDemangler; +pub const SETTING_EXTRACT_TYPES: &str = "analysis.swift.extractTypesFromMangledNames"; + #[no_mangle] #[allow(non_snake_case)] pub extern "C" fn CorePluginDependencies() { @@ -16,6 +19,17 @@ pub extern "C" fn CorePluginDependencies() { pub extern "C" fn CorePluginInit() -> bool { binaryninja::tracing_init!("Plugin.Swift"); + let settings = Settings::new(); + settings.register_setting_json( + SETTING_EXTRACT_TYPES, + r#"{ + "title" : "Extract Types from Mangled Names", + "type" : "boolean", + "default" : true, + "description" : "Extract parameter and return type information from Swift mangled names and apply them to function signatures. When disabled, only the demangled name is applied." + }"#, + ); + Demangler::register("Swift", SwiftDemangler); true From 3feaf74969b5d855993842e204cab04fb9725165 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Wed, 18 Feb 2026 23:41:11 -0800 Subject: [PATCH 4/4] [Swift] Add arm64 calling conventions The Swift ABI repurposes three callee-saved registers for implicit parameters (self, error, and async context). Supporting the various combinations of these requires registering several different calling conventions. The demangler is taught to explicitly apply these calling conventions to functions that need them. --- arch/arm64/arch_arm64.cpp | 123 ++++++++++++++++++ .../src/demangler/function_type.rs | 65 ++++++++- rust/src/architecture.rs | 14 ++ 3 files changed, 199 insertions(+), 3 deletions(-) diff --git a/arch/arm64/arch_arm64.cpp b/arch/arm64/arch_arm64.cpp index b71e79e82a..a56ed35b7f 100644 --- a/arch/arm64/arch_arm64.cpp +++ b/arch/arm64/arch_arm64.cpp @@ -2765,6 +2765,115 @@ class AppleArm64SystemCallConvention : public CallingConvention virtual bool IsEligibleForHeuristics() override { return false; } }; + +// Swift calling convention for ARM64. +// +// The Swift ABI repurposes three callee-saved registers for implicit parameters: +// x20 - self (swiftself): context/self parameter, passed as the last integer argument +// x21 - error (swifterror): caller initializes to zero, callee sets to error pointer on throw +// x22 - async context (swiftasync): implicit async context for async functions +// +// Each combination of these three properties requires a distinct calling convention, +// since it changes which registers are arguments, callee-saved, or implicitly defined. +// The naming scheme is "swift" with optional suffixes: "-self", "-throws", "-async". +class SwiftArm64CallingConvention : public CallingConvention +{ + bool m_hasSelf; + bool m_throws; + bool m_isAsync; + +public: + SwiftArm64CallingConvention(Architecture* arch, const string& name, bool hasSelf, bool throws, bool isAsync) + : CallingConvention(arch, name), m_hasSelf(hasSelf), m_throws(throws), m_isAsync(isAsync) + { + } + + virtual vector GetIntegerArgumentRegisters() override + { + // Self goes first because our function types list self as the first + // parameter. Parameters are assigned to registers sequentially, so + // this ensures self -> x20 and remaining args -> x0-x7. + // Async context and error go last as implicit trailing registers. + vector regs; + if (m_hasSelf) + regs.push_back(REG_X20); + regs.insert(regs.end(), {REG_X0, REG_X1, REG_X2, REG_X3, REG_X4, REG_X5, REG_X6, REG_X7}); + if (m_isAsync) + regs.push_back(REG_X22); + if (m_throws) + regs.push_back(REG_X21); + return regs; + } + + virtual vector GetFloatArgumentRegisters() override + { + return vector {REG_V0, REG_V1, REG_V2, REG_V3, REG_V4, REG_V5, REG_V6, REG_V7}; + } + + virtual vector GetCallerSavedRegisters() override + { + vector regs {REG_X0, REG_X1, REG_X2, REG_X3, REG_X4, REG_X5, REG_X6, REG_X7, REG_X8, + REG_X9, REG_X10, REG_X11, REG_X12, REG_X13, REG_X14, REG_X15, REG_X16, REG_X17, REG_X18, + REG_X30, REG_V0, REG_V1, REG_V2, REG_V3, REG_V4, REG_V5, REG_V6, REG_V7, REG_V16, REG_V17, + REG_V18, REG_V19, REG_V20, REG_V21, REG_V22, REG_V23, REG_V24, REG_V25, REG_V26, REG_V27, + REG_V28, REG_V29, REG_V30, REG_V31}; + // When used as special registers, they are no longer callee-saved. + if (m_hasSelf) + regs.push_back(REG_X20); + if (m_throws) + regs.push_back(REG_X21); + if (m_isAsync) + regs.push_back(REG_X22); + return regs; + } + + virtual vector GetCalleeSavedRegisters() override + { + vector regs {REG_X19, REG_X23, REG_X24, REG_X25, REG_X26, REG_X27, REG_X28, REG_X29}; + // Only include x20/x21/x22 as callee-saved when they are NOT repurposed. + if (!m_hasSelf) + regs.push_back(REG_X20); + if (!m_throws) + regs.push_back(REG_X21); + if (!m_isAsync) + regs.push_back(REG_X22); + return regs; + } + + virtual vector GetImplicitlyDefinedRegisters() override + { + vector regs; + // Throwing functions implicitly define x21 (the error register) on return. + if (m_throws) + regs.push_back(REG_X21); + return regs; + } + + virtual uint32_t GetIntegerReturnValueRegister() override { return REG_X0; } + + virtual uint32_t GetFloatReturnValueRegister() override { return REG_V0; } + + virtual vector GetRequiredArgumentRegisters() override + { + vector regs; + if (m_hasSelf) + regs.push_back(REG_X20); + if (m_throws) + regs.push_back(REG_X21); + if (m_isAsync) + regs.push_back(REG_X22); + return regs; + } + + virtual bool AreArgumentRegistersUsedForVarArgs() override { return false; } + + virtual bool IsEligibleForHeuristics() override + { + return m_hasSelf || m_throws || m_isAsync; + } +}; + + #define PAGE(x) (uint32_t)((x) >> 12) #define PAGE_OFF(x) (uint32_t)((x)&0xfff) #define PAGE_NO_OFF(x) (uint32_t)((x)&0xFFFFF000) @@ -3550,6 +3659,20 @@ extern "C" conv = new AppleArm64CallingConvention(arm64); arm64->RegisterCallingConvention(conv); + // Register Swift calling conventions (all combinations of self/throws/async). + for (int swiftFlags = 0; swiftFlags < 8; swiftFlags++) + { + bool hasSelf = (swiftFlags & 1) != 0; + bool throws = (swiftFlags & 2) != 0; + bool isAsync = (swiftFlags & 4) != 0; + string name = "swift"; + if (hasSelf) name += "-self"; + if (throws) name += "-throws"; + if (isAsync) name += "-async"; + conv = new SwiftArm64CallingConvention(arm64, name, hasSelf, throws, isAsync); + arm64->RegisterCallingConvention(conv); + } + for (uint32_t i = REG_X0; i <= REG_X28; i++) { if (i == REG_X16 || i == REG_X17 || i == REG_X18) // reserved by os. diff --git a/plugins/workflow_swift/src/demangler/function_type.rs b/plugins/workflow_swift/src/demangler/function_type.rs index 3313143e97..908f5940c9 100644 --- a/plugins/workflow_swift/src/demangler/function_type.rs +++ b/plugins/workflow_swift/src/demangler/function_type.rs @@ -10,6 +10,43 @@ use swift_demangler::{ use super::type_reconstruction::{make_named_type_ref, TypeRefExt}; +/// Architecture-specific register assignments for Swift implicit parameters. +struct PlatformAbi { + arch: CoreArchitecture, + error_reg: &'static str, + async_context_reg: &'static str, +} + +impl PlatformAbi { + fn for_arch(arch: &CoreArchitecture) -> Option { + match arch.name().as_ref() { + "aarch64" => Some(Self { + arch: *arch, + error_reg: "x21", + async_context_reg: "x22", + }), + _ => None, + } + } + + fn error_location(&self) -> Option { + self.register_variable(self.error_reg) + } + + fn async_context_location(&self) -> Option { + self.register_variable(self.async_context_reg) + } + + fn register_variable(&self, name: &str) -> Option { + let reg = self.arch.register_by_name(name)?; + Some(Variable::new( + VariableSourceType::RegisterVariableSourceType, + 0, + reg.id().0 as i64, + )) + } +} + /// Swift calling convention builder. /// /// Tracks which implicit parameters (self, error, async context) are present, @@ -17,6 +54,7 @@ use super::type_reconstruction::{make_named_type_ref, TypeRefExt}; /// architecture-specific calling convention when building the final function type. struct CallingConvention { arch: CoreArchitecture, + abi: Option, flags: u8, leading_params: Vec, trailing_params: Vec, @@ -30,6 +68,7 @@ impl CallingConvention { fn for_arch(arch: &CoreArchitecture) -> Self { Self { arch: *arch, + abi: PlatformAbi::for_arch(arch), flags: 0, leading_params: Vec::new(), trailing_params: Vec::new(), @@ -94,6 +133,8 @@ impl CallingConvention { /// Prepends `self` before `params` and appends error/async context after, /// then looks up the appropriate calling convention by name on the architecture. fn build_type(self, ret_type: &Type, params: Vec) -> Ref { + let cc_name = self.cc_name(); + let mut all_params = self.leading_params; all_params.extend(params); all_params.extend(self.trailing_params); @@ -103,7 +144,7 @@ impl CallingConvention { all_params.push(FunctionParameter { ty: error_ty.into(), name: "error".to_string(), - location: None, + location: self.abi.as_ref().and_then(|a| a.error_location()), }); } @@ -112,11 +153,29 @@ impl CallingConvention { all_params.push(FunctionParameter { ty: ptr_ty.into(), name: "asyncContext".to_string(), - location: None, + location: self.abi.as_ref().and_then(|a| a.async_context_location()), }); } - Type::function(ret_type, all_params, false) + if let Some(cc) = self.arch.calling_convention_by_name(&cc_name) { + Type::function_with_opts(ret_type, &all_params, false, cc, Conf::new(0, 0)) + } else { + Type::function(ret_type, all_params, false) + } + } + + fn cc_name(&self) -> String { + let mut name = String::from("swift"); + if self.flags & Self::SELF != 0 { + name.push_str("-self"); + } + if self.flags & Self::THROWS != 0 { + name.push_str("-throws"); + } + if self.flags & Self::ASYNC != 0 { + name.push_str("-async"); + } + name } } diff --git a/rust/src/architecture.rs b/rust/src/architecture.rs index 6b644e88eb..e854d53b81 100644 --- a/rust/src/architecture.rs +++ b/rust/src/architecture.rs @@ -1262,6 +1262,20 @@ pub trait ArchitectureExt: Architecture { } } + fn calling_convention_by_name(&self, name: &str) -> Option> { + let name = name.to_cstr(); + unsafe { + let result = NonNull::new(BNGetArchitectureCallingConventionByName( + self.as_ref().handle, + name.as_ptr(), + ))?; + Some(CoreCallingConvention::ref_from_raw( + result.as_ptr(), + self.as_ref().handle(), + )) + } + } + fn calling_conventions(&self) -> Array { unsafe { let mut count = 0;