From ddb551fcd7c0fe5c4d09c942df6d3d218c999750 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 16 Sep 2025 09:56:53 +0200 Subject: [PATCH 1/2] Add test for overridden `malloc`/`free` across dylibs This currently fails on macOS. --- .github/workflows/ci.yml | 6 +++- Cargo.toml | 6 +++- libmimalloc-sys/src/lib.rs | 7 ++++ test-override-with-dylib/Cargo.toml | 17 +++++++++ test-override-with-dylib/build.rs | 29 +++++++++++++++ test-override-with-dylib/src/dep.c | 21 +++++++++++ test-override-with-dylib/src/main.rs | 53 ++++++++++++++++++++++++++++ 7 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 test-override-with-dylib/Cargo.toml create mode 100644 test-override-with-dylib/build.rs create mode 100644 test-override-with-dylib/src/dep.c create mode 100644 test-override-with-dylib/src/main.rs 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/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..d29446e --- /dev/null +++ b/test-override-with-dylib/src/main.rs @@ -0,0 +1,53 @@ +//! 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()) }; + assert_eq!(dep, here); +} From af89d046a2e871758297e79af1289f510f5511fe Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 16 Sep 2025 10:26:06 +0200 Subject: [PATCH 2/2] Enable dyld interposing on Apple platforms when overriding Enables the MI_OSX_ZONE and MI_OSX_INTERPOSE flags. These allow using `malloc`/`free` on pointers returned from dynamic libraries (which will otherwise fail, as the dynamic libraries use the System allocator). These are enabled by default in mimalloc's CMakeLists.txt, but since we build without that, we need to specify these flags ourselves. --- libmimalloc-sys/build.rs | 5 +++++ test-override-with-dylib/src/main.rs | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) 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/test-override-with-dylib/src/main.rs b/test-override-with-dylib/src/main.rs index d29446e..4f23912 100644 --- a/test-override-with-dylib/src/main.rs +++ b/test-override-with-dylib/src/main.rs @@ -49,5 +49,15 @@ fn main() { // 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()) }; - assert_eq!(dep, here); + + 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); + } }