cuda_interposer/
lib.rs

1use std::{env, ffi::CString, os::raw::c_void, sync::OnceLock};
2use tracing::debug;
3
4// Re-exports for macros
5pub use libc;
6pub use once_cell;
7pub use paste;
8pub use tracing;
9
10// ─── Library Loading ─────────────────────────────────────────────────────────
11struct DlHandle(*mut c_void);
12unsafe impl Send for DlHandle {}
13unsafe impl Sync for DlHandle {}
14
15static CUDA_LIB: OnceLock<DlHandle> = OnceLock::new();
16
17fn get_libcuda() -> *mut c_void {
18    let handle_wrapper = CUDA_LIB.get_or_init(|| unsafe {
19        let mut paths = vec![
20            "/usr/local/cuda/compat/libcuda.so".to_string(),
21            "/usr/lib/x86_64-linux-gnu/libcuda.so".to_string(),
22            "/usr/lib64/libcuda.so".to_string(),
23            "/usr/local/cuda/targets/x86_64-linux/lib/stubs/libcuda.so".to_string(),
24        ];
25
26        if let Some(cuda_home) = env::var_os("CUDA_HOME") {
27            let path = format!("{}/compat/libcuda.so", cuda_home.to_string_lossy());
28            paths.insert(0, path);
29        }
30
31        for path in paths.iter() {
32            let s = CString::new(path.clone()).unwrap();
33            let flags = libc::RTLD_NOW | libc::RTLD_LOCAL | libc::RTLD_NODELETE;
34            let handle = libc::dlopen(s.as_ptr(), flags);
35            if !handle.is_null() {
36                debug!("Loaded real CUDA driver from: {}", path);
37                return DlHandle(handle);
38            }
39        }
40        panic!("Failed to find/load libcuda.so. Ensure it is in LD_LIBRARY_PATH.");
41    });
42    handle_wrapper.0
43}
44
45pub fn dlsym_next(symbol: &[u8]) -> *mut c_void {
46    let handle = get_libcuda();
47    unsafe { libc::dlsym(handle, symbol.as_ptr() as *const _) }
48}
49
50// ─── Macros ──────────────────────────────────────────────────────────────────
51
52/// Installs the `cuGetProcAddress` hooks required for the interposer to function.
53/// This macro automatically includes the `hook_map.rs` generated by `cuda-interposer-build`.
54#[macro_export]
55macro_rules! install_hooks {
56    () => {
57        use std::{
58            ffi::CStr,
59            os::raw::{c_char, c_int, c_void},
60        };
61        use tracing::debug;
62
63        fn get_local_hook(name: &str) -> Option<*mut $crate::libc::c_void> {
64            // Include returns the closure expression from hook_map.rs
65            let hook_fn = include!(concat!(env!("OUT_DIR"), "/hook_map.rs"));
66            hook_fn(name)
67        }
68
69        type CUresult = u32; // enum
70        type CUdriverProcAddressQueryResult = u32; // enum
71
72        $crate::cuda_hook! {
73            pub unsafe extern "C" fn cuGetProcAddress_v2(
74                symbol: *const $crate::libc::c_char,
75                pfn: *mut *mut $crate::libc::c_void,
76                cuda_version: $crate::libc::c_int,
77                flags: u64,
78                symbol_status: *mut CUdriverProcAddressQueryResult
79            ) -> CUresult {
80                let sym_name_c = unsafe { ::std::ffi::CStr::from_ptr(symbol) };
81                let sym_name = sym_name_c.to_string_lossy();
82
83                // A. Call real implementation
84                let real_fn = *__real_cuGetProcAddress_v2;
85                let ret = unsafe { real_fn(symbol, pfn, cuda_version, flags, symbol_status) };
86
87                // B. Intercept
88                if let Some(our_ptr) = get_local_hook(&sym_name) {
89                    $crate::tracing::debug!("Hooking symbol: {}", sym_name);
90                    unsafe { *pfn = our_ptr };
91                    return 0; // CUDA_SUCCESS
92                }
93                ret
94            }
95        }
96
97        $crate::cuda_hook! {
98            pub unsafe extern "C" fn cuGetProcAddress(
99                symbol: *const $crate::libc::c_char,
100                pfn: *mut *mut $crate::libc::c_void,
101                cuda_version: $crate::libc::c_int,
102                flags: u64,
103                symbol_status: *mut CUdriverProcAddressQueryResult
104            ) -> CUresult {
105                let sym_name_c = unsafe { ::std::ffi::CStr::from_ptr(symbol) };
106                let sym_name = sym_name_c.to_string_lossy();
107
108                let real_fn = *__real_cuGetProcAddress;
109                let ret = unsafe { real_fn(symbol, pfn, cuda_version, flags, symbol_status) };
110
111                if let Some(our_ptr) = get_local_hook(&sym_name) {
112                    $crate::tracing::debug!("Hooking symbol: {}", sym_name);
113                    unsafe { *pfn = our_ptr };
114                    return 0; // CUDA_SUCCESS
115                }
116                ret
117            }
118        }
119    };
120}
121
122#[macro_export]
123macro_rules! cuda_hook {
124    (
125        pub unsafe extern "C" fn $fname:ident( $($arg:ident : $arg_ty:ty),* $(,)? )
126        -> $ret:ty
127        $body:block
128    ) => {
129        $crate::paste::paste! {
130            #[allow(non_upper_case_globals)]
131            pub static [<__real_ $fname>]: $crate::once_cell::sync::Lazy<
132                unsafe extern "C" fn($($arg_ty),*) -> $ret
133            > = $crate::once_cell::sync::Lazy::new(|| {
134                let name = concat!(stringify!($fname), "\0");
135                let sym = $crate::dlsym_next(name.as_bytes());
136                if sym.is_null() {
137                    panic!("Missing symbol: {}", stringify!($fname));
138                }
139                unsafe { std::mem::transmute(sym) }
140            });
141
142            #[unsafe(no_mangle)]
143            pub unsafe extern "C" fn $fname( $($arg : $arg_ty),* ) -> $ret {
144                 $body
145            }
146        }
147    };
148}
149
150#[macro_export]
151macro_rules! generate_proxy {
152    // Internal: Generate specific alias function
153    (
154        @generate_alias
155        alias: $alias:ident,
156        target_fn: $fname:ident,
157        args: ( [ $( ($arg:ident : $arg_ty:ty) ),* ] ),
158        ret: $ret:ty
159    ) => {
160        $crate::paste::paste! {
161            #[unsafe(no_mangle)]
162            pub unsafe extern "C" fn $alias( $( $arg : $arg_ty ),* ) -> $ret {
163                let f = *[<__REAL_ $fname:upper>];
164                f( $( $arg ),* )
165            }
166        }
167    };
168
169    // Internal: Recurse over aliases
170    (
171        @recurse_aliases
172        target_fn: $fname:ident,
173        ret: $ret:ty,
174        args_tt: $args_tt:tt,
175        aliases: [ $($alias:ident),* ]
176    ) => {
177        $(
178            $crate::generate_proxy!(
179                @generate_alias
180                alias: $alias,
181                target_fn: $fname,
182                args: $args_tt,
183                ret: $ret
184            );
185        )*
186    };
187
188    // Internal: Generate Main Function and Lazy static
189    (
190        @generate_main
191        fn $fname:ident ( [ $( ($arg:ident : $arg_ty:ty) ),* ] ) -> $ret:ty;
192        target_symbol: $real_sym:ident
193    ) => {
194        $crate::paste::paste! {
195            static [<__REAL_ $fname:upper>]: $crate::once_cell::sync::Lazy<
196                 extern "C" fn( $($arg_ty),* ) -> $ret
197            > = $crate::once_cell::sync::Lazy::new(|| {
198                let name = concat!(stringify!($real_sym), "\0");
199                let ptr = $crate::dlsym_next(name.as_bytes());
200                if ptr.is_null() {
201                    eprintln!("fatal: symbol '{}' not found in underlying libcuda", name);
202                    std::process::abort();
203                }
204                unsafe { std::mem::transmute(ptr) }
205            });
206
207            #[unsafe(no_mangle)]
208            pub extern "C" fn $fname( $( $arg : $arg_ty ),* ) -> $ret {
209                let f = *[<__REAL_ $fname:upper>];
210                f( $( $arg ),* )
211            }
212        }
213    };
214
215    // Entry Point
216    (
217        fn $fname:ident $args_tt:tt -> $ret:ty;
218        name: $real_sym:ident
219        $(, aliases: $($alias:ident),* )?
220    ) => {
221        $crate::generate_proxy!(
222            @generate_main
223            fn $fname $args_tt -> $ret;
224            target_symbol: $real_sym
225        );
226
227        $(
228            $crate::generate_proxy!(
229                @recurse_aliases
230                target_fn: $fname,
231                ret: $ret,
232                args_tt: $args_tt,
233                aliases: [ $($alias),* ]
234            );
235        )?
236    };
237}