native-lib: allow multiple libraries and/or dirs
This commit is contained in:
parent
c10a629224
commit
80a95b745e
7 changed files with 70 additions and 57 deletions
|
|
@ -398,9 +398,11 @@ to Miri failing to detect cases of undefined behavior in a program.
|
|||
**unsound** since the fallback body might not be checking for all UB.
|
||||
* `-Zmiri-native-lib=<path to a shared object file>` is an experimental flag for providing support
|
||||
for calling native functions from inside the interpreter via FFI. The flag is supported only on
|
||||
Unix systems. Functions not provided by that file are still executed via the usual Miri shims.
|
||||
Unix systems. Functions not provided by that file are still executed via the usual Miri shims. If
|
||||
a path to a directory is specified, all files in that directory are included nonrecursively. This
|
||||
flag can be passed multiple times to specify multiple files and/or directories.
|
||||
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause Undefined Behavior in
|
||||
Miri itself! And of course, Miri cannot do any checks on the actions taken by the native code.
|
||||
Miri itself! And of course, Miri often cannot do any checks on the actions taken by the native 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 and
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
assert!(!matches!(info.kind, AllocKind::Dead));
|
||||
|
||||
// This allocation does not have a base address yet, pick or reuse one.
|
||||
if this.machine.native_lib.is_some() {
|
||||
if !this.machine.native_lib.is_empty() {
|
||||
// In native lib mode, we use the "real" address of the bytes for this allocation.
|
||||
// This ensures the interpreted program and native code have the same view of memory.
|
||||
let params = this.machine.get_default_alloc_params();
|
||||
|
|
@ -413,7 +413,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
) -> InterpResult<'tcx, MiriAllocBytes> {
|
||||
let this = self.eval_context_ref();
|
||||
assert!(this.tcx.try_get_global_alloc(id).is_some());
|
||||
if this.machine.native_lib.is_some() {
|
||||
if !this.machine.native_lib.is_empty() {
|
||||
// In native lib mode, MiriAllocBytes for global allocations are handled via `prepared_alloc_bytes`.
|
||||
// This additional call ensures that some `MiriAllocBytes` are always prepared, just in case
|
||||
// this function gets called before the first time `addr_from_alloc_id` gets called.
|
||||
|
|
|
|||
|
|
@ -692,11 +692,18 @@ fn main() {
|
|||
};
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-native-lib=") {
|
||||
let filename = param.to_string();
|
||||
if std::path::Path::new(&filename).exists() {
|
||||
if let Some(other_filename) = miri_config.native_lib {
|
||||
show_error!("-Zmiri-native-lib is already set to {}", other_filename.display());
|
||||
let file_path = std::path::Path::new(&filename);
|
||||
if file_path.exists() {
|
||||
// For directories, nonrecursively add all normal files inside
|
||||
if let Ok(dir) = file_path.read_dir() {
|
||||
for lib in dir.filter_map(|res| res.ok()) {
|
||||
if lib.file_type().unwrap().is_file() {
|
||||
miri_config.native_lib.push(lib.path().to_owned());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
miri_config.native_lib.push(filename.into());
|
||||
}
|
||||
miri_config.native_lib = Some(filename.into());
|
||||
} else {
|
||||
show_error!("-Zmiri-native-lib `{}` does not exist", filename);
|
||||
}
|
||||
|
|
@ -731,12 +738,12 @@ fn main() {
|
|||
"Tree Borrows does not support integer-to-pointer casts, and hence requires strict provenance"
|
||||
);
|
||||
}
|
||||
if miri_config.native_lib.is_some() {
|
||||
if !miri_config.native_lib.is_empty() {
|
||||
show_error!("Tree Borrows is not compatible with calling native functions");
|
||||
}
|
||||
}
|
||||
// Native calls and strict provenance are not compatible.
|
||||
if miri_config.native_lib.is_some() && miri_config.provenance_mode == ProvenanceMode::Strict {
|
||||
if !miri_config.native_lib.is_empty() && miri_config.provenance_mode == ProvenanceMode::Strict {
|
||||
show_error!("strict provenance is not compatible with calling native functions");
|
||||
}
|
||||
// You can set either one seed or many.
|
||||
|
|
|
|||
|
|
@ -148,9 +148,8 @@ pub struct MiriConfig {
|
|||
pub report_progress: Option<u32>,
|
||||
/// Whether Stacked Borrows and Tree Borrows retagging should recurse into fields of datatypes.
|
||||
pub retag_fields: RetagFields,
|
||||
/// The location of a shared object file to load when calling external functions
|
||||
/// FIXME! consider allowing users to specify paths to multiple files, or to a directory
|
||||
pub native_lib: Option<PathBuf>,
|
||||
/// The location of the shared object files to load when calling external functions
|
||||
pub native_lib: Vec<PathBuf>,
|
||||
/// Run a garbage collector for BorTags every N basic blocks.
|
||||
pub gc_interval: u32,
|
||||
/// The number of CPUs to be reported by miri.
|
||||
|
|
@ -197,7 +196,7 @@ impl Default for MiriConfig {
|
|||
preemption_rate: 0.01, // 1%
|
||||
report_progress: None,
|
||||
retag_fields: RetagFields::Yes,
|
||||
native_lib: None,
|
||||
native_lib: vec![],
|
||||
gc_interval: 10_000,
|
||||
num_cpus: 1,
|
||||
page_size: None,
|
||||
|
|
|
|||
|
|
@ -558,9 +558,9 @@ pub struct MiriMachine<'tcx> {
|
|||
|
||||
/// Handle of the optional shared object file for native functions.
|
||||
#[cfg(unix)]
|
||||
pub native_lib: Option<(libloading::Library, std::path::PathBuf)>,
|
||||
pub native_lib: Vec<(libloading::Library, std::path::PathBuf)>,
|
||||
#[cfg(not(unix))]
|
||||
pub native_lib: Option<!>,
|
||||
pub native_lib: Vec<!>,
|
||||
|
||||
/// Run a garbage collector for BorTags every N basic blocks.
|
||||
pub(crate) gc_interval: u32,
|
||||
|
|
@ -720,7 +720,7 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
extern_statics: FxHashMap::default(),
|
||||
rng: RefCell::new(rng),
|
||||
#[cfg(target_os = "linux")]
|
||||
allocator: if config.native_lib.is_some() {
|
||||
allocator: if !config.native_lib.is_empty() {
|
||||
Some(Rc::new(RefCell::new(crate::alloc::isolated_alloc::IsolatedAlloc::new())))
|
||||
} else { None },
|
||||
tracked_alloc_ids: config.tracked_alloc_ids.clone(),
|
||||
|
|
@ -732,7 +732,7 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
basic_block_count: 0,
|
||||
monotonic_clock: MonotonicClock::new(config.isolated_op == IsolatedOp::Allow),
|
||||
#[cfg(unix)]
|
||||
native_lib: config.native_lib.as_ref().map(|lib_file_path| {
|
||||
native_lib: config.native_lib.iter().map(|lib_file_path| {
|
||||
let host_triple = rustc_session::config::host_tuple();
|
||||
let target_triple = tcx.sess.opts.target_triple.tuple();
|
||||
// Check if host target == the session target.
|
||||
|
|
@ -752,11 +752,11 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
},
|
||||
lib_file_path.clone(),
|
||||
)
|
||||
}),
|
||||
}).collect(),
|
||||
#[cfg(not(unix))]
|
||||
native_lib: config.native_lib.as_ref().map(|_| {
|
||||
native_lib: config.native_lib.iter().map(|_| {
|
||||
panic!("calling functions from native libraries via FFI is only supported on Unix")
|
||||
}),
|
||||
}).collect(),
|
||||
gc_interval: config.gc_interval,
|
||||
since_gc: 0,
|
||||
num_cpus: config.num_cpus,
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
|
||||
// First deal with any external C functions in linked .so file.
|
||||
#[cfg(unix)]
|
||||
if this.machine.native_lib.as_ref().is_some() {
|
||||
if !this.machine.native_lib.is_empty() {
|
||||
use crate::shims::native_lib::EvalContextExt as _;
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -87,47 +87,52 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
/// if it exists. The function must be in one of the shared object files specified:
|
||||
/// we do *not* return pointers to functions in dependencies of libraries.
|
||||
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.
|
||||
let (lib, lib_path) = this.machine.native_lib.as_ref().unwrap();
|
||||
let func: libloading::Symbol<'_, unsafe extern "C" fn()> =
|
||||
unsafe { lib.get(link_name.as_str().as_bytes()).ok()? };
|
||||
#[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`.
|
||||
let fn_ptr = *func.deref() as *mut std::ffi::c_void;
|
||||
// Try getting the function from one of the shared libraries.
|
||||
for (lib, lib_path) in &this.machine.native_lib {
|
||||
let Ok(func): Result<libloading::Symbol<'_, unsafe extern "C" fn()>, _> =
|
||||
(unsafe { lib.get(link_name.as_str().as_bytes()) })
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
#[expect(clippy::as_conversions)] // fn-ptr to raw-ptr cast needs `as`.
|
||||
let fn_ptr = *func.deref() as *mut std::ffi::c_void;
|
||||
|
||||
// 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.
|
||||
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed();
|
||||
unsafe {
|
||||
if libc::dladdr(fn_ptr, info.as_mut_ptr()) != 0 {
|
||||
let info = info.assume_init();
|
||||
#[cfg(target_os = "cygwin")]
|
||||
let fname_ptr = info.dli_fname.as_ptr();
|
||||
#[cfg(not(target_os = "cygwin"))]
|
||||
let fname_ptr = info.dli_fname;
|
||||
assert!(!fname_ptr.is_null());
|
||||
if std::ffi::CStr::from_ptr(fname_ptr).to_str().unwrap()
|
||||
!= lib_path.to_str().unwrap()
|
||||
{
|
||||
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.
|
||||
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed();
|
||||
unsafe {
|
||||
if libc::dladdr(fn_ptr, info.as_mut_ptr()) != 0 {
|
||||
let info = info.assume_init();
|
||||
#[cfg(target_os = "cygwin")]
|
||||
let fname_ptr = info.dli_fname.as_ptr();
|
||||
#[cfg(not(target_os = "cygwin"))]
|
||||
let fname_ptr = info.dli_fname;
|
||||
assert!(!fname_ptr.is_null());
|
||||
if std::ffi::CStr::from_ptr(fname_ptr).to_str().unwrap()
|
||||
!= lib_path.to_str().unwrap()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return a pointer to the function.
|
||||
Some(CodePtr(fn_ptr))
|
||||
// Return a pointer to the function.
|
||||
return Some(CodePtr(fn_ptr));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue