Skip to content

Commit 9e8e277

Browse files
committed
review 4: a new hope
1 parent f2eb0e3 commit 9e8e277

File tree

8 files changed

+106
-128
lines changed

8 files changed

+106
-128
lines changed

src/bin/miri.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
335335
fn exit(exit_code: i32) -> ! {
336336
// Drop the tracing guard before exiting, so tracing calls are flushed correctly.
337337
deinit_loggers();
338+
#[cfg(target_os = "linux")]
338339
miri::native_lib::register_retcode_sv(exit_code);
339340
std::process::exit(exit_code);
340341
}
@@ -752,6 +753,7 @@ fn main() {
752753
if !miri_config.native_lib.is_empty() && miri_config.native_lib_enable_tracing {
753754
// FIXME(ptrace): This should display a diagnostic / warning on error
754755
// SAFETY: No other threads are running
756+
#[cfg(target_os = "linux")]
755757
let _ = unsafe { miri::native_lib::init_sv() };
756758
}
757759
run_compiler_and_exit(

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub use rustc_const_eval::interpret::{self, AllocMap, Provenance as _};
9797
use rustc_middle::{bug, span_bug};
9898
use tracing::{info, trace};
9999

100+
#[cfg(target_os = "linux")]
100101
pub mod native_lib {
101102
pub use crate::shims::{init_sv, register_retcode_sv};
102103
}

src/shims/native_lib/mod.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,46 @@ pub mod trace;
2121

2222
use crate::*;
2323

24+
/// The final results of an FFI trace, containing every relevant event detected
25+
/// by the tracer. Sent by the supervisor after receiving a `SIGUSR1` signal.
26+
///
27+
/// The sender for this channel should live on the parent process.
28+
#[allow(dead_code)]
29+
#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))]
30+
#[derive(Debug)]
31+
pub struct MemEvents {
32+
/// An ordered list of memory accesses that occurred. These should be assumed
33+
/// to be overcautious; that is, if the size of an access is uncertain it is
34+
/// pessimistically rounded up, and if the type (read/write/both) is uncertain
35+
/// it is reported as whatever would be safest to assume; i.e. a read + maybe-write
36+
/// becomes a read + write, etc.
37+
pub acc_events: Vec<AccessEvent>,
38+
}
39+
40+
/// A single memory access.
41+
#[allow(dead_code)]
42+
#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))]
43+
#[derive(Debug)]
44+
pub enum AccessEvent {
45+
/// A read may have occurred on no more than the specified address range.
46+
Read(AccessRange),
47+
/// A write may have occurred on no more than the specified address range.
48+
Write(AccessRange),
49+
}
50+
51+
/// The actual size of a performed access, bounded by size.
52+
#[allow(dead_code)]
53+
#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))]
54+
#[derive(Clone, Debug)]
55+
pub struct AccessRange {
56+
/// The base address in memory where an access occurred.
57+
pub addr: usize,
58+
/// The smallest size this access could have been.
59+
pub min_size: usize,
60+
/// The largest size this access could have been.
61+
pub max_size: usize,
62+
}
63+
2464
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
2565
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
2666
/// Call native host function and return the output as an immediate.
@@ -30,7 +70,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
3070
dest: &MPlaceTy<'tcx>,
3171
ptr: CodePtr,
3272
libffi_args: Vec<libffi::high::Arg<'a>>,
33-
) -> trace::CallResult<'tcx> {
73+
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
3474
let this = self.eval_context_mut();
3575
#[cfg(target_os = "linux")]
3676
let alloc = this.machine.allocator.as_ref().unwrap();
@@ -108,7 +148,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
108148
interp_ok(ImmTy::from_scalar(scalar, dest.layout))
109149
};
110150

111-
trace::do_ffi(alloc, ffi_fn)
151+
trace::Supervisor::do_ffi(alloc, ffi_fn)
112152
}
113153

114154
/// Get the pointer to the function of the specified name in the shared object file,

src/shims/native_lib/trace/child.rs

Lines changed: 19 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ use std::rc::Rc;
44
use ipc_channel::ipc;
55
use nix::sys::{ptrace, signal};
66
use nix::unistd;
7+
use rustc_const_eval::interpret::InterpResult;
78

89
use super::CALLBACK_STACK_SIZE;
9-
use super::messages::{Confirmation, MemEvents, StartFfiInfo, TraceRequest};
10+
use super::messages::{Confirmation, StartFfiInfo, TraceRequest};
1011
use super::parent::{ChildListener, sv_loop};
1112
use crate::alloc::isolated_alloc::IsolatedAlloc;
13+
use crate::shims::native_lib::MemEvents;
1214

