Backtrace WebAssembly function JIT frames (#759)
* Create backtrace * Extend unwind information with FDE data. * Expose backtrace via API/Trap * wasmtime_call returns not-str * Return Arc<JITFrameTag> * rename frame -> function * Fix windows crashes and unwrap UNWIND_HISTORY_TABLE * mmaps -> entries * pass a backtrace in ActionOutcome * add test_trap_stack_overflow * Update cranelift version.
This commit is contained in:
42
Cargo.lock
generated
42
Cargo.lock
generated
@@ -341,24 +341,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-bforest"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd3225fff1be118941c5fb66f1fb1f7f3e86468fac0e7364713c4fb99b72632b"
|
||||
checksum = "40af6e6f7419110906d0f7b4b8084d3216be64d7da77aa12887885ebe0fc2776"
|
||||
dependencies = [
|
||||
"cranelift-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3e3e6679892029f76a99b9059d2b74e77ac03637d573bb014bc21579ec1b7da"
|
||||
checksum = "88583eb22e5cd0fe1ef66f0b80d0063d21d30e84e887d08b3d369def3ea7b4be"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cranelift-bforest",
|
||||
"cranelift-codegen-meta",
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-entity",
|
||||
"gimli",
|
||||
"log",
|
||||
"serde",
|
||||
"smallvec",
|
||||
@@ -368,9 +369,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-meta"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cabe691548e28ca82ebd218f2fe76eec4c5629b64ef3db8b58443b7d9047275"
|
||||
checksum = "353872c984943b9134d7c835eb9d12932bd90f4992dbe666593771bee920d673"
|
||||
dependencies = [
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-entity",
|
||||
@@ -378,24 +379,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-shared"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d173252ffade4aa6e929090977b9a4cd5ac28e15a42626f878be3844b3000ad9"
|
||||
checksum = "cd1e96479a56981cce5c8f14d26773195d662ccdbbeca39fb8eba22b5ca8ea6a"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-entity"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3498ad9ba021715716a1c52e2b31d7829a149913fb0d88493e4b07d3b772b656"
|
||||
checksum = "37bc8dc3d4274ededc687d84821f491a8a03447dbb7481983936220892cf55b4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-frontend"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521a30773b8de74345c807a38853f055aca8fecaa39a0fc7698bfebc5a3da515"
|
||||
checksum = "4ea33e55bda8c425f3f533355b03e3a43cf29b4e228b35b868b6a1c43b6a139e"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"log",
|
||||
@@ -405,9 +406,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-native"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624e755cbe984e437308968239736e7f9aa3193e99928fb76eec7a1946627b34"
|
||||
checksum = "2c22cfaaa5e69eddaddd6cfa7a76233de964b9b2245e81a5f47dae739931ad0d"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"raw-cpuid",
|
||||
@@ -416,9 +417,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-wasm"
|
||||
version = "0.54.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0320733e518ab9e0e2d1a22034d40e2851fb403ed14db5220cf9b86576b9cfd4"
|
||||
checksum = "038f8fd636abc83ccd6f110e0891766521c3599b52173c0f37e61c684f19a15d"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
@@ -426,7 +427,7 @@ dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"wasmparser 0.45.2",
|
||||
"wasmparser 0.47.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1964,12 +1965,6 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1527c84a5bd585215f29c06b0e2a5274e478ad4dfc970d26ffad66fdc6cb311d"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.45.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b4eab1d9971d0803729cba3617b56eb04fcb4bd25361cb63880ed41a42f20d5"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.47.0"
|
||||
@@ -2188,6 +2183,7 @@ dependencies = [
|
||||
name = "wasmtime-runtime"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"indexmap",
|
||||
|
||||
@@ -147,7 +147,7 @@ impl WrappedCallable for WasmtimeFn {
|
||||
.map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?;
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
if let Err(error) = unsafe {
|
||||
self.instance.with_signals_on(|| {
|
||||
wasmtime_runtime::wasmtime_call_trampoline(
|
||||
vmctx,
|
||||
@@ -156,8 +156,12 @@ impl WrappedCallable for WasmtimeFn {
|
||||
)
|
||||
})
|
||||
} {
|
||||
let trap =
|
||||
take_api_trap().unwrap_or_else(|| Trap::new(format!("call error: {}", message)));
|
||||
let message = error.0;
|
||||
let backtrace = error.1;
|
||||
|
||||
let trap = take_api_trap().unwrap_or_else(|| {
|
||||
Trap::new_with_trace(format!("call error: {}", message), backtrace)
|
||||
});
|
||||
return Err(trap);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap};
|
||||
use wasmtime_environ::ir::types;
|
||||
use wasmtime_environ::isa::TargetIsa;
|
||||
use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex};
|
||||
use wasmtime_environ::{ir, settings, CompiledFunction, Export, Module, TrapInformation};
|
||||
use wasmtime_environ::{
|
||||
ir, settings, CompiledFunction, CompiledFunctionUnwindInfo, Export, Module, TrapInformation,
|
||||
};
|
||||
use wasmtime_jit::trampoline::ir::{
|
||||
ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind,
|
||||
};
|
||||
@@ -136,6 +138,7 @@ fn make_trampoline(
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone());
|
||||
context.func.collect_frame_layout_info();
|
||||
|
||||
let ss = context.func.create_stack_slot(StackSlotData::new(
|
||||
StackSlotKind::ExplicitSlot,
|
||||
@@ -213,8 +216,7 @@ fn make_trampoline(
|
||||
.map_err(|error| pretty_error(&context.func, Some(isa), error))
|
||||
.expect("compile_and_emit");
|
||||
|
||||
let mut unwind_info = Vec::new();
|
||||
context.emit_unwind_info(isa, &mut unwind_info);
|
||||
let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context);
|
||||
|
||||
let traps = trap_sink.traps;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::instance::Instance;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_runtime::{get_backtrace, Backtrace, BacktraceFrame};
|
||||
|
||||
/// A struct representing an aborted instruction execution, with a message
|
||||
/// indicating the cause.
|
||||
@@ -26,10 +27,21 @@ impl Trap {
|
||||
/// assert_eq!("unexpected error", trap.message());
|
||||
/// ```
|
||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
||||
Self::new_with_trace(message, get_backtrace())
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_trace<I: Into<String>>(message: I, backtrace: Backtrace) -> Self {
|
||||
let mut trace = Vec::with_capacity(backtrace.len());
|
||||
for i in 0..backtrace.len() {
|
||||
// Don't include frames without backtrace info.
|
||||
if let Some(info) = FrameInfo::try_from(backtrace[i]) {
|
||||
trace.push(info);
|
||||
}
|
||||
}
|
||||
Trap {
|
||||
inner: Arc::new(TrapInner {
|
||||
message: message.into(),
|
||||
trace: Vec::new(),
|
||||
trace,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -62,22 +74,42 @@ impl fmt::Display for Trap {
|
||||
impl std::error::Error for Trap {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FrameInfo;
|
||||
pub struct FrameInfo {
|
||||
module_name: Option<String>,
|
||||
func_index: u32,
|
||||
}
|
||||
|
||||
impl FrameInfo {
|
||||
pub fn instance(&self) -> *const Instance {
|
||||
unimplemented!("FrameInfo::instance");
|
||||
}
|
||||
|
||||
pub fn func_index() -> usize {
|
||||
unimplemented!("FrameInfo::func_index");
|
||||
pub fn func_index(&self) -> u32 {
|
||||
self.func_index
|
||||
}
|
||||
|
||||
pub fn func_offset() -> usize {
|
||||
pub fn func_offset(&self) -> usize {
|
||||
unimplemented!("FrameInfo::func_offset");
|
||||
}
|
||||
|
||||
pub fn module_offset() -> usize {
|
||||
pub fn module_offset(&self) -> usize {
|
||||
unimplemented!("FrameInfo::module_offset");
|
||||
}
|
||||
|
||||
pub fn module_name(&self) -> Option<&str> {
|
||||
self.module_name.as_deref()
|
||||
}
|
||||
|
||||
pub(crate) fn try_from(backtrace: BacktraceFrame) -> Option<FrameInfo> {
|
||||
if let Some(tag) = backtrace.tag() {
|
||||
let func_index = tag.func_index as u32;
|
||||
let module_name = tag.module_id.clone();
|
||||
Some(FrameInfo {
|
||||
func_index,
|
||||
module_name,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,3 +41,116 @@ fn test_trap_return() -> Result<(), String> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_trace() -> Result<(), String> {
|
||||
let store = Store::default();
|
||||
let binary = parse_str(
|
||||
r#"
|
||||
(module $hello_mod
|
||||
(func (export "run") (call $hello))
|
||||
(func $hello (unreachable))
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?;
|
||||
|
||||
let module =
|
||||
Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?;
|
||||
let instance = Instance::new(&module, &[])
|
||||
.map_err(|e| format!("failed to instantiate module: {:?}", e))?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
|
||||
let trace = e.trace();
|
||||
assert_eq!(trace.len(), 2);
|
||||
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[0].func_index(), 1);
|
||||
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[1].func_index(), 0);
|
||||
assert!(e.message().contains("unreachable"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_trace_cb() -> Result<(), String> {
|
||||
struct ThrowCallback;
|
||||
|
||||
impl Callable for ThrowCallback {
|
||||
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
|
||||
Err(Trap::new("cb throw"))
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store::default();
|
||||
let binary = parse_str(
|
||||
r#"
|
||||
(module $hello_mod
|
||||
(import "" "throw" (func $throw))
|
||||
(func (export "run") (call $hello))
|
||||
(func $hello (call $throw))
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?;
|
||||
|
||||
let fn_type = FuncType::new(Box::new([]), Box::new([]));
|
||||
let fn_func = Func::new(&store, fn_type, Rc::new(ThrowCallback));
|
||||
|
||||
let module =
|
||||
Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?;
|
||||
let instance = Instance::new(&module, &[fn_func.into()])
|
||||
.map_err(|e| format!("failed to instantiate module: {:?}", e))?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
|
||||
let trace = e.trace();
|
||||
assert_eq!(trace.len(), 2);
|
||||
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[0].func_index(), 1);
|
||||
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
|
||||
assert_eq!(trace[1].func_index(), 0);
|
||||
assert_eq!(e.message(), "cb throw");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_stack_overflow() -> Result<(), String> {
|
||||
let store = Store::default();
|
||||
let binary = parse_str(
|
||||
r#"
|
||||
(module $rec_mod
|
||||
(func $run (export "run") (call $run))
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?;
|
||||
|
||||
let module =
|
||||
Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?;
|
||||
let instance = Instance::new(&module, &[])
|
||||
.map_err(|e| format!("failed to instantiate module: {:?}", e))?;
|
||||
let run_func = instance.exports()[0]
|
||||
.func()
|
||||
.expect("expected function export");
|
||||
|
||||
let e = run_func.call(&[]).err().expect("error calling function");
|
||||
|
||||
let trace = e.trace();
|
||||
assert!(trace.len() >= 32);
|
||||
for i in 0..trace.len() {
|
||||
assert_eq!(trace[i].module_name().unwrap(), "rec_mod");
|
||||
assert_eq!(trace[i].func_index(), 0);
|
||||
}
|
||||
assert!(e.message().contains("call stack exhausted"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||
wasmparser = "0.47.0"
|
||||
lightbeam = { path = "../lightbeam", optional = true, version = "0.9.0" }
|
||||
indexmap = "1.0.2"
|
||||
@@ -46,7 +46,7 @@ tempfile = "3"
|
||||
target-lexicon = { version = "0.10.0", default-features = false }
|
||||
pretty_env_logger = "0.3.0"
|
||||
rand = { version = "0.7.0", default-features = false, features = ["small_rng"] }
|
||||
cranelift-codegen = { version = "0.54", features = ["enable-serde", "all-arch"] }
|
||||
cranelift-codegen = { version = "0.55", features = ["enable-serde", "all-arch"] }
|
||||
filetime = "0.2.7"
|
||||
|
||||
[badges]
|
||||
|
||||
6
crates/environ/src/cache/tests.rs
vendored
6
crates/environ/src/cache/tests.rs
vendored
@@ -1,7 +1,9 @@
|
||||
use super::config::tests::test_prolog;
|
||||
use super::*;
|
||||
use crate::address_map::{FunctionAddressMap, InstructionAddressMap};
|
||||
use crate::compilation::{CompiledFunction, Relocation, RelocationTarget, TrapInformation};
|
||||
use crate::compilation::{
|
||||
CompiledFunction, CompiledFunctionUnwindInfo, Relocation, RelocationTarget, TrapInformation,
|
||||
};
|
||||
use crate::module::{MemoryPlan, MemoryStyle, Module};
|
||||
use cranelift_codegen::{binemit, ir, isa, settings, ValueLocRange};
|
||||
use cranelift_entity::EntityRef;
|
||||
@@ -259,7 +261,7 @@ fn new_module_cache_data(rng: &mut impl Rng) -> ModuleCacheData {
|
||||
CompiledFunction {
|
||||
body: (0..(i * 3 / 2)).collect(),
|
||||
jt_offsets: sm,
|
||||
unwind_info: (0..(i * 3 / 2)).collect(),
|
||||
unwind_info: CompiledFunctionUnwindInfo::Windows((0..(i * 3 / 2)).collect()),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -4,13 +4,136 @@
|
||||
use crate::cache::ModuleCacheDataTupleType;
|
||||
use crate::module;
|
||||
use crate::module_environ::FunctionBodyData;
|
||||
use cranelift_codegen::{binemit, ir, isa};
|
||||
use cranelift_codegen::{binemit, ir, isa, Context};
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, ModuleTranslationState, WasmError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Range;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FDERelocEntry(pub i64, pub usize, pub u8);
|
||||
|
||||
/// Relocation entry for unwind info.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CompiledFunctionUnwindInfoReloc {
|
||||
/// Entry offest in the code block.
|
||||
pub offset: u32,
|
||||
/// Entry addend relative to the code block.
|
||||
pub addend: u32,
|
||||
}
|
||||
|
||||
/// Compiled function unwind information.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CompiledFunctionUnwindInfo {
|
||||
/// No info.
|
||||
None,
|
||||
/// Windows UNWIND_INFO.
|
||||
Windows(Vec<u8>),
|
||||
/// Frame layout info.
|
||||
FrameLayout(Vec<u8>, usize, Vec<FDERelocEntry>),
|
||||
}
|
||||
|
||||
impl CompiledFunctionUnwindInfo {
|
||||
/// Constructs unwind info object.
|
||||
pub fn new(isa: &dyn isa::TargetIsa, context: &Context) -> Self {
|
||||
use cranelift_codegen::binemit::{
|
||||
FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc,
|
||||
};
|
||||
use cranelift_codegen::isa::CallConv;
|
||||
|
||||
struct Sink(Vec<u8>, usize, Vec<FDERelocEntry>);
|
||||
impl FrameUnwindSink for Sink {
|
||||
fn len(&self) -> FrameUnwindOffset {
|
||||
self.0.len()
|
||||
}
|
||||
fn bytes(&mut self, b: &[u8]) {
|
||||
self.0.extend_from_slice(b);
|
||||
}
|
||||
fn reserve(&mut self, len: usize) {
|
||||
self.0.reserve(len)
|
||||
}
|
||||
fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) {
|
||||
self.2.push(FDERelocEntry(
|
||||
0,
|
||||
off,
|
||||
match r {
|
||||
Reloc::Abs4 => 4,
|
||||
Reloc::Abs8 => 8,
|
||||
_ => {
|
||||
panic!("unexpected reloc type");
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
fn set_entry_offset(&mut self, off: FrameUnwindOffset) {
|
||||
self.1 = off;
|
||||
}
|
||||
}
|
||||
|
||||
let kind = match context.func.signature.call_conv {
|
||||
CallConv::SystemV | CallConv::Fast | CallConv::Cold => FrameUnwindKind::Libunwind,
|
||||
CallConv::WindowsFastcall => FrameUnwindKind::Fastcall,
|
||||
_ => {
|
||||
return CompiledFunctionUnwindInfo::None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut sink = Sink(Vec::new(), 0, Vec::new());
|
||||
context.emit_unwind_info(isa, kind, &mut sink);
|
||||
|
||||
let Sink(data, offset, relocs) = sink;
|
||||
if data.is_empty() {
|
||||
return CompiledFunctionUnwindInfo::None;
|
||||
}
|
||||
|
||||
match kind {
|
||||
FrameUnwindKind::Fastcall => CompiledFunctionUnwindInfo::Windows(data),
|
||||
FrameUnwindKind::Libunwind => {
|
||||
CompiledFunctionUnwindInfo::FrameLayout(data, offset, relocs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retuns true is no unwind info data.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
CompiledFunctionUnwindInfo::None => true,
|
||||
CompiledFunctionUnwindInfo::Windows(d) => d.is_empty(),
|
||||
CompiledFunctionUnwindInfo::FrameLayout(c, _, _) => c.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns size of serilized unwind info.
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
CompiledFunctionUnwindInfo::None => 0,
|
||||
CompiledFunctionUnwindInfo::Windows(d) => d.len(),
|
||||
CompiledFunctionUnwindInfo::FrameLayout(c, _, _) => c.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes data into byte array.
|
||||
pub fn serialize(&self, dest: &mut [u8], relocs: &mut Vec<CompiledFunctionUnwindInfoReloc>) {
|
||||
match self {
|
||||
CompiledFunctionUnwindInfo::None => (),
|
||||
CompiledFunctionUnwindInfo::Windows(d) => {
|
||||
dest.copy_from_slice(d);
|
||||
}
|
||||
CompiledFunctionUnwindInfo::FrameLayout(code, _fde_offset, r) => {
|
||||
dest.copy_from_slice(code);
|
||||
r.iter().for_each(move |r| {
|
||||
assert_eq!(r.2, 8);
|
||||
relocs.push(CompiledFunctionUnwindInfoReloc {
|
||||
offset: r.1 as u32,
|
||||
addend: r.0 as u32,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiled function: machine code body, jump table offsets, and unwind information.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CompiledFunction {
|
||||
@@ -21,7 +144,7 @@ pub struct CompiledFunction {
|
||||
pub jt_offsets: ir::JumpTableOffsets,
|
||||
|
||||
/// The unwind information.
|
||||
pub unwind_info: Vec<u8>,
|
||||
pub unwind_info: CompiledFunctionUnwindInfo,
|
||||
}
|
||||
|
||||
type Functions = PrimaryMap<DefinedFuncIndex, CompiledFunction>;
|
||||
@@ -50,7 +173,7 @@ impl Compilation {
|
||||
.map(|(body_range, jt_offsets, unwind_range)| CompiledFunction {
|
||||
body: buffer[body_range].to_vec(),
|
||||
jt_offsets,
|
||||
unwind_info: buffer[unwind_range].to_vec(),
|
||||
unwind_info: CompiledFunctionUnwindInfo::Windows(buffer[unwind_range].to_vec()),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
use crate::address_map::{FunctionAddressMap, InstructionAddressMap};
|
||||
use crate::cache::{ModuleCacheData, ModuleCacheDataTupleType, ModuleCacheEntry};
|
||||
use crate::compilation::{
|
||||
Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, TrapInformation,
|
||||
Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Relocation,
|
||||
RelocationTarget, TrapInformation,
|
||||
};
|
||||
use crate::func_environ::{
|
||||
get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name,
|
||||
@@ -204,6 +205,7 @@ impl crate::compilation::Compiler for Cranelift {
|
||||
context.func.name = get_func_name(func_index);
|
||||
context.func.signature =
|
||||
module.signatures[module.functions[func_index]].clone();
|
||||
context.func.collect_frame_layout_info();
|
||||
if generate_debug_info {
|
||||
context.func.collect_debug_info();
|
||||
}
|
||||
@@ -217,7 +219,6 @@ impl crate::compilation::Compiler for Cranelift {
|
||||
)?;
|
||||
|
||||
let mut code_buf: Vec<u8> = Vec::new();
|
||||
let mut unwind_info = Vec::new();
|
||||
let mut reloc_sink = RelocSink::new(func_index);
|
||||
let mut trap_sink = TrapSink::new();
|
||||
let mut stackmap_sink = binemit::NullStackmapSink {};
|
||||
@@ -233,7 +234,7 @@ impl crate::compilation::Compiler for Cranelift {
|
||||
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
|
||||
})?;
|
||||
|
||||
context.emit_unwind_info(isa, &mut unwind_info);
|
||||
let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context);
|
||||
|
||||
let address_transform = if generate_debug_info {
|
||||
let body_len = code_buf.len();
|
||||
|
||||
@@ -362,8 +362,8 @@ impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environm
|
||||
}
|
||||
|
||||
impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
|
||||
fn is_wasm_parameter(&self, func: &ir::Function, index: usize) -> bool {
|
||||
func.signature.params[index].purpose == ir::ArgumentPurpose::Normal
|
||||
fn is_wasm_parameter(&self, signature: &ir::Signature, index: usize) -> bool {
|
||||
signature.params[index].purpose == ir::ArgumentPurpose::Normal
|
||||
}
|
||||
|
||||
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> WasmResult<ir::Table> {
|
||||
|
||||
@@ -44,8 +44,9 @@ pub use crate::address_map::{
|
||||
};
|
||||
pub use crate::cache::{create_new_config as cache_create_new_config, init as cache_init};
|
||||
pub use crate::compilation::{
|
||||
Compilation, CompileError, CompiledFunction, Compiler, Relocation, RelocationTarget,
|
||||
Relocations, TrapInformation, Traps,
|
||||
Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo,
|
||||
CompiledFunctionUnwindInfoReloc, Compiler, Relocation, RelocationTarget, Relocations,
|
||||
TrapInformation, Traps,
|
||||
};
|
||||
pub use crate::cranelift::Cranelift;
|
||||
pub use crate::data_structures::*;
|
||||
|
||||
@@ -11,11 +11,11 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-native = "0.54"
|
||||
cranelift-frontend = "0.54"
|
||||
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-native = "0.55"
|
||||
cranelift-frontend = "0.55"
|
||||
wasmtime-environ = { path = "../environ", version = "0.9.0" }
|
||||
wasmtime-runtime = { path = "../runtime", version = "0.9.0" }
|
||||
wasmtime-debug = { path = "../debug", version = "0.9.0" }
|
||||
|
||||
@@ -6,7 +6,10 @@ use std::cmp::max;
|
||||
use std::{fmt, mem, ptr, slice};
|
||||
use thiserror::Error;
|
||||
use wasmtime_environ::ir;
|
||||
use wasmtime_runtime::{wasmtime_call_trampoline, Export, InstanceHandle, VMInvokeArgument};
|
||||
use wasmtime_runtime::{
|
||||
wasmtime_call_trampoline, Backtrace, Export, InstanceHandle, TrapMessageAndStack,
|
||||
VMInvokeArgument,
|
||||
};
|
||||
|
||||
/// A runtime value.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
@@ -103,6 +106,8 @@ pub enum ActionOutcome {
|
||||
Trapped {
|
||||
/// The trap message.
|
||||
message: String,
|
||||
/// Backtrace.
|
||||
trace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -191,7 +196,7 @@ pub fn invoke(
|
||||
compiler.publish_compiled_code();
|
||||
|
||||
// Call the trampoline.
|
||||
if let Err(message) = unsafe {
|
||||
if let Err(TrapMessageAndStack(message, trace)) = unsafe {
|
||||
instance.with_signals_on(|| {
|
||||
wasmtime_call_trampoline(
|
||||
callee_vmctx,
|
||||
@@ -200,7 +205,7 @@ pub fn invoke(
|
||||
)
|
||||
})
|
||||
} {
|
||||
return Ok(ActionOutcome::Trapped { message });
|
||||
return Ok(ActionOutcome::Trapped { message, trace });
|
||||
}
|
||||
|
||||
// Load the return values out of `values_vec`.
|
||||
|
||||
@@ -2,14 +2,45 @@
|
||||
|
||||
use crate::function_table::FunctionTable;
|
||||
use region;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::{cmp, mem};
|
||||
use wasmtime_environ::{Compilation, CompiledFunction};
|
||||
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
||||
|
||||
struct CodeMemoryEntry {
|
||||
mmap: ManuallyDrop<Mmap>,
|
||||
table: ManuallyDrop<FunctionTable>,
|
||||
}
|
||||
|
||||
impl CodeMemoryEntry {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
mmap: ManuallyDrop::new(Mmap::new()),
|
||||
table: ManuallyDrop::new(FunctionTable::new()),
|
||||
}
|
||||
}
|
||||
fn with_capacity(cap: usize) -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
mmap: ManuallyDrop::new(Mmap::with_at_least(cap)?),
|
||||
table: ManuallyDrop::new(FunctionTable::new()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CodeMemoryEntry {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Table needs to be freed before mmap.
|
||||
ManuallyDrop::drop(&mut self.table);
|
||||
ManuallyDrop::drop(&mut self.mmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory manager for executable code.
|
||||
pub struct CodeMemory {
|
||||
current: (Mmap, FunctionTable),
|
||||
mmaps: Vec<(Mmap, FunctionTable)>,
|
||||
current: CodeMemoryEntry,
|
||||
entries: Vec<CodeMemoryEntry>,
|
||||
position: usize,
|
||||
published: usize,
|
||||
}
|
||||
@@ -23,8 +54,8 @@ impl CodeMemory {
|
||||
/// Create a new `CodeMemory` instance.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current: (Mmap::new(), FunctionTable::new()),
|
||||
mmaps: Vec::new(),
|
||||
current: CodeMemoryEntry::new(),
|
||||
entries: Vec::new(),
|
||||
position: 0,
|
||||
published: 0,
|
||||
}
|
||||
@@ -81,19 +112,20 @@ impl CodeMemory {
|
||||
self.push_current(0)
|
||||
.expect("failed to push current memory map");
|
||||
|
||||
for (m, t) in &mut self.mmaps[self.published..] {
|
||||
for CodeMemoryEntry { mmap: m, table: t } in &mut self.entries[self.published..] {
|
||||
// Remove write access to the pages due to the relocation fixups.
|
||||
t.publish(m.as_ptr() as u64)
|
||||
.expect("failed to publish function table");
|
||||
|
||||
if !m.is_empty() {
|
||||
unsafe {
|
||||
region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute)
|
||||
}
|
||||
.expect("unable to make memory readonly and executable");
|
||||
}
|
||||
|
||||
t.publish(m.as_ptr() as u64)
|
||||
.expect("failed to publish function table");
|
||||
}
|
||||
|
||||
self.published = self.mmaps.len();
|
||||
self.published = self.entries.len();
|
||||
}
|
||||
|
||||
/// Allocate `size` bytes of memory which can be made executable later by
|
||||
@@ -103,7 +135,7 @@ impl CodeMemory {
|
||||
///
|
||||
/// TODO: Add an alignment flag.
|
||||
fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable), String> {
|
||||
if self.current.0.len() - self.position < size {
|
||||
if self.current.mmap.len() - self.position < size {
|
||||
self.push_current(cmp::max(0x10000, size))?;
|
||||
}
|
||||
|
||||
@@ -111,8 +143,8 @@ impl CodeMemory {
|
||||
self.position += size;
|
||||
|
||||
Ok((
|
||||
&mut self.current.0.as_mut_slice()[old_position..self.position],
|
||||
&mut self.current.1,
|
||||
&mut self.current.mmap.as_mut_slice()[old_position..self.position],
|
||||
&mut self.current.table,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -153,12 +185,19 @@ impl CodeMemory {
|
||||
// Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary)
|
||||
let padding = ((func.body.len() + 3) & !3) - func.body.len();
|
||||
let (unwind, remainder) = remainder.split_at_mut(padding + func.unwind_info.len());
|
||||
unwind[padding..].copy_from_slice(&func.unwind_info);
|
||||
let mut relocs = Vec::new();
|
||||
func.unwind_info
|
||||
.serialize(&mut unwind[padding..], &mut relocs);
|
||||
|
||||
let unwind_start = func_end + (padding as u32);
|
||||
let unwind_end = unwind_start + (func.unwind_info.len() as u32);
|
||||
|
||||
table.add_function(func_start, func_end, unwind_start);
|
||||
relocs.iter_mut().for_each(move |r| {
|
||||
r.offset += unwind_start;
|
||||
r.addend += func_start;
|
||||
});
|
||||
|
||||
table.add_function(func_start, func_end, unwind_start, &relocs);
|
||||
|
||||
(unwind_end, remainder, table, vmfunc)
|
||||
}
|
||||
@@ -174,20 +213,17 @@ impl CodeMemory {
|
||||
fn push_current(&mut self, new_size: usize) -> Result<(), String> {
|
||||
let previous = mem::replace(
|
||||
&mut self.current,
|
||||
(
|
||||
if new_size == 0 {
|
||||
Mmap::new()
|
||||
} else {
|
||||
Mmap::with_at_least(cmp::max(0x10000, new_size))?
|
||||
},
|
||||
FunctionTable::new(),
|
||||
),
|
||||
if new_size == 0 {
|
||||
CodeMemoryEntry::new()
|
||||
} else {
|
||||
CodeMemoryEntry::with_capacity(cmp::max(0x10000, new_size))?
|
||||
},
|
||||
);
|
||||
|
||||
if !previous.0.is_empty() {
|
||||
self.mmaps.push(previous);
|
||||
if !previous.mmap.is_empty() {
|
||||
self.entries.push(previous);
|
||||
} else {
|
||||
assert_eq!(previous.1.len(), 0);
|
||||
assert_eq!(previous.table.len(), 0);
|
||||
}
|
||||
|
||||
self.position = 0;
|
||||
|
||||
@@ -16,12 +16,12 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap};
|
||||
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
|
||||
use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex};
|
||||
use wasmtime_environ::{
|
||||
Compilation, CompileError, CompiledFunction, Compiler as _C, FunctionBodyData, Module,
|
||||
ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets,
|
||||
Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Compiler as _C,
|
||||
FunctionBodyData, Module, ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets,
|
||||
};
|
||||
use wasmtime_runtime::{
|
||||
get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard,
|
||||
VMFunctionBody,
|
||||
get_mut_trap_registry, jit_function_registry, InstantiationError, SignatureRegistry,
|
||||
TrapRegistrationGuard, VMFunctionBody,
|
||||
};
|
||||
|
||||
/// Select which kind of compilation to use.
|
||||
@@ -51,6 +51,7 @@ pub struct Compiler {
|
||||
|
||||
code_memory: CodeMemory,
|
||||
trap_registration_guards: Vec<TrapRegistrationGuard>,
|
||||
jit_function_ranges: Vec<(usize, usize)>,
|
||||
trampoline_park: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||
signatures: SignatureRegistry,
|
||||
strategy: CompilationStrategy,
|
||||
@@ -66,6 +67,7 @@ impl Compiler {
|
||||
isa,
|
||||
code_memory: CodeMemory::new(),
|
||||
trap_registration_guards: Vec::new(),
|
||||
jit_function_ranges: Vec::new(),
|
||||
trampoline_park: HashMap::new(),
|
||||
signatures: SignatureRegistry::new(),
|
||||
fn_builder_ctx: FunctionBuilderContext::new(),
|
||||
@@ -85,6 +87,10 @@ impl Drop for Compiler {
|
||||
// Having a custom drop implementation we are independent from the field order
|
||||
// in the struct what reduces potential human error.
|
||||
self.trap_registration_guards.clear();
|
||||
|
||||
for (start, end) in self.jit_function_ranges.iter() {
|
||||
jit_function_registry::unregister(*start, *end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +161,18 @@ impl Compiler {
|
||||
&mut self.trap_registration_guards,
|
||||
);
|
||||
|
||||
for (i, allocated) in allocated_functions.iter() {
|
||||
let ptr = (*allocated) as *const VMFunctionBody;
|
||||
let body_len = compilation.get(i).body.len();
|
||||
self.jit_function_ranges
|
||||
.push((ptr as usize, ptr as usize + body_len));
|
||||
let tag = jit_function_registry::JITFunctionTag {
|
||||
module_id: module.name.clone(),
|
||||
func_index: i.index(),
|
||||
};
|
||||
jit_function_registry::register(ptr as usize, ptr as usize + body_len, tag);
|
||||
}
|
||||
|
||||
let dbg = if let Some(debug_data) = debug_data {
|
||||
let target_config = self.isa.frontend_config();
|
||||
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module);
|
||||
@@ -215,6 +233,7 @@ impl Compiler {
|
||||
signature,
|
||||
value_size,
|
||||
)?;
|
||||
|
||||
entry.insert(body);
|
||||
body
|
||||
}
|
||||
@@ -266,6 +285,7 @@ fn make_trampoline(
|
||||
|
||||
let mut context = Context::new();
|
||||
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
||||
context.func.collect_frame_layout_info();
|
||||
|
||||
{
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||
@@ -326,7 +346,6 @@ fn make_trampoline(
|
||||
}
|
||||
|
||||
let mut code_buf = Vec::new();
|
||||
let mut unwind_info = Vec::new();
|
||||
let mut reloc_sink = RelocSink {};
|
||||
let mut trap_sink = binemit::NullTrapSink {};
|
||||
let mut stackmap_sink = binemit::NullStackmapSink {};
|
||||
@@ -346,7 +365,7 @@ fn make_trampoline(
|
||||
)))
|
||||
})?;
|
||||
|
||||
context.emit_unwind_info(isa, &mut unwind_info);
|
||||
let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context);
|
||||
|
||||
Ok(code_memory
|
||||
.allocate_for_function(&CompiledFunction {
|
||||
|
||||
@@ -2,42 +2,7 @@
|
||||
//!
|
||||
//! This module is primarily used to track JIT functions on Windows for stack walking and unwind.
|
||||
|
||||
/// Represents a runtime function table.
|
||||
///
|
||||
/// The runtime function table is not implemented for non-Windows target platforms.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) struct FunctionTable;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
impl FunctionTable {
|
||||
/// Creates a new function table.
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Returns the number of functions in the table, also referred to as its 'length'.
|
||||
///
|
||||
/// For non-Windows platforms, the table will always be empty.
|
||||
pub fn len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
/// Adds a function to the table based off of the start offset, end offset, and unwind offset.
|
||||
///
|
||||
/// The offsets are from the "module base", which is provided when the table is published.
|
||||
///
|
||||
/// For non-Windows platforms, this is a no-op.
|
||||
pub fn add_function(&mut self, _start: u32, _end: u32, _unwind: u32) {}
|
||||
|
||||
/// Publishes the function table using the given base address.
|
||||
///
|
||||
/// A published function table will automatically be deleted when it is dropped.
|
||||
///
|
||||
/// For non-Windows platforms, this is a no-op.
|
||||
pub fn publish(&mut self, _base_address: u64) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
type FunctionTableReloc = wasmtime_environ::CompiledFunctionUnwindInfoReloc;
|
||||
|
||||
/// Represents a runtime function table.
|
||||
///
|
||||
@@ -66,7 +31,14 @@ impl FunctionTable {
|
||||
/// Adds a function to the table based off of the start offset, end offset, and unwind offset.
|
||||
///
|
||||
/// The offsets are from the "module base", which is provided when the table is published.
|
||||
pub fn add_function(&mut self, start: u32, end: u32, unwind: u32) {
|
||||
pub fn add_function(
|
||||
&mut self,
|
||||
start: u32,
|
||||
end: u32,
|
||||
unwind: u32,
|
||||
_relocs: &[FunctionTableReloc],
|
||||
) {
|
||||
assert_eq!(_relocs.len(), 0);
|
||||
use winapi::um::winnt;
|
||||
|
||||
assert!(!self.published, "table has already been published");
|
||||
@@ -133,3 +105,106 @@ impl Drop for FunctionTable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a runtime function table.
|
||||
///
|
||||
/// This is used to register JIT code with the operating system to enable stack walking and unwinding.
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
pub(crate) struct FunctionTable {
|
||||
functions: Vec<u32>,
|
||||
relocs: Vec<FunctionTableReloc>,
|
||||
published: Option<Vec<usize>>,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
impl FunctionTable {
|
||||
/// Creates a new function table.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
functions: Vec::new(),
|
||||
relocs: Vec::new(),
|
||||
published: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of functions in the table, also referred to as its 'length'.
|
||||
pub fn len(&self) -> usize {
|
||||
self.functions.len()
|
||||
}
|
||||
|
||||
/// Adds a function to the table based off of the start offset, end offset, and unwind offset.
|
||||
///
|
||||
/// The offsets are from the "module base", which is provided when the table is published.
|
||||
pub fn add_function(
|
||||
&mut self,
|
||||
_start: u32,
|
||||
_end: u32,
|
||||
unwind: u32,
|
||||
relocs: &[FunctionTableReloc],
|
||||
) {
|
||||
assert!(self.published.is_none(), "table has already been published");
|
||||
self.functions.push(unwind);
|
||||
self.relocs.extend_from_slice(relocs);
|
||||
}
|
||||
|
||||
/// Publishes the function table using the given base address.
|
||||
///
|
||||
/// A published function table will automatically be deleted when it is dropped.
|
||||
pub fn publish(&mut self, base_address: u64) -> Result<(), String> {
|
||||
if self.published.is_some() {
|
||||
return Err("function table was already published".into());
|
||||
}
|
||||
|
||||
if self.functions.is_empty() {
|
||||
assert_eq!(self.relocs.len(), 0);
|
||||
self.published = Some(vec![]);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// libunwind import
|
||||
fn __register_frame(fde: *const u8);
|
||||
}
|
||||
|
||||
for reloc in self.relocs.iter() {
|
||||
let addr = base_address + (reloc.offset as u64);
|
||||
let target = base_address + (reloc.addend as u64);
|
||||
unsafe {
|
||||
std::ptr::write(addr as *mut u64, target);
|
||||
}
|
||||
}
|
||||
|
||||
let mut fdes = Vec::with_capacity(self.functions.len());
|
||||
for unwind_offset in self.functions.iter() {
|
||||
let addr = base_address + (*unwind_offset as u64);
|
||||
let off = unsafe { std::ptr::read::<u32>(addr as *const u32) } as usize + 4;
|
||||
|
||||
let fde = (addr + off as u64) as usize;
|
||||
unsafe {
|
||||
__register_frame(fde as *const _);
|
||||
}
|
||||
fdes.push(fde);
|
||||
}
|
||||
|
||||
self.published = Some(fdes);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
impl Drop for FunctionTable {
|
||||
fn drop(&mut self) {
|
||||
extern "C" {
|
||||
// libunwind import
|
||||
fn __deregister_frame(fde: *const u8);
|
||||
}
|
||||
|
||||
if self.published.is_some() {
|
||||
unsafe {
|
||||
for fde in self.published.as_ref().unwrap() {
|
||||
__deregister_frame(*fde as *const _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ memoffset = "0.5.3"
|
||||
itertools = "0.8.2"
|
||||
capstone = "0.6.0"
|
||||
thiserror = "1.0.9"
|
||||
cranelift-codegen = "0.54"
|
||||
cranelift-codegen = "0.55"
|
||||
multi_mut = "0.1"
|
||||
either = "1.5"
|
||||
typemap = "0.3"
|
||||
|
||||
@@ -20,6 +20,7 @@ indexmap = "1.0.2"
|
||||
thiserror = "1.0.4"
|
||||
more-asserts = "0.2.1"
|
||||
cfg-if = "0.1.9"
|
||||
backtrace = "0.3.40"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] }
|
||||
|
||||
148
crates/runtime/src/backtrace.rs
Normal file
148
crates/runtime/src/backtrace.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
//! Backtrace object and utilities.
|
||||
|
||||
use crate::jit_function_registry;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Information about backtrace frame.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct BacktraceFrame {
|
||||
pc: usize,
|
||||
}
|
||||
|
||||
impl Default for BacktraceFrame {
|
||||
fn default() -> Self {
|
||||
Self { pc: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl BacktraceFrame {
|
||||
/// Current PC or IP pointer for the frame.
|
||||
pub fn pc(&self) -> usize {
|
||||
self.pc
|
||||
}
|
||||
/// Additinal frame information.
|
||||
pub fn tag(&self) -> Option<Arc<jit_function_registry::JITFunctionTag>> {
|
||||
jit_function_registry::find(self.pc)
|
||||
}
|
||||
}
|
||||
|
||||
const BACKTRACE_LIMIT: usize = 32;
|
||||
|
||||
/// Backtrace during WebAssembly trap.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Backtrace {
|
||||
len: usize,
|
||||
frames: [BacktraceFrame; BACKTRACE_LIMIT],
|
||||
}
|
||||
|
||||
impl Backtrace {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
len: 0,
|
||||
frames: [Default::default(); BACKTRACE_LIMIT],
|
||||
}
|
||||
}
|
||||
fn full(&self) -> bool {
|
||||
self.len >= BACKTRACE_LIMIT
|
||||
}
|
||||
fn push(&mut self, frame: BacktraceFrame) {
|
||||
assert!(self.len < BACKTRACE_LIMIT);
|
||||
self.frames[self.len] = frame;
|
||||
self.len += 1;
|
||||
}
|
||||
/// Amount of the backtrace frames.
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Backtrace {
|
||||
type Output = BacktraceFrame;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
assert!(index < self.len);
|
||||
&self.frames[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Backtrace {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Backtrace![")?;
|
||||
for i in 0..self.len() {
|
||||
let frame = &self.frames[i];
|
||||
writeln!(f, " {:x}: {:?}", frame.pc(), frame.tag())?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
|
||||
fn capture_stack<F>(mut f: F)
|
||||
where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
use backtrace::trace;
|
||||
trace(|frame| {
|
||||
let pc = frame.ip() as usize;
|
||||
f(pc)
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
|
||||
fn capture_stack<F>(mut f: F)
|
||||
where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr;
|
||||
use winapi::um::winnt::{
|
||||
RtlCaptureContext, RtlLookupFunctionEntry, RtlVirtualUnwind, CONTEXT, UNW_FLAG_NHANDLER,
|
||||
};
|
||||
|
||||
#[repr(C, align(16))]
|
||||
struct WrappedContext(CONTEXT);
|
||||
|
||||
unsafe {
|
||||
let mut ctx = WrappedContext(MaybeUninit::uninit().assume_init());
|
||||
RtlCaptureContext(&mut ctx.0);
|
||||
let mut unwind_history_table = MaybeUninit::zeroed().assume_init();
|
||||
while ctx.0.Rip != 0 {
|
||||
let cont = f(ctx.0.Rip as usize);
|
||||
if !cont {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut image_base: u64 = 0;
|
||||
let mut handler_data: *mut core::ffi::c_void = ptr::null_mut();
|
||||
let mut establisher_frame: u64 = 0;
|
||||
let rf = RtlLookupFunctionEntry(ctx.0.Rip, &mut image_base, &mut unwind_history_table);
|
||||
if rf.is_null() {
|
||||
ctx.0.Rip = ptr::read(ctx.0.Rsp as *const u64);
|
||||
ctx.0.Rsp += 8;
|
||||
} else {
|
||||
RtlVirtualUnwind(
|
||||
UNW_FLAG_NHANDLER,
|
||||
image_base,
|
||||
ctx.0.Rip,
|
||||
rf,
|
||||
&mut ctx.0,
|
||||
&mut handler_data,
|
||||
&mut establisher_frame,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns current backtrace. Only registered wasmtime functions will be listed.
|
||||
pub fn get_backtrace() -> Backtrace {
|
||||
let mut frames = Backtrace::new();
|
||||
capture_stack(|pc| {
|
||||
if let Some(_) = jit_function_registry::find(pc) {
|
||||
frames.push(BacktraceFrame { pc });
|
||||
}
|
||||
!frames.full()
|
||||
});
|
||||
frames
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use crate::memory::LinearMemory;
|
||||
use crate::mmap::Mmap;
|
||||
use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||
use crate::table::Table;
|
||||
use crate::traphandlers::wasmtime_call;
|
||||
use crate::traphandlers::{wasmtime_call, TrapMessageAndStack};
|
||||
use crate::vmcontext::{
|
||||
VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport,
|
||||
VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
||||
@@ -568,7 +568,7 @@ impl Instance {
|
||||
|
||||
// Make the call.
|
||||
unsafe { wasmtime_call(callee_vmctx, callee_address) }
|
||||
.map_err(InstantiationError::StartTrap)
|
||||
.map_err(|TrapMessageAndStack(msg, _)| InstantiationError::StartTrap(msg))
|
||||
}
|
||||
|
||||
/// Invoke the WebAssembly start function of the instance, if one is present.
|
||||
|
||||
83
crates/runtime/src/jit_function_registry.rs
Normal file
83
crates/runtime/src/jit_function_registry.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
lazy_static! {
|
||||
static ref REGISTRY: RwLock<JITFunctionRegistry> = RwLock::new(JITFunctionRegistry::default());
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JITFunctionTag {
|
||||
pub module_id: Option<String>,
|
||||
pub func_index: usize,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for JITFunctionTag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(ref module_id) = self.module_id {
|
||||
write!(f, "{}", module_id)?;
|
||||
} else {
|
||||
write!(f, "(module)")?;
|
||||
}
|
||||
write!(f, ":{}", self.func_index)
|
||||
}
|
||||
}
|
||||
|
||||
struct JITFunctionRegistry {
|
||||
ranges: BTreeMap<usize, (usize, Arc<JITFunctionTag>)>,
|
||||
}
|
||||
|
||||
impl Default for JITFunctionRegistry {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ranges: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JITFunctionRegistry {
|
||||
fn register(&mut self, fn_start: usize, fn_end: usize, tag: JITFunctionTag) {
|
||||
self.ranges.insert(fn_end, (fn_start, Arc::new(tag)));
|
||||
}
|
||||
|
||||
fn unregister(&mut self, fn_end: usize) {
|
||||
self.ranges.remove(&fn_end);
|
||||
}
|
||||
|
||||
fn find(&self, pc: usize) -> Option<&Arc<JITFunctionTag>> {
|
||||
self.ranges
|
||||
.range(pc..)
|
||||
.next()
|
||||
.and_then(|(end, (start, s))| {
|
||||
if *start <= pc && pc < *end {
|
||||
Some(s)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(fn_start: usize, fn_end: usize, tag: JITFunctionTag) {
|
||||
REGISTRY
|
||||
.write()
|
||||
.expect("jit function registry lock got poisoned")
|
||||
.register(fn_start, fn_end, tag);
|
||||
}
|
||||
|
||||
pub fn unregister(_fn_start: usize, fn_end: usize) {
|
||||
REGISTRY
|
||||
.write()
|
||||
.expect("jit function registry lock got poisoned")
|
||||
.unregister(fn_end);
|
||||
}
|
||||
|
||||
pub fn find(pc: usize) -> Option<Arc<JITFunctionTag>> {
|
||||
REGISTRY
|
||||
.read()
|
||||
.expect("jit function registry lock got poisoned")
|
||||
.find(pc)
|
||||
.cloned()
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
)
|
||||
)]
|
||||
|
||||
mod backtrace;
|
||||
mod export;
|
||||
mod imports;
|
||||
mod instance;
|
||||
@@ -34,8 +35,10 @@ mod trap_registry;
|
||||
mod traphandlers;
|
||||
mod vmcontext;
|
||||
|
||||
pub mod jit_function_registry;
|
||||
pub mod libcalls;
|
||||
|
||||
pub use crate::backtrace::{get_backtrace, Backtrace, BacktraceFrame};
|
||||
pub use crate::export::Export;
|
||||
pub use crate::imports::Imports;
|
||||
pub use crate::instance::{InstanceHandle, InstantiationError, LinkError};
|
||||
@@ -44,7 +47,7 @@ pub use crate::mmap::Mmap;
|
||||
pub use crate::sig_registry::SignatureRegistry;
|
||||
pub use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||
pub use crate::trap_registry::{get_mut_trap_registry, get_trap_registry, TrapRegistrationGuard};
|
||||
pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline};
|
||||
pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline, TrapMessageAndStack};
|
||||
pub use crate::vmcontext::{
|
||||
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
|
||||
VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! WebAssembly trap handling, which is built on top of the lower-level
|
||||
//! signalhandling mechanisms.
|
||||
|
||||
use crate::backtrace::{get_backtrace, Backtrace};
|
||||
use crate::trap_registry::get_trap_registry;
|
||||
use crate::trap_registry::TrapDescription;
|
||||
use crate::vmcontext::{VMContext, VMFunctionBody};
|
||||
@@ -18,7 +19,7 @@ extern "C" {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static RECORDED_TRAP: Cell<Option<TrapDescription>> = Cell::new(None);
|
||||
static RECORDED_TRAP: Cell<Option<(TrapDescription, Backtrace)>> = Cell::new(None);
|
||||
static JMP_BUF: Cell<*const u8> = Cell::new(ptr::null());
|
||||
static RESET_GUARD_PAGE: Cell<bool> = Cell::new(false);
|
||||
}
|
||||
@@ -50,6 +51,8 @@ pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) {
|
||||
trap_code: ir::TrapCode::StackOverflow,
|
||||
});
|
||||
|
||||
let trap_backtrace = get_backtrace();
|
||||
|
||||
if reset_guard_page {
|
||||
RESET_GUARD_PAGE.with(|v| v.set(true));
|
||||
}
|
||||
@@ -60,7 +63,7 @@ pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) {
|
||||
None,
|
||||
"Only one trap per thread can be recorded at a moment!"
|
||||
);
|
||||
data.set(Some(trap_desc))
|
||||
data.set(Some((trap_desc, trap_backtrace)))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,15 +111,22 @@ fn reset_guard_page() {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn reset_guard_page() {}
|
||||
|
||||
fn trap_message() -> String {
|
||||
/// Stores trace message with backtrace.
|
||||
#[derive(Debug)]
|
||||
pub struct TrapMessageAndStack(pub String, pub Backtrace);
|
||||
|
||||
fn trap_message_and_stack() -> TrapMessageAndStack {
|
||||
let trap_desc = RECORDED_TRAP
|
||||
.with(|data| data.replace(None))
|
||||
.expect("trap_message must be called after trap occurred");
|
||||
|
||||
format!(
|
||||
"wasm trap: {}, source location: {}",
|
||||
trap_code_to_expected_string(trap_desc.trap_code),
|
||||
trap_desc.source_loc,
|
||||
TrapMessageAndStack(
|
||||
format!(
|
||||
"wasm trap: {}, source location: {}",
|
||||
trap_code_to_expected_string(trap_desc.0.trap_code),
|
||||
trap_desc.0.source_loc,
|
||||
),
|
||||
trap_desc.1,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -146,9 +156,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
|
||||
vmctx: *mut VMContext,
|
||||
callee: *const VMFunctionBody,
|
||||
values_vec: *mut u8,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), TrapMessageAndStack> {
|
||||
if WasmtimeCallTrampoline(vmctx as *mut u8, callee, values_vec) == 0 {
|
||||
Err(trap_message())
|
||||
Err(trap_message_and_stack())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -160,9 +170,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
|
||||
pub unsafe extern "C" fn wasmtime_call(
|
||||
vmctx: *mut VMContext,
|
||||
callee: *const VMFunctionBody,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), TrapMessageAndStack> {
|
||||
if WasmtimeCall(vmctx as *mut u8, callee) == 0 {
|
||||
Err(trap_message())
|
||||
Err(trap_message_and_stack())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ edition = "2018"
|
||||
wasmtime-runtime = { path = "../runtime", version = "0.9.0" }
|
||||
wasmtime-environ = { path = "../environ", version = "0.9.0" }
|
||||
wasmtime-jit = { path = "../jit", version = "0.9.0" }
|
||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||
target-lexicon = "0.10.0"
|
||||
log = { version = "0.4.8", default-features = false }
|
||||
libc = "0.2.60"
|
||||
|
||||
@@ -16,9 +16,9 @@ wasmtime-runtime = { path = "../runtime", version = "0.9.0" }
|
||||
wasmtime-environ = { path = "../environ", version = "0.9.0" }
|
||||
wasmtime-jit = { path = "../jit", version = "0.9.0" }
|
||||
wasi-common = { path = "../wasi-common", version = "0.9.0" }
|
||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
||||
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||
target-lexicon = "0.10.0"
|
||||
log = { version = "0.4.8", default-features = false }
|
||||
wig = { path = "../wasi-common/wig", version = "0.9.2" }
|
||||
|
||||
@@ -9,7 +9,7 @@ topdir=$(dirname "$0")/..
|
||||
cd "$topdir"
|
||||
|
||||
# All the cranelift-* crates have the same version number
|
||||
version="0.53"
|
||||
version="0.55"
|
||||
|
||||
# Update all of the Cargo.toml files.
|
||||
echo "Updating crate versions to $version"
|
||||
|
||||
Reference in New Issue
Block a user