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]]
|
[[package]]
|
||||||
name = "cranelift-bforest"
|
name = "cranelift-bforest"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd3225fff1be118941c5fb66f1fb1f7f3e86468fac0e7364713c4fb99b72632b"
|
checksum = "40af6e6f7419110906d0f7b4b8084d3216be64d7da77aa12887885ebe0fc2776"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cranelift-entity",
|
"cranelift-entity",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cranelift-codegen"
|
name = "cranelift-codegen"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3e3e6679892029f76a99b9059d2b74e77ac03637d573bb014bc21579ec1b7da"
|
checksum = "88583eb22e5cd0fe1ef66f0b80d0063d21d30e84e887d08b3d369def3ea7b4be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cranelift-bforest",
|
"cranelift-bforest",
|
||||||
"cranelift-codegen-meta",
|
"cranelift-codegen-meta",
|
||||||
"cranelift-codegen-shared",
|
"cranelift-codegen-shared",
|
||||||
"cranelift-entity",
|
"cranelift-entity",
|
||||||
|
"gimli",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
@@ -368,9 +369,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cranelift-codegen-meta"
|
name = "cranelift-codegen-meta"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cabe691548e28ca82ebd218f2fe76eec4c5629b64ef3db8b58443b7d9047275"
|
checksum = "353872c984943b9134d7c835eb9d12932bd90f4992dbe666593771bee920d673"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cranelift-codegen-shared",
|
"cranelift-codegen-shared",
|
||||||
"cranelift-entity",
|
"cranelift-entity",
|
||||||
@@ -378,24 +379,24 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cranelift-codegen-shared"
|
name = "cranelift-codegen-shared"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d173252ffade4aa6e929090977b9a4cd5ac28e15a42626f878be3844b3000ad9"
|
checksum = "cd1e96479a56981cce5c8f14d26773195d662ccdbbeca39fb8eba22b5ca8ea6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cranelift-entity"
|
name = "cranelift-entity"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3498ad9ba021715716a1c52e2b31d7829a149913fb0d88493e4b07d3b772b656"
|
checksum = "37bc8dc3d4274ededc687d84821f491a8a03447dbb7481983936220892cf55b4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cranelift-frontend"
|
name = "cranelift-frontend"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "521a30773b8de74345c807a38853f055aca8fecaa39a0fc7698bfebc5a3da515"
|
checksum = "4ea33e55bda8c425f3f533355b03e3a43cf29b4e228b35b868b6a1c43b6a139e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cranelift-codegen",
|
"cranelift-codegen",
|
||||||
"log",
|
"log",
|
||||||
@@ -405,9 +406,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cranelift-native"
|
name = "cranelift-native"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "624e755cbe984e437308968239736e7f9aa3193e99928fb76eec7a1946627b34"
|
checksum = "2c22cfaaa5e69eddaddd6cfa7a76233de964b9b2245e81a5f47dae739931ad0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cranelift-codegen",
|
"cranelift-codegen",
|
||||||
"raw-cpuid",
|
"raw-cpuid",
|
||||||
@@ -416,9 +417,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cranelift-wasm"
|
name = "cranelift-wasm"
|
||||||
version = "0.54.0"
|
version = "0.55.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0320733e518ab9e0e2d1a22034d40e2851fb403ed14db5220cf9b86576b9cfd4"
|
checksum = "038f8fd636abc83ccd6f110e0891766521c3599b52173c0f37e61c684f19a15d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cranelift-codegen",
|
"cranelift-codegen",
|
||||||
"cranelift-entity",
|
"cranelift-entity",
|
||||||
@@ -426,7 +427,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"wasmparser 0.45.2",
|
"wasmparser 0.47.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1964,12 +1965,6 @@ version = "0.42.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1527c84a5bd585215f29c06b0e2a5274e478ad4dfc970d26ffad66fdc6cb311d"
|
checksum = "1527c84a5bd585215f29c06b0e2a5274e478ad4dfc970d26ffad66fdc6cb311d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasmparser"
|
|
||||||
version = "0.45.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b4eab1d9971d0803729cba3617b56eb04fcb4bd25361cb63880ed41a42f20d5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.47.0"
|
version = "0.47.0"
|
||||||
@@ -2188,6 +2183,7 @@ dependencies = [
|
|||||||
name = "wasmtime-runtime"
|
name = "wasmtime-runtime"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ impl WrappedCallable for WasmtimeFn {
|
|||||||
.map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?;
|
.map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?;
|
||||||
|
|
||||||
// Call the trampoline.
|
// Call the trampoline.
|
||||||
if let Err(message) = unsafe {
|
if let Err(error) = unsafe {
|
||||||
self.instance.with_signals_on(|| {
|
self.instance.with_signals_on(|| {
|
||||||
wasmtime_runtime::wasmtime_call_trampoline(
|
wasmtime_runtime::wasmtime_call_trampoline(
|
||||||
vmctx,
|
vmctx,
|
||||||
@@ -156,8 +156,12 @@ impl WrappedCallable for WasmtimeFn {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
} {
|
} {
|
||||||
let trap =
|
let message = error.0;
|
||||||
take_api_trap().unwrap_or_else(|| Trap::new(format!("call error: {}", message)));
|
let backtrace = error.1;
|
||||||
|
|
||||||
|
let trap = take_api_trap().unwrap_or_else(|| {
|
||||||
|
Trap::new_with_trace(format!("call error: {}", message), backtrace)
|
||||||
|
});
|
||||||
return Err(trap);
|
return Err(trap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap};
|
|||||||
use wasmtime_environ::ir::types;
|
use wasmtime_environ::ir::types;
|
||||||
use wasmtime_environ::isa::TargetIsa;
|
use wasmtime_environ::isa::TargetIsa;
|
||||||
use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex};
|
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::{
|
use wasmtime_jit::trampoline::ir::{
|
||||||
ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind,
|
ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind,
|
||||||
};
|
};
|
||||||
@@ -136,6 +138,7 @@ fn make_trampoline(
|
|||||||
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone());
|
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(
|
let ss = context.func.create_stack_slot(StackSlotData::new(
|
||||||
StackSlotKind::ExplicitSlot,
|
StackSlotKind::ExplicitSlot,
|
||||||
@@ -213,8 +216,7 @@ fn make_trampoline(
|
|||||||
.map_err(|error| pretty_error(&context.func, Some(isa), error))
|
.map_err(|error| pretty_error(&context.func, Some(isa), error))
|
||||||
.expect("compile_and_emit");
|
.expect("compile_and_emit");
|
||||||
|
|
||||||
let mut unwind_info = Vec::new();
|
let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context);
|
||||||
context.emit_unwind_info(isa, &mut unwind_info);
|
|
||||||
|
|
||||||
let traps = trap_sink.traps;
|
let traps = trap_sink.traps;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::instance::Instance;
|
use crate::instance::Instance;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use wasmtime_runtime::{get_backtrace, Backtrace, BacktraceFrame};
|
||||||
|
|
||||||
/// A struct representing an aborted instruction execution, with a message
|
/// A struct representing an aborted instruction execution, with a message
|
||||||
/// indicating the cause.
|
/// indicating the cause.
|
||||||
@@ -26,10 +27,21 @@ impl Trap {
|
|||||||
/// assert_eq!("unexpected error", trap.message());
|
/// assert_eq!("unexpected error", trap.message());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
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 {
|
Trap {
|
||||||
inner: Arc::new(TrapInner {
|
inner: Arc::new(TrapInner {
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
trace: Vec::new(),
|
trace,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,22 +74,42 @@ impl fmt::Display for Trap {
|
|||||||
impl std::error::Error for Trap {}
|
impl std::error::Error for Trap {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FrameInfo;
|
pub struct FrameInfo {
|
||||||
|
module_name: Option<String>,
|
||||||
|
func_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
impl FrameInfo {
|
impl FrameInfo {
|
||||||
pub fn instance(&self) -> *const Instance {
|
pub fn instance(&self) -> *const Instance {
|
||||||
unimplemented!("FrameInfo::instance");
|
unimplemented!("FrameInfo::instance");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn func_index() -> usize {
|
pub fn func_index(&self) -> u32 {
|
||||||
unimplemented!("FrameInfo::func_index");
|
self.func_index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn func_offset() -> usize {
|
pub fn func_offset(&self) -> usize {
|
||||||
unimplemented!("FrameInfo::func_offset");
|
unimplemented!("FrameInfo::func_offset");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn module_offset() -> usize {
|
pub fn module_offset(&self) -> usize {
|
||||||
unimplemented!("FrameInfo::module_offset");
|
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(())
|
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]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||||
wasmparser = "0.47.0"
|
wasmparser = "0.47.0"
|
||||||
lightbeam = { path = "../lightbeam", optional = true, version = "0.9.0" }
|
lightbeam = { path = "../lightbeam", optional = true, version = "0.9.0" }
|
||||||
indexmap = "1.0.2"
|
indexmap = "1.0.2"
|
||||||
@@ -46,7 +46,7 @@ tempfile = "3"
|
|||||||
target-lexicon = { version = "0.10.0", default-features = false }
|
target-lexicon = { version = "0.10.0", default-features = false }
|
||||||
pretty_env_logger = "0.3.0"
|
pretty_env_logger = "0.3.0"
|
||||||
rand = { version = "0.7.0", default-features = false, features = ["small_rng"] }
|
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"
|
filetime = "0.2.7"
|
||||||
|
|
||||||
[badges]
|
[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::config::tests::test_prolog;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::address_map::{FunctionAddressMap, InstructionAddressMap};
|
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 crate::module::{MemoryPlan, MemoryStyle, Module};
|
||||||
use cranelift_codegen::{binemit, ir, isa, settings, ValueLocRange};
|
use cranelift_codegen::{binemit, ir, isa, settings, ValueLocRange};
|
||||||
use cranelift_entity::EntityRef;
|
use cranelift_entity::EntityRef;
|
||||||
@@ -259,7 +261,7 @@ fn new_module_cache_data(rng: &mut impl Rng) -> ModuleCacheData {
|
|||||||
CompiledFunction {
|
CompiledFunction {
|
||||||
body: (0..(i * 3 / 2)).collect(),
|
body: (0..(i * 3 / 2)).collect(),
|
||||||
jt_offsets: sm,
|
jt_offsets: sm,
|
||||||
unwind_info: (0..(i * 3 / 2)).collect(),
|
unwind_info: CompiledFunctionUnwindInfo::Windows((0..(i * 3 / 2)).collect()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -4,13 +4,136 @@
|
|||||||
use crate::cache::ModuleCacheDataTupleType;
|
use crate::cache::ModuleCacheDataTupleType;
|
||||||
use crate::module;
|
use crate::module;
|
||||||
use crate::module_environ::FunctionBodyData;
|
use crate::module_environ::FunctionBodyData;
|
||||||
use cranelift_codegen::{binemit, ir, isa};
|
use cranelift_codegen::{binemit, ir, isa, Context};
|
||||||
use cranelift_entity::PrimaryMap;
|
use cranelift_entity::PrimaryMap;
|
||||||
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, ModuleTranslationState, WasmError};
|
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, ModuleTranslationState, WasmError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use thiserror::Error;
|
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.
|
/// Compiled function: machine code body, jump table offsets, and unwind information.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct CompiledFunction {
|
pub struct CompiledFunction {
|
||||||
@@ -21,7 +144,7 @@ pub struct CompiledFunction {
|
|||||||
pub jt_offsets: ir::JumpTableOffsets,
|
pub jt_offsets: ir::JumpTableOffsets,
|
||||||
|
|
||||||
/// The unwind information.
|
/// The unwind information.
|
||||||
pub unwind_info: Vec<u8>,
|
pub unwind_info: CompiledFunctionUnwindInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Functions = PrimaryMap<DefinedFuncIndex, CompiledFunction>;
|
type Functions = PrimaryMap<DefinedFuncIndex, CompiledFunction>;
|
||||||
@@ -50,7 +173,7 @@ impl Compilation {
|
|||||||
.map(|(body_range, jt_offsets, unwind_range)| CompiledFunction {
|
.map(|(body_range, jt_offsets, unwind_range)| CompiledFunction {
|
||||||
body: buffer[body_range].to_vec(),
|
body: buffer[body_range].to_vec(),
|
||||||
jt_offsets,
|
jt_offsets,
|
||||||
unwind_info: buffer[unwind_range].to_vec(),
|
unwind_info: CompiledFunctionUnwindInfo::Windows(buffer[unwind_range].to_vec()),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
use crate::address_map::{FunctionAddressMap, InstructionAddressMap};
|
use crate::address_map::{FunctionAddressMap, InstructionAddressMap};
|
||||||
use crate::cache::{ModuleCacheData, ModuleCacheDataTupleType, ModuleCacheEntry};
|
use crate::cache::{ModuleCacheData, ModuleCacheDataTupleType, ModuleCacheEntry};
|
||||||
use crate::compilation::{
|
use crate::compilation::{
|
||||||
Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, TrapInformation,
|
Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Relocation,
|
||||||
|
RelocationTarget, TrapInformation,
|
||||||
};
|
};
|
||||||
use crate::func_environ::{
|
use crate::func_environ::{
|
||||||
get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name,
|
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.name = get_func_name(func_index);
|
||||||
context.func.signature =
|
context.func.signature =
|
||||||
module.signatures[module.functions[func_index]].clone();
|
module.signatures[module.functions[func_index]].clone();
|
||||||
|
context.func.collect_frame_layout_info();
|
||||||
if generate_debug_info {
|
if generate_debug_info {
|
||||||
context.func.collect_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 code_buf: Vec<u8> = Vec::new();
|
||||||
let mut unwind_info = Vec::new();
|
|
||||||
let mut reloc_sink = RelocSink::new(func_index);
|
let mut reloc_sink = RelocSink::new(func_index);
|
||||||
let mut trap_sink = TrapSink::new();
|
let mut trap_sink = TrapSink::new();
|
||||||
let mut stackmap_sink = binemit::NullStackmapSink {};
|
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))
|
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 address_transform = if generate_debug_info {
|
||||||
let body_len = code_buf.len();
|
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> {
|
impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> {
|
||||||
fn is_wasm_parameter(&self, func: &ir::Function, index: usize) -> bool {
|
fn is_wasm_parameter(&self, signature: &ir::Signature, index: usize) -> bool {
|
||||||
func.signature.params[index].purpose == ir::ArgumentPurpose::Normal
|
signature.params[index].purpose == ir::ArgumentPurpose::Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> WasmResult<ir::Table> {
|
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::cache::{create_new_config as cache_create_new_config, init as cache_init};
|
||||||
pub use crate::compilation::{
|
pub use crate::compilation::{
|
||||||
Compilation, CompileError, CompiledFunction, Compiler, Relocation, RelocationTarget,
|
Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo,
|
||||||
Relocations, TrapInformation, Traps,
|
CompiledFunctionUnwindInfoReloc, Compiler, Relocation, RelocationTarget, Relocations,
|
||||||
|
TrapInformation, Traps,
|
||||||
};
|
};
|
||||||
pub use crate::cranelift::Cranelift;
|
pub use crate::cranelift::Cranelift;
|
||||||
pub use crate::data_structures::*;
|
pub use crate::data_structures::*;
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ readme = "README.md"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-native = "0.54"
|
cranelift-native = "0.55"
|
||||||
cranelift-frontend = "0.54"
|
cranelift-frontend = "0.55"
|
||||||
wasmtime-environ = { path = "../environ", version = "0.9.0" }
|
wasmtime-environ = { path = "../environ", version = "0.9.0" }
|
||||||
wasmtime-runtime = { path = "../runtime", version = "0.9.0" }
|
wasmtime-runtime = { path = "../runtime", version = "0.9.0" }
|
||||||
wasmtime-debug = { path = "../debug", 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 std::{fmt, mem, ptr, slice};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wasmtime_environ::ir;
|
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.
|
/// A runtime value.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -103,6 +106,8 @@ pub enum ActionOutcome {
|
|||||||
Trapped {
|
Trapped {
|
||||||
/// The trap message.
|
/// The trap message.
|
||||||
message: String,
|
message: String,
|
||||||
|
/// Backtrace.
|
||||||
|
trace: Backtrace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +196,7 @@ pub fn invoke(
|
|||||||
compiler.publish_compiled_code();
|
compiler.publish_compiled_code();
|
||||||
|
|
||||||
// Call the trampoline.
|
// Call the trampoline.
|
||||||
if let Err(message) = unsafe {
|
if let Err(TrapMessageAndStack(message, trace)) = unsafe {
|
||||||
instance.with_signals_on(|| {
|
instance.with_signals_on(|| {
|
||||||
wasmtime_call_trampoline(
|
wasmtime_call_trampoline(
|
||||||
callee_vmctx,
|
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`.
|
// Load the return values out of `values_vec`.
|
||||||
|
|||||||
@@ -2,14 +2,45 @@
|
|||||||
|
|
||||||
use crate::function_table::FunctionTable;
|
use crate::function_table::FunctionTable;
|
||||||
use region;
|
use region;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
use std::{cmp, mem};
|
use std::{cmp, mem};
|
||||||
use wasmtime_environ::{Compilation, CompiledFunction};
|
use wasmtime_environ::{Compilation, CompiledFunction};
|
||||||
use wasmtime_runtime::{Mmap, VMFunctionBody};
|
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.
|
/// Memory manager for executable code.
|
||||||
pub struct CodeMemory {
|
pub struct CodeMemory {
|
||||||
current: (Mmap, FunctionTable),
|
current: CodeMemoryEntry,
|
||||||
mmaps: Vec<(Mmap, FunctionTable)>,
|
entries: Vec<CodeMemoryEntry>,
|
||||||
position: usize,
|
position: usize,
|
||||||
published: usize,
|
published: usize,
|
||||||
}
|
}
|
||||||
@@ -23,8 +54,8 @@ impl CodeMemory {
|
|||||||
/// Create a new `CodeMemory` instance.
|
/// Create a new `CodeMemory` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: (Mmap::new(), FunctionTable::new()),
|
current: CodeMemoryEntry::new(),
|
||||||
mmaps: Vec::new(),
|
entries: Vec::new(),
|
||||||
position: 0,
|
position: 0,
|
||||||
published: 0,
|
published: 0,
|
||||||
}
|
}
|
||||||
@@ -81,19 +112,20 @@ impl CodeMemory {
|
|||||||
self.push_current(0)
|
self.push_current(0)
|
||||||
.expect("failed to push current memory map");
|
.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() {
|
if !m.is_empty() {
|
||||||
unsafe {
|
unsafe {
|
||||||
region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute)
|
region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute)
|
||||||
}
|
}
|
||||||
.expect("unable to make memory readonly and executable");
|
.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
|
/// Allocate `size` bytes of memory which can be made executable later by
|
||||||
@@ -103,7 +135,7 @@ impl CodeMemory {
|
|||||||
///
|
///
|
||||||
/// TODO: Add an alignment flag.
|
/// TODO: Add an alignment flag.
|
||||||
fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable), String> {
|
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))?;
|
self.push_current(cmp::max(0x10000, size))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +143,8 @@ impl CodeMemory {
|
|||||||
self.position += size;
|
self.position += size;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
&mut self.current.0.as_mut_slice()[old_position..self.position],
|
&mut self.current.mmap.as_mut_slice()[old_position..self.position],
|
||||||
&mut self.current.1,
|
&mut self.current.table,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,12 +185,19 @@ impl CodeMemory {
|
|||||||
// Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary)
|
// 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 padding = ((func.body.len() + 3) & !3) - func.body.len();
|
||||||
let (unwind, remainder) = remainder.split_at_mut(padding + func.unwind_info.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_start = func_end + (padding as u32);
|
||||||
let unwind_end = unwind_start + (func.unwind_info.len() 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)
|
(unwind_end, remainder, table, vmfunc)
|
||||||
}
|
}
|
||||||
@@ -174,20 +213,17 @@ impl CodeMemory {
|
|||||||
fn push_current(&mut self, new_size: usize) -> Result<(), String> {
|
fn push_current(&mut self, new_size: usize) -> Result<(), String> {
|
||||||
let previous = mem::replace(
|
let previous = mem::replace(
|
||||||
&mut self.current,
|
&mut self.current,
|
||||||
(
|
if new_size == 0 {
|
||||||
if new_size == 0 {
|
CodeMemoryEntry::new()
|
||||||
Mmap::new()
|
} else {
|
||||||
} else {
|
CodeMemoryEntry::with_capacity(cmp::max(0x10000, new_size))?
|
||||||
Mmap::with_at_least(cmp::max(0x10000, new_size))?
|
},
|
||||||
},
|
|
||||||
FunctionTable::new(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if !previous.0.is_empty() {
|
if !previous.mmap.is_empty() {
|
||||||
self.mmaps.push(previous);
|
self.entries.push(previous);
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(previous.1.len(), 0);
|
assert_eq!(previous.table.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.position = 0;
|
self.position = 0;
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap};
|
|||||||
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
|
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
|
||||||
use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex};
|
use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex};
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
Compilation, CompileError, CompiledFunction, Compiler as _C, FunctionBodyData, Module,
|
Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Compiler as _C,
|
||||||
ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets,
|
FunctionBodyData, Module, ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets,
|
||||||
};
|
};
|
||||||
use wasmtime_runtime::{
|
use wasmtime_runtime::{
|
||||||
get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard,
|
get_mut_trap_registry, jit_function_registry, InstantiationError, SignatureRegistry,
|
||||||
VMFunctionBody,
|
TrapRegistrationGuard, VMFunctionBody,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Select which kind of compilation to use.
|
/// Select which kind of compilation to use.
|
||||||
@@ -51,6 +51,7 @@ pub struct Compiler {
|
|||||||
|
|
||||||
code_memory: CodeMemory,
|
code_memory: CodeMemory,
|
||||||
trap_registration_guards: Vec<TrapRegistrationGuard>,
|
trap_registration_guards: Vec<TrapRegistrationGuard>,
|
||||||
|
jit_function_ranges: Vec<(usize, usize)>,
|
||||||
trampoline_park: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
trampoline_park: HashMap<*const VMFunctionBody, *const VMFunctionBody>,
|
||||||
signatures: SignatureRegistry,
|
signatures: SignatureRegistry,
|
||||||
strategy: CompilationStrategy,
|
strategy: CompilationStrategy,
|
||||||
@@ -66,6 +67,7 @@ impl Compiler {
|
|||||||
isa,
|
isa,
|
||||||
code_memory: CodeMemory::new(),
|
code_memory: CodeMemory::new(),
|
||||||
trap_registration_guards: Vec::new(),
|
trap_registration_guards: Vec::new(),
|
||||||
|
jit_function_ranges: Vec::new(),
|
||||||
trampoline_park: HashMap::new(),
|
trampoline_park: HashMap::new(),
|
||||||
signatures: SignatureRegistry::new(),
|
signatures: SignatureRegistry::new(),
|
||||||
fn_builder_ctx: FunctionBuilderContext::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
|
// Having a custom drop implementation we are independent from the field order
|
||||||
// in the struct what reduces potential human error.
|
// in the struct what reduces potential human error.
|
||||||
self.trap_registration_guards.clear();
|
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,
|
&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 dbg = if let Some(debug_data) = debug_data {
|
||||||
let target_config = self.isa.frontend_config();
|
let target_config = self.isa.frontend_config();
|
||||||
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module);
|
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module);
|
||||||
@@ -215,6 +233,7 @@ impl Compiler {
|
|||||||
signature,
|
signature,
|
||||||
value_size,
|
value_size,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
entry.insert(body);
|
entry.insert(body);
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
@@ -266,6 +285,7 @@ fn make_trampoline(
|
|||||||
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig);
|
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);
|
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 code_buf = Vec::new();
|
||||||
let mut unwind_info = Vec::new();
|
|
||||||
let mut reloc_sink = RelocSink {};
|
let mut reloc_sink = RelocSink {};
|
||||||
let mut trap_sink = binemit::NullTrapSink {};
|
let mut trap_sink = binemit::NullTrapSink {};
|
||||||
let mut stackmap_sink = binemit::NullStackmapSink {};
|
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
|
Ok(code_memory
|
||||||
.allocate_for_function(&CompiledFunction {
|
.allocate_for_function(&CompiledFunction {
|
||||||
|
|||||||
@@ -2,42 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This module is primarily used to track JIT functions on Windows for stack walking and unwind.
|
//! This module is primarily used to track JIT functions on Windows for stack walking and unwind.
|
||||||
|
|
||||||
/// Represents a runtime function table.
|
type FunctionTableReloc = wasmtime_environ::CompiledFunctionUnwindInfoReloc;
|
||||||
///
|
|
||||||
/// 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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a runtime function table.
|
/// 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.
|
/// 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.
|
/// 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;
|
use winapi::um::winnt;
|
||||||
|
|
||||||
assert!(!self.published, "table has already been published");
|
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"
|
itertools = "0.8.2"
|
||||||
capstone = "0.6.0"
|
capstone = "0.6.0"
|
||||||
thiserror = "1.0.9"
|
thiserror = "1.0.9"
|
||||||
cranelift-codegen = "0.54"
|
cranelift-codegen = "0.55"
|
||||||
multi_mut = "0.1"
|
multi_mut = "0.1"
|
||||||
either = "1.5"
|
either = "1.5"
|
||||||
typemap = "0.3"
|
typemap = "0.3"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ indexmap = "1.0.2"
|
|||||||
thiserror = "1.0.4"
|
thiserror = "1.0.4"
|
||||||
more-asserts = "0.2.1"
|
more-asserts = "0.2.1"
|
||||||
cfg-if = "0.1.9"
|
cfg-if = "0.1.9"
|
||||||
|
backtrace = "0.3.40"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] }
|
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::mmap::Mmap;
|
||||||
use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
||||||
use crate::table::Table;
|
use crate::table::Table;
|
||||||
use crate::traphandlers::wasmtime_call;
|
use crate::traphandlers::{wasmtime_call, TrapMessageAndStack};
|
||||||
use crate::vmcontext::{
|
use crate::vmcontext::{
|
||||||
VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport,
|
VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport,
|
||||||
VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
||||||
@@ -568,7 +568,7 @@ impl Instance {
|
|||||||
|
|
||||||
// Make the call.
|
// Make the call.
|
||||||
unsafe { wasmtime_call(callee_vmctx, callee_address) }
|
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.
|
/// 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 export;
|
||||||
mod imports;
|
mod imports;
|
||||||
mod instance;
|
mod instance;
|
||||||
@@ -34,8 +35,10 @@ mod trap_registry;
|
|||||||
mod traphandlers;
|
mod traphandlers;
|
||||||
mod vmcontext;
|
mod vmcontext;
|
||||||
|
|
||||||
|
pub mod jit_function_registry;
|
||||||
pub mod libcalls;
|
pub mod libcalls;
|
||||||
|
|
||||||
|
pub use crate::backtrace::{get_backtrace, Backtrace, BacktraceFrame};
|
||||||
pub use crate::export::Export;
|
pub use crate::export::Export;
|
||||||
pub use crate::imports::Imports;
|
pub use crate::imports::Imports;
|
||||||
pub use crate::instance::{InstanceHandle, InstantiationError, LinkError};
|
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::sig_registry::SignatureRegistry;
|
||||||
pub use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish};
|
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::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::{
|
pub use crate::vmcontext::{
|
||||||
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
|
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
|
||||||
VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! WebAssembly trap handling, which is built on top of the lower-level
|
//! WebAssembly trap handling, which is built on top of the lower-level
|
||||||
//! signalhandling mechanisms.
|
//! signalhandling mechanisms.
|
||||||
|
|
||||||
|
use crate::backtrace::{get_backtrace, Backtrace};
|
||||||
use crate::trap_registry::get_trap_registry;
|
use crate::trap_registry::get_trap_registry;
|
||||||
use crate::trap_registry::TrapDescription;
|
use crate::trap_registry::TrapDescription;
|
||||||
use crate::vmcontext::{VMContext, VMFunctionBody};
|
use crate::vmcontext::{VMContext, VMFunctionBody};
|
||||||
@@ -18,7 +19,7 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
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 JMP_BUF: Cell<*const u8> = Cell::new(ptr::null());
|
||||||
static RESET_GUARD_PAGE: Cell<bool> = Cell::new(false);
|
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,
|
trap_code: ir::TrapCode::StackOverflow,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let trap_backtrace = get_backtrace();
|
||||||
|
|
||||||
if reset_guard_page {
|
if reset_guard_page {
|
||||||
RESET_GUARD_PAGE.with(|v| v.set(true));
|
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,
|
None,
|
||||||
"Only one trap per thread can be recorded at a moment!"
|
"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"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn reset_guard_page() {}
|
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
|
let trap_desc = RECORDED_TRAP
|
||||||
.with(|data| data.replace(None))
|
.with(|data| data.replace(None))
|
||||||
.expect("trap_message must be called after trap occurred");
|
.expect("trap_message must be called after trap occurred");
|
||||||
|
|
||||||
format!(
|
TrapMessageAndStack(
|
||||||
"wasm trap: {}, source location: {}",
|
format!(
|
||||||
trap_code_to_expected_string(trap_desc.trap_code),
|
"wasm trap: {}, source location: {}",
|
||||||
trap_desc.source_loc,
|
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,
|
vmctx: *mut VMContext,
|
||||||
callee: *const VMFunctionBody,
|
callee: *const VMFunctionBody,
|
||||||
values_vec: *mut u8,
|
values_vec: *mut u8,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), TrapMessageAndStack> {
|
||||||
if WasmtimeCallTrampoline(vmctx as *mut u8, callee, values_vec) == 0 {
|
if WasmtimeCallTrampoline(vmctx as *mut u8, callee, values_vec) == 0 {
|
||||||
Err(trap_message())
|
Err(trap_message_and_stack())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -160,9 +170,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline(
|
|||||||
pub unsafe extern "C" fn wasmtime_call(
|
pub unsafe extern "C" fn wasmtime_call(
|
||||||
vmctx: *mut VMContext,
|
vmctx: *mut VMContext,
|
||||||
callee: *const VMFunctionBody,
|
callee: *const VMFunctionBody,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), TrapMessageAndStack> {
|
||||||
if WasmtimeCall(vmctx as *mut u8, callee) == 0 {
|
if WasmtimeCall(vmctx as *mut u8, callee) == 0 {
|
||||||
Err(trap_message())
|
Err(trap_message_and_stack())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ edition = "2018"
|
|||||||
wasmtime-runtime = { path = "../runtime", version = "0.9.0" }
|
wasmtime-runtime = { path = "../runtime", version = "0.9.0" }
|
||||||
wasmtime-environ = { path = "../environ", version = "0.9.0" }
|
wasmtime-environ = { path = "../environ", version = "0.9.0" }
|
||||||
wasmtime-jit = { path = "../jit", version = "0.9.0" }
|
wasmtime-jit = { path = "../jit", version = "0.9.0" }
|
||||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||||
target-lexicon = "0.10.0"
|
target-lexicon = "0.10.0"
|
||||||
log = { version = "0.4.8", default-features = false }
|
log = { version = "0.4.8", default-features = false }
|
||||||
libc = "0.2.60"
|
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-environ = { path = "../environ", version = "0.9.0" }
|
||||||
wasmtime-jit = { path = "../jit", version = "0.9.0" }
|
wasmtime-jit = { path = "../jit", version = "0.9.0" }
|
||||||
wasi-common = { path = "../wasi-common", version = "0.9.0" }
|
wasi-common = { path = "../wasi-common", version = "0.9.0" }
|
||||||
cranelift-codegen = { version = "0.54", features = ["enable-serde"] }
|
cranelift-codegen = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-entity = { version = "0.54", features = ["enable-serde"] }
|
cranelift-entity = { version = "0.55", features = ["enable-serde"] }
|
||||||
cranelift-wasm = { version = "0.54", features = ["enable-serde"] }
|
cranelift-wasm = { version = "0.55", features = ["enable-serde"] }
|
||||||
target-lexicon = "0.10.0"
|
target-lexicon = "0.10.0"
|
||||||
log = { version = "0.4.8", default-features = false }
|
log = { version = "0.4.8", default-features = false }
|
||||||
wig = { path = "../wasi-common/wig", version = "0.9.2" }
|
wig = { path = "../wasi-common/wig", version = "0.9.2" }
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ topdir=$(dirname "$0")/..
|
|||||||
cd "$topdir"
|
cd "$topdir"
|
||||||
|
|
||||||
# All the cranelift-* crates have the same version number
|
# All the cranelift-* crates have the same version number
|
||||||
version="0.53"
|
version="0.55"
|
||||||
|
|
||||||
# Update all of the Cargo.toml files.
|
# Update all of the Cargo.toml files.
|
||||||
echo "Updating crate versions to $version"
|
echo "Updating crate versions to $version"
|
||||||
|
|||||||
Reference in New Issue
Block a user