1315
/// A handle to the single, shared supervisor process across all `MiriMachine`s.
1416
/// Since it would be very difficult to trace multiple FFI calls in parallel, we
@@ -32,12 +34,6 @@ pub struct Supervisor {
3234
event_rx: ipc::IpcReceiver<MemEvents>,
3335
}
3436

35-
pub struct SvFfiGuard<'a> {
36-
alloc: &'a Rc<RefCell<IsolatedAlloc>>,
37-
sv_guard: std::sync::MutexGuard<'static, Option<Supervisor>>,
38-
cb_stack: Option<*mut [u8; CALLBACK_STACK_SIZE]>,
39-
}
40-
4137
/// Marker representing that an error occurred during creation of the supervisor.
4238
#[derive(Debug)]
4339
pub struct SvInitError;
@@ -48,25 +44,19 @@ impl Supervisor {
4844
SUPERVISOR.lock().unwrap().is_some()
4945
}
5046

51-
/// Begins preparations for doing an FFI call. This should be called at
52-
/// the last possible moment before entering said call. `alloc` points to
53-
/// the allocator which handed out the memory used for this machine.
54-
///
47+
/// Performs an arbitrary FFI call, enabling tracing from the supervisor.
5548
/// As this locks the supervisor via a mutex, no other threads may enter FFI
56-
/// until this one returns and its guard is dropped via `end_ffi`. The
57-
/// pointer returned should be passed to `end_ffi` to avoid a memory leak.
58-
///
59-
/// SAFETY: The resulting guard must be dropped *via `end_ffi`* immediately
60-
/// after the desired call has concluded.
61-
pub unsafe fn start_ffi(alloc: &Rc<RefCell<IsolatedAlloc>>) -> SvFfiGuard<'_> {
49+
/// until this function returns.
50+
pub fn do_ffi<'tcx>(
51+
alloc: &Rc<RefCell<IsolatedAlloc>>,
52+
f: impl FnOnce() -> InterpResult<'tcx, crate::ImmTy<'tcx>>,
53+
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
6254
let mut sv_guard = SUPERVISOR.lock().unwrap();
6355
// If the supervisor is not initialised for whatever reason, fast-return.
6456
// This might be desired behaviour, as even on platforms where ptracing
6557
// is not implemented it enables us to enforce that only one FFI call
6658
// happens at a time.
67-
let Some(sv) = sv_guard.as_mut() else {
68-
return SvFfiGuard { alloc, sv_guard, cb_stack: None };
69-
};
59+
let Some(sv) = sv_guard.as_mut() else { return f().map(|v| (v, None)) };
7060

7161
// Get pointers to all the pages the supervisor must allow accesses in
7262
// and prepare the callback stack.
@@ -97,21 +87,8 @@ impl Supervisor {
9787
// modifications to our memory - simply waiting on the recv() doesn't
9888
// count.
9989
signal::raise(signal::SIGSTOP).unwrap();
100-
SvFfiGuard { alloc, sv_guard, cb_stack: Some(raw_stack_ptr) }
101-
}
10290

