Skip to content
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

Add API for capturing backtrace #1559

Merged
merged 12 commits into from
Sep 28, 2020
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,34 @@ 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 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 ()]>;

/// Miri-provided extern function to resolve a frame pointer obtained
/// from `miri_get_backtrace`. The `flags` argument must be `0`,
/// and `MiriFrame` should be declared as follows:
///
/// ```rust
/// #[repr(C)]
/// struct MiriFrame {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be repr(C)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use mplace_field to write to fields, which ends up using FieldsShape to map the definition-order fields to their actual offsets in memory.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so we exploit that we actually have access to the user-defined type... sneaky.^^

/// // 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 line number currently being executed in `filename`, starting from '1'.
/// lineno: u32,
/// // The column number currently being executed in `filename`, starting from '1'.
/// colno: u32,
/// }
/// ```
///
/// The fields must be declared in exactly the same order as they appear in `MiriFrame` above.
/// 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 begin unwinding with the given payload.
///
/// This is internal and unstable and should not be used; we give it here
Expand Down
121 changes: 121 additions & 0 deletions src/shims/backtrace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::*;
use helpers::check_arg_count;
use rustc_middle::ty::{self, TypeAndMut};
use rustc_ast::ast::Mutability;
use rustc_span::BytePos;
use rustc_target::abi::Size;
use std::convert::TryInto as _;
use crate::rustc_target::abi::LayoutOf 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(
&mut self,
args: &[OpTy<'tcx, Tag>],
dest: PlaceTy<'tcx, Tag>
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let tcx = this.tcx;
let &[flags] = check_arg_count(args)?;

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

let mut data = Vec::new();
for frame in this.active_thread_stack().iter().rev() {
data.push((frame.instance, frame.current_span().lo()));
}

let ptrs: Vec<_> = data.into_iter().map(|(instance, pos)| {
// We represent a frame pointer by using the `span.lo` value
// as an offset into the function's allocation. This gives us an
// opaque pointer that we can return to user code, and allows us
// to reconstruct the needed frame information in `handle_miri_resolve_frame`.
// Note that we never actually read or write anything from/to this pointer -
// all of the data is represented by the pointer value itself.
let mut fn_ptr = this.memory.create_fn_alloc(FnVal::Instance(instance));
fn_ptr.offset = Size::from_bytes(pos.0);
Scalar::Ptr(fn_ptr)
}).collect();

let len = ptrs.len();

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());

// 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_immediate_to_mplace(ptr.into(), place)?;
}

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

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

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

let ptr = match this.read_scalar(ptr)?.check_init()? {
Scalar::Ptr(ptr) => ptr,
Scalar::Raw { .. } => throw_ub_format!("expected a pointer in `rust_miri_resolve_frame`, found {:?}", ptr)
};

let fn_instance = if let Some(GlobalAlloc::Function(instance)) = this.tcx.get_global_alloc(ptr.alloc_id) {
instance
} else {
throw_ub_format!("expected function pointer, found {:?}", ptr);
};

if dest.layout.layout.fields.count() != 4 {
throw_ub_format!("bad declaration of miri_resolve_frame - should return a struct with 4 fields");
}

let pos = BytePos(ptr.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.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;

let name_alloc = this.allocate_str(&name, MiriMemoryKind::Rust.into());
let filename_alloc = this.allocate_str(&filename, MiriMemoryKind::Rust.into());
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() {
throw_ub_format!("miri_resolve_frame must be declared with a `#[repr(C)]` return type");
}
}

this.write_immediate(name_alloc.to_ref(), this.mplace_field(dest, 0)?.into())?;
this.write_immediate(filename_alloc.to_ref(), 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())?;
Ok(())
}
}
15 changes: 14 additions & 1 deletion src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ use std::{convert::{TryInto, TryFrom}, iter};
use log::trace;

use rustc_hir::def_id::DefId;
use rustc_middle::{mir, ty};
use rustc_middle::mir;
use rustc_target::{abi::{Align, Size}, spec::PanicStrategy};
use rustc_middle::ty;
use rustc_apfloat::Float;
use rustc_span::symbol::sym;

use crate::*;
use super::backtrace::EvalContextExt as _;
use helpers::check_arg_count;

impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
Expand Down Expand Up @@ -211,6 +213,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.machine.static_roots.push(ptr.alloc_id);
}

// Obtains a Miri backtrace. See the README for details.
"miri_get_backtrace" => {
Aaron1011 marked this conversation as resolved.
Show resolved Hide resolved
this.handle_miri_get_backtrace(args, dest)?;
}

// Resolves a Miri backtrace frame. See the README for details.
"miri_resolve_frame" => {
this.handle_miri_resolve_frame(args, dest)?;
}


