]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/shims/ffi_support.rs
Add 'src/tools/miri/' from commit '75dd959a3a40eb5b4574f8d2e23aa6efbeb33573'
[rust.git] / src / tools / miri / src / shims / ffi_support.rs
1 use libffi::{high::call as ffi, low::CodePtr};
2 use std::ops::Deref;
3
4 use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
5 use rustc_span::Symbol;
6 use rustc_target::abi::HasDataLayout;
7
8 use crate::*;
9
10 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
11
12 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
13     /// Extract the scalar value from the result of reading a scalar from the machine,
14     /// and convert it to a `CArg`.
15     fn scalar_to_carg(
16         k: Scalar<Provenance>,
17         arg_type: Ty<'tcx>,
18         cx: &impl HasDataLayout,
19     ) -> InterpResult<'tcx, CArg> {
20         match arg_type.kind() {
21             // If the primitive provided can be converted to a type matching the type pattern
22             // then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
23             // the ints
24             ty::Int(IntTy::I8) => {
25                 return Ok(CArg::Int8(k.to_i8()?));
26             }
27             ty::Int(IntTy::I16) => {
28                 return Ok(CArg::Int16(k.to_i16()?));
29             }
30             ty::Int(IntTy::I32) => {
31                 return Ok(CArg::Int32(k.to_i32()?));
32             }
33             ty::Int(IntTy::I64) => {
34                 return Ok(CArg::Int64(k.to_i64()?));
35             }
36             ty::Int(IntTy::Isize) => {
37                 // This will fail if host != target, but then the entire FFI thing probably won't work well
38                 // in that situation.
39                 return Ok(CArg::ISize(k.to_machine_isize(cx)?.try_into().unwrap()));
40             }
41             // the uints
42             ty::Uint(UintTy::U8) => {
43                 return Ok(CArg::UInt8(k.to_u8()?));
44             }
45             ty::Uint(UintTy::U16) => {
46                 return Ok(CArg::UInt16(k.to_u16()?));
47             }
48             ty::Uint(UintTy::U32) => {
49                 return Ok(CArg::UInt32(k.to_u32()?));
50             }
51             ty::Uint(UintTy::U64) => {
52                 return Ok(CArg::UInt64(k.to_u64()?));
53             }
54             ty::Uint(UintTy::Usize) => {
55                 // This will fail if host != target, but then the entire FFI thing probably won't work well
56                 // in that situation.
57                 return Ok(CArg::USize(k.to_machine_usize(cx)?.try_into().unwrap()));
58             }
59             _ => {}
60         }
61         // If no primitives were returned then we have an unsupported type.
62         throw_unsup_format!(
63             "unsupported scalar argument type to external C function: {:?}",
64             arg_type
65         );
66     }
67
68     /// Call external C function and
69     /// store output, depending on return type in the function signature.
70     fn call_external_c_and_store_return<'a>(
71         &mut self,
72         link_name: Symbol,
73         dest: &PlaceTy<'tcx, Provenance>,
74         ptr: CodePtr,
75         libffi_args: Vec<libffi::high::Arg<'a>>,
76     ) -> InterpResult<'tcx, ()> {
77         let this = self.eval_context_mut();
78
79         // Unsafe because of the call to external C code.
80         // Because this is calling a C function it is not necessarily sound,
81         // but there is no way around this and we've checked as much as we can.
82         unsafe {
83             // If the return type of a function is a primitive integer type,
84             // then call the function (`ptr`) with arguments `libffi_args`, store the return value as the specified
85             // primitive integer type, and then write this value out to the miri memory as an integer.
86             match dest.layout.ty.kind() {
87                 // ints
88                 ty::Int(IntTy::I8) => {
89                     let x = ffi::call::<i8>(ptr, libffi_args.as_slice());
90                     this.write_int(x, dest)?;
91                     return Ok(());
92                 }
93                 ty::Int(IntTy::I16) => {
94                     let x = ffi::call::<i16>(ptr, libffi_args.as_slice());
95                     this.write_int(x, dest)?;
96                     return Ok(());
97                 }
98                 ty::Int(IntTy::I32) => {
99                     let x = ffi::call::<i32>(ptr, libffi_args.as_slice());
100                     this.write_int(x, dest)?;
101                     return Ok(());
102                 }
103                 ty::Int(IntTy::I64) => {
104                     let x = ffi::call::<i64>(ptr, libffi_args.as_slice());
105                     this.write_int(x, dest)?;
106                     return Ok(());
107                 }
108                 ty::Int(IntTy::Isize) => {
109                     let x = ffi::call::<isize>(ptr, libffi_args.as_slice());
110                     // `isize` doesn't `impl Into<i128>`, so convert manually.
111                     // Convert to `i64` since this covers both 32- and 64-bit machines.
112                     this.write_int(i64::try_from(x).unwrap(), dest)?;
113                     return Ok(());
114                 }
115                 // uints
116                 ty::Uint(UintTy::U8) => {
117                     let x = ffi::call::<u8>(ptr, libffi_args.as_slice());
118                     this.write_int(x, dest)?;
119                     return Ok(());
120                 }
121                 ty::Uint(UintTy::U16) => {
122                     let x = ffi::call::<u16>(ptr, libffi_args.as_slice());
123                     this.write_int(x, dest)?;
124                     return Ok(());
125                 }
126                 ty::Uint(UintTy::U32) => {
127                     let x = ffi::call::<u32>(ptr, libffi_args.as_slice());
128                     this.write_int(x, dest)?;
129                     return Ok(());
130                 }
131                 ty::Uint(UintTy::U64) => {
132                     let x = ffi::call::<u64>(ptr, libffi_args.as_slice());
133                     this.write_int(x, dest)?;
134                     return Ok(());
135                 }
136                 ty::Uint(UintTy::Usize) => {
137                     let x = ffi::call::<usize>(ptr, libffi_args.as_slice());
138                     // `usize` doesn't `impl Into<i128>`, so convert manually.
139                     // Convert to `u64` since this covers both 32- and 64-bit machines.
140                     this.write_int(u64::try_from(x).unwrap(), dest)?;
141                     return Ok(());
142                 }
143                 // Functions with no declared return type (i.e., the default return)
144                 // have the output_type `Tuple([])`.
145                 ty::Tuple(t_list) =>
146                     if t_list.len() == 0 {
147                         ffi::call::<()>(ptr, libffi_args.as_slice());
148                         return Ok(());
149                     },
150                 _ => {}
151             }
152             // FIXME ellen! deal with all the other return types
153             throw_unsup_format!("unsupported return type to external C function: {:?}", link_name);
154         }
155     }
156
157     /// Get the pointer to the function of the specified name in the shared object file,
158     /// if it exists. The function must be in the shared object file specified: we do *not*
159     /// return pointers to functions in dependencies of the library.  
160     fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
161         let this = self.eval_context_mut();
162         // Try getting the function from the shared library.
163         // On windows `_lib_path` will be unused, hence the name starting with `_`.
164         let (lib, _lib_path) = this.machine.external_so_lib.as_ref().unwrap();
165         let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
166             match lib.get(link_name.as_str().as_bytes()) {
167                 Ok(x) => x,
168                 Err(_) => {
169                     return None;
170                 }
171             }
172         };
173
174         // FIXME: this is a hack!
175         // The `libloading` crate will automatically load system libraries like `libc`.
176         // On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
177         // and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
178         // library if it can't find the symbol in the library itself.
179         // So, in order to check if the function was actually found in the specified
180         // `machine.external_so_lib` we need to check its `dli_fname` and compare it to
181         // the specified SO file path.
182         // This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
183         // from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
184         // using the `libc` crate where this interface is public.
185         // No `libc::dladdr` on windows.
186         #[cfg(unix)]
187         let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
188         #[cfg(unix)]
189         unsafe {
190             if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
191                 if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap()
192                     != _lib_path.to_str().unwrap()
193                 {
194                     return None;
195                 }
196             }
197         }
198         // Return a pointer to the function.
199         Some(CodePtr(*func.deref() as *mut _))
200     }
201
202     /// Call specified external C function, with supplied arguments.
203     /// Need to convert all the arguments from their hir representations to
204     /// a form compatible with C (through `libffi` call).
205     /// Then, convert return from the C call into a corresponding form that
206     /// can be stored in Miri internal memory.
207     fn call_external_c_fct(
208         &mut self,
209         link_name: Symbol,
210         dest: &PlaceTy<'tcx, Provenance>,
211         args: &[OpTy<'tcx, Provenance>],
212     ) -> InterpResult<'tcx, bool> {
213         // Get the pointer to the function in the shared object file if it exists.
214         let code_ptr = match self.get_func_ptr_explicitly_from_lib(link_name) {
215             Some(ptr) => ptr,
216             None => {
217                 // Shared object file does not export this function -- try the shims next.
218                 return Ok(false);
219             }
220         };
221
222         let this = self.eval_context_mut();
223
224         // Get the function arguments, and convert them to `libffi`-compatible form.
225         let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
226         for cur_arg in args.iter() {
227             libffi_args.push(Self::scalar_to_carg(
228                 this.read_scalar(cur_arg)?,
229                 cur_arg.layout.ty,
230                 this,
231             )?);
232         }
233
234         // Convert them to `libffi::high::Arg` type.
235         let libffi_args = libffi_args
236             .iter()
237             .map(|cur_arg| cur_arg.arg_downcast())
238             .collect::<Vec<libffi::high::Arg<'_>>>();
239
240         // Call the function and store output, depending on return type in the function signature.
241         self.call_external_c_and_store_return(link_name, dest, code_ptr, libffi_args)?;
242         Ok(true)
243     }
244 }
245
246 #[derive(Debug, Clone)]
247 /// Enum of supported arguments to external C functions.
248 // We introduce this enum instead of just calling `ffi::arg` and storing a list
249 // of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
250 // to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
251 // and we need to store a copy of the value, and pass a reference to this copy to C instead.
252 pub enum CArg {
253     /// 8-bit signed integer.
254     Int8(i8),
255     /// 16-bit signed integer.
256     Int16(i16),
257     /// 32-bit signed integer.
258     Int32(i32),
259     /// 64-bit signed integer.
260     Int64(i64),
261     /// isize.
262     ISize(isize),
263     /// 8-bit unsigned integer.
264     UInt8(u8),
265     /// 16-bit unsigned integer.
266     UInt16(u16),
267     /// 32-bit unsigned integer.
268     UInt32(u32),
269     /// 64-bit unsigned integer.
270     UInt64(u64),
271     /// usize.
272     USize(usize),
273 }
274
275 impl<'a> CArg {
276     /// Convert a `CArg` to a `libffi` argument type.
277     fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
278         match self {
279             CArg::Int8(i) => ffi::arg(i),
280             CArg::Int16(i) => ffi::arg(i),
281             CArg::Int32(i) => ffi::arg(i),
282             CArg::Int64(i) => ffi::arg(i),
283             CArg::ISize(i) => ffi::arg(i),
284             CArg::UInt8(i) => ffi::arg(i),
285             CArg::UInt16(i) => ffi::arg(i),
286             CArg::UInt32(i) => ffi::arg(i),
287             CArg::UInt64(i) => ffi::arg(i),
288             CArg::USize(i) => ffi::arg(i),
289         }
290     }
291 }