Skip to content
Merged
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
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,23 +358,28 @@ extern "Rust" {
/// `ptr` has to point to the beginning of an allocated block.
fn miri_static_root(ptr: *const u8);

// Miri-provided extern function to get the amount of frames in the current backtrace.
// The `flags` argument must be `0`.
fn miri_backtrace_size(flags: u64) -> usize;

/// Miri-provided extern function to obtain a backtrace of the current call stack.
/// This returns a boxed slice of pointers - each pointer is an opaque value
/// that is only useful when passed to `miri_resolve_frame`
/// The `flags` argument must be `0`.
fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>;
/// This writes a slice of pointers into `buf` - each pointer is an opaque value
/// that is only useful when passed to `miri_resolve_frame`.
/// `buf` must have `miri_backtrace_size(0) * pointer_size` bytes of space.
/// The `flags` argument must be `1`.
fn miri_get_backtrace(flags: u64, buf: *mut *mut ());

/// Miri-provided extern function to resolve a frame pointer obtained
/// from `miri_get_backtrace`. The `flags` argument must be `0`,
/// from `miri_get_backtrace`. The `flags` argument must be `1`,
/// and `MiriFrame` should be declared as follows:
///
/// ```rust
/// #[repr(C)]
/// struct MiriFrame {
/// // The name of the function being executed, encoded in UTF-8
/// name: Box<[u8]>,
/// // The filename of the function being executed, encoded in UTF-8
/// filename: Box<[u8]>,
/// // The size of the name of the function being executed, encoded in UTF-8
/// name_len: usize,
/// // The size of filename of the function being executed, encoded in UTF-8
/// filename_len: usize,
/// // The line number currently being executed in `filename`, starting from '1'.
/// lineno: u32,
/// // The column number currently being executed in `filename`, starting from '1'.
Expand All @@ -390,6 +395,11 @@ extern "Rust" {
/// This function can be called on any thread (not just the one which obtained `frame`).
fn miri_resolve_frame(frame: *mut (), flags: u64) -> MiriFrame;

/// Miri-provided extern function to get the name and filename of the frame provided by `miri_resolve_frame`.
/// `name_buf` and `filename_buf` should be allocated with the `name_len` and `filename_len` fields of `MiriFrame`.
/// The flags argument must be `0`.
fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);

/// Miri-provided extern function to begin unwinding with the given payload.
///
/// This is internal and unstable and should not be used; we give it here
Expand Down
200 changes: 148 additions & 52 deletions src/shims/backtrace.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
use crate::*;
use rustc_ast::ast::Mutability;
use rustc_middle::ty::layout::LayoutOf as _;
use rustc_middle::ty::{self, TypeAndMut};
use rustc_span::{BytePos, Symbol};
use rustc_middle::ty::{self, Instance, TypeAndMut};
use rustc_span::{BytePos, Loc, Symbol};
use rustc_target::{abi::Size, spec::abi::Abi};
use std::convert::TryInto as _;

impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
fn handle_miri_get_backtrace(
fn handle_miri_backtrace_size(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Tag>],
dest: &PlaceTy<'tcx, Tag>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let tcx = this.tcx;
let &[ref flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;

let flags = this.read_scalar(flags)?.to_u64()?;
if flags != 0 {
throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags);
throw_unsup_format!("unknown `miri_backtrace_size` flags {}", flags);
}

let frame_count = this.active_thread_stack().len();

this.write_scalar(Scalar::from_machine_usize(frame_count.try_into().unwrap(), this), dest)
}

fn handle_miri_get_backtrace(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Tag>],
dest: &PlaceTy<'tcx, Tag>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let tcx = this.tcx;

let flags = if let Some(flags_op) = args.get(0) {
this.read_scalar(flags_op)?.to_u64()?
} else {
throw_ub_format!("expected at least 1 argument")
};

let mut data = Vec::new();
for frame in this.active_thread_stack().iter().rev() {
let mut span = frame.current_span();
Expand All @@ -49,46 +69,60 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
})
.collect();

let len = ptrs.len();
let len: u64 = ptrs.len().try_into().unwrap();

let ptr_ty = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Mut });

let array_ty = tcx.mk_array(ptr_ty, ptrs.len().try_into().unwrap());
let array_layout = this.layout_of(tcx.mk_array(ptr_ty, len)).unwrap();

