489: Su Engine: Decoding and encoding runtime state. r=losfair a=losfair

This PR implements a managed runtime that is able to suspend a running WebAssembly program at arbitrary point in time, decode its machine state (registers and stack) into the corresponding WebAssembly abstract state (call frames, locals and the value stack), and encode the abstract state back into machine state for resuming execution later.

Features enabled by this PR include:

- Tier (compiler backend) switching at runtime.
- Debugging with backtraces and local variables.
- Suspending to disk.
- Live migration.

The name "Su" corresponds to 「溯」 in Chinese, originating from 「溯洄从之」 in The Book of Songs.

Co-authored-by: losfair <zhy20000919@hotmail.com>
This commit is contained in:
bors[bot] 2019-07-05 01:39:41 +00:00
commit d26cee8bba
29 changed files with 2968 additions and 586 deletions

82
Cargo.lock generated
View File

@ -16,6 +16,11 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "approx"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayref"
version = "0.3.5"
@ -65,6 +70,16 @@ dependencies = [
"libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bincode"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bindgen"
version = "0.46.0"
@ -163,6 +178,16 @@ name = "cfg-if"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cgmath"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clang-sys"
version = "0.26.4"
@ -203,6 +228,15 @@ dependencies = [
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "colored"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "constant_time_eq"
version = "0.1.3"
@ -706,6 +740,14 @@ dependencies = [
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.8"
@ -814,6 +856,18 @@ dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.6.5"
@ -999,6 +1053,11 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rgb"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc-demangle"
version = "0.1.15"
@ -1496,7 +1555,10 @@ dependencies = [
name = "wasmer-runtime-core"
version = "0.5.3"
dependencies = [
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2b_simd 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"field-offset 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1523,6 +1585,7 @@ name = "wasmer-singlepass-backend"
version = "0.5.3"
dependencies = [
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dynasm 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"dynasmrt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1635,15 +1698,28 @@ dependencies = [
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winconsole"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
"checksum backtrace 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)" = "1a13fc43f04daf08ab4f71e3d27e1fc27fc437d3e95ac0063a796d92fb40f39b"
"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
"checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7"
"checksum bindgen 0.46.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f7f7f0701772b17de73e4f5cbcb1dd6926f4706cba4c1ab62c5367f8bdc94e1"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum blake2b_simd 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce2571a6cd634670daa2977cc894c1cc2ba57c563c498e5a82c35446f34d056e"
@ -1655,10 +1731,12 @@ dependencies = [
"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d"
"checksum cexpr 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7fa24eb00d5ffab90eaeaf1092ac85c04c64aaf358ea6f84505b8116d24c6af"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64a4b57c8f4e3a2e9ac07e0f6abc9c24b6fc9e1b54c3478cfb598f3d0023e51c"
"checksum clang-sys 0.26.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ef0c1bcf2e99c649104bd7a7012d8f8802684400e03db0ec0af48583c6fa0e4"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum cmake 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "2ca4386c8954b76a8415b63959337d940d724b336cabd3afe189c2b51a7e1ff0"
"checksum colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6cdb90b60f2927f8d76139c72dbde7e10c3a2bc47c8594c9c7a66529f2687c03"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
"checksum cranelift-bforest 0.31.0 (git+https://github.com/wasmerio/cranelift.git?rev=2ada531d79b34a9e6c94c81f2615677e22d68780)" = "<none>"
"checksum cranelift-codegen 0.31.0 (git+https://github.com/wasmerio/cranelift.git?rev=2ada531d79b34a9e6c94c81f2615677e22d68780)" = "<none>"
@ -1715,6 +1793,7 @@ dependencies = [
"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
@ -1729,6 +1808,7 @@ dependencies = [
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
@ -1749,6 +1829,7 @@ dependencies = [
"checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58"
"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
"checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
@ -1804,3 +1885,4 @@ dependencies = [
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
"checksum winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef84b96d10db72dd980056666d7f1e7663ce93d82fa33b63e71c966f4cf5032"

74
examples/iterative_hash/Cargo.lock generated Normal file
View File

@ -0,0 +1,74 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "blake2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "crypto-mac"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "generic-array"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "iterative_hash"
version = "0.1.0"
dependencies = [
"blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "opaque-debug"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "subtle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "typenum"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91721a6330935673395a0607df4d49a9cb90ae12d259f1b3e0a3f6e1d486872e"
"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409"
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"

View File

@ -0,0 +1,12 @@
[package]
name = "iterative_hash"
version = "0.1.0"
authors = ["losfair <zhy20000919@hotmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
[dependencies]
blake2 = "0.8"

View File

@ -0,0 +1,16 @@
use blake2::{Blake2b, Digest};
fn main() {
let mut data: Vec<u8> = b"test".to_vec();
for i in 0.. {
let mut hasher = Blake2b::new();
hasher.input(&data);
let out = hasher.result();
data = out.to_vec();
if i % 1000000 == 0 {
println!("Round {}: {:?}", i, data);
}
}
}

View File

@ -0,0 +1,67 @@
#[link(wasm_import_module = "wasmer_suspend")]
extern "C" {
fn suspend();
}
use std::collections::BTreeMap;
#[derive(Default)]
struct Node {
count: usize,
children: BTreeMap<char, Node>,
}
impl Node {
fn insert(&mut self, mut s: impl Iterator<Item = char>) {
match s.next() {
Some(x) => {
self.children.entry(x).or_default().insert(s);
}
None => {
self.count += 1;
}
}
}
fn for_each_dyn(&self, cb: &dyn Fn(&str, usize), prefix: &mut String) {
if self.count > 0 {
cb(&prefix, self.count);
}
for (k, v) in self.children.iter() {
prefix.push(*k);
v.for_each_dyn(cb, prefix);
prefix.pop().unwrap();
}
}
}
fn main() {
let mut root = Node::default();
root.insert("Ava".chars());
root.insert("Alexander".chars());
root.insert("Aiden".chars());
root.insert("Bella".chars());
root.insert("Brianna".chars());
root.insert("Brielle".chars());
root.insert("Charlotte".chars());
root.insert("Chloe".chars());
root.insert("Camila".chars());
println!("Tree ready, suspending.");
unsafe {
suspend();
}
root.for_each_dyn(
&|seq, count| {
println!("{}: {}", seq, count);
unsafe {
suspend();
}
},
&mut "".into(),
);
println!("[END]");
}

View File

@ -164,6 +164,7 @@ impl Intrinsics {
let memory_base_ty = i8_ty;
let memory_bound_ty = i8_ty;
let internals_ty = i64_ty;
let interrupt_signal_mem_ty = i8_ty;
let local_function_ty = i8_ptr_ty;
let anyfunc_ty = context.struct_type(
@ -222,6 +223,9 @@ impl Intrinsics {
internals_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
interrupt_signal_mem_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
local_function_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),

View File

@ -17,6 +17,7 @@ impl FunctionMiddleware for CallTrace {
Event::Internal(InternalEvent::FunctionBegin(id)) => sink.push(Event::Internal(
InternalEvent::Breakpoint(Box::new(move |_| {
eprintln!("func ({})", id);
Ok(())
})),
)),
_ => {}

View File

@ -94,11 +94,9 @@ impl FunctionMiddleware for Metering {
sink.push(Event::WasmOwned(Operator::If {
ty: WpTypeOrFuncType::Type(WpType::EmptyBlockType),
}));
sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new(
move |ctx| unsafe {
(ctx.throw)(Box::new(ExecutionLimitExceededError));
},
))));
sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new(|_| {
Err(Box::new(ExecutionLimitExceededError))
}))));
sink.push(Event::WasmOwned(Operator::End));
}
_ => {}

View File

@ -18,6 +18,8 @@ errno = "0.2.4"
libc = "0.2.49"
hex = "0.3.2"
smallvec = "0.6.9"
bincode = "1.1"
colored = "1.8"
# Dependencies for caching.
[dependencies.serde]
@ -47,6 +49,7 @@ field-offset = "0.1.1"
[build-dependencies]
blake2b_simd = "0.4.1"
rustc_version = "0.2.3"
cc = "1.0"
[features]
debug = []

View File

@ -28,4 +28,16 @@ fn main() {
if rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly {
println!("cargo:rustc-cfg=nightly");
}
if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
cc::Build::new()
.file("image-loading-linux-x86-64.s")
.compile("image-loading");
} else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
cc::Build::new()
.file("image-loading-macos-x86-64.s")
.compile("image-loading");
} else {
}
}

View File

@ -0,0 +1,69 @@
# NOTE: Keep this consistent with `fault.rs`.
.globl run_on_alternative_stack
run_on_alternative_stack:
# (stack_end, stack_begin)
# We need to ensure 16-byte alignment here.
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
pushq %rbp
movq %rsp, -16(%rdi)
leaq run_on_alternative_stack.returning(%rip), %rax
movq %rax, -24(%rdi)
movq %rsi, %rsp
movq (%rsp), %xmm0
add $8, %rsp
movq (%rsp), %xmm1
add $8, %rsp
movq (%rsp), %xmm2
add $8, %rsp
movq (%rsp), %xmm3
add $8, %rsp
movq (%rsp), %xmm4
add $8, %rsp
movq (%rsp), %xmm5
add $8, %rsp
movq (%rsp), %xmm6
add $8, %rsp
movq (%rsp), %xmm7
add $8, %rsp
popq %rbp
popq %rax
popq %rbx
popq %rcx
popq %rdx
popq %rdi
popq %rsi
popq %r8
popq %r9
popq %r10
popq %r11
popq %r12
popq %r13
popq %r14
popq %r15
retq
run_on_alternative_stack.returning:
movq (%rsp), %rsp
popq %rbp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
retq

View File

@ -0,0 +1,69 @@
# NOTE: Keep this consistent with `fault.rs`.
.globl _run_on_alternative_stack
_run_on_alternative_stack:
# (stack_end, stack_begin)
# We need to ensure 16-byte alignment here.
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
pushq %rbp
movq %rsp, -16(%rdi)
leaq _run_on_alternative_stack.returning(%rip), %rax
movq %rax, -24(%rdi)
movq %rsi, %rsp
movq (%rsp), %xmm0
add $8, %rsp
movq (%rsp), %xmm1
add $8, %rsp
movq (%rsp), %xmm2
add $8, %rsp
movq (%rsp), %xmm3
add $8, %rsp
movq (%rsp), %xmm4
add $8, %rsp
movq (%rsp), %xmm5
add $8, %rsp
movq (%rsp), %xmm6
add $8, %rsp
movq (%rsp), %xmm7
add $8, %rsp
popq %rbp
popq %rax
popq %rbx
popq %rcx
popq %rdx
popq %rdi
popq %rsi
popq %r8
popq %r9
popq %r10
popq %r11
popq %r12
popq %r13
popq %r14
popq %r15
retq
_run_on_alternative_stack.returning:
movq (%rsp), %rsp
popq %rbp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
retq

View File

@ -1,6 +1,7 @@
use crate::{
error::CompileResult,
module::ModuleInner,
state::ModuleStateMap,
typed_func::Wasm,
types::{LocalFuncIndex, SigIndex},
vm,
@ -8,6 +9,7 @@ use crate::{
use crate::{
cache::{Artifact, Error as CacheError},
codegen::BreakpointMap,
module::ModuleInfo,
sys::Memory,
};
@ -84,6 +86,14 @@ pub trait RunnableModule: Send + Sync {
local_func_index: LocalFuncIndex,
) -> Option<NonNull<vm::Func>>;
fn get_module_state_map(&self) -> Option<ModuleStateMap> {
None
}
fn get_breakpoints(&self) -> Option<BreakpointMap> {
None
}
/// A wasm trampoline contains the necessary data to dynamically call an exported wasm function.
/// Given a particular signature index, we are returned a trampoline that is matched with that
/// signature and an invoke function that can call the trampoline.

View File

@ -9,6 +9,7 @@ use crate::{
};
use smallvec::SmallVec;
use std::any::Any;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::marker::PhantomData;
@ -16,6 +17,10 @@ use std::sync::{Arc, RwLock};
use wasmparser::{self, WasmDecoder};
use wasmparser::{Operator, Type as WpType};
pub type BreakpointHandler =
Box<Fn(BreakpointInfo) -> Result<(), Box<dyn Any>> + Send + Sync + 'static>;
pub type BreakpointMap = Arc<HashMap<usize, BreakpointHandler>>;
#[derive(Debug)]
pub enum Event<'a, 'b> {
Internal(InternalEvent),
@ -26,7 +31,7 @@ pub enum Event<'a, 'b> {
pub enum InternalEvent {
FunctionBegin(u32),
FunctionEnd,
Breakpoint(Box<Fn(BkptInfo) + Send + Sync + 'static>),
Breakpoint(BreakpointHandler),
SetInternal(u32),
GetInternal(u32),
}
@ -43,8 +48,8 @@ impl fmt::Debug for InternalEvent {
}
}
pub struct BkptInfo {
pub throw: unsafe fn(Box<dyn Any>) -> !,
pub struct BreakpointInfo<'a> {
pub fault: Option<&'a dyn Any>,
}
pub trait ModuleCodeGenerator<FCG: FunctionCodeGenerator<E>, RM: RunnableModule, E: Debug> {

View File

@ -0,0 +1,464 @@
mod raw {
use std::ffi::c_void;
extern "C" {
pub fn run_on_alternative_stack(stack_end: *mut u64, stack_begin: *mut u64) -> u64;
pub fn setjmp(env: *mut c_void) -> i32;
pub fn longjmp(env: *mut c_void, val: i32) -> !;
}
}
use crate::codegen::{BreakpointInfo, BreakpointMap};
use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR, XMM};
use crate::vm;
use libc::{mmap, mprotect, siginfo_t, MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
use nix::sys::signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGINT,
SIGSEGV, SIGTRAP,
};
use std::any::Any;
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once;
pub(crate) unsafe fn run_on_alternative_stack(stack_end: *mut u64, stack_begin: *mut u64) -> u64 {
raw::run_on_alternative_stack(stack_end, stack_begin)
}
const TRAP_STACK_SIZE: usize = 1048576; // 1MB
const SETJMP_BUFFER_LEN: usize = 27;
type SetJmpBuffer = [i32; SETJMP_BUFFER_LEN];
struct UnwindInfo {
jmpbuf: SetJmpBuffer, // in
breakpoints: Option<BreakpointMap>,
payload: Option<Box<Any>>, // out
}
thread_local! {
static UNWIND: UnsafeCell<Option<UnwindInfo>> = UnsafeCell::new(None);
}
struct InterruptSignalMem(*mut u8);
unsafe impl Send for InterruptSignalMem {}
unsafe impl Sync for InterruptSignalMem {}
const INTERRUPT_SIGNAL_MEM_SIZE: usize = 4096;
lazy_static! {
static ref INTERRUPT_SIGNAL_MEM: InterruptSignalMem = {
let ptr = unsafe {
mmap(
::std::ptr::null_mut(),
INTERRUPT_SIGNAL_MEM_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0,
)
};
if ptr as isize == -1 {
panic!("cannot allocate code memory");
}
InterruptSignalMem(ptr as _)
};
}
static INTERRUPT_SIGNAL_DELIVERED: AtomicBool = AtomicBool::new(false);
pub unsafe fn get_wasm_interrupt_signal_mem() -> *mut u8 {
INTERRUPT_SIGNAL_MEM.0
}
pub unsafe fn set_wasm_interrupt() {
let mem: *mut u8 = INTERRUPT_SIGNAL_MEM.0;
if mprotect(mem as _, INTERRUPT_SIGNAL_MEM_SIZE, PROT_NONE) < 0 {
panic!("cannot set PROT_NONE on signal mem");
}
}
pub unsafe fn clear_wasm_interrupt() {
let mem: *mut u8 = INTERRUPT_SIGNAL_MEM.0;
if mprotect(mem as _, INTERRUPT_SIGNAL_MEM_SIZE, PROT_READ | PROT_WRITE) < 0 {
panic!("cannot set PROT_READ | PROT_WRITE on signal mem");
}
}
pub unsafe fn catch_unsafe_unwind<R, F: FnOnce() -> R>(
f: F,
breakpoints: Option<BreakpointMap>,
) -> Result<R, Box<Any>> {
let unwind = UNWIND.with(|x| x.get());
let old = (*unwind).take();
*unwind = Some(UnwindInfo {
jmpbuf: [0; SETJMP_BUFFER_LEN],
breakpoints: breakpoints,
payload: None,
});
if raw::setjmp(&mut (*unwind).as_mut().unwrap().jmpbuf as *mut SetJmpBuffer as *mut _) != 0 {
// error
let ret = (*unwind).as_mut().unwrap().payload.take().unwrap();
*unwind = old;
Err(ret)
} else {
let ret = f();
// implicit control flow to the error case...
*unwind = old;
Ok(ret)
}
}
pub unsafe fn begin_unsafe_unwind(e: Box<Any>) -> ! {
let unwind = UNWIND.with(|x| x.get());
let inner = (*unwind)
.as_mut()
.expect("not within a catch_unsafe_unwind scope");
inner.payload = Some(e);
raw::longjmp(&mut inner.jmpbuf as *mut SetJmpBuffer as *mut _, 0xffff);
}
unsafe fn with_breakpoint_map<R, F: FnOnce(Option<&BreakpointMap>) -> R>(f: F) -> R {
let unwind = UNWIND.with(|x| x.get());
let inner = (*unwind)
.as_mut()
.expect("not within a catch_unsafe_unwind scope");
f(inner.breakpoints.as_ref())
}
pub fn allocate_and_run<R, F: FnOnce() -> R>(size: usize, f: F) -> R {
struct Context<F: FnOnce() -> R, R> {
f: Option<F>,
ret: Option<R>,
}
extern "C" fn invoke<F: FnOnce() -> R, R>(ctx: &mut Context<F, R>) {
let f = ctx.f.take().unwrap();
ctx.ret = Some(f());
}
unsafe {
let mut ctx = Context {
f: Some(f),
ret: None,
};
assert!(size % 16 == 0);
assert!(size >= 4096);
let mut stack: Vec<u64> = vec![0; size / 8];
let end_offset = stack.len();
stack[end_offset - 4] = invoke::<F, R> as usize as u64;
// NOTE: Keep this consistent with `image-loading-*.s`.
stack[end_offset - 4 - 10] = &mut ctx as *mut Context<F, R> as usize as u64; // rdi
const NUM_SAVED_REGISTERS: usize = 23;
let stack_begin = stack
.as_mut_ptr()
.offset((end_offset - 4 - NUM_SAVED_REGISTERS) as isize);
let stack_end = stack.as_mut_ptr().offset(end_offset as isize);
raw::run_on_alternative_stack(stack_end, stack_begin);
ctx.ret.take().unwrap()
}
}
extern "C" fn signal_trap_handler(
signum: ::nix::libc::c_int,
siginfo: *mut siginfo_t,
ucontext: *mut c_void,
) {
unsafe {
let fault = get_fault_info(siginfo as _, ucontext);
let mut unwind_result: Box<dyn Any> = Box::new(());
let should_unwind = allocate_and_run(TRAP_STACK_SIZE, || {
let mut is_suspend_signal = false;
match Signal::from_c_int(signum) {
Ok(SIGTRAP) => {
// breakpoint
let out: Option<Result<(), Box<dyn Any>>> = with_breakpoint_map(|bkpt_map| {
bkpt_map.and_then(|x| x.get(&(fault.ip as usize))).map(|x| {
x(BreakpointInfo {
fault: Some(&fault),
})
})
});
match out {
Some(Ok(())) => {
return false;
}
Some(Err(e)) => {
unwind_result = e;
return true;
}
None => {}
}
}
Ok(SIGSEGV) | Ok(SIGBUS) => {
if fault.faulting_addr as usize == get_wasm_interrupt_signal_mem() as usize {
is_suspend_signal = true;
clear_wasm_interrupt();
INTERRUPT_SIGNAL_DELIVERED.store(false, Ordering::SeqCst);
}
}
_ => {}
}
// TODO: make this safer
let ctx = &mut *(fault.known_registers[X64Register::GPR(GPR::R15).to_index().0].unwrap()
as *mut vm::Ctx);
let rsp = fault.known_registers[X64Register::GPR(GPR::RSP).to_index().0].unwrap();
let msm = (*ctx.module)
.runnable_module
.get_module_state_map()
.unwrap();
let code_base = (*ctx.module).runnable_module.get_code().unwrap().as_ptr() as usize;
let es_image = read_stack(
&msm,
code_base,
rsp as usize as *const u64,
fault.known_registers,
Some(fault.ip as usize as u64),
);
if is_suspend_signal {
let image = build_instance_image(ctx, es_image);
unwind_result = Box::new(image);
} else {
use colored::*;
if es_image.frames.len() > 0 {
eprintln!(
"\n{}",
"Wasmer encountered an error while running your WebAssembly program."
.bold()
.red()
);
es_image.print_backtrace_if_needed();
}
// Just let the error propagate otherrwise
}
true
});
if should_unwind {
begin_unsafe_unwind(unwind_result);
}
}
}
extern "C" fn sigint_handler(
_signum: ::nix::libc::c_int,
_siginfo: *mut siginfo_t,
_ucontext: *mut c_void,
) {
if INTERRUPT_SIGNAL_DELIVERED.swap(true, Ordering::SeqCst) {
eprintln!("Got another SIGINT before trap is triggered on WebAssembly side, aborting");
process::abort();
}
unsafe {
set_wasm_interrupt();
}
}
pub fn ensure_sighandler() {
INSTALL_SIGHANDLER.call_once(|| unsafe {
install_sighandler();
});
}
static INSTALL_SIGHANDLER: Once = Once::new();
unsafe fn install_sighandler() {
let sa_trap = SigAction::new(
SigHandler::SigAction(signal_trap_handler),
SaFlags::SA_ONSTACK,
SigSet::empty(),
);
sigaction(SIGFPE, &sa_trap).unwrap();
sigaction(SIGILL, &sa_trap).unwrap();
sigaction(SIGSEGV, &sa_trap).unwrap();
sigaction(SIGBUS, &sa_trap).unwrap();
sigaction(SIGTRAP, &sa_trap).unwrap();
let sa_interrupt = SigAction::new(
SigHandler::SigAction(sigint_handler),
SaFlags::SA_ONSTACK,
SigSet::empty(),
);
sigaction(SIGINT, &sa_interrupt).unwrap();
}
pub struct FaultInfo {
pub faulting_addr: *const c_void,
pub ip: *const c_void,
pub known_registers: [Option<u64>; 24],
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo {
use libc::{
_libc_xmmreg, ucontext_t, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8,
REG_R9, REG_RAX, REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP,
};
fn read_xmm(reg: &_libc_xmmreg) -> u64 {
(reg.element[0] as u64) | ((reg.element[1] as u64) << 32)
}
#[allow(dead_code)]
#[repr(C)]
struct siginfo_t {
si_signo: i32,
si_errno: i32,
si_code: i32,
si_addr: u64,
// ...
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let gregs = &(*ucontext).uc_mcontext.gregs;
let fpregs = &*(*ucontext).uc_mcontext.fpregs;
let mut known_registers: [Option<u64>; 24] = [None; 24];
known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(gregs[REG_R15 as usize] as _);
known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(gregs[REG_R14 as usize] as _);
known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(gregs[REG_R13 as usize] as _);
known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(gregs[REG_R12 as usize] as _);
known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(gregs[REG_R11 as usize] as _);
known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(gregs[REG_R10 as usize] as _);
known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(gregs[REG_R9 as usize] as _);
known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(gregs[REG_R8 as usize] as _);
known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(gregs[REG_RSI as usize] as _);
known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(gregs[REG_RDI as usize] as _);
known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(gregs[REG_RDX as usize] as _);
known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(gregs[REG_RCX as usize] as _);
known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(gregs[REG_RBX as usize] as _);
known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(gregs[REG_RAX as usize] as _);
known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(gregs[REG_RBP as usize] as _);
known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(gregs[REG_RSP as usize] as _);
known_registers[X64Register::XMM(XMM::XMM0).to_index().0] = Some(read_xmm(&fpregs._xmm[0]));
known_registers[X64Register::XMM(XMM::XMM1).to_index().0] = Some(read_xmm(&fpregs._xmm[1]));
known_registers[X64Register::XMM(XMM::XMM2).to_index().0] = Some(read_xmm(&fpregs._xmm[2]));
known_registers[X64Register::XMM(XMM::XMM3).to_index().0] = Some(read_xmm(&fpregs._xmm[3]));
known_registers[X64Register::XMM(XMM::XMM4).to_index().0] = Some(read_xmm(&fpregs._xmm[4]));
known_registers[X64Register::XMM(XMM::XMM5).to_index().0] = Some(read_xmm(&fpregs._xmm[5]));
known_registers[X64Register::XMM(XMM::XMM6).to_index().0] = Some(read_xmm(&fpregs._xmm[6]));
known_registers[X64Register::XMM(XMM::XMM7).to_index().0] = Some(read_xmm(&fpregs._xmm[7]));
FaultInfo {
faulting_addr: si_addr as usize as _,
ip: gregs[REG_RIP as usize] as _,
known_registers,
}
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo {
#[allow(dead_code)]
#[repr(C)]
struct ucontext_t {
uc_onstack: u32,
uc_sigmask: u32,
uc_stack: libc::stack_t,
uc_link: *const ucontext_t,
uc_mcsize: u64,
uc_mcontext: *const mcontext_t,
}
#[repr(C)]
struct exception_state {
trapno: u16,
cpu: u16,
err: u32,
faultvaddr: u64,
}
#[repr(C)]
struct regs {
rax: u64,
rbx: u64,
rcx: u64,
rdx: u64,
rdi: u64,
rsi: u64,
rbp: u64,
rsp: u64,
r8: u64,
r9: u64,
r10: u64,
r11: u64,
r12: u64,
r13: u64,
r14: u64,
r15: u64,
rip: u64,
rflags: u64,
cs: u64,
fs: u64,
gs: u64,
}
#[repr(C)]
struct fpstate {
_unused: [u8; 168],
xmm: [[u64; 2]; 8],
}
#[allow(dead_code)]
#[repr(C)]
struct mcontext_t {
es: exception_state,
ss: regs,
fs: fpstate,
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let ss = &(*(*ucontext).uc_mcontext).ss;
let fs = &(*(*ucontext).uc_mcontext).fs;
let mut known_registers: [Option<u64>; 24] = [None; 24];
known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(ss.r15);
known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(ss.r14);
known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(ss.r13);
known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(ss.r12);
known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(ss.r11);
known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(ss.r10);
known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(ss.r9);
known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(ss.r8);
known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(ss.rsi);
known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(ss.rdi);
known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(ss.rdx);
known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(ss.rcx);
known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(ss.rbx);
known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(ss.rax);
known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(ss.rbp);
known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(ss.rsp);
known_registers[X64Register::XMM(XMM::XMM0).to_index().0] = Some(fs.xmm[0][0]);
known_registers[X64Register::XMM(XMM::XMM1).to_index().0] = Some(fs.xmm[1][0]);
known_registers[X64Register::XMM(XMM::XMM2).to_index().0] = Some(fs.xmm[2][0]);
known_registers[X64Register::XMM(XMM::XMM3).to_index().0] = Some(fs.xmm[3][0]);
known_registers[X64Register::XMM(XMM::XMM4).to_index().0] = Some(fs.xmm[4][0]);
known_registers[X64Register::XMM(XMM::XMM5).to_index().0] = Some(fs.xmm[5][0]);
known_registers[X64Register::XMM(XMM::XMM6).to_index().0] = Some(fs.xmm[6][0]);
known_registers[X64Register::XMM(XMM::XMM7).to_index().0] = Some(fs.xmm[7][0]);
FaultInfo {
faulting_addr: si_addr,
ip: ss.rip as _,
known_registers,
}
}

View File

@ -46,7 +46,7 @@ impl IsExport for Export {
/// ```
pub struct ImportObject {
map: Rc<RefCell<HashMap<String, Box<dyn LikeNamespace>>>>,
state_creator: Option<Rc<Fn() -> (*mut c_void, fn(*mut c_void))>>,
pub(crate) state_creator: Option<Rc<Fn() -> (*mut c_void, fn(*mut c_void))>>,
pub allow_missing_functions: bool,
}

View File

@ -43,6 +43,9 @@ pub mod vm;
pub mod vmcalls;
#[cfg(all(unix, target_arch = "x86_64"))]
pub use trampoline_x64 as trampoline;
#[cfg(all(unix, target_arch = "x86_64"))]
pub mod fault;
pub mod state;
use self::error::CompileResult;
#[doc(inline)]

View File

@ -223,13 +223,6 @@ pub fn read_module<
let fcg = mcg
.next_function(Arc::clone(&info))
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
middlewares
.run(
Some(fcg),
Event::Internal(InternalEvent::FunctionBegin(id as u32)),
&info.read().unwrap(),
)
.map_err(|x| LoadError::Codegen(x))?;
let info_read = info.read().unwrap();
let sig = info_read
@ -271,6 +264,13 @@ pub fn read_module<
body_begun = true;
fcg.begin_body(&info.read().unwrap())
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
middlewares
.run(
Some(fcg),
Event::Internal(InternalEvent::FunctionBegin(id as u32)),
&info.read().unwrap(),
)
.map_err(|x| LoadError::Codegen(x))?;
}
middlewares
.run(Some(fcg), Event::Wasm(op), &info.read().unwrap())

View File

@ -0,0 +1,840 @@
use std::collections::BTreeMap;
use std::ops::Bound::{Included, Unbounded};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct RegisterIndex(pub usize);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum WasmAbstractValue {
Runtime,
Const(u64),
}
#[derive(Clone, Debug)]
pub struct MachineState {
pub stack_values: Vec<MachineValue>,
pub register_values: Vec<MachineValue>,
pub wasm_stack: Vec<WasmAbstractValue>,
pub wasm_stack_private_depth: usize,
pub wasm_inst_offset: usize,
}
#[derive(Clone, Debug, Default)]
pub struct MachineStateDiff {
pub last: Option<usize>,
pub stack_push: Vec<MachineValue>,
pub stack_pop: usize,
pub reg_diff: Vec<(RegisterIndex, MachineValue)>,
pub wasm_stack_push: Vec<WasmAbstractValue>,
pub wasm_stack_pop: usize,
pub wasm_stack_private_depth: usize, // absolute value; not a diff.
pub wasm_inst_offset: usize, // absolute value; not a diff.
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum MachineValue {
Undefined,
Vmctx,
PreserveRegister(RegisterIndex),
CopyStackBPRelative(i32), // relative to Base Pointer, in byte offset
ExplicitShadow, // indicates that all values above this are above the shadow region
WasmStack(usize),
WasmLocal(usize),
}
#[derive(Clone, Debug)]
pub struct FunctionStateMap {
pub initial: MachineState,
pub local_function_id: usize,
pub locals: Vec<WasmAbstractValue>,
pub shadow_size: usize, // for single-pass backend, 32 bytes on x86-64
pub diffs: Vec<MachineStateDiff>,
pub wasm_function_header_target_offset: Option<SuspendOffset>,
pub wasm_offset_to_target_offset: BTreeMap<usize, SuspendOffset>,
pub loop_offsets: BTreeMap<usize, OffsetInfo>, /* suspend_offset -> info */
pub call_offsets: BTreeMap<usize, OffsetInfo>, /* suspend_offset -> info */
pub trappable_offsets: BTreeMap<usize, OffsetInfo>, /* suspend_offset -> info */
}
#[derive(Clone, Copy, Debug)]
pub enum SuspendOffset {
Loop(usize),
Call(usize),
Trappable(usize),
}
#[derive(Clone, Debug)]
pub struct OffsetInfo {
pub diff_id: usize,
pub activate_offset: usize,
}
#[derive(Clone, Debug)]
pub struct ModuleStateMap {
pub local_functions: BTreeMap<usize, FunctionStateMap>,
pub total_size: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WasmFunctionStateDump {
pub local_function_id: usize,
pub wasm_inst_offset: usize,
pub stack: Vec<Option<u64>>,
pub locals: Vec<Option<u64>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExecutionStateImage {
pub frames: Vec<WasmFunctionStateDump>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstanceImage {
pub memory: Option<Vec<u8>>,
pub globals: Vec<u64>,
pub execution_state: ExecutionStateImage,
}
impl ModuleStateMap {
fn lookup_call_ip(&self, ip: usize, base: usize) -> Option<(&FunctionStateMap, MachineState)> {
if ip < base || ip - base >= self.total_size {
None
} else {
let (_, fsm) = self
.local_functions
.range((Unbounded, Included(&(ip - base))))
.last()
.unwrap();
match fsm.call_offsets.get(&(ip - base)) {
Some(x) => Some((fsm, fsm.diffs[x.diff_id].build_state(fsm))),
None => None,
}
}
}
fn lookup_trappable_ip(
&self,
ip: usize,
base: usize,
) -> Option<(&FunctionStateMap, MachineState)> {
if ip < base || ip - base >= self.total_size {
None
} else {
let (_, fsm) = self
.local_functions
.range((Unbounded, Included(&(ip - base))))
.last()
.unwrap();
match fsm.trappable_offsets.get(&(ip - base)) {
Some(x) => Some((fsm, fsm.diffs[x.diff_id].build_state(fsm))),
None => None,
}
}
}
fn lookup_loop_ip(&self, ip: usize, base: usize) -> Option<(&FunctionStateMap, MachineState)> {
if ip < base || ip - base >= self.total_size {
None
} else {
let (_, fsm) = self
.local_functions
.range((Unbounded, Included(&(ip - base))))
.last()
.unwrap();
match fsm.loop_offsets.get(&(ip - base)) {
Some(x) => Some((fsm, fsm.diffs[x.diff_id].build_state(fsm))),
None => None,
}
}
}
}
impl FunctionStateMap {
pub fn new(
initial: MachineState,
local_function_id: usize,
shadow_size: usize,
locals: Vec<WasmAbstractValue>,
) -> FunctionStateMap {
FunctionStateMap {
initial,
local_function_id,
shadow_size,
locals,
diffs: vec![],
wasm_function_header_target_offset: None,
wasm_offset_to_target_offset: BTreeMap::new(),
loop_offsets: BTreeMap::new(),
call_offsets: BTreeMap::new(),
trappable_offsets: BTreeMap::new(),
}
}
}
impl MachineState {
pub fn diff(&self, old: &MachineState) -> MachineStateDiff {
let first_diff_stack_depth: usize = self
.stack_values
.iter()
.zip(old.stack_values.iter())
.enumerate()
.find(|&(_, (&a, &b))| a != b)
.map(|x| x.0)
.unwrap_or(old.stack_values.len().min(self.stack_values.len()));
assert_eq!(self.register_values.len(), old.register_values.len());
let reg_diff: Vec<_> = self
.register_values
.iter()
.zip(old.register_values.iter())
.enumerate()
.filter(|&(_, (&a, &b))| a != b)
.map(|(i, (&a, _))| (RegisterIndex(i), a))
.collect();
let first_diff_wasm_stack_depth: usize = self
.wasm_stack
.iter()
.zip(old.wasm_stack.iter())
.enumerate()
.find(|&(_, (&a, &b))| a != b)
.map(|x| x.0)
.unwrap_or(old.wasm_stack.len().min(self.wasm_stack.len()));
MachineStateDiff {
last: None,
stack_push: self.stack_values[first_diff_stack_depth..].to_vec(),
stack_pop: old.stack_values.len() - first_diff_stack_depth,
reg_diff: reg_diff,
wasm_stack_push: self.wasm_stack[first_diff_wasm_stack_depth..].to_vec(),
wasm_stack_pop: old.wasm_stack.len() - first_diff_wasm_stack_depth,
wasm_stack_private_depth: self.wasm_stack_private_depth,
wasm_inst_offset: self.wasm_inst_offset,
}
}
}
impl MachineStateDiff {
pub fn build_state(&self, m: &FunctionStateMap) -> MachineState {
let mut chain: Vec<&MachineStateDiff> = vec![];
chain.push(self);
let mut current = self.last;
while let Some(x) = current {
let that = &m.diffs[x];
current = that.last;
chain.push(that);
}
chain.reverse();
let mut state = m.initial.clone();
for x in chain {
for _ in 0..x.stack_pop {
state.stack_values.pop().unwrap();
}
for v in &x.stack_push {
state.stack_values.push(*v);
}
for &(index, v) in &x.reg_diff {
state.register_values[index.0] = v;
}
for _ in 0..x.wasm_stack_pop {
state.wasm_stack.pop().unwrap();
}
for v in &x.wasm_stack_push {
state.wasm_stack.push(*v);
}
}
state.wasm_stack_private_depth = self.wasm_stack_private_depth;
state.wasm_inst_offset = self.wasm_inst_offset;
state
}
}
impl ExecutionStateImage {
pub fn print_backtrace_if_needed(&self) {
use std::env;
if let Ok(x) = env::var("WASMER_BACKTRACE") {
if x == "1" {
eprintln!("{}", self.colored_output());
return;
}
}
eprintln!("Run with `WASMER_BACKTRACE=1` environment variable to display a backtrace.");
}
pub fn colored_output(&self) -> String {
use colored::*;
fn join_strings(x: impl Iterator<Item = String>, sep: &str) -> String {
let mut ret = String::new();
let mut first = true;
for s in x {
if first {
first = false;
} else {
ret += sep;
}
ret += &s;
}
ret
}
fn format_optional_u64_sequence(x: &[Option<u64>]) -> String {
if x.len() == 0 {
"(empty)".into()
} else {
join_strings(
x.iter().enumerate().map(|(i, x)| {
format!(
"[{}] = {}",
i,
x.map(|x| format!("{}", x))
.unwrap_or_else(|| "?".to_string())
.bold()
.cyan()
)
}),
", ",
)
}
}
let mut ret = String::new();
if self.frames.len() == 0 {
ret += &"Unknown fault address, cannot read stack.".yellow();
ret += "\n";
} else {
ret += &"Backtrace:".bold();
ret += "\n";
for (i, f) in self.frames.iter().enumerate() {
ret += &format!("* Frame {} @ Local function {}", i, f.local_function_id).bold();
ret += "\n";
ret += &format!(
" {} {}\n",
"Offset:".bold().yellow(),
format!("{}", f.wasm_inst_offset).bold().cyan(),
);
ret += &format!(
" {} {}\n",
"Locals:".bold().yellow(),
format_optional_u64_sequence(&f.locals)
);
ret += &format!(
" {} {}\n\n",
"Stack:".bold().yellow(),
format_optional_u64_sequence(&f.stack)
);
}
}
ret
}
}
impl InstanceImage {
pub fn from_bytes(input: &[u8]) -> Option<InstanceImage> {
use bincode::deserialize;
match deserialize(input) {
Ok(x) => Some(x),
Err(_) => None,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
use bincode::serialize;
serialize(self).unwrap()
}
}
#[cfg(all(unix, target_arch = "x86_64"))]
pub mod x64 {
use super::*;
use crate::codegen::BreakpointMap;
use crate::fault::{catch_unsafe_unwind, run_on_alternative_stack};
use crate::structures::TypedIndex;
use crate::types::LocalGlobalIndex;
use crate::vm::Ctx;
use std::any::Any;
pub fn new_machine_state() -> MachineState {
MachineState {
stack_values: vec![],
register_values: vec![MachineValue::Undefined; 16 + 8],
wasm_stack: vec![],
wasm_stack_private_depth: 0,
wasm_inst_offset: ::std::usize::MAX,
}
}
#[warn(unused_variables)]
pub unsafe fn invoke_call_return_on_stack(
msm: &ModuleStateMap,
code_base: usize,
image: InstanceImage,
vmctx: &mut Ctx,
breakpoints: Option<BreakpointMap>,
) -> Result<u64, Box<dyn Any>> {
let mut stack: Vec<u64> = vec![0; 1048576 * 8 / 8]; // 8MB stack
let mut stack_offset: usize = stack.len();
stack_offset -= 3; // placeholder for call return
let mut last_stack_offset: u64 = 0; // rbp
let mut known_registers: [Option<u64>; 24] = [None; 24];
let local_functions_vec: Vec<&FunctionStateMap> =
msm.local_functions.iter().map(|(_, v)| v).collect();
// Bottom to top
for f in image.execution_state.frames.iter().rev() {
let fsm = local_functions_vec[f.local_function_id];
let suspend_offset = if f.wasm_inst_offset == ::std::usize::MAX {
fsm.wasm_function_header_target_offset
} else {
fsm.wasm_offset_to_target_offset
.get(&f.wasm_inst_offset)
.map(|x| *x)
}
.expect("instruction is not a critical point");
let (activate_offset, diff_id) = match suspend_offset {
SuspendOffset::Loop(x) => fsm.loop_offsets.get(&x),
SuspendOffset::Call(x) => fsm.call_offsets.get(&x),
SuspendOffset::Trappable(x) => fsm.trappable_offsets.get(&x),
}
.map(|x| (x.activate_offset, x.diff_id))
.expect("offset cannot be found in table");
let diff = &fsm.diffs[diff_id];
let state = diff.build_state(fsm);
stack_offset -= 1;
stack[stack_offset] = stack.as_ptr().offset(last_stack_offset as isize) as usize as u64; // push rbp
last_stack_offset = stack_offset as _;
let mut got_explicit_shadow = false;
for v in state.stack_values.iter() {
match *v {
MachineValue::Undefined => stack_offset -= 1,
MachineValue::Vmctx => {
stack_offset -= 1;
stack[stack_offset] = vmctx as *mut Ctx as usize as u64;
}
MachineValue::PreserveRegister(index) => {
stack_offset -= 1;
stack[stack_offset] = known_registers[index.0].unwrap_or(0);
}
MachineValue::CopyStackBPRelative(byte_offset) => {
assert!(byte_offset % 8 == 0);
let target_offset = (byte_offset / 8) as isize;
let v = stack[(last_stack_offset as isize + target_offset) as usize];
stack_offset -= 1;
stack[stack_offset] = v;
}
MachineValue::ExplicitShadow => {
assert!(fsm.shadow_size % 8 == 0);
stack_offset -= fsm.shadow_size / 8;
got_explicit_shadow = true;
}
MachineValue::WasmStack(x) => {
stack_offset -= 1;
match state.wasm_stack[x] {
WasmAbstractValue::Const(x) => {
stack[stack_offset] = x;
}
WasmAbstractValue::Runtime => {
stack[stack_offset] = f.stack[x].unwrap();
}
}
}
MachineValue::WasmLocal(x) => {
stack_offset -= 1;
match fsm.locals[x] {
WasmAbstractValue::Const(x) => {
stack[stack_offset] = x;
}
WasmAbstractValue::Runtime => {
stack[stack_offset] = f.locals[x].unwrap();
}
}
}
}
}
if !got_explicit_shadow {
assert!(fsm.shadow_size % 8 == 0);
stack_offset -= fsm.shadow_size / 8;
}
for (i, v) in state.register_values.iter().enumerate() {
match *v {
MachineValue::Undefined => {}
MachineValue::Vmctx => {
known_registers[i] = Some(vmctx as *mut Ctx as usize as u64);
}
MachineValue::WasmStack(x) => match state.wasm_stack[x] {
WasmAbstractValue::Const(x) => {
known_registers[i] = Some(x);
}
WasmAbstractValue::Runtime => {
known_registers[i] = Some(f.stack[x].unwrap());
}
},
MachineValue::WasmLocal(x) => match fsm.locals[x] {
WasmAbstractValue::Const(x) => {
known_registers[i] = Some(x);
}
WasmAbstractValue::Runtime => {
known_registers[i] = Some(f.locals[x].unwrap());
}
},
_ => unreachable!(),
}
}
// no need to check 16-byte alignment here because it's possible that we're not at a call entry.
stack_offset -= 1;
stack[stack_offset] = (code_base + activate_offset) as u64; // return address
}
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R15).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R14).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R13).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R12).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R11).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R10).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R9).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::R8).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::RSI).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::RDI).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::RDX).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::RCX).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::RBX).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = known_registers[X64Register::GPR(GPR::RAX).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] = stack.as_ptr().offset(last_stack_offset as isize) as usize as u64; // rbp
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM7).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM6).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM5).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM4).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM3).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM2).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM1).to_index().0].unwrap_or(0);
stack_offset -= 1;
stack[stack_offset] =
known_registers[X64Register::XMM(XMM::XMM0).to_index().0].unwrap_or(0);
if let Some(ref memory) = image.memory {
assert!(vmctx.internal.memory_bound <= memory.len());
if vmctx.internal.memory_bound < memory.len() {
let grow: unsafe extern "C" fn(ctx: &mut Ctx, memory_index: usize, delta: usize) =
::std::mem::transmute((*vmctx.internal.intrinsics).memory_grow);
grow(
vmctx,
0,
(memory.len() - vmctx.internal.memory_bound) / 65536,
);
assert_eq!(vmctx.internal.memory_bound, memory.len());
}
::std::slice::from_raw_parts_mut(
vmctx.internal.memory_base,
vmctx.internal.memory_bound,
)
.copy_from_slice(memory);
}
let globals_len = (*vmctx.module).info.globals.len();
for i in 0..globals_len {
(*(*vmctx.local_backing).globals[LocalGlobalIndex::new(i)].vm_local_global()).data =
image.globals[i];
}
drop(image); // free up host memory
catch_unsafe_unwind(
|| {
run_on_alternative_stack(
stack.as_mut_ptr().offset(stack.len() as isize),
stack.as_mut_ptr().offset(stack_offset as isize),
)
},
breakpoints,
)
}
pub fn build_instance_image(
vmctx: &mut Ctx,
execution_state: ExecutionStateImage,
) -> InstanceImage {
unsafe {
let memory = if vmctx.internal.memory_base.is_null() {
None
} else {
Some(
::std::slice::from_raw_parts(
vmctx.internal.memory_base,
vmctx.internal.memory_bound,
)
.to_vec(),
)
};
// FIXME: Imported globals
let globals_len = (*vmctx.module).info.globals.len();
let globals: Vec<u64> = (0..globals_len)
.map(|i| {
(*vmctx.local_backing).globals[LocalGlobalIndex::new(i)]
.get()
.to_u64()
})
.collect();
InstanceImage {
memory: memory,
globals: globals,
execution_state: execution_state,
}
}
}
#[warn(unused_variables)]
pub unsafe fn read_stack(
msm: &ModuleStateMap,
code_base: usize,
mut stack: *const u64,
initially_known_registers: [Option<u64>; 24],
mut initial_address: Option<u64>,
) -> ExecutionStateImage {
let mut known_registers: [Option<u64>; 24] = initially_known_registers;
let mut results: Vec<WasmFunctionStateDump> = vec![];
for _ in 0.. {
let ret_addr = initial_address.take().unwrap_or_else(|| {
let x = *stack;
stack = stack.offset(1);
x
});
let (fsm, state) = match msm
.lookup_call_ip(ret_addr as usize, code_base)
.or_else(|| msm.lookup_trappable_ip(ret_addr as usize, code_base))
.or_else(|| msm.lookup_loop_ip(ret_addr as usize, code_base))
{
Some(x) => x,
_ => return ExecutionStateImage { frames: results },
};
let mut wasm_stack: Vec<Option<u64>> = state
.wasm_stack
.iter()
.map(|x| match *x {
WasmAbstractValue::Const(x) => Some(x),
WasmAbstractValue::Runtime => None,
})
.collect();
let mut wasm_locals: Vec<Option<u64>> = fsm
.locals
.iter()
.map(|x| match *x {
WasmAbstractValue::Const(x) => Some(x),
WasmAbstractValue::Runtime => None,
})
.collect();
// This must be before the next loop because that modifies `known_registers`.
for (i, v) in state.register_values.iter().enumerate() {
match *v {
MachineValue::Undefined => {}
MachineValue::Vmctx => {}
MachineValue::WasmStack(idx) => {
if let Some(v) = known_registers[i] {
wasm_stack[idx] = Some(v);
} else {
eprintln!(
"BUG: Register {} for WebAssembly stack slot {} has unknown value.",
i, idx
);
}
}
MachineValue::WasmLocal(idx) => {
if let Some(v) = known_registers[i] {
wasm_locals[idx] = Some(v);
}
}
_ => unreachable!(),
}
}
let mut found_shadow = false;
for v in state.stack_values.iter() {
match *v {
MachineValue::ExplicitShadow => {
found_shadow = true;
break;
}
_ => {}
}
}
if !found_shadow {
stack = stack.offset((fsm.shadow_size / 8) as isize);
}
for v in state.stack_values.iter().rev() {
match *v {
MachineValue::ExplicitShadow => {
stack = stack.offset((fsm.shadow_size / 8) as isize);
}
MachineValue::Undefined => {
stack = stack.offset(1);
}
MachineValue::Vmctx => {
stack = stack.offset(1);
}
MachineValue::PreserveRegister(idx) => {
known_registers[idx.0] = Some(*stack);
stack = stack.offset(1);
}
MachineValue::CopyStackBPRelative(_) => {
stack = stack.offset(1);
}
MachineValue::WasmStack(idx) => {
wasm_stack[idx] = Some(*stack);
stack = stack.offset(1);
}
MachineValue::WasmLocal(idx) => {
wasm_locals[idx] = Some(*stack);
stack = stack.offset(1);
}
}
}
stack = stack.offset(1); // RBP
wasm_stack.truncate(
wasm_stack
.len()
.checked_sub(state.wasm_stack_private_depth)
.unwrap(),
);
let wfs = WasmFunctionStateDump {
local_function_id: fsm.local_function_id,
wasm_inst_offset: state.wasm_inst_offset,
stack: wasm_stack,
locals: wasm_locals,
};
results.push(wfs);
}
unreachable!();
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GPR {
RAX,
RCX,
RDX,
RBX,
RSP,
RBP,
RSI,
RDI,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum XMM {
XMM0,
XMM1,
XMM2,
XMM3,
XMM4,
XMM5,
XMM6,
XMM7,
}
pub enum X64Register {
GPR(GPR),
XMM(XMM),
}
impl X64Register {
pub fn to_index(&self) -> RegisterIndex {
match *self {
X64Register::GPR(x) => RegisterIndex(x as usize),
X64Register::XMM(x) => RegisterIndex(x as usize + 16),
}
}
}
}

View File

@ -7,6 +7,8 @@
//! Variadic functions are not supported because `rax` is used by the trampoline code.
use crate::loader::CodeMemory;
use crate::vm::Ctx;
use std::fmt;
use std::{mem, slice};
lazy_static! {
@ -98,6 +100,46 @@ impl TrampolineBufferBuilder {
idx
}
pub fn add_context_rsp_state_preserving_trampoline(
&mut self,
target: unsafe extern "C" fn(&mut Ctx, *const CallContext, *const u64),
context: *const CallContext,
) -> usize {
let idx = self.offsets.len();
self.offsets.push(self.code.len());
self.code.extend_from_slice(&[
0x53, // push %rbx
0x41, 0x54, // push %r12
0x41, 0x55, // push %r13
0x41, 0x56, // push %r14
0x41, 0x57, // push %r15
]);
self.code.extend_from_slice(&[
0x48, 0xbe, // movabsq ?, %rsi
]);
self.code.extend_from_slice(value_to_bytes(&context));
self.code.extend_from_slice(&[
0x48, 0x89, 0xe2, // mov %rsp, %rdx
]);
self.code.extend_from_slice(&[
0x48, 0xb8, // movabsq ?, %rax
]);
self.code.extend_from_slice(value_to_bytes(&target));
self.code.extend_from_slice(&[
0xff, 0xd0, // callq *%rax
]);
self.code.extend_from_slice(&[
0x48, 0x81, 0xc4, // add ?, %rsp
]);
self.code.extend_from_slice(value_to_bytes(&40i32)); // 5 * 8
self.code.extend_from_slice(&[
0xc3, //retq
]);
idx
}
/// Adds a callinfo trampoline.
///
/// This generates a trampoline function that collects `num_params` parameters into an array
@ -196,6 +238,12 @@ impl TrampolineBuffer {
}
}
impl fmt::Debug for TrampolineBuffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TrampolineBuffer {{}}")
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -180,6 +180,10 @@ where
_phantom: PhantomData,
}
}
pub fn get_vm_func(&self) -> NonNull<vm::Func> {
self.f
}
}
impl<'a, Args, Rets> Func<'a, Args, Rets, Host>
@ -364,30 +368,35 @@ macro_rules! impl_traits {
impl< $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap> ExternalFunction<($( $x ),*), Rets> for FN {
#[allow(non_snake_case)]
fn to_raw(&self) -> NonNull<vm::Func> {
assert_eq!(mem::size_of::<Self>(), 0, "you cannot use a closure that captures state for `Func`.");
if mem::size_of::<Self>() == 0 {
/// This is required for the llvm backend to be able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap>( ctx: &mut Ctx $( ,$x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct {
let f: FN = unsafe { mem::transmute_copy(&()) };
/// This is required for the llvm backend to be able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap>( ctx: &mut Ctx $( ,$x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct {
let f: FN = unsafe { mem::transmute_copy(&()) };
let err = match panic::catch_unwind(panic::AssertUnwindSafe(|| {
f( ctx $( ,WasmExternType::from_native($x) )* ).report()
})) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};
let err = match panic::catch_unwind(panic::AssertUnwindSafe(|| {
f( ctx $( ,WasmExternType::from_native($x) )* ).report()
})) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};
unsafe {
(&*ctx.module).runnable_module.do_early_trap(err)
unsafe {
(&*ctx.module).runnable_module.do_early_trap(err)
}
}
}
NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap()
NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap()
} else {
assert_eq!(mem::size_of::<Self>(), mem::size_of::<usize>(), "you cannot use a closure that captures state for `Func`.");
NonNull::new(unsafe {
::std::mem::transmute_copy::<_, *mut vm::Func>(self)
}).unwrap()
}
}
}

