miri/shims/
native_lib.rs

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