// Write pointers into array
let alloc =
this.allocate(this.layout_of(array_ty).unwrap(), MiriMemoryKind::Rust.into())?;
for (i, ptr) in ptrs.into_iter().enumerate() {
let place = this.mplace_index(&alloc, i as u64)?;
this.write_pointer(ptr, &place.into())?;
}
match flags {
// storage for pointers is allocated by miri
// deallocating the slice is undefined behavior with a custom global allocator
0 => {
let &[_flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;

let alloc = this.allocate(array_layout, MiriMemoryKind::Rust.into())?;

// Write pointers into array
for (i, ptr) in ptrs.into_iter().enumerate() {
let place = this.mplace_index(&alloc, i as u64)?;

this.write_pointer(ptr, &place.into())?;
}

this.write_immediate(
Immediate::new_slice(Scalar::from_maybe_pointer(alloc.ptr, this), len, this),
dest,
)?;
}
// storage for pointers is allocated by the caller
1 => {
let &[_flags, ref buf] = this.check_shim(abi, Abi::Rust, link_name, args)?;

let buf_place = this.deref_operand(buf)?;

let ptr_layout = this.layout_of(ptr_ty)?;

for (i, ptr) in ptrs.into_iter().enumerate() {
let offset = ptr_layout.size * i.try_into().unwrap();

let op_place =
buf_place.offset(offset, MemPlaceMeta::None, ptr_layout, this)?;

this.write_pointer(ptr, &op_place.into())?;
}
}
_ => throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags),
};

this.write_immediate(
Immediate::new_slice(
Scalar::from_maybe_pointer(alloc.ptr, this),
len.try_into().unwrap(),
this,
),
dest,
)?;
Ok(())
}

fn handle_miri_resolve_frame(
fn resolve_frame_pointer(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Tag>],
dest: &PlaceTy<'tcx, Tag>,
) -> InterpResult<'tcx> {
ptr: &OpTy<'tcx, Tag>,
) -> InterpResult<'tcx, (Instance<'tcx>, Loc, String, String)> {
let this = self.eval_context_mut();
let tcx = this.tcx;
let &[ref ptr, ref flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;

let flags = this.read_scalar(flags)?.to_u64()?;
if flags != 0 {
throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags);
}

let ptr = this.read_pointer(ptr)?;
// Take apart the pointer, we need its pieces.
Expand All @@ -101,6 +135,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
throw_ub_format!("expected function pointer, found {:?}", ptr);
};

let lo =
this.tcx.sess.source_map().lookup_char_pos(BytePos(offset.bytes().try_into().unwrap()));

let name = fn_instance.to_string();
let filename = lo.file.name.prefer_remapped().to_string();

Ok((fn_instance, lo, name, filename))
}

fn handle_miri_resolve_frame(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Tag>],
dest: &PlaceTy<'tcx, Tag>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let &[ref ptr, ref flags] = this.check_shim(abi, Abi::Rust, link_name, args)?;

let flags = this.read_scalar(flags)?.to_u64()?;

let (fn_instance, lo, name, filename) = this.resolve_frame_pointer(ptr)?;

// Reconstruct the original function pointer,
// which we pass to user code.
let fn_ptr = this.memory.create_fn_alloc(FnVal::Instance(fn_instance));
Expand All @@ -115,23 +172,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
);
}

let pos = BytePos(offset.bytes().try_into().unwrap());
let name = fn_instance.to_string();

let lo = tcx.sess.source_map().lookup_char_pos(pos);

let filename = lo.file.name.prefer_remapped().to_string();
let lineno: u32 = lo.line as u32;
// `lo.col` is 0-based - add 1 to make it 1-based for the caller.
let colno: u32 = lo.col.0 as u32 + 1;

// These are "mutable" allocations as we consider them to be owned by the callee.
let name_alloc = this.allocate_str(&name, MiriMemoryKind::Rust.into(), Mutability::Mut);
let filename_alloc =
this.allocate_str(&filename, MiriMemoryKind::Rust.into(), Mutability::Mut);
let lineno_alloc = Scalar::from_u32(lineno);
let colno_alloc = Scalar::from_u32(colno);

let dest = this.force_allocation(dest)?;
if let ty::Adt(adt, _) = dest.layout.ty.kind() {
if !adt.repr.c() {
Expand All @@ -141,10 +185,38 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
}
}

