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