Skip to content

[WIP] Allow passing const pointers to FFI in Miri #110099

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,11 @@ impl<'tcx, 'a, Prov: Provenance, Extra, Bytes: AllocBytes> AllocRef<'a, 'tcx, Pr
pub(crate) fn has_provenance(&self) -> bool {
!self.alloc.provenance().range_empty(self.range, &self.tcx)
}

/// Get the base address of the backing allocation
pub fn base_addr(&self) -> *const u8 {
self.alloc.base_addr()
}
}

impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Expand Down
8 changes: 4 additions & 4 deletions src/tools/miri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ log = "0.4"
rand = "0.8"
smallvec = "1.7"

libffi = "3.2.0"
libloading = "0.7"

# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
# for more information.
Expand All @@ -33,11 +36,8 @@ measureme = "10.0.0"
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[target.'cfg(target_os = "linux")'.dependencies]
libffi = "3.2.0"
libloading = "0.7"

[dev-dependencies]
cc = "1.0"
colored = "2"
ui_test = "0.5"
rustc_version = "0.4"
Expand Down
73 changes: 73 additions & 0 deletions src/tools/miri/src/aligned_bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::borrow::Cow;
use std::alloc::{Allocator, Global, Layout};
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use rustc_abi::{Size, Align};
use rustc_middle::mir::interpret::AllocBytes;

enum FillBytes<'a> {
Bytes(&'a [u8]),
Zero(Size),
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AlignedBytes(NonNull<[u8]>, Align);

impl AlignedBytes {
fn alloc(fill: FillBytes<'_>, align: Align) -> Option<Self> {
let len = match fill {
FillBytes::Bytes(b) => b.len(),
FillBytes::Zero(s) => s.bytes() as usize,
};

let layout = Layout::from_size_align(len, align.bytes() as usize)
.unwrap();
let mut bytes = Global.allocate_zeroed(layout)
.ok()?;

let slice = unsafe { bytes.as_mut() };
match fill {
FillBytes::Bytes(data) => slice.copy_from_slice(data),
FillBytes::Zero(_) => (),
}

debug_assert_eq!(bytes.as_ptr().cast::<()>() as usize % align.bytes() as usize, 0);

Some(Self(bytes, align))
}
}

impl Deref for AlignedBytes {
type Target = [u8];

fn deref(&self) -> &Self::Target {
unsafe { self.0.as_ref() }
}
}

impl DerefMut for AlignedBytes {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.0.as_mut() }
}
}

impl AllocBytes for AlignedBytes {
fn adjust_to_align(self, align: Align) -> Self {
if self.1 >= align {
self
} else {
let out = Self::alloc(FillBytes::Bytes(&*self), align)
.unwrap();
out
}
}

fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, align: Align) -> Self {
Self::alloc(FillBytes::Bytes(&*slice.into()), align)
.unwrap()
}

fn zeroed(size: Size, align: Align) -> Option<Self> {
Self::alloc(FillBytes::Zero(size), align)
}
}
3 changes: 3 additions & 0 deletions src/tools/miri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#![feature(local_key_cell_methods)]
#![feature(is_terminal)]
#![feature(round_ties_even)]
#![feature(allocator_api)]
// Configure clippy and other lints
#![allow(
clippy::collapsible_else_if,
Expand Down Expand Up @@ -43,6 +44,7 @@
#![recursion_limit = "256"]

extern crate rustc_apfloat;
extern crate rustc_abi;
extern crate rustc_ast;
#[macro_use]
extern crate rustc_middle;
Expand Down Expand Up @@ -73,6 +75,7 @@ mod operator;
mod range_map;
mod shims;
mod tag_gc;
mod aligned_bytes;

// Establish a "crate-wide prelude": we often import `crate::*`.

Expand Down
8 changes: 4 additions & 4 deletions src/tools/miri/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,9 @@ pub struct MiriMachine<'mir, 'tcx> {
pub(crate) basic_block_count: u64,

/// Handle of the optional shared object file for external functions.
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub external_so_lib: Option<(libloading::Library, std::path::PathBuf)>,
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
pub external_so_lib: Option<!>,

/// Run a garbage collector for BorTags every N basic blocks.
Expand Down Expand Up @@ -549,7 +549,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
report_progress: config.report_progress,
basic_block_count: 0,
clock: Clock::new(config.isolated_op == IsolatedOp::Allow),
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "windows"))]
external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| {
let target_triple = layout_cx.tcx.sess.opts.target_triple.triple();
// Check if host target == the session target.
Expand All @@ -571,7 +571,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
lib_file_path.clone(),
)
}),
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
external_so_lib: config.external_so_file.as_ref().map(|_| {
panic!("loading external .so files is only supported on Linux")
}),
Expand Down
128 changes: 90 additions & 38 deletions src/tools/miri/src/shims/ffi_support.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
use std::ffi::c_void;
use libffi::{high::call as ffi, low::CodePtr};
use std::ops::Deref;
use std::path::Path;

use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
use rustc_middle::ty::{self as ty, IntTy, UintTy};
use rustc_span::Symbol;
use rustc_target::abi::HasDataLayout;
use rustc_middle::ty::layout::{HasParamEnv, TyAndLayout};

use crate::*;
use crate::intptrcast::GlobalStateInner;

impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}

pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'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(
&mut self,
k: Scalar<Provenance>,
arg_type: Ty<'tcx>,
cx: &impl HasDataLayout,
arg_type: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, CArg> {
match arg_type.kind() {
let this = self.eval_context_mut();

match arg_type.ty.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
Expand All @@ -36,7 +41,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
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_target_isize(cx)?.try_into().unwrap()));
return Ok(CArg::ISize(k.to_target_isize(this)?.try_into().unwrap()));
}
// the uints
ty::Uint(UintTy::U8) => {
Expand All @@ -54,7 +59,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
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_target_usize(cx)?.try_into().unwrap()));
return Ok(CArg::USize(k.to_target_usize(this)?.try_into().unwrap()));
}
// pointers
ty::RawPtr(ty::TypeAndMut { mutbl, ty }) if mutbl.is_not() => {
// FIXME: Add warning for types that are probably not C-portable
// FIXME: This should
// - expose the pointer
if let Scalar::Ptr(ptr, _) = k {
if let Provenance::Concrete { alloc_id, tag } = ptr.provenance {
GlobalStateInner::expose_ptr(
this,
alloc_id,
tag,
)?;
}

let pointee = this.tcx.layout_of(this.param_env().and(*ty))
.unwrap();
let alloc = this.get_ptr_alloc(
ptr.into(),
pointee.layout.size(),
pointee.layout.align().abi,
)?;

// If none, the pointee is a ZST, so we can just magic up an address for it
let addr = alloc.map(|a| a.base_addr())
.unwrap_or_else(|| arg_type.layout.align().abi.bytes() as usize as *const _);
return Ok(CArg::Ptr(addr as *const c_void));
}
}
_ => {}
}
Expand Down Expand Up @@ -154,14 +187,50 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}

/// Check whether a loaded symbol is correct to use - on linux this ensures it comes from
/// the expected so, on windows this always returns true currently because lookup doesn't check
/// dependencies.
fn verify_sym(_func: &libloading::Symbol<'_, unsafe extern "C" fn()>, _lib_path: &Path) -> bool {
#[cfg(unix)]
{
// 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.
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
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 false;
}
}
}
return true;
}

#[cfg(windows)]
{
return true;
}
}

/// 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 (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,
Expand All @@ -171,30 +240,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
};

// 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.
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
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;
}
}
if Self::verify_sym(&func, lib_path) {
// Return a pointer to the function.
Some(CodePtr(*func.deref() as *mut _))
} else {
None
}
// Return a pointer to the function.
Some(CodePtr(*func.deref() as *mut _))
}

/// Call specified external C function, with supplied arguments.
Expand All @@ -217,15 +268,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
};

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,
let scalar = self.eval_context_mut().read_scalar(cur_arg)?;
libffi_args.push(self.scalar_to_carg(
scalar,
cur_arg.layout,
)?);
}

Expand Down Expand Up @@ -268,6 +317,8 @@ pub enum CArg {
UInt64(u64),
/// usize.
USize(usize),
/// A pointer to a value
Ptr(*const c_void),
}

impl<'a> CArg {
Expand All @@ -284,6 +335,7 @@ impl<'a> CArg {
CArg::UInt32(i) => ffi::arg(i),
CArg::UInt64(i) => ffi::arg(i),
CArg::USize(i) => ffi::arg(i),
CArg::Ptr(i) => ffi::arg(i),
}
}
}
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let this = self.eval_context_mut();

// First deal with any external C functions in linked .so file.
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "windows"))]
if this.machine.external_so_lib.as_ref().is_some() {
use crate::shims::ffi_support::EvalContextExt as _;
// An Ok(false) here means that the function being called was not exported
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![warn(clippy::integer_arithmetic)]

mod backtrace;
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub mod ffi_support;
pub mod foreign_items;
pub mod intrinsics;
Expand Down
Loading