103-
/// Undoes FFI-related preparations, allowing Miri to continue as normal, then
104-
/// gets the memory accesses and changes performed during the FFI call. Note
105-
/// that this may include some spurious accesses done by `libffi` itself in
106-
/// the process of executing the function call.
107-
///
108-
/// SAFETY: The `sv_guard` and `raw_stack_ptr` passed must be the same ones
109-
/// received by a prior call to `start_ffi`, and the allocator must be the
110-
/// one passed to it also.
111-
pub unsafe fn end_ffi(guard: SvFfiGuard<'_>) -> Option<MemEvents> {
112-
let alloc = guard.alloc;
113-
let mut sv_guard = guard.sv_guard;
114-
let cb_stack = guard.cb_stack;
91+
let res = f();
11592

11693
// We can't use IPC channels here to signal that FFI mode has ended,
11794
// since they might allocate memory which could get us stuck in a SIGTRAP
@@ -125,17 +102,14 @@ impl Supervisor {
125102
// This is safe! It just sets memory to normal expected permissions.
126103
alloc.borrow_mut().end_ffi();
127104

128-
// If this is `None`, then `raw_stack_ptr` is None and does not need to
129-
// be deallocated (and there's no need to worry about the guard, since
130-
// it contains nothing).
131-
let sv = sv_guard.as_mut()?;
132105
// SAFETY: Caller upholds that this pointer was allocated as a box with
133106
// this type.
134107
unsafe {
135-
drop(Box::from_raw(cb_stack.unwrap()));
108+
drop(Box::from_raw(raw_stack_ptr));
136109
}
137110
// On the off-chance something really weird happens, don't block forever.
138-
sv.event_rx
111+
let events = sv
112+
.event_rx
139113
.try_recv_timeout(std::time::Duration::from_secs(5))
140114
.map_err(|e| {
141115
match e {
@@ -144,14 +118,16 @@ impl Supervisor {
144118
panic!("Waiting for accesses from supervisor timed out!"),
145119
}
146120
})
147-
.ok()
121+
.ok();
122+
123+
res.map(|v| (v, events))
148124
}
149125
}
150126

151127
/// Initialises the supervisor process. If this function errors, then the
152128
/// supervisor process could not be created successfully; else, the caller
153-
/// is now the child process and can communicate via `start_ffi`/`end_ffi`,
154-
/// receiving back events through `get_events`.
129+
/// is now the child process and can communicate via `do_ffi`, receiving back
130+
/// events at the end.
155131
///
156132
/// # Safety
157133
/// The invariants for `fork()` must be upheld by the caller, namely either:

src/shims/native_lib/trace/messages.rs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@
1919
//!
2020
//! NB: sending these events out of order, skipping steps, etc. will result in
2121
//! unspecified behaviour from the supervisor process, so use the abstractions
22-
//! in `super::child` (namely `start_ffi()` and `end_ffi()`) to handle this. It is
22+
//! in `super::child` (namely `do_ffi()`) to handle this. It is
2323
//! trivially easy to cause a deadlock or crash by messing this up!
2424
25-
use std::ops::Range;
26-
2725
/// An IPC request sent by the child process to the parent.
2826
///
2927
/// The sender for this channel should live on the child process.
@@ -58,27 +56,3 @@ pub struct StartFfiInfo {
5856
/// The sender for this channel should live on the parent process.
5957
#[derive(serde::Serialize, serde::Deserialize, Debug)]
6058
pub struct Confirmation;
61-
62-
/// The final results of an FFI trace, containing every relevant event detected
63-
/// by the tracer. Sent by the supervisor after receiving a `SIGUSR1` signal.
64-
///
65-
/// The sender for this channel should live on the parent process.
66-
#[derive(serde::Serialize, serde::Deserialize, Debug)]
67-
pub struct MemEvents {
68-
/// An ordered list of memory accesses that occurred. These should be assumed
69-
/// to be overcautious; that is, if the size of an access is uncertain it is
70-
/// pessimistically rounded up, and if the type (read/write/both) is uncertain
71-
/// it is reported as whatever would be safest to assume; i.e. a read + maybe-write
72-
/// becomes a read + write, etc.
73-
pub acc_events: Vec<AccessEvent>,
74-
}
75-
76-
/// A single memory access, conservatively overestimated
77-
/// in case of ambiguity.
78-
#[derive(serde::Serialize, serde::Deserialize, Debug)]
79-
pub enum AccessEvent {
80-
/// A read may have occurred on no more than the specified address range.
81-
Read(Range<usize>),
82-
/// A write may have occurred on no more than the specified address range.
83-
Write(Range<usize>),
84-
}

src/shims/native_lib/trace/mod.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,9 @@ mod child;
22
pub mod messages;
33
mod parent;
44

5-
use std::cell::RefCell;
6-
use std::rc::Rc;
7-
8-
use rustc_const_eval::interpret::InterpResult;
9-
105
pub use self::child::{Supervisor, init_sv, register_retcode_sv};
11-
use crate::alloc::isolated_alloc::IsolatedAlloc;
126

137
/// The size of the temporary stack we use for callbacks that the server executes in the client.
148
/// This should be big enough that `mempr_on` and `mempr_off` can safely be jumped into with the
159
/// stack pointer pointing to a "stack" of this size without overflowing it.
1610
const CALLBACK_STACK_SIZE: usize = 1024;
17-
18-
/// Wrapper type for what we get back from an FFI call; the former is its actual
19-
/// return value, and the latter is the list of memory accesses that occurred during
20-
/// this call.
21-
pub type CallResult<'tcx> = InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<messages::MemEvents>)>;
22-
23-
/// Performs an arbitrary FFI call, enabling tracing from the supervisor.
24-
pub fn do_ffi<'tcx>(
25-
alloc: &Rc<RefCell<IsolatedAlloc>>,
26-
f: impl FnOnce() -> InterpResult<'tcx, crate::ImmTy<'tcx>>,
27-
) -> CallResult<'tcx> {
28-
// SAFETY: We don't touch the machine memory past this point.
29-
let guard = unsafe { Supervisor::start_ffi(alloc) };
30-
31-
f().map(|v| {
32-
let memevents = unsafe { Supervisor::end_ffi(guard) };
33-
(v, memevents)
34-
})
35-
}

0 commit comments

Comments
 (0)