diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de9e80e..bc2ab8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: Test libmimalloc-sys crate bindings (no secure) run: cargo run -p libmimalloc-sys-test - + - name: Build (extended) run: cargo build --features extended @@ -87,6 +87,10 @@ jobs: - name: Test libmimalloc-sys crate bindings (v3, extended) run: cargo run --features libmimalloc-sys-test/v3,libmimalloc-sys-test/extended -p libmimalloc-sys-test + - name: Test override dylib + if: ${{ !contains(matrix.os, 'windows') }} + run: cargo run -ptest-override-with-dylib --features override + lint: name: Rustfmt / Clippy runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 11588ae..4aee4be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,11 @@ license = "MIT" readme = "README.md" [workspace] -members = ["libmimalloc-sys", "libmimalloc-sys/sys-test"] +members = [ + "libmimalloc-sys", + "libmimalloc-sys/sys-test", + "test-override-with-dylib", +] [badges] travis-ci = { repository = "purpleprotocol/mimalloc_rust" } diff --git a/libmimalloc-sys/build.rs b/libmimalloc-sys/build.rs index e059bfd..0a05784 100644 --- a/libmimalloc-sys/build.rs +++ b/libmimalloc-sys/build.rs @@ -15,6 +15,7 @@ fn main() { let target_os = env::var("CARGO_CFG_TARGET_OS").expect("target_os not defined!"); let target_family = env::var("CARGO_CFG_TARGET_FAMILY").expect("target_family not defined!"); + let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").expect("target_vendor not defined!"); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("target_arch not defined!"); if target_family != "windows" { @@ -27,6 +28,10 @@ fn main() { if target_family != "windows" { build.define("MI_MALLOC_OVERRIDE", None); } + if target_vendor == "apple" { + build.define("MI_OSX_ZONE", Some("1")); + build.define("MI_OSX_INTERPOSE", Some("1")); + } } if env::var_os("CARGO_FEATURE_SECURE").is_some() { diff --git a/libmimalloc-sys/src/lib.rs b/libmimalloc-sys/src/lib.rs index ecc9f3a..7ca66f1 100644 --- a/libmimalloc-sys/src/lib.rs +++ b/libmimalloc-sys/src/lib.rs @@ -89,4 +89,11 @@ mod tests { let ptr = unsafe { mi_realloc_aligned(ptr as *mut c_void, 8, 8) } as *mut u8; unsafe { mi_free(ptr as *mut c_void) }; } + + #[cfg(all(feature = "override", target_vendor = "apple"))] + #[test] + fn mimalloc_and_libc_are_interoperable_when_overridden() { + let ptr = unsafe { mi_malloc(42) }; + unsafe { libc::free(ptr) }; + } } diff --git a/test-override-with-dylib/Cargo.toml b/test-override-with-dylib/Cargo.toml new file mode 100644 index 0000000..c1381fc --- /dev/null +++ b/test-override-with-dylib/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "test-override-with-dylib" +version = "0.0.0" +license = "MIT OR Apache-2.0" +description = "A test helper for mimalloc" +edition = "2018" +publish = false + +[dependencies] +libc = { version = "^0.2.8", default-features = false } +libmimalloc-sys = { path = "../libmimalloc-sys" } + +[build-dependencies] +cc = "^1.0.13" + +[features] +override = ["libmimalloc-sys/override"] diff --git a/test-override-with-dylib/build.rs b/test-override-with-dylib/build.rs new file mode 100644 index 0000000..eefbc32 --- /dev/null +++ b/test-override-with-dylib/build.rs @@ -0,0 +1,29 @@ +//! Build shared library `dep.c`. +use std::{env, path::PathBuf}; + +fn main() { + println!("cargo:rerun-if-changed=src/dep.c"); + + let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + + // NOTE: Only for testing, extension is wrong when cross-compiling. + let dylib = out_dir.join(format!( + "{}dep{}", + env::consts::DLL_PREFIX, + env::consts::DLL_SUFFIX + )); + + let status = cc::Build::new() + .get_compiler() + .to_command() + .arg("src/dep.c") + .arg("-shared") + .arg("-o") + .arg(&dylib) + .status() + .unwrap(); + assert!(status.success()); + + println!("cargo:rustc-link-lib=dylib=dep"); + println!("cargo:rustc-link-search=native={}", out_dir.display()); +} diff --git a/test-override-with-dylib/src/dep.c b/test-override-with-dylib/src/dep.c new file mode 100644 index 0000000..eefbdc5 --- /dev/null +++ b/test-override-with-dylib/src/dep.c @@ -0,0 +1,21 @@ +#define _GNU_SOURCE +#include +#include +#include + +const char* dep_lookup_malloc_address(void) { + Dl_info info; + if (!dladdr((void *)malloc, &info)) { + printf("failed finding `malloc`\n"); + abort(); + } + return info.dli_fname; +} + +void* dep_malloc(size_t size) { + return malloc(size); +} + +void dep_free(void* ptr) { + free(ptr); +} diff --git a/test-override-with-dylib/src/main.rs b/test-override-with-dylib/src/main.rs new file mode 100644 index 0000000..4f23912 --- /dev/null +++ b/test-override-with-dylib/src/main.rs @@ -0,0 +1,63 @@ +//! Test that when overriding, that the `malloc` and `free` symbols are +//! interoperable, even across a dylib boundary. +use core::ffi::{c_char, c_void, CStr}; + +// Make sure that `rustc` links this. +use libmimalloc_sys as _; + +extern "C-unwind" { + fn dep_lookup_malloc_address() -> *const c_char; + fn dep_malloc(size: libc::size_t) -> *mut c_void; + fn dep_free(ptr: *mut c_void); +} + +fn lookup_malloc_address() -> *const c_char { + unsafe { + let mut info: libc::Dl_info = core::mem::zeroed(); + let fnptr: unsafe extern "C" fn(libc::size_t) -> *mut c_void = libc::malloc; + let fnptr = fnptr as *const c_void; + if libc::dladdr(fnptr, &mut info) == 0 { + libc::printf(b"failed finding `malloc`\n\0".as_ptr().cast()); + libc::abort(); + } + info.dli_fname + } +} + +fn main() { + // Check that pointers created with `malloc` in a dylib dependency can be + // free'd with `free` here. + let ptr = unsafe { libc::malloc(10) }; + unsafe { dep_free(ptr) }; + let ptr = unsafe { dep_malloc(10) }; + unsafe { libc::free(ptr) }; + + // If overidden, test that the same is true for `mi_malloc` being + // interoperable with `free`. + if cfg!(feature = "override") { + let ptr = unsafe { libmimalloc_sys::mi_malloc(10) }; + unsafe { dep_free(ptr) }; + let ptr = unsafe { libmimalloc_sys::mi_malloc(10) }; + unsafe { libc::free(ptr) }; + + let ptr = unsafe { libc::malloc(10) }; + unsafe { libmimalloc_sys::mi_free(ptr) }; + let ptr = unsafe { dep_malloc(10) }; + unsafe { libmimalloc_sys::mi_free(ptr) }; + } + + // Extra check that the symbol was actually from the same place. + let dep = unsafe { CStr::from_ptr(dep_lookup_malloc_address()) }; + let here = unsafe { CStr::from_ptr(lookup_malloc_address()) }; + + if cfg!(target_vendor = "apple") { + // macOS / Mach-O symbols are not overriden in dependencies, they are + // hooked into with `zone_register`. + assert_eq!( + dep.to_str().unwrap(), + "/usr/lib/system/libsystem_malloc.dylib" + ); + } else { + assert_eq!(dep, here); + } +}