// Standard C allocation
"malloc" => {
let &[size] = check_arg_count(args)?;
Expand Down
2 changes: 1 addition & 1 deletion src/shims/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

mod backtrace;
pub mod foreign_items;
pub mod intrinsics;
pub mod posix;
Expand Down
13 changes: 13 additions & 0 deletions tests/compile-fail/backtrace/bad-backtrace-decl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
extern "Rust" {
fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>;
fn miri_resolve_frame(ptr: *mut (), flags: u64);
}

fn main() {
let frames = unsafe { miri_get_backtrace(0) };
for frame in frames.into_iter() {
unsafe {
miri_resolve_frame(*frame, 0); //~ ERROR Undefined Behavior: bad declaration of miri_resolve_frame - should return a struct with 4 fields
}
}
}
9 changes: 9 additions & 0 deletions tests/compile-fail/backtrace/bad-backtrace-ptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extern "Rust" {
fn miri_resolve_frame(ptr: *mut (), flags: u64);
}

fn main() {
unsafe {
miri_resolve_frame(0 as *mut _, 0); //~ ERROR Undefined Behavior: expected a pointer
}
}
9 changes: 9 additions & 0 deletions tests/compile-fail/backtrace/bad-backtrace-version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extern "Rust" {
fn miri_resolve_frame(ptr: *mut (), flags: u64);
}

fn main() {
unsafe {
miri_resolve_frame(0 as *mut _, 1); //~ ERROR unsupported operation: unknown `miri_resolve_frame` flags 1
}
}
41 changes: 41 additions & 0 deletions tests/run-pass/backtrace-api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// normalize-stderr-test ".*rustlib" -> "RUSTLIB"
Aaron1011 marked this conversation as resolved.
Show resolved Hide resolved
// normalize-stderr-test "RUSTLIB/(.*):\d+:\d+ "-> "RUSTLIB/$1:LL:COL "
// normalize-stderr-test "::<.*>" -> ""

extern "Rust" {
fn miri_get_backtrace(flags: u64) -> Box<[*mut ()]>;
fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame;
}

#[derive(Debug)]
#[repr(C)]
struct MiriFrame {
name: Box<[u8]>,
filename: Box<[u8]>,
lineno: u32,
colno: u32
}

#[inline(never)] fn func_a() -> Box<[*mut ()]> { func_b::<u8>() }
#[inline(never)] fn func_b<T>() -> Box<[*mut ()]> { func_c() }
#[inline(never)] fn func_c() -> Box<[*mut ()]> { unsafe { miri_get_backtrace(0) } }

fn main() {
let mut seen_main = false;
let frames = func_a();
for frame in frames.into_iter() {
let miri_frame = unsafe { miri_resolve_frame(*frame, 0) };
let name = String::from_utf8(miri_frame.name.into()).unwrap();
let filename = String::from_utf8(miri_frame.filename.into()).unwrap();

// Print every frame to stderr.
let out = format!("{}:{}:{} ({})", filename, miri_frame.lineno, miri_frame.colno, name);
eprintln!("{}", out);
// Print the 'main' frame (and everything before it) to stdout, skipping
// the printing of internal (and possibly fragile) libstd frames.
if !seen_main {
println!("{}", out);
seen_main = name == "main";
}
}
}
13 changes: 13 additions & 0 deletions tests/run-pass/backtrace-api.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
$DIR/backtrace-api.rs:21:59 (func_c)
$DIR/backtrace-api.rs:20:53 (func_b)
$DIR/backtrace-api.rs:19:50 (func_a)
$DIR/backtrace-api.rs:25:18 (main)
RUSTLIB/src/rust/library/core/src/ops/function.rs:LL:COL (<fn() as std::ops::FnOnce<()>>::call_once - shim(fn()))
RUSTLIB/src/rust/library/std/src/sys_common/backtrace.rs:LL:COL (std::sys_common::backtrace::__rust_begin_short_backtrace)
RUSTLIB/src/rust/library/std/src/rt.rs:LL:COL (std::rt::lang_start::{closure#0})
RUSTLIB/src/rust/library/core/src/ops/function.rs:LL:COL (std::ops::function::impls::call_once)
RUSTLIB/src/rust/library/std/src/panicking.rs:LL:COL (std::panicking::r#try::do_call)
RUSTLIB/src/rust/library/std/src/panicking.rs:LL:COL (std::panicking::r#try)
RUSTLIB/src/rust/library/std/src/panic.rs:LL:COL (std::panic::catch_unwind)
RUSTLIB/src/rust/library/std/src/rt.rs:LL:COL (std::rt::lang_start_internal)
RUSTLIB/src/rust/library/std/src/rt.rs:LL:COL (std::rt::lang_start)
4 changes: 4 additions & 0 deletions tests/run-pass/backtrace-api.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$DIR/backtrace-api.rs:21:59 (func_c)
$DIR/backtrace-api.rs:20:53 (func_b::<u8>)
$DIR/backtrace-api.rs:19:50 (func_a)
$DIR/backtrace-api.rs:25:18 (main)