From f601c3a58e002a90214ebe3125199ad7e4d61383 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:17:31 -0700 Subject: [PATCH] Start work on Cross-FFI functions --- .gitignore | 8 ++ README.md => README-old.md | 0 unshell-logger/Cargo.lock | 16 +++ unshell-logger/Cargo.toml | 7 + unshell-logger/src/lib.rs | 14 ++ unshell-module-load/Cargo.lock | 179 +++++++++++++++++++++++++ unshell-module-load/Cargo.toml | 11 ++ unshell-module-load/src/main.rs | 61 +++++++++ unshell-module-test/.cargo/config.toml | 0 unshell-module-test/Cargo.lock | 54 ++++++++ unshell-module-test/Cargo.toml | 24 ++++ unshell-module-test/build.sh | 12 ++ unshell-module-test/src/lib.rs | 48 +++++++ unshell-modules/Cargo.lock | 32 +++++ unshell-modules/Cargo.toml | 11 ++ unshell-modules/src/callable.rs | 32 +++++ unshell-modules/src/lib.rs | 103 ++++++++++++++ 17 files changed, 612 insertions(+) create mode 100644 .gitignore rename README.md => README-old.md (100%) create mode 100644 unshell-logger/Cargo.lock create mode 100644 unshell-logger/Cargo.toml create mode 100644 unshell-logger/src/lib.rs create mode 100644 unshell-module-load/Cargo.lock create mode 100644 unshell-module-load/Cargo.toml create mode 100644 unshell-module-load/src/main.rs create mode 100644 unshell-module-test/.cargo/config.toml create mode 100644 unshell-module-test/Cargo.lock create mode 100644 unshell-module-test/Cargo.toml create mode 100755 unshell-module-test/build.sh create mode 100644 unshell-module-test/src/lib.rs create mode 100644 unshell-modules/Cargo.lock create mode 100644 unshell-modules/Cargo.toml create mode 100644 unshell-modules/src/callable.rs create mode 100644 unshell-modules/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eed51e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# will have compiled files and executables +/target +target/ +.DS_Store + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/README.md b/README-old.md similarity index 100% rename from README.md rename to README-old.md diff --git a/unshell-logger/Cargo.lock b/unshell-logger/Cargo.lock new file mode 100644 index 0000000..d697be3 --- /dev/null +++ b/unshell-logger/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "unshell-logger" +version = "0.1.0" +dependencies = [ + "log", +] diff --git a/unshell-logger/Cargo.toml b/unshell-logger/Cargo.toml new file mode 100644 index 0000000..bf9af90 --- /dev/null +++ b/unshell-logger/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unshell-logger" +version = "0.1.0" +edition = "2024" + +[dependencies] +log = "0.4.28" diff --git a/unshell-logger/src/lib.rs b/unshell-logger/src/lib.rs new file mode 100644 index 0000000..416f484 --- /dev/null +++ b/unshell-logger/src/lib.rs @@ -0,0 +1,14 @@ +use log::{LevelFilter, Log, SetLoggerError}; + +#[allow(dead_code)] +pub type SetupLogger = + extern "C" fn(logger: &'static dyn Log, level: LevelFilter) -> Result<(), SetLoggerError>; + +#[unsafe(no_mangle)] +pub extern "C" fn setup_logger( + logger: &'static dyn log::Log, + level: log::LevelFilter, +) -> Result<(), log::SetLoggerError> { + log::set_max_level(level); + log::set_logger(logger) +} diff --git a/unshell-module-load/Cargo.lock b/unshell-module-load/Cargo.lock new file mode 100644 index 0000000..c86803b --- /dev/null +++ b/unshell-module-load/Cargo.lock @@ -0,0 +1,179 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unshell-logger" +version = "0.1.0" +dependencies = [ + "log", +] + +[[package]] +name = "unshell-module-load" +version = "0.1.0" +dependencies = [ + "libloading", + "log", + "pretty_env_logger", + "unshell-logger", + "unshell-modules", +] + +[[package]] +name = "unshell-modules" +version = "0.1.0" +dependencies = [ + "libloading", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/unshell-module-load/Cargo.toml b/unshell-module-load/Cargo.toml new file mode 100644 index 0000000..3f5e9d1 --- /dev/null +++ b/unshell-module-load/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "unshell-module-load" +version = "0.1.0" +edition = "2024" + +[dependencies] +libloading = "0.8.9" +log = "0.4.28" +pretty_env_logger = "0.5.0" +unshell-logger = {path = "../unshell-logger"} +unshell-modules = {path = "../unshell-modules"} diff --git a/unshell-module-load/src/main.rs b/unshell-module-load/src/main.rs new file mode 100644 index 0000000..06d8cd9 --- /dev/null +++ b/unshell-module-load/src/main.rs @@ -0,0 +1,61 @@ +use std::any::Any; + +use libloading::{Library, Symbol}; +use log::{info, warn}; +use unshell_logger::SetupLogger; +use unshell_modules::{module, module_interface}; +// use unshell_modules::IntoFunctionPtr; +// use unshell_modules::test; +// use unshell_modules::ExportFunction; + +// fn test1() { +// warn!("Test1 not called"); +// } + +// fn test2() { +// warn!("Test2 not called"); +// } +// fn test3() { +// warn!("Test3 not called"); +// } + +module_interface! { + Interface { + fn test1(); + fn test2(); + fn test3(); + } +} + +fn main() { + // println!("Hello, world!"); + + // test(); + + pretty_env_logger::init(); + + warn!("Warning message"); + + unsafe { + let lib = Library::new("../unshell-module-test/target/release/libunshell_module_test.so") + .unwrap(); + + let ret = lib.get::(b"setup_logger"); + + if let Ok(setup_logger) = ret { + setup_logger(log::logger(), log::max_level()).unwrap(); + } else { + warn!("setup_logger not found"); + } + + let module = lib.get:: Interface>(b"functions").unwrap(); + + let i = module(); + + // i.test1(); + + info!("Func: {:?}", i.test1); + + i.test1(); + } +} diff --git a/unshell-module-test/.cargo/config.toml b/unshell-module-test/.cargo/config.toml new file mode 100644 index 0000000..e69de29 diff --git a/unshell-module-test/Cargo.lock b/unshell-module-test/Cargo.lock new file mode 100644 index 0000000..fc0fdea --- /dev/null +++ b/unshell-module-test/Cargo.lock @@ -0,0 +1,54 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "unshell-logger" +version = "0.1.0" +dependencies = [ + "log", +] + +[[package]] +name = "unshell-module-test" +version = "0.1.0" +dependencies = [ + "log", + "unshell-logger", + "unshell-modules", +] + +[[package]] +name = "unshell-modules" +version = "0.1.0" +dependencies = [ + "libloading", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" diff --git a/unshell-module-test/Cargo.toml b/unshell-module-test/Cargo.toml new file mode 100644 index 0000000..db023b1 --- /dev/null +++ b/unshell-module-test/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "unshell-module-test" +version = "0.1.0" +edition = "2024" + + +[lib] +crate-type = ["cdylib"] + +[profile.release] +strip = true # Strip symbols from the binary +# strip = "debuginfo" +opt-level = "s" # Optimize for size +lto = true +codegen-units = 1 +panic = "abort" +debug = false + + +[dependencies] +log = "0.4.28" +unshell-logger = {path = "../unshell-logger"} +unshell-modules = {path = "../unshell-modules"} +# log = "0.4.28" diff --git a/unshell-module-test/build.sh b/unshell-module-test/build.sh new file mode 100755 index 0000000..e67eced --- /dev/null +++ b/unshell-module-test/build.sh @@ -0,0 +1,12 @@ +# RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none" \ +# rustup run nightly cargo build --release + +# rustup run nightly cargo build \ +# --release \ +# --no-default-features \ +# -Zbuild-std="core,std,alloc,proc_macro,panic_abort" \ +# -Zbuild-std-features="panic_immediate_abort" + + +# RUSTFLAGS="-Z build-std" \ +rustup run nightly cargo build --release diff --git a/unshell-module-test/src/lib.rs b/unshell-module-test/src/lib.rs new file mode 100644 index 0000000..abc7569 --- /dev/null +++ b/unshell-module-test/src/lib.rs @@ -0,0 +1,48 @@ +#![no_main] +#[macro_use] +extern crate log; + +pub use unshell_logger::setup_logger; +use unshell_modules::{module, module_interface}; + +// #[unsafe(no_mangle)] +extern "C" fn test1() { + warn!("Test1 called"); +} +// #[unsafe(no_mangle)] +extern "C" fn test2() { + warn!("Test2 called"); +} +// #[unsafe(no_mangle)] +extern "C" fn test3() { + warn!("Test3 called"); +} + +module_interface! { + Interface { + fn test1(); + fn test2(); + fn test3(); + } +} + +#[unsafe(no_mangle)] +pub fn test() { + info!("Module loaded"); +} + +#[unsafe(no_mangle)] +pub fn functions() -> Interface { + info!("Module loaded"); + // let m = TestModule::new(); + let i = unsafe { Interface::from_raw(test1, test2, test3) }; + + i.test1(); + + i +} + +#[unsafe(no_mangle)] +pub fn testfunc() { + info!("testfunc called"); +} diff --git a/unshell-modules/Cargo.lock b/unshell-modules/Cargo.lock new file mode 100644 index 0000000..8e701a3 --- /dev/null +++ b/unshell-modules/Cargo.lock @@ -0,0 +1,32 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "unshell-modules" +version = "0.1.0" +dependencies = [ + "libloading", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" diff --git a/unshell-modules/Cargo.toml b/unshell-modules/Cargo.toml new file mode 100644 index 0000000..b124559 --- /dev/null +++ b/unshell-modules/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "unshell-modules" +version = "0.1.0" +edition = "2024" + +[features] +default = ["exec"] +"exec" = ["libloading"] + +[dependencies] +libloading = {version = "0.8.9", optional = true} diff --git a/unshell-modules/src/callable.rs b/unshell-modules/src/callable.rs new file mode 100644 index 0000000..de4ebd4 --- /dev/null +++ b/unshell-modules/src/callable.rs @@ -0,0 +1,32 @@ +// enum CallableArg { +// Void, +// String, +// Int32, +// Uint32, +// } + +// enum CallableArgFilled { +// String(String), +// Int32(i32), +// Uint32(u32), +// } + +// struct Callable { +// name: String, +// args: Vec, +// return_type: CallableArg, +// } + +// impl Callable { +// fn new(name: String, args: Vec) -> Self { +// Callable { name, args } +// } +// fn call(&self, args: Vec, lib: &libloading::Library) -> Result<(), String> { +// unsafe { +// //TODO: Call the function with the given arguments +// let func = lib.get::< (Find function type) >(self.name.as_bytes()); +// unsafe { func() }; +// } +// Ok(()) +// } +// } diff --git a/unshell-modules/src/lib.rs b/unshell-modules/src/lib.rs new file mode 100644 index 0000000..082c94f --- /dev/null +++ b/unshell-modules/src/lib.rs @@ -0,0 +1,103 @@ +#[macro_export] +macro_rules! module { + ($module_name:ident { $(fn $fn_name:ident($($arg:ident : $ty:ty),* $(,)?) $(-> $ret:ty)?);* $(;)? }) => { + #[allow(non_camel_case_types)] + pub struct $module_name; + + impl $module_name { + $( + #[inline(always)] + pub fn $fn_name(&self, $($arg: $ty),*) $(-> $ret)? { + $fn_name($($arg),*) + } + )* + + /// Create a new instance of this module + pub fn new() -> Self { + Self + } + } + + impl Default for $module_name { + fn default() -> Self { + Self::new() + } + } + }; +} + +#[macro_export] +macro_rules! module_interface { + ($interface_name:ident { $(fn $fn_name:ident($($arg:ident : $ty:ty),* $(,)?) $(-> $ret:ty)?);* $(;)? }) => { + #[repr(C)] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + pub struct $interface_name { + $( + $fn_name: extern "C" fn($($ty),*) $(-> $ret)?, + )* + } + + impl $interface_name { + /// Unsafe cast from a module type to this interface + /// + /// # Safety + /// + /// The caller must ensure that: + /// - The module has exactly the same function signatures in the same order + /// - The functions follow the C calling convention + /// - The module's memory layout matches this interface + pub unsafe fn from_module(module: &T) -> Self { + *(module as *const T as *const Self) + } + + /// Create from raw function pointers + /// + /// # Safety + /// + /// The caller must ensure all function pointers are valid and have + /// the correct signatures + pub unsafe fn from_raw( + $($fn_name: extern "C" fn($($ty),*) $(-> $ret)?),* + ) -> Self { + Self { + $($fn_name),* + } + } + + $( + #[inline(always)] + pub fn $fn_name(&self, $($arg: $ty),*) $(-> $ret)? { + (self.$fn_name)($($arg),*) + } + )* + } + }; +} +// mod callable; + +// pub enum Error { +// LibLoadingError(libloading::Error), +// } + +// #[cfg(feature = "exec")] +// pub struct Module { +// lib: libloading::Library, +// } + +// impl Module { +// pub fn new(path: &str) -> Result { +// let lib = +// unsafe { libloading::Library::new(path) }.map_err(|e| Error::LibLoadingError(e))?; +// Ok(Module { lib }) +// } +// pub fn functions(&self) { +// // self.lib. + +// // self.lib.get(name).map_err(|e| Error::LibLoadingError(e)) +// } +// } + +// pub use callable::Callable; +// pub use callable::Function; +// pub use callable::wrap;