miri/shims/native_lib/
mod.rs

1//! Implements calling functions from a native library.
2
3// FIXME: disabled since it fails to build on many targets.
4//#[cfg(target_os = "linux")]
5//pub mod trace;
6
7use std::ops::Deref;
8
9use libffi::high::call as ffi;
10use libffi::low::CodePtr;
11use rustc_abi::{BackendRepr, HasDataLayout, Size};
12use rustc_middle::mir::interpret::Pointer;
13use rustc_middle::ty::{self as ty, IntTy, UintTy};
14use rustc_span::Symbol;
15
16//#[cfg(target_os = "linux")]
17//use self::trace::Supervisor;
18use crate::*;
19
20//#[cfg(target_os = "linux")]
21//type CallResult<'tcx> = InterpResult<'tcx, (ImmTy<'tcx>, Option<self::trace::messages::MemEvents>)>;
22//#[cfg(not(target_os = "linux"))]
23type CallResult<'tcx> = InterpResult<'tcx, (ImmTy<'tcx>, Option<!>)>;
24
25impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
26trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
27    /// Call native host function and return the output as an immediate.
28    fn call_native_with_args<'a>(
29        &mut self,
30        link_name: Symbol,
31        dest: &MPlaceTy<'tcx>,
32        ptr: CodePtr,
33        libffi_args: Vec<libffi::high::Arg<'a>>,
34    ) -> CallResult<'tcx> {
35        let this = self.eval_context_mut();
36        //#[cfg(target_os = "linux")]
37        //let alloc = this.machine.allocator.as_ref().unwrap();
38
39        // SAFETY: We don't touch the machine memory past this point.
40        //#[cfg(target_os = "linux")]
41        //let (guard, stack_ptr) = unsafe { Supervisor::start_ffi(alloc) };
42
43        // Call the function (`ptr`) with arguments `libffi_args`, and obtain the return value
44        // as the specified primitive integer type
45        let res = 'res: {
46            let scalar = match dest.layout.ty.kind() {
47                // ints
48                ty::Int(IntTy::I8) => {
49                    // Unsafe because of the call to native code.
50                    // Because this is calling a C function it is not necessarily sound,
51                    // but there is no way around this and we've checked as much as we can.
52                    let x = unsafe { ffi::call::<i8>(ptr, libffi_args.as_slice()) };
53                    Scalar::from_i8(x)
54                }
55                ty::Int(IntTy::I16) => {
56                    let x = unsafe { ffi::call::<i16>(ptr, libffi_args.as_slice()) };
57                    Scalar::from_i16(x)
58                }
59                ty::Int(IntTy::I32) => {
60                    let x = unsafe { ffi::call::<i32>(ptr, libffi_args.as_slice()) };
61                    Scalar::from_i32(x)
62                }
63                ty::Int(IntTy::I64) => {
64                    let x = unsafe { ffi::call::<i64>(ptr, libffi_args.as_slice()) };
65                    Scalar::from_i64(x)
66                }
67                ty::Int(IntTy::Isize) => {
68                    let x = unsafe { ffi::call::<isize>(ptr, libffi_args.as_slice()) };
69                    Scalar::from_target_isize(x.try_into().unwrap(), this)
70                }
71                // uints
72                ty::Uint(UintTy::U8) => {
73                    let x = unsafe { ffi::call::<u8>(ptr, libffi_args.as_slice()) };
74                    Scalar::from_u8(x)
75                }
76                ty::Uint(UintTy::U16) => {
77                    let x = unsafe { ffi::call::<u16>(ptr, libffi_args.as_slice()) };
78                    Scalar::from_u16(x)
79                }
80                ty::Uint(UintTy::U32) => {
81                    let x = unsafe { ffi::call::<u32>(ptr, libffi_args.as_slice()) };
82                    Scalar::from_u32(x)
83                }
84                ty::Uint(UintTy::U64) => {
85                    let x = unsafe { ffi::call::<u64>(ptr, libffi_args.as_slice()) };
86                    Scalar::from_u64(x)
87                }
88                ty::Uint(UintTy::Usize) => {
89                    let x = unsafe { ffi::call::<usize>(ptr, libffi_args.as_slice()) };
90                    Scalar::from_target_usize(x.try_into().unwrap(), this)
91                }
92                // Functions with no declared return type (i.e., the default return)
93                // have the output_type `Tuple([])`.
94                ty::Tuple(t_list) if (*t_list).deref().is_empty() => {
95                    unsafe { ffi::call::<()>(ptr, libffi_args.as_slice()) };
96                    break 'res interp_ok(ImmTy::uninit(dest.layout));
97                }
98                ty::RawPtr(..) => {
99                    let x = unsafe { ffi::call::<*const ()>(ptr, libffi_args.as_slice()) };
100                    let ptr = Pointer::new(Provenance::Wildcard, Size::from_bytes(x.addr()));
101                    Scalar::from_pointer(ptr, this)
102                }
103                _ =>
104                    break 'res Err(err_unsup_format!(
105                        "unsupported return type for native call: {:?}",
106                        link_name
107                    ))
108                    .into(),
109            };
110            interp_ok(ImmTy::from_scalar(scalar, dest.layout))
111        };
112
113        // SAFETY: We got the guard and stack pointer from start_ffi, and
114        // the allocator is the same
115        //#[cfg(target_os = "linux")]
116        //let events = unsafe { Supervisor::end_ffi(alloc, guard, stack_ptr) };
117        //#[cfg(not(target_os = "linux"))]
118        let events = None;
119
120        interp_ok((res?, events))
121    }
122
123    /// Get the pointer to the function of the specified name in the shared object file,
124    /// if it exists. The function must be in one of the shared object files specified:
125    /// we do *not* return pointers to functions in dependencies of libraries.
126    fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
127        let this = self.eval_context_mut();
128        // Try getting the function from one of the shared libraries.
129        for (lib, lib_path) in &this.machine.native_lib {
130            let Ok(func): Result<libloading::Symbol<'_, unsafe extern "C" fn()>, _> =
131                (unsafe { lib.get(link_name.as_str().as_bytes()) })
132            else {
133                continue;
134            };
135            #[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`.
136            let fn_ptr = *func.deref() as *mut std::ffi::c_void;
137
138            // FIXME: this is a hack!
139            // The `libloading` crate will automatically load system libraries like `libc`.
140            // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
141            // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
142            // library if it can't find the symbol in the library itself.
143            // So, in order to check if the function was actually found in the specified
144            // `machine.external_so_lib` we need to check its `dli_fname` and compare it to
145            // the specified SO file path.
146            // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
147            // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
148            // using the `libc` crate where this interface is public.
149            let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed();
150            unsafe {
151                let res = libc::dladdr(fn_ptr, info.as_mut_ptr());
152                assert!(res != 0, "failed to load info about function we already loaded");
153                let info = info.assume_init();
154                #[cfg(target_os = "cygwin")]
155                let fname_ptr = info.dli_fname.as_ptr();
156                #[cfg(not(target_os = "cygwin"))]
157                let fname_ptr = info.dli_fname;
158                assert!(!fname_ptr.is_null());
159                if std::ffi::CStr::from_ptr(fname_ptr).to_str().unwrap()
160                    != lib_path.to_str().unwrap()
161                {
162                    // The function is not actually in this .so, check the next one.
163                    continue;
164                }
165            }
166
167            // Return a pointer to the function.
168            return Some(CodePtr(fn_ptr));
169        }
170        None
171    }
172}
173
174impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
175pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
176    /// Call the native host function, with supplied arguments.
177    /// Needs to convert all the arguments from their Miri representations to
178    /// a native form (through `libffi` call).
179    /// Then, convert the return value from the native form into something that
180    /// can be stored in Miri's internal memory.
181    fn call_native_fn(
182        &mut self,
183        link_name: Symbol,
184        dest: &MPlaceTy<'tcx>,
185        args: &[OpTy<'tcx>],
186    ) -> InterpResult<'tcx, bool> {
187        let this = self.eval_context_mut();
188        // Get the pointer to the function in the shared object file if it exists.
189        let code_ptr = match this.get_func_ptr_explicitly_from_lib(link_name) {
190            Some(ptr) => ptr,
191            None => {
192                // Shared object file does not export this function -- try the shims next.
193                return interp_ok(false);
194            }
195        };
196
197        // Get the function arguments, and convert them to `libffi`-compatible form.
198        let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
199        for arg in args.iter() {
200            if !matches!(arg.layout.backend_repr, BackendRepr::Scalar(_)) {
201                throw_unsup_format!("only scalar argument types are support for native calls")
202            }
203            let imm = this.read_immediate(arg)?;
204            libffi_args.push(imm_to_carg(&imm, this)?);
205            // If we are passing a pointer, expose its provenance. Below, all exposed memory
206            // (previously exposed and new exposed) will then be properly prepared.
207            if matches!(arg.layout.ty.kind(), ty::RawPtr(..)) {
208                let ptr = imm.to_scalar().to_pointer(this)?;
209                let Some(prov) = ptr.provenance else {
210                    // Pointer without provenance may not access any memory anyway, skip.
211                    continue;
212                };
213                // The first time this happens, print a warning.
214                if !this.machine.native_call_mem_warned.replace(true) {
215                    // Newly set, so first time we get here.
216                    this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem {
217                        //#[cfg(target_os = "linux")]
218                        //tracing: self::trace::Supervisor::is_enabled(),
219                        //#[cfg(not(target_os = "linux"))]
220                        tracing: false,
221                    });
222                }
223
224                this.expose_provenance(prov)?;
225            }
226        }
227
228        // Prepare all exposed memory.
229        this.prepare_exposed_for_native_call()?;
230
231        // Convert them to `libffi::high::Arg` type.
232        let libffi_args = libffi_args
233            .iter()
234            .map(|arg| arg.arg_downcast())
235            .collect::<Vec<libffi::high::Arg<'_>>>();
236
237        // Call the function and store output, depending on return type in the function signature.
238        let (ret, maybe_memevents) =
239            this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?;
240
241        if cfg!(target_os = "linux")
242            && let Some(events) = maybe_memevents
243        {
244            trace!("Registered FFI events:\n{events:#0x?}");
245        }
246
247        this.write_immediate(*ret, dest)?;
248        interp_ok(true)
249    }
250}
251
252#[derive(Debug, Clone)]
253/// Enum of supported arguments to external C functions.
254// We introduce this enum instead of just calling `ffi::arg` and storing a list
255// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
256// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
257// and we need to store a copy of the value, and pass a reference to this copy to C instead.
258enum CArg {
259    /// 8-bit signed integer.
260    Int8(i8),
261    /// 16-bit signed integer.
262    Int16(i16),
263    /// 32-bit signed integer.
264    Int32(i32),
265    /// 64-bit signed integer.
266    Int64(i64),
267    /// isize.
268    ISize(isize),
269    /// 8-bit unsigned integer.
270    UInt8(u8),
271    /// 16-bit unsigned integer.
272    UInt16(u16),
273    /// 32-bit unsigned integer.
274    UInt32(u32),
275    /// 64-bit unsigned integer.
276    UInt64(u64),
277    /// usize.
278    USize(usize),
279    /// Raw pointer, stored as C's `void*`.
280    RawPtr(*mut std::ffi::c_void),
281}
282
283impl<'a> CArg {
284    /// Convert a `CArg` to a `libffi` argument type.
285    fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
286        match self {
287            CArg::Int8(i) => ffi::arg(i),
288            CArg::Int16(i) => ffi::arg(i),
289            CArg::Int32(i) => ffi::arg(i),
290            CArg::Int64(i) => ffi::arg(i),
291            CArg::ISize(i) => ffi::arg(i),
292            CArg::UInt8(i) => ffi::arg(i),
293            CArg::UInt16(i) => ffi::arg(i),
294            CArg::UInt32(i) => ffi::arg(i),
295            CArg::UInt64(i) => ffi::arg(i),
296            CArg::USize(i) => ffi::arg(i),
297            CArg::RawPtr(i) => ffi::arg(i),
298        }
299    }
300}
301
302/// Extract the scalar value from the result of reading a scalar from the machine,
303/// and convert it to a `CArg`.
304fn imm_to_carg<'tcx>(v: &ImmTy<'tcx>, cx: &impl HasDataLayout) -> InterpResult<'tcx, CArg> {
305    interp_ok(match v.layout.ty.kind() {
306        // If the primitive provided can be converted to a type matching the type pattern
307        // then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
308        // the ints
309        ty::Int(IntTy::I8) => CArg::Int8(v.to_scalar().to_i8()?),
310        ty::Int(IntTy::I16) => CArg::Int16(v.to_scalar().to_i16()?),
311        ty::Int(IntTy::I32) => CArg::Int32(v.to_scalar().to_i32()?),
312        ty::Int(IntTy::I64) => CArg::Int64(v.to_scalar().to_i64()?),
313        ty::Int(IntTy::Isize) =>
314            CArg::ISize(v.to_scalar().to_target_isize(cx)?.try_into().unwrap()),
315        // the uints
316        ty::Uint(UintTy::U8) => CArg::UInt8(v.to_scalar().to_u8()?),
317        ty::Uint(UintTy::U16) => CArg::UInt16(v.to_scalar().to_u16()?),
318        ty::Uint(UintTy::U32) => CArg::UInt32(v.to_scalar().to_u32()?),
319        ty::Uint(UintTy::U64) => CArg::UInt64(v.to_scalar().to_u64()?),
320        ty::Uint(UintTy::Usize) =>
321            CArg::USize(v.to_scalar().to_target_usize(cx)?.try_into().unwrap()),
322        ty::RawPtr(..) => {
323            let s = v.to_scalar().to_pointer(cx)?.addr();
324            // This relies on the `expose_provenance` in `prepare_for_native_call`.
325            CArg::RawPtr(std::ptr::with_exposed_provenance_mut(s.bytes_usize()))
326        }
327        _ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty),
328    })
329}