this.write_immediate(name_alloc.to_ref(this), &this.mplace_field(&dest, 0)?.into())?;
this.write_immediate(filename_alloc.to_ref(this), &this.mplace_field(&dest, 1)?.into())?;
this.write_scalar(lineno_alloc, &this.mplace_field(&dest, 2)?.into())?;
this.write_scalar(colno_alloc, &this.mplace_field(&dest, 3)?.into())?;
match flags {
0 => {
// These are "mutable" allocations as we consider them to be owned by the callee.
let name_alloc =
this.allocate_str(&name, MiriMemoryKind::Rust.into(), Mutability::Mut);
let filename_alloc =
this.allocate_str(&filename, MiriMemoryKind::Rust.into(), Mutability::Mut);

this.write_immediate(
name_alloc.to_ref(this),
&this.mplace_field(&dest, 0)?.into(),
)?;
this.write_immediate(
filename_alloc.to_ref(this),
&this.mplace_field(&dest, 1)?.into(),
)?;
}
1 => {
this.write_scalar(
Scalar::from_machine_usize(name.len().try_into().unwrap(), this),
&this.mplace_field(&dest, 0)?.into(),
)?;
this.write_scalar(
Scalar::from_machine_usize(filename.len().try_into().unwrap(), this),
&this.mplace_field(&dest, 1)?.into(),
)?;
}
_ => throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags),
}

this.write_scalar(Scalar::from_u32(lineno), &this.mplace_field(&dest, 2)?.into())?;
this.write_scalar(Scalar::from_u32(colno), &this.mplace_field(&dest, 3)?.into())?;

// Support a 4-field struct for now - this is deprecated
// and slated for removal.
Expand All @@ -154,4 +226,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx

Ok(())
}

fn handle_miri_resolve_frame_names(
&mut self,
abi: Abi,
link_name: Symbol,
args: &[OpTy<'tcx, Tag>],
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();

let &[ref ptr, ref flags, ref name_ptr, ref filename_ptr] =
this.check_shim(abi, Abi::Rust, link_name, args)?;

let flags = this.read_scalar(flags)?.to_u64()?;
if flags != 0 {
throw_unsup_format!("unknown `miri_resolve_frame_names` flags {}", flags);
}

let (_, _, name, filename) = this.resolve_frame_pointer(ptr)?;

this.memory.write_bytes(this.read_pointer(name_ptr)?, name.bytes())?;
this.memory.write_bytes(this.read_pointer(filename_ptr)?, filename.bytes())?;

Ok(())
}
}
9 changes: 9 additions & 0 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.machine.static_roots.push(alloc_id);
}

// Obtains the size of a Miri backtrace. See the README for details.
"miri_backtrace_size" => {
this.handle_miri_backtrace_size(abi, link_name, args, dest)?;
}

// Obtains a Miri backtrace. See the README for details.
"miri_get_backtrace" => {
// `check_shim` happens inside `handle_miri_get_backtrace`.
Expand All @@ -392,6 +397,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.handle_miri_resolve_frame(abi, link_name, args, dest)?;
}

// Writes the function and file names of a Miri backtrace frame into a user provided buffer. See the README for details.
"miri_resolve_frame_names" => {
this.handle_miri_resolve_frame_names(abi, link_name, args)?;
}

// Standard C allocation
"malloc" => {
Expand Down
9 changes: 9 additions & 0 deletions tests/compile-fail/backtrace/bad-backtrace-flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extern "Rust" {
fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
}

fn main() {
unsafe {
miri_get_backtrace(2, 0 as *mut _); //~ ERROR unsupported operation: unknown `miri_get_backtrace` flags 2
}
}
25 changes: 25 additions & 0 deletions tests/compile-fail/backtrace/bad-backtrace-resolve-flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[repr(C)]
struct MiriFrame {
name_len: usize,
filename_len: usize,
lineno: u32,
colno: u32,
fn_ptr: *mut (),
}

extern "Rust" {
fn miri_backtrace_size(flags: u64) -> usize;
fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame;
}

fn main() {
unsafe {
let mut buf = vec![0 as *mut _; miri_backtrace_size(0)];

miri_get_backtrace(1, buf.as_mut_ptr());

// miri_resolve_frame will error from an invalid backtrace before it will from invalid flags
miri_resolve_frame(buf[0], 2); //~ ERROR unsupported operation: unknown `miri_resolve_frame` flags 2
}
}
16 changes: 16 additions & 0 deletions tests/compile-fail/backtrace/bad-backtrace-resolve-names-flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
extern "Rust" {
fn miri_backtrace_size(flags: u64) -> usize;
fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);
}

fn main() {
unsafe {
let mut buf = vec![0 as *mut _; miri_backtrace_size(0)];

miri_get_backtrace(1, buf.as_mut_ptr());

// miri_resolve_frame_names will error from an invalid backtrace before it will from invalid flags
miri_resolve_frame_names(buf[0], 2, 0 as *mut _, 0 as *mut _); //~ ERROR unsupported operation: unknown `miri_resolve_frame_names` flags 2
}
}
Loading