View File

@ -38,8 +38,8 @@ pub struct Ctx {
/// These are pointers to things that are known to be owned
/// by the owning `Instance`.
local_backing: *mut LocalBacking,
import_backing: *mut ImportBacking,
pub local_backing: *mut LocalBacking,
pub import_backing: *mut ImportBacking,
pub module: *const ModuleInner,
//// This is intended to be user-supplied, per-instance
@ -100,6 +100,8 @@ pub struct InternalCtx {
pub memory_bound: usize,
pub internals: *mut [u64; INTERNALS_SIZE], // TODO: Make this dynamic?
pub interrupt_signal_mem: *mut u8,
}
static INTERNAL_FIELDS: AtomicUsize = AtomicUsize::new(0);
@ -207,6 +209,17 @@ fn get_intrinsics_for_module(m: &ModuleInfo) -> *const Intrinsics {
}
}
#[cfg(all(unix, target_arch = "x86_64"))]
fn get_interrupt_signal_mem() -> *mut u8 {
unsafe { crate::fault::get_wasm_interrupt_signal_mem() }
}
#[cfg(not(all(unix, target_arch = "x86_64")))]
fn get_interrupt_signal_mem() -> *mut u8 {
static mut REGION: u64 = 0;
unsafe { &mut REGION as *mut u64 as *mut u8 }
}
impl Ctx {
#[doc(hidden)]
pub unsafe fn new(
@ -245,6 +258,8 @@ impl Ctx {
memory_bound: mem_bound,
internals: &mut local_backing.internals.0,
interrupt_signal_mem: get_interrupt_signal_mem(),
},
local_functions: local_backing.local_functions.as_ptr(),
@ -296,6 +311,8 @@ impl Ctx {
memory_bound: mem_bound,
internals: &mut local_backing.internals.0,
interrupt_signal_mem: get_interrupt_signal_mem(),
},
local_functions: local_backing.local_functions.as_ptr(),
@ -419,9 +436,13 @@ impl Ctx {
12 * (mem::size_of::<usize>() as u8)
}
pub fn offset_local_functions() -> u8 {
pub fn offset_interrupt_signal_mem() -> u8 {
13 * (mem::size_of::<usize>() as u8)
}
pub fn offset_local_functions() -> u8 {
14 * (mem::size_of::<usize>() as u8)
}
}
enum InnerFunc {}
@ -640,6 +661,11 @@ mod vm_offset_tests {
offset_of!(InternalCtx => internals).get_byte_offset(),
);
assert_eq!(
Ctx::offset_interrupt_signal_mem() as usize,
offset_of!(InternalCtx => interrupt_signal_mem).get_byte_offset(),
);
assert_eq!(
Ctx::offset_local_functions() as usize,
offset_of!(Ctx => local_functions).get_byte_offset(),

View File

@ -18,3 +18,4 @@ nix = "0.13.0"
libc = "0.2.49"
smallvec = "0.6.9"
hashbrown = "0.1"
colored = "1.8"

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,5 @@
use dynasmrt::{x64::Assembler, AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi};
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GPR {
RAX,
RCX,
RDX,
RBX,
RSP,
RBP,
RSI,
RDI,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum XMM {
XMM0,
XMM1,
XMM2,
XMM3,
XMM4,
XMM5,
XMM6,
XMM7,
}
pub use wasmer_runtime_core::state::x64::{GPR, XMM};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Location {
@ -87,7 +54,7 @@ pub trait Emitter {
type Offset;
fn get_label(&mut self) -> Self::Label;
fn get_offset(&mut self) -> Self::Offset;
fn get_offset(&self) -> Self::Offset;
fn emit_u64(&mut self, x: u64);
@ -488,7 +455,7 @@ impl Emitter for Assembler {
self.new_dynamic_label()
}
fn get_offset(&mut self) -> AssemblyOffset {
fn get_offset(&self) -> AssemblyOffset {
self.offset()
}

View File

@ -22,7 +22,7 @@ extern crate smallvec;
mod codegen_x64;
mod emitter_x64;
mod machine;
mod protect_unix;
pub mod protect_unix;
pub use codegen_x64::X64FunctionCode as FunctionCodeGenerator;
pub use codegen_x64::X64ModuleCodeGenerator as ModuleCodeGenerator;

View File

@ -1,6 +1,8 @@
use crate::emitter_x64::*;
use smallvec::SmallVec;
use std::collections::HashSet;
use wasmer_runtime_core::state::x64::X64Register;
use wasmer_runtime_core::state::*;
use wasmparser::Type as WpType;
struct MachineStackOffset(usize);
@ -10,6 +12,7 @@ pub struct Machine {
used_xmms: HashSet<XMM>,
stack_offset: MachineStackOffset,
save_area_offset: Option<MachineStackOffset>,
pub state: MachineState,
}
impl Machine {
@ -19,6 +22,7 @@ impl Machine {
used_xmms: HashSet::new(),
stack_offset: MachineStackOffset(0),
save_area_offset: None,
state: x64::new_machine_state(),
}
}
@ -129,13 +133,13 @@ impl Machine {
pub fn acquire_locations<E: Emitter>(
&mut self,
assembler: &mut E,
tys: &[WpType],
tys: &[(WpType, MachineValue)],
zeroed: bool,
) -> SmallVec<[Location; 1]> {
let mut ret = smallvec![];
let mut delta_stack_offset: usize = 0;
for ty in tys {
for (ty, mv) in tys {
let loc = match *ty {
WpType::F32 | WpType::F64 => self.pick_xmm().map(Location::XMM),
WpType::I32 | WpType::I64 => self.pick_gpr().map(Location::GPR),
@ -151,9 +155,14 @@ impl Machine {
};
if let Location::GPR(x) = loc {
self.used_gprs.insert(x);
self.state.register_values[X64Register::GPR(x).to_index().0] = *mv;
} else if let Location::XMM(x) = loc {
self.used_xmms.insert(x);
self.state.register_values[X64Register::XMM(x).to_index().0] = *mv;
} else {
self.state.stack_values.push(*mv);
}
self.state.wasm_stack.push(WasmAbstractValue::Runtime);
ret.push(loc);
}
@ -180,9 +189,13 @@ impl Machine {
match *loc {
Location::GPR(ref x) => {
assert_eq!(self.used_gprs.remove(x), true);
self.state.register_values[X64Register::GPR(*x).to_index().0] =
MachineValue::Undefined;
}
Location::XMM(ref x) => {
assert_eq!(self.used_xmms.remove(x), true);
self.state.register_values[X64Register::XMM(*x).to_index().0] =
MachineValue::Undefined;
}
Location::Memory(GPR::RBP, x) => {
if x >= 0 {
@ -194,9 +207,11 @@ impl Machine {
}
self.stack_offset.0 -= 8;
delta_stack_offset += 8;
self.state.stack_values.pop().unwrap();
}
_ => {}
}
self.state.wasm_stack.pop().unwrap();
}
if delta_stack_offset != 0 {
@ -213,12 +228,17 @@ impl Machine {
match *loc {
Location::GPR(ref x) => {
assert_eq!(self.used_gprs.remove(x), true);
self.state.register_values[X64Register::GPR(*x).to_index().0] =
MachineValue::Undefined;
}
Location::XMM(ref x) => {
assert_eq!(self.used_xmms.remove(x), true);
self.state.register_values[X64Register::XMM(*x).to_index().0] =
MachineValue::Undefined;
}
_ => {}
}
// Wasm state popping is deferred to `release_locations_only_osr_state`.
}
}
@ -241,9 +261,11 @@ impl Machine {
}
self.stack_offset.0 -= 8;
delta_stack_offset += 8;
self.state.stack_values.pop().unwrap();
}
_ => {}
}
// Wasm state popping is deferred to `release_locations_only_osr_state`.
}
if delta_stack_offset != 0 {
@ -255,6 +277,12 @@ impl Machine {
}
}
pub fn release_locations_only_osr_state(&mut self, n: usize) {
for _ in 0..n {
self.state.wasm_stack.pop().unwrap();
}
}
pub fn release_locations_keep_state<E: Emitter>(&self, assembler: &mut E, locs: &[Location]) {
let mut delta_stack_offset: usize = 0;
let mut stack_offset = self.stack_offset.0;
@ -314,7 +342,11 @@ impl Machine {
allocated += 1;
get_local_location(old_idx)
}
Location::Memory(_, _) => loc,
Location::Memory(_, _) => {
let old_idx = allocated;
allocated += 1;
get_local_location(old_idx)
}
_ => unreachable!(),
});
}
@ -325,6 +357,19 @@ impl Machine {
allocated += 1;
}
for (i, loc) in locations.iter().enumerate() {
match *loc {
Location::GPR(x) => {
self.state.register_values[X64Register::GPR(x).to_index().0] =
MachineValue::WasmLocal(i);
}
Location::Memory(_, _) => {
self.state.stack_values.push(MachineValue::WasmLocal(i));
}
_ => unreachable!(),
}
}
// How many machine stack slots did all the locals use?
let num_mem_slots = locations
.iter()
@ -346,15 +391,21 @@ impl Machine {
// Save callee-saved registers.
for loc in locations.iter() {
if let Location::GPR(_) = *loc {
if let Location::GPR(x) = *loc {
a.emit_push(Size::S64, *loc);
self.stack_offset.0 += 8;
self.state.stack_values.push(MachineValue::PreserveRegister(
X64Register::GPR(x).to_index(),
));
}
}
// Save R15 for vmctx use.
a.emit_push(Size::S64, Location::GPR(GPR::R15));
self.stack_offset.0 += 8;
self.state.stack_values.push(MachineValue::PreserveRegister(
X64Register::GPR(GPR::R15).to_index(),
));
// Save the offset of static area.
self.save_area_offset = Some(MachineStackOffset(self.stack_offset.0));
@ -366,7 +417,17 @@ impl Machine {
Location::GPR(_) => {
a.emit_mov(Size::S64, loc, locations[i]);
}
_ => break,
Location::Memory(_, _) => match locations[i] {
Location::GPR(_) => {
a.emit_mov(Size::S64, loc, locations[i]);
}
Location::Memory(_, _) => {
a.emit_mov(Size::S64, loc, Location::GPR(GPR::RAX));
a.emit_mov(Size::S64, Location::GPR(GPR::RAX), locations[i]);
}
_ => unreachable!(),
},
_ => unreachable!(),
}
}
@ -429,7 +490,11 @@ mod test {
fn test_release_locations_keep_state_nopanic() {
let mut machine = Machine::new();
let mut assembler = Assembler::new().unwrap();
let locs = machine.acquire_locations(&mut assembler, &[WpType::I32; 10], false);
let locs = machine.acquire_locations(
&mut assembler,
&[(WpType::I32, MachineValue::Undefined); 10],
false,
);
machine.release_locations_keep_state(&mut assembler, &locs);
}

View File

@ -9,77 +9,18 @@
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
//! unless you have memory unsafety elsewhere in your code.
//!
use libc::{c_int, c_void, siginfo_t};
use nix::sys::signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
SIGTRAP,
};
use std::any::Any;
use std::cell::{Cell, RefCell, UnsafeCell};
use std::collections::HashMap;
use std::ptr;
use std::sync::Arc;
use std::sync::Once;
use wasmer_runtime_core::codegen::BkptInfo;
use std::cell::Cell;
use wasmer_runtime_core::codegen::BreakpointMap;
use wasmer_runtime_core::fault::{begin_unsafe_unwind, catch_unsafe_unwind, ensure_sighandler};
use wasmer_runtime_core::typed_func::WasmTrapInfo;
extern "C" fn signal_trap_handler(
signum: ::nix::libc::c_int,
siginfo: *mut siginfo_t,
ucontext: *mut c_void,
) {
unsafe {
match Signal::from_c_int(signum) {
Ok(SIGTRAP) => {
let (_, ip) = get_faulting_addr_and_ip(siginfo as _, ucontext);
let bkpt_map = BKPT_MAP.with(|x| x.borrow().last().map(|x| x.clone()));
if let Some(bkpt_map) = bkpt_map {
if let Some(ref x) = bkpt_map.get(&(ip as usize)) {
(x)(BkptInfo { throw: throw });
return;
}
}
}
_ => {}
}
do_unwind(signum, siginfo as _, ucontext);
}
}
extern "C" {
pub fn setjmp(env: *mut c_void) -> c_int;
fn longjmp(env: *mut c_void, val: c_int) -> !;
}
pub unsafe fn install_sighandler() {
let sa = SigAction::new(
SigHandler::SigAction(signal_trap_handler),
SaFlags::SA_ONSTACK,
SigSet::empty(),
);
sigaction(SIGFPE, &sa).unwrap();
sigaction(SIGILL, &sa).unwrap();
sigaction(SIGSEGV, &sa).unwrap();
sigaction(SIGBUS, &sa).unwrap();
sigaction(SIGTRAP, &sa).unwrap();
}
const SETJMP_BUFFER_LEN: usize = 27;
pub static SIGHANDLER_INIT: Once = Once::new();
thread_local! {
pub static SETJMP_BUFFER: UnsafeCell<[c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
pub static CAUGHT_ADDRESSES: Cell<(*const c_void, *const c_void)> = Cell::new((ptr::null(), ptr::null()));
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
pub static TRAP_EARLY_DATA: Cell<Option<Box<dyn Any>>> = Cell::new(None);
pub static BKPT_MAP: RefCell<Vec<Arc<HashMap<usize, Box<Fn(BkptInfo) + Send + Sync + 'static>>>>> = RefCell::new(Vec::new());
}
pub unsafe fn trigger_trap() -> ! {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
longjmp(jmp_buf as *mut c_void, 0)
begin_unsafe_unwind(Box::new(()));
}
pub enum CallProtError {
@ -87,157 +28,26 @@ pub enum CallProtError {
Error(Box<dyn Any>),
}
pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, CallProtError> {
pub fn call_protected<T>(
f: impl FnOnce() -> T,
breakpoints: Option<BreakpointMap>,
) -> Result<T, CallProtError> {
ensure_sighandler();
unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
let prev_jmp_buf = *jmp_buf;
SIGHANDLER_INIT.call_once(|| {
install_sighandler();
});
let signum = setjmp(jmp_buf as *mut _);
if signum != 0 {
*jmp_buf = prev_jmp_buf;
if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(CallProtError::Error(data))
} else {
// let (faulting_addr, _inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
// let signal = match Signal::from_c_int(signum) {
// Ok(SIGFPE) => "floating-point exception",
// Ok(SIGILL) => "illegal instruction",
// Ok(SIGSEGV) => "segmentation violation",
// Ok(SIGBUS) => "bus error",
// Err(_) => "error while getting the Signal",
// _ => "unknown trapped signal",
// };
// // When the trap-handler is fully implemented, this will return more information.
// Err(RuntimeError::Trap {
// msg: format!("unknown trap at {:p} - {}", faulting_addr, signal).into(),
// }
// .into())
Err(CallProtError::Trap(WasmTrapInfo::Unknown))
let ret = catch_unsafe_unwind(|| f(), breakpoints);
match ret {
Ok(x) => Ok(x),
Err(e) => {
if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(CallProtError::Error(data))
} else {
Err(CallProtError::Error(e))
}
}
} else {
let ret = f(); // TODO: Switch stack?
*jmp_buf = prev_jmp_buf;
Ok(ret)
}
}
}
pub unsafe fn throw(payload: Box<dyn Any>) -> ! {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
if *jmp_buf == [0; SETJMP_BUFFER_LEN] {
::std::process::abort();
}
TRAP_EARLY_DATA.with(|cell| cell.replace(Some(payload)));
longjmp(jmp_buf as *mut ::nix::libc::c_void, 0xffff);
}
/// Unwinds to last protected_call.
pub unsafe fn do_unwind(signum: i32, siginfo: *const c_void, ucontext: *const c_void) -> ! {
// Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.)
// itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should
// temporarily disable the signal handlers to debug it.
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
if *jmp_buf == [0; SETJMP_BUFFER_LEN] {
::std::process::abort();
}
CAUGHT_ADDRESSES.with(|cell| cell.set(get_faulting_addr_and_ip(siginfo, ucontext)));
longjmp(jmp_buf as *mut ::nix::libc::c_void, signum)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
unsafe fn get_faulting_addr_and_ip(
siginfo: *const c_void,
ucontext: *const c_void,
) -> (*const c_void, *const c_void) {
use libc::{ucontext_t, RIP};
#[allow(dead_code)]
#[repr(C)]
struct siginfo_t {
si_signo: i32,
si_errno: i32,
si_code: i32,
si_addr: u64,
// ...
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let rip = (*ucontext).uc_mcontext.gregs[RIP as usize];
(si_addr as _, rip as _)
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
unsafe fn get_faulting_addr_and_ip(
siginfo: *const c_void,
ucontext: *const c_void,
) -> (*const c_void, *const c_void) {
#[allow(dead_code)]
#[repr(C)]
struct ucontext_t {
uc_onstack: u32,
uc_sigmask: u32,
uc_stack: libc::stack_t,
uc_link: *const ucontext_t,
uc_mcsize: u64,
uc_mcontext: *const mcontext_t,
}
#[repr(C)]
struct exception_state {
trapno: u16,
cpu: u16,
err: u32,
faultvaddr: u64,
}
#[repr(C)]
struct regs {
rax: u64,
rbx: u64,
rcx: u64,
rdx: u64,
rdi: u64,
rsi: u64,
rbp: u64,
rsp: u64,
r8: u64,
r9: u64,
r10: u64,
r11: u64,
r12: u64,
r13: u64,
r14: u64,
r15: u64,
rip: u64,
rflags: u64,
cs: u64,
fs: u64,
gs: u64,
}
#[allow(dead_code)]
#[repr(C)]
struct mcontext_t {
es: exception_state,
ss: regs,
// ...
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let rip = (*(*ucontext).uc_mcontext).ss.rip;
(si_addr, rip as _)
begin_unsafe_unwind(payload);
}

View File

@ -19,7 +19,6 @@ use wasmer_clif_backend::CraneliftCompiler;
use wasmer_llvm_backend::LLVMCompiler;
use wasmer_runtime::{
cache::{Cache as BaseCache, FileSystemCache, WasmHash, WASMER_VERSION_HASH},
error::RuntimeError,
Func, Value,
};
use wasmer_runtime_core::{
@ -112,6 +111,10 @@ struct Run {
)]
loader: Option<LoaderName>,
#[cfg(feature = "backend:singlepass")]
#[structopt(long = "resume")]
resume: Option<String>,
#[structopt(long = "command-name", hidden = true)]
command_name: Option<String>,
@ -151,7 +154,7 @@ impl FromStr for LoaderName {
}
#[allow(dead_code)]
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
enum Backend {
Cranelift,
Singlepass,
@ -483,7 +486,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
.map_err(|e| format!("{:?}", e))?;
} else {
if cfg!(feature = "wasi") && wasmer_wasi::is_wasi_module(&module) {
let import_object = wasmer_wasi::generate_import_object(
let mut import_object = wasmer_wasi::generate_import_object(
if let Some(cn) = &options.command_name {
[cn.clone()]
} else {
@ -502,24 +505,95 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
mapped_dirs,
);
let instance = module
let mut instance = module
.instantiate(&import_object)
.map_err(|e| format!("Can't instantiate module: {:?}", e))?;
let start: Func<(), ()> = instance.func("_start").map_err(|e| format!("{:?}", e))?;
let result = start.call();
#[cfg(feature = "backend:singlepass")]
unsafe {
if options.backend == Backend::Singlepass {
use wasmer_runtime_core::fault::{catch_unsafe_unwind, ensure_sighandler};
use wasmer_runtime_core::state::{
x64::invoke_call_return_on_stack, InstanceImage,
};
use wasmer_runtime_core::vm::Ctx;
if let Err(ref err) = result {
match err {
RuntimeError::Trap { msg } => panic!("wasm trap occured: {}", msg),
RuntimeError::Error { data } => {
if let Some(error_code) = data.downcast_ref::<wasmer_wasi::ExitCode>() {
std::process::exit(error_code.code as i32)
ensure_sighandler();
let start_raw: extern "C" fn(&mut Ctx) =
::std::mem::transmute(start.get_vm_func());
let mut image: Option<InstanceImage> = if let Some(ref path) = options.resume {
let mut f = File::open(path).unwrap();
let mut out: Vec<u8> = vec![];
f.read_to_end(&mut out).unwrap();
Some(InstanceImage::from_bytes(&out).expect("failed to decode image"))
} else {
None
};
let breakpoints = instance.module.runnable_module.get_breakpoints();
loop {
let ret = if let Some(image) = image.take() {
let msm = instance
.module
.runnable_module
.get_module_state_map()
.unwrap();
let code_base =
instance.module.runnable_module.get_code().unwrap().as_ptr()
as usize;
invoke_call_return_on_stack(
&msm,
code_base,
image,
instance.context_mut(),
breakpoints.clone(),
)
.map(|_| ())
} else {
catch_unsafe_unwind(
|| start_raw(instance.context_mut()),
breakpoints.clone(),
)
};
if let Err(e) = ret {
if let Some(new_image) = e.downcast_ref::<InstanceImage>() {
let op = interactive_shell(InteractiveShellContext {
image: Some(new_image.clone()),
});
match op {
ShellExitOperation::ContinueWith(new_image) => {
image = Some(new_image);
}
}
} else {
return Err("Error while executing WebAssembly".into());
}
} else {
return Ok(());
}
}
}
panic!("error: {:?}", err)
}
{
use wasmer_runtime::error::RuntimeError;
let result = start.call();
if let Err(ref err) = result {
match err {
RuntimeError::Trap { msg } => panic!("wasm trap occured: {}", msg),
RuntimeError::Error { data } => {
if let Some(error_code) = data.downcast_ref::<wasmer_wasi::ExitCode>() {
std::process::exit(error_code.code as i32)
}
}
}
panic!("error: {:?}", err)
}
}
} else {
let import_object = wasmer_runtime_core::import::ImportObject::new();
@ -544,11 +618,95 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
Ok(())
}
#[cfg(feature = "backend:singlepass")]
struct InteractiveShellContext {
image: Option<wasmer_runtime_core::state::InstanceImage>,
}
#[cfg(feature = "backend:singlepass")]
#[derive(Debug)]
enum ShellExitOperation {
ContinueWith(wasmer_runtime_core::state::InstanceImage),
}
#[cfg(feature = "backend:singlepass")]
fn interactive_shell(mut ctx: InteractiveShellContext) -> ShellExitOperation {
use std::io::Write;
let mut stdout = ::std::io::stdout();
let stdin = ::std::io::stdin();
loop {
print!("Wasmer> ");
stdout.flush().unwrap();
let mut line = String::new();
stdin.read_line(&mut line).unwrap();
let mut parts = line.split(" ").filter(|x| x.len() > 0).map(|x| x.trim());
let cmd = parts.next();
if cmd.is_none() {
println!("Command required");
continue;
}
let cmd = cmd.unwrap();
match cmd {
"snapshot" => {
let path = parts.next();
if path.is_none() {
println!("Usage: snapshot [out_path]");
continue;
}
let path = path.unwrap();
if let Some(ref image) = ctx.image {
let buf = image.to_bytes();
let mut f = match File::create(path) {
Ok(x) => x,
Err(e) => {
println!("Cannot open output file at {}: {:?}", path, e);
continue;
}
};
if let Err(e) = f.write_all(&buf) {
println!("Cannot write to output file at {}: {:?}", path, e);
continue;
}
println!("Done");
} else {
println!("Program state not available");
}
}
"continue" | "c" => {
if let Some(image) = ctx.image.take() {
return ShellExitOperation::ContinueWith(image);
} else {
println!("Program state not available, cannot continue execution");
}
}
"backtrace" | "bt" => {
if let Some(ref image) = ctx.image {
println!("{}", image.execution_state.colored_output());
} else {
println!("State not available");
}
}
"exit" | "quit" => {
exit(0);
}
"" => {}
_ => {
println!("Unknown command: {}", cmd);
}
}
}
}
fn run(options: Run) {
match execute_wasm(&options) {
Ok(()) => {}
Err(message) => {
eprintln!("{:?}", message);
eprintln!("execute_wasm: {:?}", message);
exit(1);
}
}