Skip to content

Commit acbf7f3

Browse files
committed
wip: hook up bits
1 parent 0913b24 commit acbf7f3

File tree

4 files changed

+125
-20
lines changed

4 files changed

+125
-20
lines changed

src/eval.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ impl Default for MiriConfig {
201201
report_progress: None,
202202
retag_fields: RetagFields::Yes,
203203
native_lib: vec![],
204-
native_lib_enable_tracing: false,
204+
native_lib_enable_tracing: true,
205205
gc_interval: 10_000,
206206
num_cpus: 1,
207207
page_size: None,

src/shims/native_lib/mod.rs

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,27 @@ pub struct MemEvents {
4040
/// A single memory access.
4141
#[allow(dead_code)]
4242
#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))]
43-
#[derive(Debug)]
43+
#[derive(Clone, Debug)]
4444
pub enum AccessEvent {
4545
/// A read occurred on this memory range.
4646
Read(AccessRange),
47-
/// A read occurred on this memory range.
47+
/// A read occurred on this memory range.
4848
Write(AccessRange),
4949
}
5050

51+
impl AccessEvent {
52+
fn get_range(&self) -> AccessRange {
53+
match self {
54+
AccessEvent::Read(access_range) => access_range.clone(),
55+
AccessEvent::Write(access_range) => access_range.clone(),
56+
}
57+
}
58+
59+
fn is_read(&self) -> bool {
60+
matches!(self, AccessEvent::Read(_))
61+
}
62+
}
63+
5164
/// The memory touched by a given access.
5265
#[allow(dead_code)]
5366
#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))]
@@ -198,6 +211,78 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
198211
}
199212
None
200213
}
214+
215+
/// Applies the `events` to Miri's internal state. The event vector must be
216+
/// ordered sequentially by when the accesses happened, and the sizes are
217+
/// assumed to be exact.
218+
fn tracing_apply_accesses(&mut self, events: MemEvents) -> InterpResult<'tcx> {
219+
let this = self.eval_context_mut();
220+
221+
// TODO: This could be optimised! If we first "compress" down the reads
222+
// and writes to discard redundant reads and writes and sort them by
223+
// address, we can take this from worst-case O(accesses * allocations)
224+
// to O(log(accesses) * allocations).
225+
for evt in events.acc_events {
226+
let mut todo = this.exposed_allocs();
227+
let mut done = rustc_data_structures::fx::FxHashSet::default();
228+
while let Some(alloc_id) = todo.pop() {
229+
if done.insert(alloc_id) {
230+
continue;
231+
}
232+
233+
let info = this.get_alloc_info(alloc_id);
234+
// If there is no data behind this pointer, skip this.
235+
if !matches!(info.kind, AllocKind::LiveData) {
236+
continue;
237+
}
238+
239+
// Get the (size, len) pair for the current allocation...
240+
let (alloc_addr, alloc_len) = {
241+
let alloc = this.get_alloc_raw(alloc_id)?;
242+
(alloc.get_bytes_unchecked_raw().addr(), alloc.len())
243+
};
244+
245+
// ...and for the current accesses, checking if they overlap.
246+
let rg = evt.get_range();
247+
if !(rg.addr <= alloc_addr.strict_add(alloc_len)
248+
&& alloc_addr <= rg.addr.strict_add(rg.size))
249+
{
250+
continue;
251+
}
252+
253+
// Shift the overlap range to be an offset from the allocation base addr.
254+
let unshifted_overlap = std::cmp::max(rg.addr, alloc_addr)
255+
..std::cmp::min(rg.addr.strict_add(rg.size), alloc_addr.strict_add(alloc_len));
256+
let overlap = unshifted_overlap.start.strict_sub(alloc_addr)
257+
..unshifted_overlap.end.strict_sub(alloc_addr);
258+
259+
if evt.is_read() {
260+
let alloc = this.get_alloc_raw(alloc_id)?;
261+
let p_map = alloc.provenance();
262+
for idx in overlap {
263+
// If a provenance was read by the foreign code, expose it and add it to the todo list.
264+
if let Some(prov) = p_map.get(Size::from_bytes(idx), this) {
265+
// Do this extra check since we get bytewise provenance,
266+
// so otherwise we risk inserting 4/8 copies of it per pointer.
267+
// TODO: Can freestanding bytes even have
268+
if let Some(prov_id) = prov.get_alloc_id()
269+
&& !todo.contains(&prov_id)
270+
&& !done.contains(&prov_id)
271+
{
272+
todo.push(prov_id);
273+
}
274+
this.expose_provenance(prov)?;
275+
}
276+
}
277+
} else {
278+
let (_alloc_mut, _m) = this.get_alloc_raw_mut(alloc_id)?;
279+
// TODO: expose a way to write wildcards on a given range and mark it as init
280+
}
281+
}
282+
}
283+
284+
interp_ok(())
285+
}
201286
}
202287

