Auto merge of #2363 - emarteca:int-function-args-returns, r=oli-obk
Adding support for external C functions that have integer (or empty) args and/or returns Starts addressing `@https://github.com/rust-lang/miri/issues/11` ### Implementation Adding support for calling external C functions that have any number of integer arguments (types of integers: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`) and an integer return type (or `void`). As suggested in `@https://github.com/rust-lang/miri/issues/11,` the [`libffi` crate](https://docs.rs/libffi/latest/libffi/index.html) is used to dispatch the calls to external C functions. #### Modifications Main modifications are to: * [helper.rs](https://github.com/emarteca/miri/blob/int-function-args-returns/src/helpers.rs) : adding a function `call_and_add_external_c_fct_to_context` to read the code pointer to the external C function, dispatch the call, and save the return in MIRI internal memory. Handles all conversions between MIRI and C values (using some macros, also defined in this file). * [foreign_items.rs](https://github.com/emarteca/miri/blob/int-function-args-returns/src/shims/foreign_items.rs) : handles the calling of `call_and_add_external_c_fct_to_context` in [helper.rs](https://github.com/emarteca/miri/blob/int-function-args-returns/src/helpers.rs) when a foreign item is encountered. Also adds some structs to model C representations of arguments, and the signature of the external C call. ### Testing Adds tests for the following external functions which are now supported: * [int tests](https://github.com/emarteca/miri/blob/int-function-args-returns/tests/pass/external_C/int_c_tests.rs): - adds 2 to a provided int (no type of int specified, so autocasts) - takes the sum of its 12 arguments (tests stack spill) - adds 3 to a 16 bit int - adds an `i16` to an `i64` - returns -10 as an unsigned int * [void tests](https://github.com/emarteca/miri/blob/int-function-args-returns/tests/pass/external_C/print_from_c.rs) - void function that prints from C ### Code review The code in this PR was reviewed by `@maurer` on [another fork](https://github.com/maurer/miri/pull/1) -- thanks!
This commit is contained in:
commit
64185014af
8
.gitignore
vendored
8
.gitignore
vendored
@ -2,10 +2,18 @@ target
|
||||
/doc
|
||||
tex/*/out
|
||||
*.dot
|
||||
*.out
|
||||
*.rs.bk
|
||||
.vscode
|
||||
*.mm_profdata
|
||||
perf.data
|
||||
perf.data.old
|
||||
flamegraph.svg
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
<<<<<<< HEAD
|
||||
tests/extern-so/libtestlib.so
|
||||
=======
|
||||
>>>>>>> master
|
||||
>>>>>>> 58ba05a0 (C FFI support for functions with int args and returns)
|
||||
.auto-*
|
||||
|
38
Cargo.lock
generated
38
Cargo.lock
generated
@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "abort_on_panic"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955f37ac58af2416bac687c8ab66a4ccba282229bd7422a28d2281a5e66a6116"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
@ -327,6 +333,36 @@ version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "libffi"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e08093a2ddeee94bd0c830a53d895ff91f1f3bb0f9b3c8c6b00739cdf76bc1d"
|
||||
dependencies = [
|
||||
"abort_on_panic",
|
||||
"libc",
|
||||
"libffi-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libffi-sys"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab4106b7f09d7b87d021334d5618fac1dfcfb824d4c5fe111ff0074dfd242e15"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.5"
|
||||
@ -401,6 +437,8 @@ dependencies = [
|
||||
"getrandom",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libffi",
|
||||
"libloading",
|
||||
"log",
|
||||
"measureme",
|
||||
"rand",
|
||||
|
@ -20,6 +20,8 @@ doctest = false # and no doc tests
|
||||
[dependencies]
|
||||
getrandom = { version = "0.2", features = ["std"] }
|
||||
env_logger = "0.9"
|
||||
libffi = "3.0.0"
|
||||
libloading = "0.7"
|
||||
log = "0.4"
|
||||
shell-escape = "0.1.4"
|
||||
rand = "0.8"
|
||||
|
11
README.md
11
README.md
@ -346,6 +346,17 @@ to Miri failing to detect cases of undefined behavior in a program.
|
||||
this flag is **unsound**.
|
||||
* `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak
|
||||
memory effects.
|
||||
* `-Zmiri-extern-so-file=<path to a shared object file>` is an experimental flag for providing support
|
||||
for FFI calls. Functions not provided by that file are still executed via the usual Miri shims.
|
||||
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause undefined behaviour in Miri itself!
|
||||
And of course, Miri cannot do any checks on the actions taken by the external code.
|
||||
Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions
|
||||
working on file descriptors, you will have to replace *all* of them, or the two kinds of
|
||||
file descriptors will be mixed up.
|
||||
This is **work in progress**; currently, only integer arguments and return values are
|
||||
supported (and no, pointer/integer casts to work around this limitation will not work;
|
||||
they will fail horribly).
|
||||
Follow [the discussion on supporting other types](https://github.com/rust-lang/miri/issues/2365).
|
||||
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
|
||||
This can be used to find which parts of your program are executing slowly under Miri.
|
||||
The profile is written out to a file with the prefix `<name>`, and can be processed
|
||||
|
6
build.rs
Normal file
6
build.rs
Normal file
@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
// Re-export the TARGET environment variable so it can
|
||||
// be accessed by miri.
|
||||
let target = std::env::var("TARGET").unwrap();
|
||||
println!("cargo:rustc-env=TARGET={:?}", target);
|
||||
}
|
1
miri
1
miri
@ -108,6 +108,7 @@ esac
|
||||
|
||||
## Prepare the environment
|
||||
# Determine some toolchain properties
|
||||
# export the target so its available in miri
|
||||
TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2)
|
||||
SYSROOT=$(rustc +$TOOLCHAIN --print sysroot)
|
||||
LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib
|
||||
|
@ -530,6 +530,19 @@ fn main() {
|
||||
"full" => BacktraceStyle::Full,
|
||||
_ => show_error!("-Zmiri-backtrace may only be 0, 1, or full"),
|
||||
};
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-extern-so-file=") {
|
||||
let filename = param.to_string();
|
||||
if std::path::Path::new(&filename).exists() {
|
||||
if let Some(other_filename) = miri_config.external_so_file {
|
||||
panic!(
|
||||
"-Zmiri-extern-so-file external SO file is already set to {}",
|
||||
other_filename.display()
|
||||
);
|
||||
}
|
||||
miri_config.external_so_file = Some(filename.into());
|
||||
} else {
|
||||
panic!("-Zmiri-extern-so-file path {} does not exist", filename);
|
||||
}
|
||||
} else {
|
||||
// Forward to rustc.
|
||||
rustc_args.push(arg);
|
||||
|
@ -3,6 +3,7 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::iter;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::path::PathBuf;
|
||||
use std::thread;
|
||||
|
||||
use log::info;
|
||||
@ -128,6 +129,9 @@ pub struct MiriConfig {
|
||||
pub report_progress: Option<u32>,
|
||||
/// Whether Stacked Borrows retagging should recurse into fields of datatypes.
|
||||
pub retag_fields: bool,
|
||||
/// The location of a shared object file to load when calling external functions
|
||||
/// FIXME! consider allowing users to specify paths to multiple SO files, or to a directory
|
||||
pub external_so_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for MiriConfig {
|
||||
@ -159,6 +163,7 @@ fn default() -> MiriConfig {
|
||||
preemption_rate: 0.01, // 1%
|
||||
report_progress: None,
|
||||
retag_fields: false,
|
||||
external_so_file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,10 +358,14 @@ pub struct Evaluator<'mir, 'tcx> {
|
||||
pub(crate) report_progress: Option<u32>,
|
||||
/// The number of blocks that passed since the last progress report.
|
||||
pub(crate) since_progress_report: u32,
|
||||
|
||||
/// Handle of the optional shared object file for external functions.
|
||||
pub external_so_lib: Option<(libloading::Library, std::path::PathBuf)>,
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
|
||||
pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Self {
|
||||
let target_triple = &layout_cx.tcx.sess.opts.target_triple.to_string();
|
||||
let local_crates = helpers::get_local_crates(layout_cx.tcx);
|
||||
let layouts =
|
||||
PrimitiveLayouts::new(layout_cx).expect("Couldn't get layouts of primitive types");
|
||||
@ -412,6 +416,24 @@ pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>)
|
||||
preemption_rate: config.preemption_rate,
|
||||
report_progress: config.report_progress,
|
||||
since_progress_report: 0,
|
||||
external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| {
|
||||
// Check if host target == the session target.
|
||||
if option_env!("TARGET") == Some(target_triple) {
|
||||
panic!(
|
||||
"calling external C functions in linked .so file requires target and host to be the same"
|
||||
);
|
||||
}
|
||||
// Note: it is the user's responsibility to provide a correct SO file.
|
||||
// WATCH OUT: If an invalid/incorrect SO file is specified, this can cause
|
||||
// undefined behaviour in Miri itself!
|
||||
(
|
||||
unsafe {
|
||||
libloading::Library::new(lib_file_path)
|
||||
.expect("Failed to read specified shared object file")
|
||||
},
|
||||
lib_file_path.clone(),
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
291
src/shims/ffi_support.rs
Normal file
291
src/shims/ffi_support.rs
Normal file
@ -0,0 +1,291 @@
|
||||
use libffi::{high::call as ffi, low::CodePtr};
|
||||
use std::ops::Deref;
|
||||
|
||||
use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
|
||||
use rustc_span::Symbol;
|
||||
use rustc_target::abi::HasDataLayout;
|
||||
|
||||
use crate::*;
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
||||
/// Extract the scalar value from the result of reading a scalar from the machine,
|
||||
/// and convert it to a `CArg`.
|
||||
fn scalar_to_carg(
|
||||
k: ScalarMaybeUninit<Provenance>,
|
||||
arg_type: Ty<'tcx>,
|
||||
cx: &impl HasDataLayout,
|
||||
) -> InterpResult<'tcx, CArg> {
|
||||
match arg_type.kind() {
|
||||
// If the primitive provided can be converted to a type matching the type pattern
|
||||
// then create a `CArg` of this primitive value with the corresponding `CArg` constructor.
|
||||
// the ints
|
||||
ty::Int(IntTy::I8) => {
|
||||
return Ok(CArg::Int8(k.to_i8()?));
|
||||
}
|
||||
ty::Int(IntTy::I16) => {
|
||||
return Ok(CArg::Int16(k.to_i16()?));
|
||||
}
|
||||
ty::Int(IntTy::I32) => {
|
||||
return Ok(CArg::Int32(k.to_i32()?));
|
||||
}
|
||||
ty::Int(IntTy::I64) => {
|
||||
return Ok(CArg::Int64(k.to_i64()?));
|
||||
}
|
||||
ty::Int(IntTy::Isize) => {
|
||||
// This will fail if host != target, but then the entire FFI thing probably won't work well
|
||||
// in that situation.
|
||||
return Ok(CArg::ISize(k.to_machine_isize(cx)?.try_into().unwrap()));
|
||||
}
|
||||
// the uints
|
||||
ty::Uint(UintTy::U8) => {
|
||||
return Ok(CArg::UInt8(k.to_u8()?));
|
||||
}
|
||||
ty::Uint(UintTy::U16) => {
|
||||
return Ok(CArg::UInt16(k.to_u16()?));
|
||||
}
|
||||
ty::Uint(UintTy::U32) => {
|
||||
return Ok(CArg::UInt32(k.to_u32()?));
|
||||
}
|
||||
ty::Uint(UintTy::U64) => {
|
||||
return Ok(CArg::UInt64(k.to_u64()?));
|
||||
}
|
||||
ty::Uint(UintTy::Usize) => {
|
||||
// This will fail if host != target, but then the entire FFI thing probably won't work well
|
||||
// in that situation.
|
||||
return Ok(CArg::USize(k.to_machine_usize(cx)?.try_into().unwrap()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// If no primitives were returned then we have an unsupported type.
|
||||
throw_unsup_format!(
|
||||
"unsupported scalar argument type to external C function: {:?}",
|
||||
arg_type
|
||||
);
|
||||
}
|
||||
|
||||
/// Call external C function and
|
||||
/// store output, depending on return type in the function signature.
|
||||
fn call_external_c_and_store_return<'a>(
|
||||
&mut self,
|
||||
link_name: Symbol,
|
||||
dest: &PlaceTy<'tcx, Provenance>,
|
||||
ptr: CodePtr,
|
||||
libffi_args: Vec<libffi::high::Arg<'a>>,
|
||||
) -> InterpResult<'tcx, ()> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// Unsafe because of the call to external C code.
|
||||
// Because this is calling a C function it is not necessarily sound,
|
||||
// but there is no way around this and we've checked as much as we can.
|
||||
unsafe {
|
||||
// If the return type of a function is a primitive integer type,
|
||||
// then call the function (`ptr`) with arguments `libffi_args`, store the return value as the specified
|
||||
// primitive integer type, and then write this value out to the miri memory as an integer.
|
||||
match dest.layout.ty.kind() {
|
||||
// ints
|
||||
ty::Int(IntTy::I8) => {
|
||||
let x = ffi::call::<i8>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Int(IntTy::I16) => {
|
||||
let x = ffi::call::<i16>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Int(IntTy::I32) => {
|
||||
let x = ffi::call::<i32>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Int(IntTy::I64) => {
|
||||
let x = ffi::call::<i64>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Int(IntTy::Isize) => {
|
||||
let x = ffi::call::<isize>(ptr, libffi_args.as_slice());
|
||||
// `isize` doesn't `impl Into<i128>`, so convert manually.
|
||||
// Convert to `i64` since this covers both 32- and 64-bit machines.
|
||||
this.write_int(i64::try_from(x).unwrap(), dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
// uints
|
||||
ty::Uint(UintTy::U8) => {
|
||||
let x = ffi::call::<u8>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Uint(UintTy::U16) => {
|
||||
let x = ffi::call::<u16>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Uint(UintTy::U32) => {
|
||||
let x = ffi::call::<u32>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Uint(UintTy::U64) => {
|
||||
let x = ffi::call::<u64>(ptr, libffi_args.as_slice());
|
||||
this.write_int(x, dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
ty::Uint(UintTy::Usize) => {
|
||||
let x = ffi::call::<usize>(ptr, libffi_args.as_slice());
|
||||
// `usize` doesn't `impl Into<i128>`, so convert manually.
|
||||
// Convert to `u64` since this covers both 32- and 64-bit machines.
|
||||
this.write_int(u64::try_from(x).unwrap(), dest)?;
|
||||
return Ok(());
|
||||
}
|
||||
// Functions with no declared return type (i.e., the default return)
|
||||
// have the output_type `Tuple([])`.
|
||||
ty::Tuple(t_list) =>
|
||||
if t_list.len() == 0 {
|
||||
ffi::call::<()>(ptr, libffi_args.as_slice());
|
||||
return Ok(());
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
// FIXME ellen! deal with all the other return types
|
||||
throw_unsup_format!("unsupported return type to external C function: {:?}", link_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the pointer to the function of the specified name in the shared object file,
|
||||
/// if it exists. The function must be in the shared object file specified: we do *not*
|
||||
/// return pointers to functions in dependencies of the library.
|
||||
fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
|
||||
let this = self.eval_context_mut();
|
||||
// Try getting the function from the shared library.
|
||||
// On windows `_lib_path` will be unused, hence the name starting with `_`.
|
||||
let (lib, _lib_path) = this.machine.external_so_lib.as_ref().unwrap();
|
||||
let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
|
||||
match lib.get(link_name.as_str().as_bytes()) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: this is a hack!
|
||||
// The `libloading` crate will automatically load system libraries like `libc`.
|
||||
// On linux `libloading` is based on `dlsym`: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#202
|
||||
// and `dlsym`(https://linux.die.net/man/3/dlsym) looks through the dependency tree of the
|
||||
// library if it can't find the symbol in the library itself.
|
||||
// So, in order to check if the function was actually found in the specified
|
||||
// `machine.external_so_lib` we need to check its `dli_fname` and compare it to
|
||||
// the specified SO file path.
|
||||
// This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
|
||||
// from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
|
||||
// using the `libc` crate where this interface is public.
|
||||
// No `libc::dladdr` on windows.
|
||||
#[cfg(unix)]
|
||||
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
|
||||
if std::ffi::CStr::from_ptr(info.assume_init().dli_fname).to_str().unwrap()
|
||||
!= _lib_path.to_str().unwrap()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return a pointer to the function.
|
||||
Some(CodePtr(*func.deref() as *mut _))
|
||||
}
|
||||
|
||||
/// Call specified external C function, with supplied arguments.
|
||||
/// Need to convert all the arguments from their hir representations to
|
||||
/// a form compatible with C (through `libffi` call).
|
||||
/// Then, convert return from the C call into a corresponding form that
|
||||
/// can be stored in Miri internal memory.
|
||||
fn call_external_c_fct(
|
||||
&mut self,
|
||||
link_name: Symbol,
|
||||
dest: &PlaceTy<'tcx, Provenance>,
|
||||
args: &[OpTy<'tcx, Provenance>],
|
||||
) -> InterpResult<'tcx, bool> {
|
||||
// Get the pointer to the function in the shared object file if it exists.
|
||||
let code_ptr = match self.get_func_ptr_explicitly_from_lib(link_name) {
|
||||
Some(ptr) => ptr,
|
||||
None => {
|
||||
// Shared object file does not export this function -- try the shims next.
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// Get the function arguments, and convert them to `libffi`-compatible form.
|
||||
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
|
||||
for cur_arg in args.iter() {
|
||||
libffi_args.push(Self::scalar_to_carg(
|
||||
this.read_scalar(cur_arg)?,
|
||||
cur_arg.layout.ty,
|
||||
this,
|
||||
)?);
|
||||
}
|
||||
|
||||
// Convert them to `libffi::high::Arg` type.
|
||||
let libffi_args = libffi_args
|
||||
.iter()
|
||||
.map(|cur_arg| cur_arg.arg_downcast())
|
||||
.collect::<Vec<libffi::high::Arg<'_>>>();
|
||||
|
||||
// Call the function and store output, depending on return type in the function signature.
|
||||
self.call_external_c_and_store_return(link_name, dest, code_ptr, libffi_args)?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Enum of supported arguments to external C functions.
|
||||
// We introduce this enum instead of just calling `ffi::arg` and storing a list
|
||||
// of `libffi::high::Arg` directly, because the `libffi::high::Arg` just wraps a reference
|
||||
// to the value it represents: https://docs.rs/libffi/latest/libffi/high/call/struct.Arg.html
|
||||
// and we need to store a copy of the value, and pass a reference to this copy to C instead.
|
||||
pub enum CArg {
|
||||
/// 8-bit signed integer.
|
||||
Int8(i8),
|
||||
/// 16-bit signed integer.
|
||||
Int16(i16),
|
||||
/// 32-bit signed integer.
|
||||
Int32(i32),
|
||||
/// 64-bit signed integer.
|
||||
Int64(i64),
|
||||
/// isize.
|
||||
ISize(isize),
|
||||
/// 8-bit unsigned integer.
|
||||
UInt8(u8),
|
||||
/// 16-bit unsigned integer.
|
||||
UInt16(u16),
|
||||
/// 32-bit unsigned integer.
|
||||
UInt32(u32),
|
||||
/// 64-bit unsigned integer.
|
||||
UInt64(u64),
|
||||
/// usize.
|
||||
USize(usize),
|
||||
}
|
||||
|
||||
impl<'a> CArg {
|
||||
/// Convert a `CArg` to a `libffi` argument type.
|
||||
fn arg_downcast(&'a self) -> libffi::high::Arg<'a> {
|
||||
match self {
|
||||
CArg::Int8(i) => ffi::arg(i),
|
||||
CArg::Int16(i) => ffi::arg(i),
|
||||
CArg::Int32(i) => ffi::arg(i),
|
||||
CArg::Int64(i) => ffi::arg(i),
|
||||
CArg::ISize(i) => ffi::arg(i),
|
||||
CArg::UInt8(i) => ffi::arg(i),
|
||||
CArg::UInt16(i) => ffi::arg(i),
|
||||
CArg::UInt32(i) => ffi::arg(i),
|
||||
CArg::UInt64(i) => ffi::arg(i),
|
||||
CArg::USize(i) => ffi::arg(i),
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
|
||||
use super::backtrace::EvalContextExt as _;
|
||||
use crate::helpers::{convert::Truncate, target_os_is_unix};
|
||||
use crate::shims::ffi_support::EvalContextExt as _;
|
||||
use crate::*;
|
||||
|
||||
/// Returned by `emulate_foreign_item_by_name`.
|
||||
@ -31,7 +32,7 @@ pub enum EmulateByNameResult<'mir, 'tcx> {
|
||||
NeedsJumping,
|
||||
/// Jumping has already been taken care of.
|
||||
AlreadyJumped,
|
||||
/// A MIR body has been found for the function
|
||||
/// A MIR body has been found for the function.
|
||||
MirBody(&'mir mir::Body<'tcx>, ty::Instance<'tcx>),
|
||||
/// The item is not supported.
|
||||
NotSupported,
|
||||
@ -369,6 +370,17 @@ fn emulate_foreign_item_by_name(
|
||||
) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
// First deal with any external C functions in linked .so file
|
||||
// (if any SO file is specified, and if the host target == the session target)
|
||||
if this.machine.external_so_lib.as_ref().is_some() {
|
||||
// An Ok(false) here means that the function being called was not exported
|
||||
// by the specified SO file; we should continue and check if it corresponds to
|
||||
// a provided shim.
|
||||
if this.call_external_c_fct(link_name, dest, args)? {
|
||||
return Ok(EmulateByNameResult::NeedsJumping);
|
||||
}
|
||||
}
|
||||
|
||||
// When adding a new shim, you should follow the following pattern:
|
||||
// ```
|
||||
// "shim_name" => {
|
||||
@ -779,9 +791,8 @@ fn emulate_foreign_item_by_name(
|
||||
target => throw_unsup_format!("the target `{}` is not supported", target),
|
||||
}
|
||||
};
|
||||
|
||||
// We only fall through to here if we did *not* hit the `_` arm above,
|
||||
// i.e., if we actually emulated the function.
|
||||
// i.e., if we actually emulated the function with one of the shims.
|
||||
Ok(EmulateByNameResult::NeedsJumping)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#![warn(clippy::integer_arithmetic)]
|
||||
|
||||
mod backtrace;
|
||||
pub mod ffi_support;
|
||||
pub mod foreign_items;
|
||||
pub mod intrinsics;
|
||||
pub mod unix;
|
||||
|
@ -1,13 +1,42 @@
|
||||
use colored::*;
|
||||
use regex::Regex;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, ffi::OsString};
|
||||
use std::{env, ffi::OsString, process::Command};
|
||||
use ui_test::{color_eyre::Result, Config, DependencyBuilder, Mode, OutputConflictHandling};
|
||||
|
||||
fn miri_path() -> PathBuf {
|
||||
PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
|
||||
}
|
||||
|
||||
// Build the shared object file for testing external C function calls.
|
||||
fn build_so_for_c_ffi_tests() -> PathBuf {
|
||||
let cc = option_env!("CC").unwrap_or("cc");
|
||||
// Target directory that we can write to.
|
||||
let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so");
|
||||
// Create the directory if it does not already exist.
|
||||
std::fs::create_dir_all(&so_target_dir)
|
||||
.expect("Failed to create directory for shared object file");
|
||||
let so_file_path = so_target_dir.join("libtestlib.so");
|
||||
let cc_output = Command::new(cc)
|
||||
.args([
|
||||
"-shared",
|
||||
"-o",
|
||||
so_file_path.to_str().unwrap(),
|
||||
"tests/extern-so/test.c",
|
||||
// Only add the functions specified in libcode.version to the shared object file.
|
||||
// This is to avoid automatically adding `malloc`, etc.
|
||||
// Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/
|
||||
"-fPIC",
|
||||
"-Wl,--version-script=tests/extern-so/libcode.version",
|
||||
])
|
||||
.output()
|
||||
.expect("failed to generate shared object file for testing external C function calls");
|
||||
if !cc_output.status.success() {
|
||||
panic!("error in generating shared object file for testing external C function calls");
|
||||
}
|
||||
so_file_path
|
||||
}
|
||||
|
||||
fn run_tests(
|
||||
mode: Mode,
|
||||
path: &str,
|
||||
@ -40,6 +69,16 @@ fn run_tests(
|
||||
flags.push(target.into());
|
||||
}
|
||||
|
||||
// If we're on linux, and we're testing the extern-so functionality,
|
||||
// then build the shared object file for testing external C function calls
|
||||
// and push the relevant compiler flag.
|
||||
if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") {
|
||||
let so_file_path = build_so_for_c_ffi_tests();
|
||||
let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file=");
|
||||
flag.push(so_file_path.into_os_string());
|
||||
flags.push(flag);
|
||||
}
|
||||
|
||||
let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
|
||||
|
||||
let output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) {
|
||||
@ -176,6 +215,10 @@ fn main() -> Result<()> {
|
||||
ui(Mode::Pass, "tests/pass-dep", WithDependencies)?;
|
||||
ui(Mode::Panic, "tests/panic", WithDependencies)?;
|
||||
ui(Mode::Fail, "tests/fail", WithDependencies)?;
|
||||
if cfg!(target_os = "linux") {
|
||||
ui(Mode::Pass, "tests/extern-so/pass", WithoutDependencies)?;
|
||||
ui(Mode::Fail, "tests/extern-so/fail", WithDependencies)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
12
tests/extern-so/fail/function_not_in_so.rs
Normal file
12
tests/extern-so/fail/function_not_in_so.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//@only-target-linux
|
||||
//@only-on-host
|
||||
|
||||
extern "C" {
|
||||
fn foo();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
foo(); //~ ERROR: unsupported operation: can't call foreign function: foo
|
||||
}
|
||||
}
|
14
tests/extern-so/fail/function_not_in_so.stderr
Normal file
14
tests/extern-so/fail/function_not_in_so.stderr
Normal file
@ -0,0 +1,14 @@
|
||||
error: unsupported operation: can't call foreign function: foo
|
||||
--> $DIR/function_not_in_so.rs:LL:CC
|
||||
|
|
||||
LL | foo();
|
||||
| ^^^^^ can't call foreign function: foo
|
||||
|
|
||||
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
|
||||
= note: backtrace:
|
||||
= note: inside `main` at $DIR/function_not_in_so.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to previous error
|
||||
|
9
tests/extern-so/libcode.version
Normal file
9
tests/extern-so/libcode.version
Normal file
@ -0,0 +1,9 @@
|
||||
CODEABI_1.0 {
|
||||
global: *add_one_int*;
|
||||
*printer*;
|
||||
*test_stack_spill*;
|
||||
*get_unsigned_int*;
|
||||
*add_int16*;
|
||||
*add_short_to_long*;
|
||||
local: *;
|
||||
};
|
46
tests/extern-so/pass/call_extern_c_fcts.rs
Normal file
46
tests/extern-so/pass/call_extern_c_fcts.rs
Normal file
@ -0,0 +1,46 @@
|
||||
//@only-target-linux
|
||||
//@only-on-host
|
||||
|
||||
extern "C" {
|
||||
fn add_one_int(x: i32) -> i32;
|
||||
fn add_int16(x: i16) -> i16;
|
||||
fn test_stack_spill(
|
||||
a: i32,
|
||||
b: i32,
|
||||
c: i32,
|
||||
d: i32,
|
||||
e: i32,
|
||||
f: i32,
|
||||
g: i32,
|
||||
h: i32,
|
||||
i: i32,
|
||||
j: i32,
|
||||
k: i32,
|
||||
l: i32,
|
||||
) -> i32;
|
||||
fn add_short_to_long(x: i16, y: i64) -> i64;
|
||||
fn get_unsigned_int() -> u32;
|
||||
fn printer();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
// test function that adds 2 to a provided int
|
||||
assert_eq!(add_one_int(1), 3);
|
||||
|
||||
// test function that takes the sum of its 12 arguments
|
||||
assert_eq!(test_stack_spill(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 78);
|
||||
|
||||
// test function that adds 3 to a 16 bit int
|
||||
assert_eq!(add_int16(-1i16), 2i16);
|
||||
|
||||
// test function that adds an i16 to an i64
|
||||
assert_eq!(add_short_to_long(-1i16, 123456789123i64), 123456789122i64);
|
||||
|
||||
// test function that returns -10 as an unsigned int
|
||||
assert_eq!(get_unsigned_int(), (-10i32) as u32);
|
||||
|
||||
// test void function that prints from C
|
||||
printer();
|
||||
}
|
||||
}
|
1
tests/extern-so/pass/call_extern_c_fcts.stdout
Normal file
1
tests/extern-so/pass/call_extern_c_fcts.stdout
Normal file
@ -0,0 +1 @@
|
||||
printing from C
|
27
tests/extern-so/test.c
Normal file
27
tests/extern-so/test.c
Normal file
@ -0,0 +1,27 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int add_one_int(int x) {
|
||||
return 2 + x;
|
||||
}
|
||||
|
||||
void printer() {
|
||||
printf("printing from C\n");
|
||||
}
|
||||
|
||||
// function with many arguments, to test functionality when some args are stored
|
||||
// on the stack
|
||||
int test_stack_spill(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l) {
|
||||
return a+b+c+d+e+f+g+h+i+j+k+l;
|
||||
}
|
||||
|
||||
unsigned int get_unsigned_int() {
|
||||
return -10;
|
||||
}
|
||||
|
||||
short add_int16(short x) {
|
||||
return x + 3;
|
||||
}
|
||||
|
||||
long add_short_to_long(short x, long y) {
|
||||
return x + y;
|
||||
}
|
Loading…
Reference in New Issue
Block a user