203288
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -223,6 +308,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
223308
}
224309
};
225310

311+
// Do we have ptrace?
312+
let tracing = trace::Supervisor::is_enabled();
313+
226314
// Get the function arguments, and convert them to `libffi`-compatible form.
227315
let mut libffi_args = Vec::<CArg>::with_capacity(args.len());
228316
for arg in args.iter() {
@@ -242,9 +330,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
242330
// The first time this happens, print a warning.
243331
if !this.machine.native_call_mem_warned.replace(true) {
244332
// Newly set, so first time we get here.
245-
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem {
246-
tracing: self::trace::Supervisor::is_enabled(),
247-
});
333+
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem { tracing });
248334
}
249335

250336
this.expose_provenance(prov)?;
@@ -270,15 +356,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
270356
// be read by FFI. The `black_box` is defensive programming as LLVM likes
271357
// to (incorrectly) optimize away ptr2int casts whose result is unused.
272358
std::hint::black_box(alloc.get_bytes_unchecked_raw().expose_provenance());
273-
// Expose all provenances in this allocation, since the native code can do $whatever.
274-
for prov in alloc.provenance().provenances() {
275-
this.expose_provenance(prov)?;
359+
360+
if !tracing {
361+
// Expose all provenances in this allocation, since the native code can do $whatever.
362+
for prov in alloc.provenance().provenances() {
363+
this.expose_provenance(prov)?;
364+
}
276365
}
277366

278367
// Prepare for possible write from native code if mutable.
279368
if info.mutbl.is_mut() {
280-
let alloc = &mut this.get_alloc_raw_mut(alloc_id)?.0;
281-
alloc.prepare_for_native_access();
369+
let alloc = this.get_alloc_raw_mut(alloc_id)?.0;
370+
if tracing {
371+
let full_range =
372+
AllocRange { start: Size::ZERO, size: Size::from_bytes(alloc.len()) };
373+
// Overwrite uninitialized bytes with 0, to ensure we don't leak whatever their value happens to be.
374+
for chunk in alloc.init_mask().clone().range_as_init_chunks(full_range) {
375+
if !chunk.is_init() {
376+
let uninit_bytes = unsafe {
377+
let start = chunk.range().start.bytes_usize();
378+
let len = chunk.range().end.bytes_usize().strict_sub(start);
379+
let ptr = alloc.get_bytes_unchecked_raw_mut().add(start);
380+
std::slice::from_raw_parts_mut(ptr, len)
381+
};
382+
uninit_bytes.fill(0);
383+
}
384+
}
385+
} else {
386+
// FIXME: Make this take an arg to determine whether it actually
387+
// writes wildcard prov & marks init, so we don't duplicate code above.
388+
alloc.prepare_for_native_access();
389+
}
282390
// Also expose *mutable* provenance for the interpreter-level allocation.
283391
std::hint::black_box(alloc.get_bytes_unchecked_raw_mut().expose_provenance());
284392
}
@@ -290,10 +398,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
290398
let (ret, maybe_memevents) =
291399
this.call_native_with_args(link_name, dest, code_ptr, libffi_args)?;
292400

293-
if cfg!(target_os = "linux")
294-
&& let Some(events) = maybe_memevents
295-
{
296-
trace!("Registered FFI events:\n{events:#0x?}");
401+
if tracing {
402+
this.tracing_apply_accesses(maybe_memevents.unwrap())?;
297403
}
298404

299405
this.write_immediate(*ret, dest)?;

src/shims/native_lib/trace/child.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@ pub unsafe fn init_sv() -> Result<(), SvInitError> {
202202
// The "Ok" case means that we couldn't ptrace.
203203
Ok(e) => return Err(e),
204204
Err(p) => {
205-
eprintln!("Supervisor process panicked!\n{p:?}\n\nTry running again without using the native-lib tracer.");
205+
eprintln!(
206+
"Supervisor process panicked!\n{p:?}\n\nTry running again without using the native-lib tracer."
207+
);
206208
std::process::exit(1);
207209
}
208210
}

src/shims/native_lib/trace/parent.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,7 @@ fn handle_segfault(
408408
match x86_operand.op_type {
409409
// We only care about memory accesses
410410
arch::x86::X86OperandType::Mem(_) => {
411-
let push = AccessRange {
412-
addr,
413-
size: x86_operand.size.into(),
414-
};
411+
let push = AccessRange { addr, size: x86_operand.size.into() };
415412
// It's called a "RegAccessType" but it also applies to memory
416413
let acc_ty = x86_operand.access.unwrap();
417414
if acc_ty.is_readable() {

0 commit comments

Comments
 (0)