From 7467ec32610acb55119b6351344e23d2895f8e63 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 14 Feb 2022 22:46:35 +0000 Subject: [PATCH 01/13] Use LLVM's inline-asm stack probing instead of an external function --- lib/compiler-llvm/src/object_file.rs | 1 - lib/compiler-llvm/src/translator/intrinsics.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/compiler-llvm/src/object_file.rs b/lib/compiler-llvm/src/object_file.rs index fff49d258cb..794cd6a52f7 100644 --- a/lib/compiler-llvm/src/object_file.rs +++ b/lib/compiler-llvm/src/object_file.rs @@ -96,7 +96,6 @@ where libcalls.insert("wasmer_vm_memory32_init".to_string(), LibCall::Memory32Init); libcalls.insert("wasmer_vm_data_drop".to_string(), LibCall::DataDrop); libcalls.insert("wasmer_vm_raise_trap".to_string(), LibCall::RaiseTrap); - libcalls.insert("wasmer_vm_probestack".to_string(), LibCall::Probestack); let elf = object::File::parse(contents).map_err(map_object_err)?; diff --git a/lib/compiler-llvm/src/translator/intrinsics.rs b/lib/compiler-llvm/src/translator/intrinsics.rs index 48f176f45a6..916c741c161 100644 --- a/lib/compiler-llvm/src/translator/intrinsics.rs +++ b/lib/compiler-llvm/src/translator/intrinsics.rs @@ -703,7 +703,7 @@ impl<'ctx> Intrinsics<'ctx> { ), readonly: context .create_enum_attribute(Attribute::get_named_enum_kind_id("readonly"), 0), - stack_probe: context.create_string_attribute("probe-stack", "wasmer_vm_probestack"), + stack_probe: context.create_string_attribute("probe-stack", "inline-asm"), void_ty, i1_ty, From 054e524001d1d0dd4bfe74fcadfbb70a84fb661f Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 25 Feb 2022 16:42:34 +0000 Subject: [PATCH 02/13] Fix stack probing in the singlepass compiler Stack probes must be done before the stack pointer is adjusted. This ensures that the stack pointer is still within the bounds of the stack when inspected by the signal handler. --- lib/compiler-singlepass/src/codegen.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/compiler-singlepass/src/codegen.rs b/lib/compiler-singlepass/src/codegen.rs index 2e68a8fc0d9..f612f371aea 100644 --- a/lib/compiler-singlepass/src/codegen.rs +++ b/lib/compiler-singlepass/src/codegen.rs @@ -510,6 +510,18 @@ impl<'a, M: Machine> FuncGen<'a, M> { // Allocate save area, without actually writing to it. static_area_size = self.machine.round_stack_adjust(static_area_size); + + // Stack probe. + // + // `rep stosq` writes data from low address to high address and may skip the stack guard page. + // so here we probe it explicitly when needed. + for i in (sig.params().len()..n) + .step_by(NATIVE_PAGE_SIZE / 8) + .skip(0) + { + self.machine.zero_location(Size::S64, locations[i]); + } + self.machine.adjust_stack(static_area_size as _); // Save callee-saved registers. @@ -586,17 +598,6 @@ impl<'a, M: Machine> FuncGen<'a, M> { Location::GPR(self.machine.get_vmctx_reg()), ); - // Stack probe. - // - // `rep stosq` writes data from low address to high address and may skip the stack guard page. - // so here we probe it explicitly when needed. - for i in (sig.params().len()..n) - .step_by(NATIVE_PAGE_SIZE / 8) - .skip(1) - { - self.machine.zero_location(Size::S64, locations[i]); - } - // Initialize all normal locals to zero. let mut init_stack_loc_cnt = 0; let mut last_stack_loc = Location::Memory(self.machine.local_pointer(), i32::MAX); From ca6f83c7d283067ac977159d52e1f3770b1efc5e Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 25 Feb 2022 16:44:02 +0000 Subject: [PATCH 03/13] Run Wasm code on a separate stack This uses the [corosensei](https://crates.io/crates/corosensei) crate to run Wasm code on a separate stack from the main thread stack. In trap handlers for stack overflows and memory out of bounds accesses, we can now check whether we are executing on the Wasm stack and reset execution back to the main thread stack when returning from the trap handler. When Wasm code needs to perform an operation which may modify internal data structures (e.g. growing a memory) then execution must switch back to the main thread stack using on_host_stack. This is necessary to avoid leaving internal data structure in an inconsistent state when a stack overflow happens. In the future, this can also be used to suspend execution of a Wasm module (#1127) by modeling it as an async function call. Fixes #2757 Fixes #2562 --- Cargo.lock | 280 ++++--- lib/api/src/sys/externals/function.rs | 73 +- lib/api/src/sys/store.rs | 10 +- lib/engine/src/artifact.rs | 2 +- lib/engine/src/trap/frame_info.rs | 8 - lib/engine/src/trap/mod.rs | 4 +- lib/vm/Cargo.toml | 2 + lib/vm/build.rs | 19 - lib/vm/src/instance/mod.rs | 7 +- lib/vm/src/libcalls.rs | 92 ++- lib/vm/src/trap/handlers.c | 66 -- lib/vm/src/trap/mod.rs | 4 +- lib/vm/src/trap/traphandlers.rs | 1020 ++++++++++++------------- rust-toolchain | 2 +- 14 files changed, 733 insertions(+), 856 deletions(-) delete mode 100644 lib/vm/build.rs delete mode 100644 lib/vm/src/trap/handlers.c diff --git a/Cargo.lock b/Cargo.lock index be39708b755..5d003d151b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,9 +60,9 @@ checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "arbitrary" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510c76ecefdceada737ea728f4f9a84bd2e1ef29f1ba555e560940fe279954de" +checksum = "c38b6b6b79f671c25e1a3e785b7b82d7562ffc9cd3efdc98627e5668a2472490" dependencies = [ "derive_arbitrary", ] @@ -106,15 +106,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", @@ -185,9 +185,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ "generic-array", ] @@ -309,9 +309,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" dependencies = [ "glob", "libc", @@ -419,6 +419,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "corosensei" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85789bf244cdbe2736a4a6912117c0ff010aba1680d81a90abaf96725e0570bc" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "windows-sys", +] + [[package]] name = "cranelift-bforest" version = "0.76.0" @@ -443,7 +455,7 @@ dependencies = [ "log", "regalloc", "smallvec", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", ] [[package]] @@ -478,14 +490,14 @@ dependencies = [ "hashbrown 0.9.1", "log", "smallvec", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", ] [[package]] name = "crc32fast" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] @@ -549,9 +561,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -562,9 +574,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -572,9 +584,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06" dependencies = [ "generic-array", ] @@ -654,9 +666,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b24629208e87a2d8b396ff43b15c4afb0a69cea3fbbaa9ed9b92b7c02f0aed73" +checksum = "98e23c06c035dac87bd802d98f368df73a7f2cb05a66ffbd1f377e821fac4af9" dependencies = [ "proc-macro2", "quote", @@ -683,13 +695,12 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837" dependencies = [ "block-buffer", "crypto-common", - "generic-array", "subtle", ] @@ -770,15 +781,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "encoding_rs" -version = "0.8.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -889,18 +891,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "flate2" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" -dependencies = [ - "cfg-if 1.0.0", - "crc32fast", - "libc", - "miniz_oxide", -] - [[package]] name = "float-cmp" version = "0.8.0" @@ -1206,9 +1196,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.115" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a8d982fa7a96a000f6ec4cfe966de9703eccde29750df2bb8949da91b0e818d" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "libfuzzer-sys" @@ -1233,9 +1223,9 @@ dependencies = [ [[package]] name = "llvm-sys" -version = "120.2.2" +version = "120.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4897352ffc39e1b2b3f7078b632222939044b76d3a99d36666c1c47203c104cc" +checksum = "ce76f8393b7a607a906087666db398d872db739622e644e58552c198ccdfdf45" dependencies = [ "cc", "lazy_static", @@ -1246,9 +1236,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -1309,9 +1299,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] @@ -1469,9 +1459,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "orbclient" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c976c5018e7f1db4359616d8b31ef8ae7d9649b11803c0b38fff67fd2999fc8" +checksum = "2d3aa1482d3a9cb7547932f54a20910090073e81b3b7b236277c91698a10f83e" dependencies = [ "libc", "raw-window-handle 0.3.4", @@ -1484,9 +1474,9 @@ dependencies = [ [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi", ] @@ -1625,9 +1615,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50" dependencies = [ "ansi_term", "ctor", @@ -1705,14 +1695,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -1734,15 +1723,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "raw-window-handle" version = "0.3.4" @@ -1940,7 +1920,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.4", + "semver 1.0.5", ] [[package]] @@ -1990,29 +1970,26 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sdl2" -version = "0.34.5" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deecbc3fa9460acff5a1e563e05cb5f31bba0aa0c214bb49a43db8159176d54b" +checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" dependencies = [ "bitflags", "lazy_static", "libc", - "raw-window-handle 0.3.4", + "raw-window-handle 0.4.2", "sdl2-sys", ] [[package]] name = "sdl2-sys" -version = "0.34.5" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a29aa21f175b5a41a6e26da572d5e5d1ee5660d35f9f9d0913e8a802098f74" +checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "cmake", - "flate2", "libc", - "tar", - "unidiff", "version-compare", ] @@ -2042,9 +2019,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" [[package]] name = "semver-parser" @@ -2102,9 +2079,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa 1.0.1", "ryu", @@ -2292,17 +2269,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tar" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "target-lexicon" version = "0.11.2" @@ -2311,9 +2277,9 @@ checksum = "422045212ea98508ae3d28025bc5aaa2bd4a9cdaecd442a08da2ee620ee9ea95" [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" @@ -2360,7 +2326,7 @@ name = "test-generator" version = "0.1.0" dependencies = [ "anyhow", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", ] [[package]] @@ -2484,9 +2450,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" dependencies = [ "cfg-if 1.0.0", "log", @@ -2497,9 +2463,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" dependencies = [ "proc-macro2", "quote", @@ -2508,18 +2474,19 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" dependencies = [ "lazy_static", + "valuable", ] [[package]] name = "tracing-subscriber" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" +checksum = "74786ce43333fcf51efe947aed9718fbe46d5c7328ec3f1029e818083966d9aa" dependencies = [ "lazy_static", "matchers", @@ -2543,9 +2510,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.54" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04343ff86b62ca40bd5dca986aadf4f10c152a9ebe9a869e456b60fa85afd3f" +checksum = "2d60539445867cdd9680b2bfe2d0428f1814b7d5c9652f09d8d3eae9d19308db" dependencies = [ "glob", "once_cell", @@ -2593,9 +2560,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -2609,23 +2576,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "unidiff" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a62719acf1933bfdbeb73a657ecd9ecece70b405125267dd549e2e2edc232c" -dependencies = [ - "encoding_rs", - "lazy_static", - "regex", -] - [[package]] name = "unix_mode" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35abed4630bb800f02451a7428205d1f37b8e125001471bfab259beee6a587ed" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vec_map" version = "0.8.2" @@ -2634,9 +2596,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version-compare" -version = "0.0.10" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" +checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" [[package]] name = "version_check" @@ -2804,7 +2766,7 @@ dependencies = [ "js-sys", "loupe", "more-asserts", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", "tempfile", "thiserror", "wasm-bindgen", @@ -2931,7 +2893,7 @@ dependencies = [ "serde", "serde_bytes", "smallvec", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", "thiserror", "wasmer-types", "wasmer-vm", @@ -2952,7 +2914,7 @@ dependencies = [ "more-asserts", "rayon", "smallvec", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", "tracing", "wasmer-compiler", "wasmer-types", @@ -2974,9 +2936,9 @@ dependencies = [ "rayon", "regex", "rustc_version 0.4.0", - "semver 1.0.4", + "semver 1.0.5", "smallvec", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -2995,7 +2957,7 @@ dependencies = [ "more-asserts", "rayon", "smallvec", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -3039,7 +3001,7 @@ dependencies = [ "rustc-demangle", "serde", "serde_bytes", - "target-lexicon 0.12.2", + "target-lexicon 0.12.3", "thiserror", "wasmer-compiler", "wasmer-types", @@ -3184,6 +3146,7 @@ dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", + "corosensei", "enum-iterator", "indexmap", "libc", @@ -3192,6 +3155,7 @@ dependencies = [ "more-asserts", "region", "rkyv", + "scopeguard", "serde", "thiserror", "wasmer-types", @@ -3487,23 +3451,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "x11-dl" -version = "2.19.1" +name = "windows-sys" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" dependencies = [ - "lazy_static", - "libc", - "pkg-config", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", ] [[package]] -name = "xattr" -version = "0.2.2" +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" dependencies = [ + "lazy_static", "libc", + "pkg-config", ] [[package]] diff --git a/lib/api/src/sys/externals/function.rs b/lib/api/src/sys/externals/function.rs index 3146c823854..8271b646a75 100644 --- a/lib/api/src/sys/externals/function.rs +++ b/lib/api/src/sys/externals/function.rs @@ -15,7 +15,7 @@ use std::fmt; use std::sync::Arc; use wasmer_engine::{Export, ExportFunction, ExportFunctionMetadata}; use wasmer_vm::{ - raise_user_trap, resume_panic, wasmer_call_trampoline, ImportInitializerFuncPtr, + on_host_stack, raise_user_trap, resume_panic, wasmer_call_trampoline, ImportInitializerFuncPtr, VMCallerCheckedAnyfunc, VMDynamicFunctionContext, VMFuncRef, VMFunction, VMFunctionBody, VMFunctionEnvironment, VMFunctionKind, VMTrampoline, }; @@ -803,32 +803,34 @@ impl VMDynamicFunctionCall for VMDynamicFunctionContext values_vec: *mut i128, ) { use std::panic::{self, AssertUnwindSafe}; - let result = panic::catch_unwind(AssertUnwindSafe(|| { - let func_ty = self.ctx.function_type(); - let mut args = Vec::with_capacity(func_ty.params().len()); - let store = self.ctx.store(); - for (i, ty) in func_ty.params().iter().enumerate() { - args.push(Val::read_value_from(store, values_vec.add(i), *ty)); - } - let returns = self.ctx.call(&args)?; - - // We need to dynamically check that the returns - // match the expected types, as well as expected length. - let return_types = returns.iter().map(|ret| ret.ty()).collect::>(); - if return_types != func_ty.results() { - return Err(RuntimeError::new(format!( - "Dynamic function returned wrong signature. Expected {:?} but got {:?}", - func_ty.results(), - return_types - ))); - } - for (i, ret) in returns.iter().enumerate() { - ret.write_value_to(values_vec.add(i)); - } - Ok(()) - })); // We get extern ref drops at the end of this block that we don't need. - // By preventing extern ref incs in the code above we can save the work of - // incrementing and decrementing. However the logic as-is is correct. + let result = on_host_stack(|| { + panic::catch_unwind(AssertUnwindSafe(|| { + let func_ty = self.ctx.function_type(); + let mut args = Vec::with_capacity(func_ty.params().len()); + let store = self.ctx.store(); + for (i, ty) in func_ty.params().iter().enumerate() { + args.push(Val::read_value_from(store, values_vec.add(i), *ty)); + } + let returns = self.ctx.call(&args)?; + + // We need to dynamically check that the returns + // match the expected types, as well as expected length. + let return_types = returns.iter().map(|ret| ret.ty()).collect::>(); + if return_types != func_ty.results() { + return Err(RuntimeError::new(format!( + "Dynamic function returned wrong signature. Expected {:?} but got {:?}", + func_ty.results(), + return_types + ))); + } + for (i, ret) in returns.iter().enumerate() { + ret.write_value_to(values_vec.add(i)); + } + Ok(()) + })) // We get extern ref drops at the end of this block that we don't need. + // By preventing extern ref incs in the code above we can save the work of + // incrementing and decrementing. However the logic as-is is correct. + }); match result { Ok(Ok(())) => {} @@ -846,6 +848,7 @@ mod inner { use std::error::Error; use std::marker::PhantomData; use std::panic::{self, AssertUnwindSafe}; + use wasmer_vm::on_host_stack; #[cfg(feature = "experimental-reference-types-extern-ref")] pub use wasmer_types::{ExternRef, VMExternRef}; @@ -1309,9 +1312,11 @@ mod inner { Func: Fn( $( $x ),* ) -> RetsAsResult + 'static { let func: &Func = unsafe { &*(&() as *const () as *const Func) }; - let result = panic::catch_unwind(AssertUnwindSafe(|| { - func( $( FromToNativeWasmType::from_native($x) ),* ).into_result() - })); + let result = on_host_stack(|| { + panic::catch_unwind(AssertUnwindSafe(|| { + func( $( FromToNativeWasmType::from_native($x) ),* ).into_result() + })) + }); match result { Ok(Ok(result)) => return result.into_c_struct(), @@ -1353,9 +1358,11 @@ mod inner { { let func: &Func = unsafe { &*(&() as *const () as *const Func) }; - let result = panic::catch_unwind(AssertUnwindSafe(|| { - func(env, $( FromToNativeWasmType::from_native($x) ),* ).into_result() - })); + let result = on_host_stack(|| { + panic::catch_unwind(AssertUnwindSafe(|| { + func(env, $( FromToNativeWasmType::from_native($x) ),* ).into_result() + })) + }); match result { Ok(Ok(result)) => return result.into_c_struct(), diff --git a/lib/api/src/sys/store.rs b/lib/api/src/sys/store.rs index 29b81e92cc7..6f301a3c76a 100644 --- a/lib/api/src/sys/store.rs +++ b/lib/api/src/sys/store.rs @@ -1,11 +1,10 @@ use crate::sys::tunables::BaseTunables; use loupe::MemoryUsage; -use std::any::Any; use std::fmt; use std::sync::{Arc, RwLock}; #[cfg(all(feature = "compiler", feature = "engine"))] use wasmer_compiler::CompilerConfig; -use wasmer_engine::{is_wasm_pc, Engine, Tunables}; +use wasmer_engine::{Engine, Tunables}; use wasmer_vm::{init_traps, TrapHandler, TrapHandlerFn}; /// The store represents all global state that can be manipulated by @@ -48,7 +47,7 @@ impl Store { { // Make sure the signal handlers are installed. // This is required for handling traps. - init_traps(is_wasm_pc); + init_traps(); Self { engine: engine.cloned(), @@ -82,11 +81,6 @@ impl PartialEq for Store { } unsafe impl TrapHandler for Store { - #[inline] - fn as_any(&self) -> &dyn Any { - self - } - fn custom_trap_handler(&self, call: &dyn Fn(&TrapHandlerFn) -> bool) -> bool { if let Some(handler) = *&self.trap_handler.read().unwrap().as_ref() { call(handler) diff --git a/lib/engine/src/artifact.rs b/lib/engine/src/artifact.rs index 0a5c5df1797..9b29f43fb40 100644 --- a/lib/engine/src/artifact.rs +++ b/lib/engine/src/artifact.rs @@ -176,7 +176,7 @@ pub trait Artifact: Send + Sync + Upcastable + MemoryUsage { /// See [`InstanceHandle::finish_instantiation`]. unsafe fn finish_instantiation( &self, - trap_handler: &dyn TrapHandler, + trap_handler: &(dyn TrapHandler + 'static), handle: &InstanceHandle, ) -> Result<(), InstantiationError> { let data_initializers = self diff --git a/lib/engine/src/trap/frame_info.rs b/lib/engine/src/trap/frame_info.rs index 9b7e4df95c5..451fa3f3505 100644 --- a/lib/engine/src/trap/frame_info.rs +++ b/lib/engine/src/trap/frame_info.rs @@ -43,14 +43,6 @@ pub struct GlobalFrameInfo { ranges: BTreeMap, } -/// Returns whether the `pc`, according to globally registered information, -/// is a wasm trap or not. -pub fn is_wasm_pc(pc: usize) -> bool { - let frame_info = FRAME_INFO.read().unwrap(); - let module_info = frame_info.module_info(pc); - module_info.is_some() -} - /// An RAII structure used to unregister a module's frame information when the /// module is destroyed. #[derive(MemoryUsage)] diff --git a/lib/engine/src/trap/mod.rs b/lib/engine/src/trap/mod.rs index ca7b548c690..d1eac6b420b 100644 --- a/lib/engine/src/trap/mod.rs +++ b/lib/engine/src/trap/mod.rs @@ -2,6 +2,6 @@ mod error; mod frame_info; pub use error::RuntimeError; pub use frame_info::{ - is_wasm_pc, register as register_frame_info, FrameInfo, FunctionExtent, - GlobalFrameInfoRegistration, FRAME_INFO, + register as register_frame_info, FrameInfo, FunctionExtent, GlobalFrameInfoRegistration, + FRAME_INFO, }; diff --git a/lib/vm/Cargo.toml b/lib/vm/Cargo.toml index 84bd4b2493e..6d9453e1507 100644 --- a/lib/vm/Cargo.toml +++ b/lib/vm/Cargo.toml @@ -24,6 +24,8 @@ serde = { version = "1.0", features = ["derive", "rc"] } rkyv = { version = "0.7.20", optional = true } loupe = { version = "0.1", features = ["enable-indexmap"] } enum-iterator = "0.7.0" +corosensei = { version = "0.1.1" } +scopeguard = "1.1.0" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["winbase", "memoryapi", "errhandlingapi"] } diff --git a/lib/vm/build.rs b/lib/vm/build.rs deleted file mode 100644 index 278eb570db7..00000000000 --- a/lib/vm/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Runtime build script compiles C code using setjmp for trap handling. - -use std::env; - -fn main() { - println!("cargo:rerun-if-changed=src/trap/handlers.c"); - - cc::Build::new() - .warnings(true) - .define( - &format!( - "CFG_TARGET_OS_{}", - env::var("CARGO_CFG_TARGET_OS").unwrap().to_uppercase() - ), - None, - ) - .file("src/trap/handlers.c") - .compile("handlers"); -} diff --git a/lib/vm/src/instance/mod.rs b/lib/vm/src/instance/mod.rs index 0db1147af84..4bac14e3a8f 100644 --- a/lib/vm/src/instance/mod.rs +++ b/lib/vm/src/instance/mod.rs @@ -395,7 +395,10 @@ impl Instance { } /// Invoke the WebAssembly start function of the instance, if one is present. - fn invoke_start_function(&self, trap_handler: &dyn TrapHandler) -> Result<(), Trap> { + fn invoke_start_function( + &self, + trap_handler: &(dyn TrapHandler + 'static), + ) -> Result<(), Trap> { let start_index = match self.module.start_function { Some(idx) => idx, None => return Ok(()), @@ -1015,7 +1018,7 @@ impl InstanceHandle { /// Only safe to call immediately after instantiation. pub unsafe fn finish_instantiation( &self, - trap_handler: &dyn TrapHandler, + trap_handler: &(dyn TrapHandler + 'static), data_initializers: &[DataInitializer<'_>], ) -> Result<(), Trap> { let instance = self.instance().as_ref(); diff --git a/lib/vm/src/libcalls.rs b/lib/vm/src/libcalls.rs index 89e7ffd5e25..83149f6ac12 100644 --- a/lib/vm/src/libcalls.rs +++ b/lib/vm/src/libcalls.rs @@ -42,7 +42,7 @@ use crate::probestack::PROBESTACK; use crate::table::{RawTableElement, TableElement}; use crate::trap::{raise_lib_trap, Trap, TrapCode}; use crate::vmcontext::VMContext; -use crate::VMExternRef; +use crate::{on_host_stack, VMExternRef}; use enum_iterator::IntoEnumIterator; use loupe::MemoryUsage; #[cfg(feature = "enable-rkyv")] @@ -155,13 +155,15 @@ pub unsafe extern "C" fn wasmer_vm_memory32_grow( delta: u32, memory_index: u32, ) -> u32 { - let instance = (&*vmctx).instance(); - let memory_index = LocalMemoryIndex::from_u32(memory_index); + on_host_stack(|| { + let instance = (&*vmctx).instance(); + let memory_index = LocalMemoryIndex::from_u32(memory_index); - instance - .memory_grow(memory_index, delta) - .map(|pages| pages.0) - .unwrap_or(u32::max_value()) + instance + .memory_grow(memory_index, delta) + .map(|pages| pages.0) + .unwrap_or(u32::max_value()) + }) } /// Implementation of memory.grow for imported 32-bit memories. @@ -175,13 +177,15 @@ pub unsafe extern "C" fn wasmer_vm_imported_memory32_grow( delta: u32, memory_index: u32, ) -> u32 { - let instance = (&*vmctx).instance(); - let memory_index = MemoryIndex::from_u32(memory_index); + on_host_stack(|| { + let instance = (&*vmctx).instance(); + let memory_index = MemoryIndex::from_u32(memory_index); - instance - .imported_memory_grow(memory_index, delta) - .map(|pages| pages.0) - .unwrap_or(u32::max_value()) + instance + .imported_memory_grow(memory_index, delta) + .map(|pages| pages.0) + .unwrap_or(u32::max_value()) + }) } /// Implementation of memory.size for locally-defined 32-bit memories. @@ -440,18 +444,20 @@ pub unsafe extern "C" fn wasmer_vm_table_grow( delta: u32, table_index: u32, ) -> u32 { - let instance = (&*vmctx).instance(); - let table_index = LocalTableIndex::from_u32(table_index); + on_host_stack(|| { + let instance = (&*vmctx).instance(); + let table_index = LocalTableIndex::from_u32(table_index); - let init_value = match instance.get_local_table(table_index).ty().ty { - Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()), - Type::FuncRef => TableElement::FuncRef(init_value.func_ref), - _ => panic!("Unrecognized table type: does not contain references"), - }; + let init_value = match instance.get_local_table(table_index).ty().ty { + Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()), + Type::FuncRef => TableElement::FuncRef(init_value.func_ref), + _ => panic!("Unrecognized table type: does not contain references"), + }; - instance - .table_grow(table_index, delta, init_value) - .unwrap_or(u32::max_value()) + instance + .table_grow(table_index, delta, init_value) + .unwrap_or(u32::max_value()) + }) } /// Implementation of `table.grow` for imported tables. @@ -466,17 +472,19 @@ pub unsafe extern "C" fn wasmer_vm_imported_table_grow( delta: u32, table_index: u32, ) -> u32 { - let instance = (&*vmctx).instance(); - let table_index = TableIndex::from_u32(table_index); - let init_value = match instance.get_table(table_index).ty().ty { - Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()), - Type::FuncRef => TableElement::FuncRef(init_value.func_ref), - _ => panic!("Unrecognized table type: does not contain references"), - }; + on_host_stack(|| { + let instance = (&*vmctx).instance(); + let table_index = TableIndex::from_u32(table_index); + let init_value = match instance.get_table(table_index).ty().ty { + Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()), + Type::FuncRef => TableElement::FuncRef(init_value.func_ref), + _ => panic!("Unrecognized table type: does not contain references"), + }; - instance - .imported_table_grow(table_index, delta, init_value) - .unwrap_or(u32::max_value()) + instance + .imported_table_grow(table_index, delta, init_value) + .unwrap_or(u32::max_value()) + }) } /// Implementation of `func.ref`. @@ -517,7 +525,7 @@ pub unsafe extern "C" fn wasmer_vm_externref_inc(externref: VMExternRef) { /// and other serious memory bugs may occur. #[no_mangle] pub unsafe extern "C" fn wasmer_vm_externref_dec(mut externref: VMExternRef) { - externref.ref_drop() + on_host_stack(|| externref.ref_drop()) } /// Implementation of `elem.drop`. @@ -527,9 +535,11 @@ pub unsafe extern "C" fn wasmer_vm_externref_dec(mut externref: VMExternRef) { /// `vmctx` must be dereferenceable. #[no_mangle] pub unsafe extern "C" fn wasmer_vm_elem_drop(vmctx: *mut VMContext, elem_index: u32) { - let elem_index = ElemIndex::from_u32(elem_index); - let instance = (&*vmctx).instance(); - instance.elem_drop(elem_index); + on_host_stack(|| { + let elem_index = ElemIndex::from_u32(elem_index); + let instance = (&*vmctx).instance(); + instance.elem_drop(elem_index); + }) } /// Implementation of `memory.copy` for locally defined memories. @@ -656,9 +666,11 @@ pub unsafe extern "C" fn wasmer_vm_memory32_init( /// `vmctx` must be dereferenceable. #[no_mangle] pub unsafe extern "C" fn wasmer_vm_data_drop(vmctx: *mut VMContext, data_index: u32) { - let data_index = DataIndex::from_u32(data_index); - let instance = (&*vmctx).instance(); - instance.data_drop(data_index) + on_host_stack(|| { + let data_index = DataIndex::from_u32(data_index); + let instance = (&*vmctx).instance(); + instance.data_drop(data_index) + }) } /// Implementation for raising a trap diff --git a/lib/vm/src/trap/handlers.c b/lib/vm/src/trap/handlers.c deleted file mode 100644 index 3273353acc2..00000000000 --- a/lib/vm/src/trap/handlers.c +++ /dev/null @@ -1,66 +0,0 @@ -// This file contains partial code from other sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md - -#include -#include -#if defined(CFG_TARGET_OS_MACOS) -#include -#include -#include -#endif -// Note that `sigsetjmp` and `siglongjmp` are used here where possible to -// explicitly pass a 0 argument to `sigsetjmp` that we don't need to preserve -// the process signal mask. This should make this call a bit faster b/c it -// doesn't need to touch the kernel signal handling routines. -// In case of macOS, stackoverflow -#if defined(CFG_TARGET_OS_WINDOWS) -// On Windows, default setjmp/longjmp sequence will try to unwind the stack -// it's fine most of the time, but not for JIT'd code that may not respect stack ordring -// Using a special setjmp here, with NULL as second parameter to disable that behaviour -// and have a regular simple setjmp/longjmp sequence -#ifdef __MINGW32__ -// MINGW64 doesn't expose the __intrinsic_setjmp function, but a similar _setjump instead -#define platform_setjmp(buf) _setjmp(buf, NULL) -#else -#define platform_setjmp(buf) __intrinsic_setjmp(buf, NULL) -#endif -#define platform_longjmp(buf, arg) longjmp(buf, arg) -#define platform_jmp_buf jmp_buf -#elif defined(CFG_TARGET_OS_MACOS) -// TODO: This is not the most performant, since it adds overhead when calling functions -// https://github.com/wasmerio/wasmer/issues/2562 -#define platform_setjmp(buf) sigsetjmp(buf, 1) -#define platform_longjmp(buf, arg) siglongjmp(buf, arg) -#define platform_jmp_buf sigjmp_buf -#else -#define platform_setjmp(buf) sigsetjmp(buf, 0) -#define platform_longjmp(buf, arg) siglongjmp(buf, arg) -#define platform_jmp_buf sigjmp_buf -#endif - -int wasmer_register_setjmp( - void **buf_storage, - void (*body)(void*), - void *payload) { - #if 0 && defined(CFG_TARGET_OS_MACOS) - // Enable this block to ba able to debug Segfault with lldb - // This will mask the EXC_BAD_ACCESS from lldb... - static int allow_bad_access = 0; - if(!allow_bad_access) { - allow_bad_access = 1; - task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, 0); - } - #endif - platform_jmp_buf buf; - if (platform_setjmp(buf) != 0) { - return 0; - } - *buf_storage = &buf; - body(payload); - return 1; -} - -void wasmer_unwind(void *JmpBuf) { - platform_jmp_buf *buf = (platform_jmp_buf*) JmpBuf; - platform_longjmp(*buf, 1); -} diff --git a/lib/vm/src/trap/mod.rs b/lib/vm/src/trap/mod.rs index 26b6c2a5321..6d406f56a79 100644 --- a/lib/vm/src/trap/mod.rs +++ b/lib/vm/src/trap/mod.rs @@ -8,7 +8,7 @@ mod traphandlers; pub use trapcode::TrapCode; pub use traphandlers::{ - catch_traps, catch_traps_with_result, raise_lib_trap, raise_user_trap, wasmer_call_trampoline, - TlsRestore, Trap, TrapHandler, TrapHandlerFn, + catch_traps, on_host_stack, raise_lib_trap, raise_user_trap, wasmer_call_trampoline, Trap, + TrapHandler, TrapHandlerFn, }; pub use traphandlers::{init_traps, resume_panic}; diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index 79660134fac..e79385cd30b 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -7,14 +7,19 @@ use super::trapcode::TrapCode; use crate::vmcontext::{VMFunctionBody, VMFunctionEnvironment, VMTrampoline}; use backtrace::Backtrace; +use corosensei::trap::{CoroutineTrapHandler, TrapHandlerRegs}; +use corosensei::{CoroutineResult, ScopedCoroutine, Yielder}; +use scopeguard::defer; use std::any::Any; -use std::cell::{Cell, UnsafeCell}; +use std::cell::Cell; use std::error::Error; use std::io; -use std::mem::{self, MaybeUninit}; -use std::ptr; +use std::mem; +#[cfg(unix)] +use std::mem::MaybeUninit; +use std::ptr::{self, NonNull}; +use std::sync::atomic::{compiler_fence, AtomicPtr, Ordering}; use std::sync::Once; -pub use tls::TlsRestore; cfg_if::cfg_if! { if #[cfg(unix)] { @@ -26,13 +31,17 @@ cfg_if::cfg_if! { } } -extern "C" { - fn wasmer_register_setjmp( - jmp_buf: *mut *const u8, - callback: extern "C" fn(*mut u8), - payload: *mut u8, - ) -> i32; - fn wasmer_unwind(jmp_buf: *const u8) -> !; +/// A package of functionality needed by `catch_traps` to figure out what to do +/// when handling a trap. +/// +/// Note that this is an `unsafe` trait at least because it's being run in the +/// context of a synchronous signal handler, so it needs to be careful to not +/// access too much state in answering these queries. +pub unsafe trait TrapHandler { + /// Uses `call` to call a custom signal handler, if one is specified. + /// + /// Returns `true` if `call` returns true, otherwise returns `false`. + fn custom_trap_handler(&self, call: &dyn Fn(&TrapHandlerFn) -> bool) -> bool; } cfg_if::cfg_if! { @@ -87,31 +96,6 @@ cfg_if::cfg_if! { } } - #[cfg(target_vendor = "apple")] - unsafe fn thread_stack() -> (usize, usize) { - let this_thread = libc::pthread_self(); - let stackaddr = libc::pthread_get_stackaddr_np(this_thread); - let stacksize = libc::pthread_get_stacksize_np(this_thread); - (stackaddr as usize - stacksize, stacksize) - } - - #[cfg(not(target_vendor = "apple"))] - unsafe fn thread_stack() -> (usize, usize) { - let this_thread = libc::pthread_self(); - let mut thread_attrs: libc::pthread_attr_t = mem::zeroed(); - let mut stackaddr: *mut libc::c_void = ptr::null_mut(); - let mut stacksize: libc::size_t = 0; - #[cfg(not(target_os = "freebsd"))] - let ok = libc::pthread_getattr_np(this_thread, &mut thread_attrs); - #[cfg(target_os = "freebsd")] - let ok = libc::pthread_attr_get_np(this_thread, &mut thread_attrs); - if ok == 0 { - libc::pthread_attr_getstack(&thread_attrs, &mut stackaddr, &mut stacksize); - libc::pthread_attr_destroy(&mut thread_attrs); - } - (stackaddr as usize, stacksize) - } - unsafe extern "C" fn trap_handler( signum: libc::c_int, siginfo: *mut libc::siginfo_t, @@ -124,56 +108,23 @@ cfg_if::cfg_if! { libc::SIGILL => &PREV_SIGILL, _ => panic!("unknown signal: {}", signum), }; - // We try to get the Code trap associated to this signal - let maybe_signal_trap = match signum { + // We try to get the fault address associated to this signal + let maybe_fault_address = match signum { libc::SIGSEGV | libc::SIGBUS => { - let addr = (*siginfo).si_addr() as usize; - let (stackaddr, stacksize) = thread_stack(); - // The stack and its guard page covers the - // range [stackaddr - guard pages .. stackaddr + stacksize). - // We assume the guard page is 1 page, and pages are 4KiB (or 16KiB in Apple Silicon) - if stackaddr - region::page::size() <= addr && addr < stackaddr + stacksize { - Some(TrapCode::StackOverflow) - } else { - Some(TrapCode::HeapAccessOutOfBounds) - } + Some((*siginfo).si_addr() as usize) } _ => None, }; - let handled = tls::with(|info| { - // If no wasm code is executing, we don't handle this as a wasm - // trap. - let info = match info { - Some(info) => info, - None => return false, - }; - - // If we hit an exception while handling a previous trap, that's - // quite bad, so bail out and let the system handle this - // recursive segfault. - // - // Otherwise flag ourselves as handling a trap, do the trap - // handling, and reset our trap handling flag. Then we figure - // out what to do based on the result of the trap handling. - let jmp_buf = info.handle_trap( - get_pc(context), - false, - maybe_signal_trap, - |handler| handler(signum, siginfo, context), - ); - - // Figure out what to do based on the result of this handling of - // the trap. Note that our sentinel value of 1 means that the - // exception was handled by a custom exception handler, so we - // keep executing. - if jmp_buf.is_null() { - false - } else if jmp_buf as usize == 1 { - true - } else { - wasmer_unwind(jmp_buf) - } - }); + + let ucontext = &mut *(context as *mut libc::ucontext_t); + let (pc, sp) = get_pc_sp(ucontext); + let handled = TrapHandlerContext::handle_trap( + pc, + sp, + maybe_fault_address, + |regs| update_context(ucontext, regs), + |handler| handler(signum, siginfo, context), + ); if handled { return; @@ -194,104 +145,177 @@ cfg_if::cfg_if! { usize, extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void), >(previous.sa_sigaction)(signum, siginfo, context) - } else if previous.sa_sigaction == libc::SIG_DFL || - previous.sa_sigaction == libc::SIG_IGN + } else if previous.sa_sigaction == libc::SIG_DFL { libc::sigaction(signum, previous, ptr::null_mut()); - } else { + } else if previous.sa_sigaction != libc::SIG_IGN { mem::transmute::( previous.sa_sigaction )(signum) } } - unsafe fn get_pc(cx: *mut libc::c_void) -> *const u8 { + unsafe fn get_pc_sp(context: &libc::ucontext_t) -> (usize, usize) { + let (pc, sp); cfg_if::cfg_if! { - if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { - let cx = &*(cx as *const libc::ucontext_t); - cx.uc_mcontext.gregs[libc::REG_RIP as usize] as *const u8 - } else if #[cfg(all(target_os = "linux", target_arch = "x86"))] { - let cx = &*(cx as *const libc::ucontext_t); - cx.uc_mcontext.gregs[libc::REG_EIP as usize] as *const u8 - } else if #[cfg(all(target_os = "android", target_arch = "x86"))] { - let cx = &*(cx as *const libc::ucontext_t); - cx.uc_mcontext.gregs[libc::REG_EIP as usize] as *const u8 - } else if #[cfg(all(target_os = "linux", target_arch = "aarch64"))] { - let cx = &*(cx as *const libc::ucontext_t); - cx.uc_mcontext.pc as *const u8 - } else if #[cfg(all(target_os = "android", target_arch = "aarch64"))] { - let cx = &*(cx as *const libc::ucontext_t); - cx.uc_mcontext.pc as *const u8 + if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "x86_64", + ))] { + pc = context.uc_mcontext.gregs[libc::REG_RIP as usize] as usize; + sp = context.uc_mcontext.gregs[libc::REG_RSP as usize] as usize; + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "x86", + ))] { + pc = context.uc_mcontext.gregs[libc::REG_EIP as usize] as usize; + sp = context.uc_mcontext.gregs[libc::REG_ESP as usize] as usize; + } else if #[cfg(all(target_os = "freebsd", target_arch = "x86"))] { + pc = context.uc_mcontext.mc_rip as usize; + sp = context.uc_mcontext.mc_rsp as usize; } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] { - let cx = &*(cx as *const libc::ucontext_t); - (*cx.uc_mcontext).__ss.__rip as *const u8 + pc = (*context.uc_mcontext).__ss.__rip as usize; + sp = (*context.uc_mcontext).__ss.__rsp as usize; + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "aarch64", + ))] { + pc = context.uc_mcontext.pc as usize; + sp = context.uc_mcontext.sp as usize; + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "arm", + ))] { + pc = context.uc_mcontext.arm_pc as usize; + sp = context.uc_mcontext.arm_sp as usize; + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + any(target_arch = "riscv64", target_arch = "riscv32"), + ))] { + pc = context.uc_mcontext.__gregs[libc::REG_PC] as usize; + sp = context.uc_mcontext.__gregs[libc::REG_SP] as usize; } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] { - use std::mem; - // TODO: This should be integrated into rust/libc - // Related issue: https://github.com/rust-lang/libc/issues/1977 - #[allow(non_camel_case_types)] - pub struct __darwin_arm_thread_state64 { - pub __x: [u64; 29], /* General purpose registers x0-x28 */ - pub __fp: u64, /* Frame pointer x29 */ - pub __lr: u64, /* Link register x30 */ - pub __sp: u64, /* Stack pointer x31 */ - pub __pc: u64, /* Program counter */ - pub __cpsr: u32, /* Current program status register */ - pub __pad: u32, /* Same size for 32-bit or 64-bit clients */ - } + pc = (*context.uc_mcontext).__ss.__pc as usize; + sp = (*context.uc_mcontext).__ss.__sp as usize; + } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] { + pc = context.uc_mcontext.mc_gpregs.gp_elr as usize; + sp = context.uc_mcontext.mc_gpregs.gp_sp as usize; + } else { + compile_error!("Unsupported platform"); + } + }; + (pc, sp) + } - let cx = &*(cx as *const libc::ucontext_t); - let uc_mcontext = mem::transmute::<_, *const __darwin_arm_thread_state64>(&(*cx.uc_mcontext).__ss); - (*uc_mcontext).__pc as *const u8 + unsafe fn update_context(context: &mut libc::ucontext_t, regs: TrapHandlerRegs) { + cfg_if::cfg_if! { + if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "x86_64", + ))] { + let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs; + context.uc_mcontext.gregs[libc::REG_RIP as usize] = rip as i64; + context.uc_mcontext.gregs[libc::REG_RSP as usize] = rsp as i64; + context.uc_mcontext.gregs[libc::REG_RBP as usize] = rbp as i64; + context.uc_mcontext.gregs[libc::REG_RDI as usize] = rdi as i64; + context.uc_mcontext.gregs[libc::REG_RSI as usize] = rsi as i64; + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "x86", + ))] { + let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs; + context.uc_mcontext.gregs[libc::REG_EIP as usize] = eip as i32; + context.uc_mcontext.gregs[libc::REG_ESP as usize] = esp as i32; + context.uc_mcontext.gregs[libc::REG_EBP as usize] = ebp as i32; + context.uc_mcontext.gregs[libc::REG_ECX as usize] = ecx as i32; + context.uc_mcontext.gregs[libc::REG_EDX as usize] = edx as i32; + } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] { + let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs; + (*context.uc_mcontext).__ss.__rip = rip; + (*context.uc_mcontext).__ss.__rsp = rsp; + (*context.uc_mcontext).__ss.__rbp = rbp; + (*context.uc_mcontext).__ss.__rdi = rdi; + (*context.uc_mcontext).__ss.__rsi = rsi; } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] { - let cx = &*(cx as *const libc::ucontext_t); - cx.uc_mcontext.mc_rip as *const u8 - } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] { - #[repr(align(16))] - #[allow(non_camel_case_types)] - pub struct gpregs { - pub gp_x: [libc::register_t; 30], - pub gp_lr: libc::register_t, - pub gp_sp: libc::register_t, - pub gp_elr: libc::register_t, - pub gp_spsr: u32, - pub gp_pad: libc::c_int, - }; - #[repr(align(16))] - #[allow(non_camel_case_types)] - pub struct fpregs { - pub fp_q: [u128; 32], - pub fp_sr: u32, - pub fp_cr: u32, - pub fp_flags: libc::c_int, - pub fp_pad: libc::c_int, - }; - #[repr(align(16))] - #[allow(non_camel_case_types)] - pub struct mcontext_t { - pub mc_gpregs: gpregs, - pub mc_fpregs: fpregs, - pub mc_flags: libc::c_int, - pub mc_pad: libc::c_int, - pub mc_spare: [u64; 8], - }; - #[repr(align(16))] - #[allow(non_camel_case_types)] - pub struct ucontext_t { - pub uc_sigmask: libc::sigset_t, - pub uc_mcontext: mcontext_t, - pub uc_link: *mut ucontext_t, - pub uc_stack: libc::stack_t, - pub uc_flags: libc::c_int, - __spare__: [libc::c_int; 4], + let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs; + context.uc_mcontext.mc_rip = rip as libc::register_t; + context.uc_mcontext.mc_rsp = rsp as libc::register_t; + context.uc_mcontext.mc_rbp = rbp as libc::register_t; + context.uc_mcontext.mc_rdi = rdi as libc::register_t; + context.uc_mcontext.mc_rsi = rsi as libc::register_t; + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "aarch64", + ))] { + let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs; + context.uc_mcontext.pc = pc; + context.uc_mcontext.sp = sp; + context.uc_mcontext.regs[0] = x0; + context.uc_mcontext.regs[1] = x1; + context.uc_mcontext.regs[29] = x29; + context.uc_mcontext.regs[30] = lr; + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + target_arch = "arm", + ))] { + let TrapHandlerRegs { + pc, + r0, + r1, + r7, + r11, + r13, + r14, + cpsr_thumb, + cpsr_endian, + } = regs; + context.uc_mcontext.arm_pc = pc; + context.uc_mcontext.arm_r0 = r0; + context.uc_mcontext.arm_r1 = r1; + context.uc_mcontext.arm_r7 = r7; + context.uc_mcontext.arm_fp = r11; + context.uc_mcontext.arm_sp = r13; + context.uc_mcontext.arm_lr = r14; + if cpsr_thumb { + context.uc_mcontext.arm_cpsr |= 0x20; + } else { + context.uc_mcontext.arm_cpsr &= !0x20; } - - let cx = &*(cx as *const ucontext_t); - cx.uc_mcontext.mc_gpregs.gp_elr as *const u8 + if cpsr_endian { + context.uc_mcontext.arm_cpsr |= 0x200; + } else { + context.uc_mcontext.arm_cpsr &= !0x200; + } + } else if #[cfg(all( + any(target_os = "linux", target_os = "android"), + any(target_arch = "riscv64", target_arch = "riscv32"), + ))] { + let TrapHandlerRegs { pc, ra, sp, a0, a1, s0 } = regs; + context.uc_mcontext.__gregs[libc::REG_PC] = pc as libc::c_ulong; + context.uc_mcontext.__gregs[libc::REG_RA] = ra as libc::c_ulong; + context.uc_mcontext.__gregs[libc::REG_SP] = sp as libc::c_ulong; + context.uc_mcontext.__gregs[libc::REG_A0] = a0 as libc::c_ulong; + context.uc_mcontext.__gregs[libc::REG_A0 + 1] = a1 as libc::c_ulong; + context.uc_mcontext.__gregs[libc::REG_S0] = s0 as libc::c_ulong; + } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] { + let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs; + (*context.uc_mcontext).__ss.__pc = pc; + (*context.uc_mcontext).__ss.__sp = sp; + (*context.uc_mcontext).__ss.__x[0] = x0; + (*context.uc_mcontext).__ss.__x[1] = x1; + (*context.uc_mcontext).__ss.__fp = x29; + (*context.uc_mcontext).__ss.__lr = lr; + } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] { + context.uc_mcontext.mc_gpregs.gp_pc = pc as libc::register_t; + context.uc_mcontext.mc_gpregs.gp_sp = sp as libc::register_t; + context.uc_mcontext.mc_gpregs.gp_x[0] = x0 as libc::register_t; + context.uc_mcontext.mc_gpregs.gp_x[1] = x1 as libc::register_t; + context.uc_mcontext.mc_gpregs.gp_x[29] = x29 as libc::register_t; + context.uc_mcontext.mc_gpregs.gp_x[30] = lr as libc::register_t; } else { - compile_error!("unsupported platform"); + compile_error!("Unsupported platform"); } - } + }; } } else if #[cfg(target_os = "windows")] { use winapi::um::errhandlingapi::*; @@ -336,45 +360,73 @@ cfg_if::cfg_if! { // return EXCEPTION_CONTINUE_SEARCH; // } + let context = &mut *(*exception_info).ContextRecord; + let (pc, sp) = get_pc_sp(context); + + // We try to get the fault address associated to this exception. + let maybe_fault_address = match record.ExceptionCode { + EXCEPTION_ACCESS_VIOLATION => Some(record.ExceptionInformation[1]), + EXCEPTION_STACK_OVERFLOW => Some(sp), + _ => None, + }; + // This is basically the same as the unix version above, only with a // few parameters tweaked here and there. - tls::with(|info| { - let info = match info { - Some(info) => info, - None => return EXCEPTION_CONTINUE_SEARCH, - }; - #[cfg(target_pointer_width = "32")] - let pc = (*(*exception_info).ContextRecord).Eip as *const u8; - - #[cfg(target_pointer_width = "64")] - let pc = (*(*exception_info).ContextRecord).Rip as *const u8; - - let jmp_buf = info.handle_trap( - pc, - record.ExceptionCode == EXCEPTION_STACK_OVERFLOW, - // TODO: fix the signal trap associated to memory access in Windows - None, - |handler| handler(exception_info), - ); - if jmp_buf.is_null() { - EXCEPTION_CONTINUE_SEARCH - } else if jmp_buf as usize == 1 { - EXCEPTION_CONTINUE_EXECUTION + let handled = TrapHandlerContext::handle_trap( + pc, + sp, + maybe_fault_address, + |regs| update_context(context, regs), + |handler| handler(exception_info), + ); + + if handled { + EXCEPTION_CONTINUE_EXECUTION + } else { + EXCEPTION_CONTINUE_SEARCH + } + } + + unsafe fn get_pc_sp(context: &CONTEXT) -> (usize, usize) { + let (pc, sp); + cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + pc = context.Rip as usize; + sp = context.Rsp as usize; + } else if #[cfg(target_arch = "x86")] { + pc = context.Rip as usize; + sp = context.Rsp as usize; } else { - wasmer_unwind(jmp_buf) + compile_error!("Unsupported platform"); } - }) + }; + (pc, sp) + } + + unsafe fn update_context(context: &mut CONTEXT, regs: TrapHandlerRegs) { + cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs; + context.Rip = rip; + context.Rsp = rsp; + context.Rbp = rbp; + context.Rdi = rdi; + context.Rsi = rsi; + } else if #[cfg(target_arch = "x86")] { + let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs; + context.Eip = eip; + context.Esp = esp; + context.Ebp = ebp; + context.Ecx = ecx; + context.Edx = edx; + } else { + compile_error!("Unsupported platform"); + } + }; } } } -/// Globally-set callback to determine whether a program counter is actually a -/// wasm trap. -/// -/// This is initialized during `init_traps` below. The definition lives within -/// `wasmer` currently. -static mut IS_WASM_PC: fn(usize) -> bool = |_| false; - /// This function is required to be called before any WebAssembly is entered. /// This will configure global state such as signal handlers to prepare the /// process to receive wasm traps. @@ -383,15 +435,9 @@ static mut IS_WASM_PC: fn(usize) -> bool = |_| false; /// WebAssembly but it must also be called once-per-thread that enters /// WebAssembly. Currently in wasmer's integration this function is called on /// creation of a `Store`. -/// -/// The `is_wasm_pc` argument is used when a trap happens to determine if a -/// program counter is the pc of an actual wasm trap or not. This is then used -/// to disambiguate faults that happen due to wasm and faults that happen due to -/// bugs in Rust or elsewhere. -pub fn init_traps(is_wasm_pc: fn(usize) -> bool) { +pub fn init_traps() { static INIT: Once = Once::new(); INIT.call_once(|| unsafe { - IS_WASM_PC = is_wasm_pc; platform_init(); }); } @@ -409,7 +455,7 @@ pub fn init_traps(is_wasm_pc: fn(usize) -> bool) { /// Additionally no Rust destructors may be on the stack. /// They will be skipped and not executed. pub unsafe fn raise_user_trap(data: Box) -> ! { - tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data))) + unwind_with(UnwindReason::UserTrap(data)) } /// Raises a trap from inside library code immediately. @@ -424,7 +470,7 @@ pub unsafe fn raise_user_trap(data: Box) -> ! { /// Additionally no Rust destructors may be on the stack. /// They will be skipped and not executed. pub unsafe fn raise_lib_trap(trap: Trap) -> ! { - tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap))) + unwind_with(UnwindReason::LibTrap(trap)) } /// Carries a Rust panic across wasm code and resumes the panic on the other @@ -436,25 +482,9 @@ pub unsafe fn raise_lib_trap(trap: Trap) -> ! { /// have been previously called and not returned. Additionally no Rust destructors may be on the /// stack. They will be skipped and not executed. pub unsafe fn resume_panic(payload: Box) -> ! { - tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload))) -} - -#[cfg(target_os = "windows")] -fn reset_guard_page() { - extern "C" { - fn _resetstkoflw() -> winapi::ctypes::c_int; - } - - // We need to restore guard page under stack to handle future stack overflows properly. - // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/resetstkoflw?view=vs-2019 - if unsafe { _resetstkoflw() } == 0 { - panic!("failed to restore stack guard page"); - } + unwind_with(UnwindReason::Panic(payload)) } -#[cfg(not(target_os = "windows"))] -fn reset_guard_page() {} - /// Stores trace message with backtrace. #[derive(Debug)] pub enum Trap { @@ -540,7 +570,7 @@ impl Trap { /// Wildly unsafe because it calls raw function pointers and reads/writes raw /// function pointers. pub unsafe fn wasmer_call_trampoline( - trap_handler: &impl TrapHandler, + trap_handler: &(impl TrapHandler + 'static), vmctx: VMFunctionEnvironment, trampoline: VMTrampoline, callee: *const VMFunctionBody, @@ -557,74 +587,155 @@ pub unsafe fn wasmer_call_trampoline( /// returning them as a `Result`. /// /// Highly unsafe since `closure` won't have any dtors run. -pub unsafe fn catch_traps(trap_handler: &dyn TrapHandler, mut closure: F) -> Result<(), Trap> +pub unsafe fn catch_traps( + trap_handler: &(dyn TrapHandler + 'static), + closure: F, +) -> Result where - F: FnMut(), + F: FnOnce() -> R, { - return CallThreadState::new(trap_handler).with(|cx| { - wasmer_register_setjmp( - cx.jmp_buf.as_ptr(), - call_closure::, - &mut closure as *mut F as *mut u8, - ) - }); + // Ensure that per-thread initialization is done. + lazy_per_thread_init()?; - extern "C" fn call_closure(payload: *mut u8) - where - F: FnMut(), - { - unsafe { (*(payload as *mut F))() } - } + on_wasm_stack(trap_handler, closure).map_err(UnwindReason::to_trap) } -/// Catches any wasm traps that happen within the execution of `closure`, -/// returning them as a `Result`, with the closure contents. -/// -/// The main difference from this method and `catch_traps`, is that is able -/// to return the results from the closure. -/// -/// # Safety -/// -/// Check [`catch_traps`]. -pub unsafe fn catch_traps_with_result( - trap_handler: &dyn TrapHandler, - mut closure: F, -) -> Result -where - F: FnMut() -> R, -{ - let mut global_results = MaybeUninit::::uninit(); - catch_traps(trap_handler, || { - global_results.as_mut_ptr().write(closure()); - })?; - Ok(global_results.assume_init()) +// We need two separate thread-local variables here: +// - YIELDER is set within the new stack and is used to unwind back to the root +// of the stack from inside it. +// - TRAP_HANDLER is set from outside the new stack and is solely used from +// signal handlers. It must be atomic since it is used by signal handlers. +// +// We also do per-thread signal stack initialization on the first time +// TRAP_HANDLER is accessed. +thread_local! { + static YIELDER: Cell>>> = Cell::new(None); + static TRAP_HANDLER: AtomicPtr = AtomicPtr::new(ptr::null_mut()); } -/// Temporary state stored on the stack which is registered in the `tls` module -/// below for calls into wasm. -pub struct CallThreadState<'a> { - unwind: UnsafeCell>, - jmp_buf: Cell<*const u8>, - reset_guard_page: Cell, - prev: Cell, - trap_handler: &'a (dyn TrapHandler + 'a), - handling_trap: Cell, +/// Read-only information that is used by signal handlers to handle and recover +/// from traps. +struct TrapHandlerContext { + inner: *const u8, + handle_trap: + fn(*const u8, usize, usize, Option, &mut dyn FnMut(TrapHandlerRegs)) -> bool, + custom_trap: *const dyn TrapHandler, +} +struct TrapHandlerContextInner { + /// Information about the currently running coroutine. This is used to + /// reset execution to the root of the coroutine when a trap is handled. + coro_trap_handler: CoroutineTrapHandler>, } -/// A package of functionality needed by `catch_traps` to figure out what to do -/// when handling a trap. -/// -/// Note that this is an `unsafe` trait at least because it's being run in the -/// context of a synchronous signal handler, so it needs to be careful to not -/// access too much state in answering these queries. -pub unsafe trait TrapHandler { - /// Converts this object into an `Any` to dynamically check its type. - fn as_any(&self) -> &dyn Any; +impl TrapHandlerContext { + /// Runs the given function with a trap handler context. The previous + /// trap handler context is preserved and restored afterwards. + fn install( + custom_trap: &(dyn TrapHandler + 'static), + coro_trap_handler: CoroutineTrapHandler>, + f: impl FnOnce() -> R, + ) -> R { + // Type-erase the trap handler function so that it can be placed in TLS. + fn func( + ptr: *const u8, + pc: usize, + sp: usize, + maybe_fault_address: Option, + update_regs: &mut dyn FnMut(TrapHandlerRegs), + ) -> bool { + unsafe { + (*(ptr as *const TrapHandlerContextInner)).handle_trap( + pc, + sp, + maybe_fault_address, + update_regs, + ) + } + } + let inner = TrapHandlerContextInner { coro_trap_handler }; + let ctx = TrapHandlerContext { + inner: &inner as *const _ as *const u8, + handle_trap: func::, + custom_trap: custom_trap, + }; - /// Uses `call` to call a custom signal handler, if one is specified. - /// - /// Returns `true` if `call` returns true, otherwise returns `false`. - fn custom_trap_handler(&self, call: &dyn Fn(&TrapHandlerFn) -> bool) -> bool; + compiler_fence(Ordering::Release); + let prev = TRAP_HANDLER.with(|ptr| { + let prev = ptr.load(Ordering::Relaxed); + ptr.store( + &ctx as *const TrapHandlerContext as *mut TrapHandlerContext, + Ordering::Relaxed, + ); + prev + }); + + defer! { + TRAP_HANDLER.with(|ptr| ptr.store(prev, Ordering::Relaxed)); + compiler_fence(Ordering::Acquire); + } + + f() + } + + /// Attempts to handle the trap if it's a wasm trap. + unsafe fn handle_trap( + pc: usize, + sp: usize, + maybe_fault_address: Option, + mut update_regs: impl FnMut(TrapHandlerRegs), + call_handler: impl Fn(&TrapHandlerFn) -> bool, + ) -> bool { + let ptr = TRAP_HANDLER.with(|ptr| ptr.load(Ordering::Relaxed)); + if ptr.is_null() { + return false; + } + + let ctx = &*ptr; + + // Check if this trap is handled by a custom trap handler. + if (*ctx.custom_trap).custom_trap_handler(&call_handler) { + return true; + } + + (ctx.handle_trap)(ctx.inner, pc, sp, maybe_fault_address, &mut update_regs) + } +} + +impl TrapHandlerContextInner { + unsafe fn handle_trap( + &self, + pc: usize, + sp: usize, + maybe_fault_address: Option, + update_regs: &mut dyn FnMut(TrapHandlerRegs), + ) -> bool { + // Check if this trap occurred while executing on the Wasm stack. We can + // only recover from traps if that is the case. + if !self.coro_trap_handler.stack_ptr_in_bounds(sp) { + return false; + } + + // Set up the register state for exception return to force the + // coroutine to return to its caller with UnwindReason::WasmTrap. + let backtrace = Backtrace::new_unresolved(); + let signal_trap = maybe_fault_address.map(|addr| { + if self.coro_trap_handler.stack_ptr_in_bounds(addr) { + TrapCode::StackOverflow + } else { + TrapCode::HeapAccessOutOfBounds + } + }); + let unwind = UnwindReason::WasmTrap { + backtrace, + signal_trap, + pc, + }; + let regs = self + .coro_trap_handler + .setup_trap_handler(move || Err(unwind)); + update_regs(regs); + true + } } enum UnwindReason { @@ -642,266 +753,108 @@ enum UnwindReason { }, } -impl<'a> CallThreadState<'a> { - #[inline] - fn new(trap_handler: &'a (dyn TrapHandler + 'a)) -> CallThreadState<'a> { - Self { - unwind: UnsafeCell::new(MaybeUninit::uninit()), - jmp_buf: Cell::new(ptr::null()), - reset_guard_page: Cell::new(false), - prev: Cell::new(ptr::null()), - trap_handler, - handling_trap: Cell::new(false), - } - } - - fn with(self, closure: impl FnOnce(&CallThreadState) -> i32) -> Result<(), Trap> { - let ret = tls::set(&self, || closure(&self))?; - if ret != 0 { - return Ok(()); - } - // We will only reach this path if ret == 0. And that will - // only happen if a trap did happen. As such, it's safe to - // assume that the `unwind` field is already initialized - // at this moment. - match unsafe { (*self.unwind.get()).as_ptr().read() } { - UnwindReason::UserTrap(data) => Err(Trap::User(data)), - UnwindReason::LibTrap(trap) => Err(trap), +impl UnwindReason { + fn to_trap(self) -> Trap { + match self { + UnwindReason::UserTrap(data) => Trap::User(data), + UnwindReason::LibTrap(trap) => trap, UnwindReason::WasmTrap { backtrace, pc, signal_trap, - } => Err(Trap::wasm(pc, backtrace, signal_trap)), + } => Trap::wasm(pc, backtrace, signal_trap), UnwindReason::Panic(panic) => std::panic::resume_unwind(panic), } } - - fn unwind_with(&self, reason: UnwindReason) -> ! { - unsafe { - (*self.unwind.get()).as_mut_ptr().write(reason); - wasmer_unwind(self.jmp_buf.get()); - } - } - - /// Trap handler using our thread-local state. - /// - /// * `pc` - the program counter the trap happened at - /// * `reset_guard_page` - whether or not to reset the guard page, - /// currently Windows specific - /// * `call_handler` - a closure used to invoke the platform-specific - /// signal handler for each instance, if available. - /// - /// Attempts to handle the trap if it's a wasm trap. Returns a few - /// different things: - /// - /// * null - the trap didn't look like a wasm trap and should continue as a - /// trap - /// * 1 as a pointer - the trap was handled by a custom trap handler on an - /// instance, and the trap handler should quickly return. - /// * a different pointer - a jmp_buf buffer to longjmp to, meaning that - /// the wasm trap was succesfully handled. - fn handle_trap( - &self, - pc: *const u8, - reset_guard_page: bool, - signal_trap: Option, - call_handler: impl Fn(&TrapHandlerFn) -> bool, - ) -> *const u8 { - // If we hit a fault while handling a previous trap, that's quite bad, - // so bail out and let the system handle this recursive segfault. - // - // Otherwise flag ourselves as handling a trap, do the trap handling, - // and reset our trap handling flag. - if self.handling_trap.replace(true) { - return ptr::null(); - } - - // First up see if we have a custom trap handler, - // in which case run it. If anything handles the trap then we - // return that the trap was handled. - if self.trap_handler.custom_trap_handler(&call_handler) { - return 1 as *const _; - } - - // If this fault wasn't in wasm code, then it's not our problem - // except if it's a StackOverflow (see below) - if unsafe { !IS_WASM_PC(pc as _) } && signal_trap != Some(TrapCode::StackOverflow) { - return ptr::null(); - } - - // TODO: stack overflow can happen at any random time (i.e. in malloc() - // in memory.grow) and it's really hard to determine if the cause was - // stack overflow and if it happened in WebAssembly module. - // - // So, let's assume that any untrusted code called from WebAssembly - // doesn't trap. Then, if we have called some WebAssembly code, it - // means the trap is stack overflow. - if self.jmp_buf.get().is_null() { - self.handling_trap.set(false); - return ptr::null(); - } - let backtrace = Backtrace::new_unresolved(); - self.reset_guard_page.set(reset_guard_page); - unsafe { - (*self.unwind.get()) - .as_mut_ptr() - .write(UnwindReason::WasmTrap { - backtrace, - signal_trap, - pc: pc as usize, - }); - } - self.handling_trap.set(false); - self.jmp_buf.get() - } } -impl<'a> Drop for CallThreadState<'a> { - fn drop(&mut self) { - if self.reset_guard_page.get() { - reset_guard_page(); - } - } -} +unsafe fn unwind_with(reason: UnwindReason) -> ! { + let yielder = YIELDER + .with(|cell| cell.replace(None)) + .expect("not running on Wasm stack"); -// A private inner module for managing the TLS state that we require across -// calls in wasm. The WebAssembly code is called from C++ and then a trap may -// happen which requires us to read some contextual state to figure out what to -// do with the trap. This `tls` module is used to persist that information from -// the caller to the trap site. -mod tls { - use super::CallThreadState; - use crate::Trap; - use std::mem; - use std::ptr; - - pub use raw::Ptr; - - // An even *more* inner module for dealing with TLS. This actually has the - // thread local variable and has functions to access the variable. - // - // Note that this is specially done to fully encapsulate that the accessors - // for tls must not be inlined. Wasmer's async support will employ stack - // switching which can resume execution on different OS threads. This means - // that borrows of our TLS pointer must never live across accesses because - // otherwise the access may be split across two threads and cause unsafety. - // - // This also means that extra care is taken by the runtime to save/restore - // these TLS values when the runtime may have crossed threads. - mod raw { - use super::CallThreadState; - use crate::Trap; - use std::cell::Cell; - use std::ptr; - - pub type Ptr = *const CallThreadState<'static>; - - // The first entry here is the `Ptr` which is what's used as part of the - // public interface of this module. The second entry is a boolean which - // allows the runtime to perform per-thread initialization if necessary - // for handling traps (e.g. setting up ports on macOS and sigaltstack on - // Unix). - thread_local!(static PTR: Cell<(Ptr, bool)> = Cell::new((ptr::null(), false))); - - #[inline(never)] // see module docs for why this is here - pub fn replace(val: Ptr) -> Result { - PTR.with(|p| { - // When a new value is configured that means that we may be - // entering WebAssembly so check to see if this thread has - // performed per-thread initialization for traps. - let (prev, mut initialized) = p.get(); - if !initialized { - super::super::lazy_per_thread_init()?; - initialized = true; - } - p.set((val, initialized)); - Ok(prev) - }) - } + yielder.as_ref().suspend(reason); - #[inline(never)] // see module docs for why this is here - pub fn get() -> Ptr { - PTR.with(|p| p.get().0) - } - } + // on_wasm_stack will forcibly reset the coroutine stack after yielding. + unreachable!(); +} - /// Opaque state used to help control TLS state across stack switches for - /// async support. - pub struct TlsRestore(raw::Ptr); - - impl TlsRestore { - /// Takes the TLS state that is currently configured and returns a - /// token that is used to replace it later. - /// - /// # Safety - /// - /// This is not a safe operation since it's intended to only be used - /// with stack switching found with fibers and async wasmer. - pub unsafe fn take() -> Result { - // Our tls pointer must be set at this time, and it must not be - // null. We need to restore the previous pointer since we're - // removing ourselves from the call-stack, and in the process we - // null out our own previous field for safety in case it's - // accidentally used later. - let raw = raw::get(); - assert!(!raw.is_null()); - let prev = (*raw).prev.replace(ptr::null()); - raw::replace(prev)?; - Ok(TlsRestore(raw)) - } +/// Runs the given function on a separate stack so that its stack usage can be +/// bounded. Stack overflows and other traps can be caught and execution +/// returned to the root of the stack. +fn on_wasm_stack T, T>( + trap_handler: &(dyn TrapHandler + 'static), + f: F, +) -> Result { + // Create a coroutine with a new stack to run the function on. + let mut coro = ScopedCoroutine::new(move |yielder, ()| { + // Save the yielder to TLS so that it can be used later. + YIELDER.with(|cell| cell.set(Some(yielder.into()))); + + Ok(f()) + }); - /// Restores a previous tls state back into this thread's TLS. - /// - /// # Safety - /// - /// This is unsafe because it's intended to only be used within the - /// context of stack switching within wasmer. - pub unsafe fn replace(self) -> Result<(), super::Trap> { - // We need to configure our previous TLS pointer to whatever is in - // TLS at this time, and then we set the current state to ourselves. - let prev = raw::get(); - assert!((*self.0).prev.get().is_null()); - (*self.0).prev.set(prev); - raw::replace(self.0)?; - Ok(()) - } + // Ensure that YIELDER is reset on exit even if the coroutine panics, + defer! { + YIELDER.with(|cell| cell.set(None)); } - /// Configures thread local state such that for the duration of the - /// execution of `closure` any call to `with` will yield `ptr`, unless this - /// is recursively called again. - pub fn set(state: &CallThreadState<'_>, closure: impl FnOnce() -> R) -> Result { - struct Reset<'a, 'b>(&'a CallThreadState<'b>); - - impl Drop for Reset<'_, '_> { - #[inline] - fn drop(&mut self) { - raw::replace(self.0.prev.replace(ptr::null())) - .expect("tls should be previously initialized"); + // Set up metadata for the trap handler for the duration of the coroutine + // execution. This is restored to its previous value afterwards. + TrapHandlerContext::install(trap_handler, coro.trap_handler(), || { + match coro.resume(()) { + CoroutineResult::Yield(trap) => { + // This came from unwind_with which requires that there be only + // Wasm code on the stack. + unsafe { + coro.force_reset(); + } + Err(trap) } + CoroutineResult::Return(result) => result, } + }) +} - // Note that this extension of the lifetime to `'static` should be - // safe because we only ever access it below with an anonymous - // lifetime, meaning `'static` never leaks out of this module. - let ptr = unsafe { mem::transmute::<*const CallThreadState<'_>, _>(state) }; - let prev = raw::replace(ptr)?; - state.prev.set(prev); - let _reset = Reset(state); - Ok(closure()) +/// When executing on the Wasm stack, temporarily switch back to the host stack +/// to perform an operation that should not be constrainted by the Wasm stack +/// limits. +/// +/// This is particularly important since the usage of the Wasm stack is under +/// the control of untrusted code. Malicious code could artificially induce a +/// stack overflow in the middle of a sensitive host operations (e.g. growing +/// a memory) which would be hard to recover from. +pub fn on_host_stack T, T>(f: F) -> T { + // Reset YIEDER to None for the duration of this call to indicate that we + // are no longer on the Wasm stack. + let yielder_ptr = YIELDER + .with(|cell| cell.replace(None)) + .expect("not running on Wasm stack"); + let yielder = unsafe { yielder_ptr.as_ref() }; + + defer! { + YIELDER.with(|cell| cell.set(Some(yielder_ptr))); } - /// Returns the last pointer configured with `set` above. Panics if `set` - /// has not been previously called and not returned. - pub fn with(closure: impl FnOnce(Option<&CallThreadState<'_>>) -> R) -> R { - let p = raw::get(); - unsafe { closure(if p.is_null() { None } else { Some(&*p) }) } - } + // on_parent_stack requires the closure to be Send so that the Yielder + // cannot be called from the parent stack. This is not a problem for us + // since we don't expose the Yielder. + struct SendWrapper(T); + unsafe impl Send for SendWrapper {} + let wrapped = SendWrapper(f); + yielder.on_parent_stack(move || (wrapped.0)()) } -#[cfg(not(unix))] +#[cfg(windows)] pub fn lazy_per_thread_init() -> Result<(), Trap> { - // Unused on Windows + // We need additional space on the stack to handle stack overflow + // exceptions. Rust's initialization code sets this to 0x5000 but this + // seems to be insufficient in practice. + use winapi::um::processthreadsapi::SetThreadStackGuarantee; + if unsafe { SetThreadStackGuarantee(&mut 0x10000) } == 0 { + panic!("failed to set thread stack guarantee"); + } + Ok(()) } @@ -913,13 +866,12 @@ pub fn lazy_per_thread_init() -> Result<(), Trap> { /// page. #[cfg(unix)] pub fn lazy_per_thread_init() -> Result<(), Trap> { - use std::cell::RefCell; use std::ptr::null_mut; thread_local! { /// Thread-local state is lazy-initialized on the first time it's used, /// and dropped when the thread exits. - static TLS: RefCell = RefCell::new(Tls::None); + static TLS: Tls = unsafe { init_sigstack() }; } /// The size of the sigaltstack (not including the guard, which will be @@ -927,7 +879,7 @@ pub fn lazy_per_thread_init() -> Result<(), Trap> { const MIN_STACK_SIZE: usize = 16 * 4096; enum Tls { - None, + OOM, Allocated { mmap_ptr: *mut libc::c_void, mmap_size: usize, @@ -935,21 +887,14 @@ pub fn lazy_per_thread_init() -> Result<(), Trap> { BigEnough, } - return TLS.with(|slot| unsafe { - let mut slot = slot.borrow_mut(); - match *slot { - Tls::None => {} - // already checked - _ => return Ok(()), - } + unsafe fn init_sigstack() -> Tls { // Check to see if the existing sigaltstack, if it exists, is big // enough. If so we don't need to allocate our own. let mut old_stack = mem::zeroed(); let r = libc::sigaltstack(ptr::null(), &mut old_stack); assert_eq!(r, 0, "learning about sigaltstack failed"); if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE { - *slot = Tls::BigEnough; - return Ok(()); + return Tls::BigEnough; } // ... but failing that we need to allocate our own, so do all that @@ -967,7 +912,7 @@ pub fn lazy_per_thread_init() -> Result<(), Trap> { 0, ); if ptr == libc::MAP_FAILED { - return Err(Trap::oom()); + return Tls::OOM; } // Prepare the stack with readable/writable memory and then register it @@ -987,11 +932,20 @@ pub fn lazy_per_thread_init() -> Result<(), Trap> { let r = libc::sigaltstack(&new_stack, ptr::null_mut()); assert_eq!(r, 0, "registering new sigaltstack failed"); - *slot = Tls::Allocated { + Tls::Allocated { mmap_ptr: ptr, mmap_size: alloc_size, - }; - Ok(()) + } + } + + // Ensure TLS runs its initializer and return an error if it failed to + // set up a separate stack for signal handlers. + return TLS.with(|tls| { + if let Tls::OOM = tls { + Err(Trap::oom()) + } else { + Ok(()) + } }); impl Drop for Tls { diff --git a/rust-toolchain b/rust-toolchain index e01e6c121d0..791d8759b87 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.56 +1.59 From 83fd79057ecd875b950cf7d0ac2bf5699b983b50 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 25 Feb 2022 17:18:36 +0000 Subject: [PATCH 04/13] Fix unused field lint --- lib/vfs/src/host_fs.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/vfs/src/host_fs.rs b/lib/vfs/src/host_fs.rs index 19475768e51..961f6685be0 100644 --- a/lib/vfs/src/host_fs.rs +++ b/lib/vfs/src/host_fs.rs @@ -205,6 +205,7 @@ pub struct File { #[cfg_attr(feature = "enable-serde", serde(skip_serializing))] pub inner: fs::File, pub host_path: PathBuf, + #[cfg(feature = "enable-serde")] flags: u16, } @@ -303,24 +304,25 @@ impl File { /// creates a new host file from a `std::fs::File` and a path pub fn new(file: fs::File, host_path: PathBuf, read: bool, write: bool, append: bool) -> Self { - let mut flags = 0; + let mut _flags = 0; if read { - flags |= Self::READ; + _flags |= Self::READ; } if write { - flags |= Self::WRITE; + _flags |= Self::WRITE; } if append { - flags |= Self::APPEND; + _flags |= Self::APPEND; } Self { inner: file, host_path, - flags, + #[cfg(feature = "enable-serde")] + _flags, } } From 2b86fe802ff2b6a7b48df3009bea1e49cc1ca4ee Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 25 Feb 2022 19:10:07 +0000 Subject: [PATCH 05/13] Fix stack probing typo --- lib/compiler-singlepass/src/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler-singlepass/src/codegen.rs b/lib/compiler-singlepass/src/codegen.rs index f612f371aea..3f28a3be932 100644 --- a/lib/compiler-singlepass/src/codegen.rs +++ b/lib/compiler-singlepass/src/codegen.rs @@ -517,7 +517,7 @@ impl<'a, M: Machine> FuncGen<'a, M> { // so here we probe it explicitly when needed. for i in (sig.params().len()..n) .step_by(NATIVE_PAGE_SIZE / 8) - .skip(0) + .skip(1) { self.machine.zero_location(Size::S64, locations[i]); } From f9d1cfdeb5a1578be2efb05d6dc68e805da16e75 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 25 Feb 2022 20:01:33 +0000 Subject: [PATCH 06/13] Fix calling host functions directly from the API --- lib/vm/src/trap/traphandlers.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index e79385cd30b..58736a94de7 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -827,13 +827,18 @@ fn on_wasm_stack T, T>( pub fn on_host_stack T, T>(f: F) -> T { // Reset YIEDER to None for the duration of this call to indicate that we // are no longer on the Wasm stack. - let yielder_ptr = YIELDER - .with(|cell| cell.replace(None)) - .expect("not running on Wasm stack"); - let yielder = unsafe { yielder_ptr.as_ref() }; + let yielder_ptr = YIELDER.with(|cell| cell.replace(None)); + + // If we are already on the host stack, execute the function directly. This + // happens if a host function is called directly from the API. + let yielder = match yielder_ptr { + Some(ptr) => unsafe { ptr.as_ref() }, + None => return f(), + }; + // Restore YIELDER upon exiting normally or unwinding. defer! { - YIELDER.with(|cell| cell.set(Some(yielder_ptr))); + YIELDER.with(|cell| cell.set(yielder_ptr)); } // on_parent_stack requires the closure to be Send so that the Yielder From 58bab0f7096cbaff8ea421801981efd2ff53182f Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 25 Feb 2022 21:33:09 +0000 Subject: [PATCH 07/13] Update nightly Rust version used to build headless --- .github/workflows/test-sys.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-sys.yaml b/.github/workflows/test-sys.yaml index 5bf6c867877..84686ad4ac7 100644 --- a/.github/workflows/test-sys.yaml +++ b/.github/workflows/test-sys.yaml @@ -226,7 +226,7 @@ jobs: - name: Install Nightly Rust for Headless uses: actions-rs/toolchain@v1 with: - toolchain: 'nightly-2021-11-01' + toolchain: 'nightly-2022-02-08' target: ${{ matrix.target }} override: true components: "rust-src" From af3d948fe5b2f59b72abfade8d66f9e75f605139 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Tue, 1 Mar 2022 00:41:38 +0000 Subject: [PATCH 08/13] Try to enable more tests --- tests/ignores.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/ignores.txt b/tests/ignores.txt index a2efd9dbb3a..e1143d1457b 100644 --- a/tests/ignores.txt +++ b/tests/ignores.txt @@ -39,13 +39,15 @@ singlepass multi_value_imports::dynamic # Singlepass doesn't support multivalue # TODO: We need to fix this in ARM. The issue is caused by libunwind overflowing # the stack while creating the stacktrace. # https://github.com/rust-lang/backtrace-rs/issues/356 -cranelift+aarch64 spec::skip_stack_guard_page # This is skipped for ARM, not fully fixed yet -llvm+aarch64 spec::skip_stack_guard_page # This is skipped for ARM, not fully fixed yet -singlepass+windows spec::skip_stack_guard_page # Needs investigation. -cranelift+windows spec::skip_stack_guard_page # Needs investigation. Issue: `STATUS_ACCESS_VIOLATION` trap happened -cranelift+macos spec::skip_stack_guard_page # Needs investigation. process didn't exit successfully: (signal: 6, SIGABRT: process abort signal) -llvm+macos spec::skip_stack_guard_page # Needs investigation. process didn't exit successfully: (signal: 6, SIGABRT: process abort signal) -dylib spec::skip_stack_guard_page # Missing trap information in dylibs +# Also neither LLVM nor Cranelift currently implement stack probing on AArch64. +# https://github.com/wasmerio/wasmer/issues/2808 +cranelift+aarch64 spec::skip_stack_guard_page +llvm+aarch64 spec::skip_stack_guard_page +# Stack probing doesn't seem to be working correctly on Windows with Cranelift +cranelift+windows spec::skip_stack_guard_page +# TODO: Needs more investigation +cranelift+macos spec::skip_stack_guard_page +llvm+macos spec::skip_stack_guard_page # Some SIMD opperations are not yet supported by Cranelift # Cranelift just added support for most of those recently, it might be easy to update From 657bbae518a649ecc98b06571a78f30fdb1fe023 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 3 Mar 2022 17:02:01 +0000 Subject: [PATCH 09/13] Remove the custom probestack trampoline in compiler-cranelift This is now handled by the generic trampoline code in the engine. --- lib/compiler-cranelift/src/compiler.rs | 38 +------------------------- lib/compiler-cranelift/src/sink.rs | 28 ++----------------- 2 files changed, 3 insertions(+), 63 deletions(-) diff --git a/lib/compiler-cranelift/src/compiler.rs b/lib/compiler-cranelift/src/compiler.rs index a08ab57668f..a13d3c5b0e2 100644 --- a/lib/compiler-cranelift/src/compiler.rs +++ b/lib/compiler-cranelift/src/compiler.rs @@ -21,7 +21,6 @@ use gimli::write::{Address, EhFrame, FrameTable}; use loupe::MemoryUsage; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use std::sync::Arc; -use target_lexicon::{Architecture, OperatingSystem}; use wasmer_compiler::CompileError; use wasmer_compiler::{CallingConvention, ModuleTranslationState, Target}; use wasmer_compiler::{ @@ -30,13 +29,8 @@ use wasmer_compiler::{ FunctionBodyData, MiddlewareBinaryReader, ModuleMiddleware, ModuleMiddlewareChain, SectionIndex, }; -use wasmer_compiler::{ - CustomSection, CustomSectionProtection, Relocation, RelocationKind, RelocationTarget, - SectionBody, -}; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::{FunctionIndex, LocalFunctionIndex, SignatureIndex}; -use wasmer_vm::libcalls::LibCall; /// A compiler that compiles a WebAssembly module with Cranelift, translating the Wasm to Cranelift IR, /// optimizing it and then translating to assembly. @@ -109,35 +103,6 @@ impl Compiler for CraneliftCompiler { let mut custom_sections = PrimaryMap::new(); - let probestack_trampoline_relocation_target = if target.triple().operating_system - == OperatingSystem::Linux - && target.triple().architecture == Architecture::X86_64 - { - let probestack_trampoline = CustomSection { - protection: CustomSectionProtection::ReadExecute, - // We create a jump to an absolute 64bits address - // with an indrect jump immediatly followed but the absolute address - // JMP [IP+0] FF 25 00 00 00 00 - // 64bits ADDR 00 00 00 00 00 00 00 00 preset to 0 until the relocation takes place - bytes: SectionBody::new_with_vec(vec![ - 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, - ]), - relocations: vec![Relocation { - kind: RelocationKind::Abs8, - reloc_target: RelocationTarget::LibCall(LibCall::Probestack), - // 6 is the size of the jmp instruction. The relocated address must follow - offset: 6, - addend: 0, - }], - }; - custom_sections.push(probestack_trampoline); - - Some(SectionIndex::new(custom_sections.len() - 1)) - } else { - None - }; - let (functions, fdes): (Vec, Vec<_>) = function_body_inputs .iter() .collect::)>>() @@ -174,8 +139,7 @@ impl Compiler for CraneliftCompiler { )?; let mut code_buf: Vec = Vec::new(); - let mut reloc_sink = - RelocSink::new(&module, func_index, probestack_trampoline_relocation_target); + let mut reloc_sink = RelocSink::new(&module, func_index); let mut trap_sink = TrapSink::new(); let mut stackmap_sink = binemit::NullStackMapSink {}; context diff --git a/lib/compiler-cranelift/src/sink.rs b/lib/compiler-cranelift/src/sink.rs index 9640e2614f0..dfc803002d0 100644 --- a/lib/compiler-cranelift/src/sink.rs +++ b/lib/compiler-cranelift/src/sink.rs @@ -2,11 +2,9 @@ use crate::translator::{irlibcall_to_libcall, irreloc_to_relocationkind}; use cranelift_codegen::binemit; -use cranelift_codegen::ir::LibCall; use cranelift_codegen::ir::{self, ExternalName}; use cranelift_entity::EntityRef as CraneliftEntityRef; use wasmer_compiler::{JumpTable, Relocation, RelocationTarget, TrapInformation}; -use wasmer_compiler::{RelocationKind, SectionIndex}; use wasmer_types::entity::EntityRef; use wasmer_types::{FunctionIndex, LocalFunctionIndex, ModuleInfo}; use wasmer_vm::TrapCode; @@ -20,9 +18,6 @@ pub(crate) struct RelocSink<'a> { /// Relocations recorded for the function. pub func_relocs: Vec, - - /// The section where the probestack trampoline call is located - pub probestack_trampoline_relocation_target: Option, } impl<'a> binemit::RelocSink for RelocSink<'a> { @@ -42,21 +37,7 @@ impl<'a> binemit::RelocSink for RelocSink<'a> { .expect("The provided function should be local"), ) } else if let ExternalName::LibCall(libcall) = *name { - match (libcall, self.probestack_trampoline_relocation_target) { - (LibCall::Probestack, Some(probestack_trampoline_relocation_target)) => { - self.func_relocs.push(Relocation { - kind: RelocationKind::X86CallPCRel4, - reloc_target: RelocationTarget::CustomSection( - probestack_trampoline_relocation_target, - ), - offset: offset, - addend: addend, - }); - // Skip the default path - return; - } - _ => RelocationTarget::LibCall(irlibcall_to_libcall(libcall)), - } + RelocationTarget::LibCall(irlibcall_to_libcall(libcall)) } else { panic!("unrecognized external name") }; @@ -93,11 +74,7 @@ impl<'a> binemit::RelocSink for RelocSink<'a> { impl<'a> RelocSink<'a> { /// Return a new `RelocSink` instance. - pub fn new( - module: &'a ModuleInfo, - func_index: FunctionIndex, - probestack_trampoline_relocation_target: Option, - ) -> Self { + pub fn new(module: &'a ModuleInfo, func_index: FunctionIndex) -> Self { let local_func_index = module .local_func_index(func_index) .expect("The provided function should be local"); @@ -105,7 +82,6 @@ impl<'a> RelocSink<'a> { module, local_func_index, func_relocs: Vec::new(), - probestack_trampoline_relocation_target, } } } From a2809145a1af7efa6cffc565f2144475e8af5305 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 3 Mar 2022 17:11:07 +0000 Subject: [PATCH 10/13] Fix x86_64 libcall trampoline in engine-universal --- lib/engine-universal/src/trampoline.rs | 2 +- tests/ignores.txt | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/engine-universal/src/trampoline.rs b/lib/engine-universal/src/trampoline.rs index e90a7c92f98..7c4c62b9729 100644 --- a/lib/engine-universal/src/trampoline.rs +++ b/lib/engine-universal/src/trampoline.rs @@ -23,7 +23,7 @@ const AARCH64_TRAMPOLINE: [u8; 16] = [ // JMP [RIP + 2] FF 25 02 00 00 00 [00 00] // 64-bit ADDR 00 00 00 00 00 00 00 00 const X86_64_TRAMPOLINE: [u8; 16] = [ - 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x25, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; fn make_trampoline( diff --git a/tests/ignores.txt b/tests/ignores.txt index e1143d1457b..ba4976b9a03 100644 --- a/tests/ignores.txt +++ b/tests/ignores.txt @@ -43,8 +43,6 @@ singlepass multi_value_imports::dynamic # Singlepass doesn't support multivalue # https://github.com/wasmerio/wasmer/issues/2808 cranelift+aarch64 spec::skip_stack_guard_page llvm+aarch64 spec::skip_stack_guard_page -# Stack probing doesn't seem to be working correctly on Windows with Cranelift -cranelift+windows spec::skip_stack_guard_page # TODO: Needs more investigation cranelift+macos spec::skip_stack_guard_page llvm+macos spec::skip_stack_guard_page From 3d52bf2f8f6dfda4326730345d817744de04af97 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 4 Mar 2022 14:46:14 +0000 Subject: [PATCH 11/13] Don't try generating a backtrace on stack overflow --- lib/vm/src/trap/traphandlers.rs | 18 +++++++++++++++--- tests/compilers/traps.rs | 10 +++------- tests/ignores.txt | 6 ------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index 58736a94de7..7fd6db45182 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -715,9 +715,6 @@ impl TrapHandlerContextInner { return false; } - // Set up the register state for exception return to force the - // coroutine to return to its caller with UnwindReason::WasmTrap. - let backtrace = Backtrace::new_unresolved(); let signal_trap = maybe_fault_address.map(|addr| { if self.coro_trap_handler.stack_ptr_in_bounds(addr) { TrapCode::StackOverflow @@ -725,6 +722,21 @@ impl TrapHandlerContextInner { TrapCode::HeapAccessOutOfBounds } }); + + // Don't try to generate a backtrace for stack overflows: unwinding + // information is often not precise enough to properly describe what is + // happenning during a function prologue, which can lead the unwinder to + // read invalid memory addresses. + // + // See: https://github.com/rust-lang/backtrace-rs/pull/357 + let backtrace = if signal_trap == Some(TrapCode::StackOverflow) { + Backtrace::from(vec![]) + } else { + Backtrace::new_unresolved() + }; + + // Set up the register state for exception return to force the + // coroutine to return to its caller with UnwindReason::WasmTrap. let unwind = UnwindReason::WasmTrap { backtrace, signal_trap, diff --git a/tests/compilers/traps.rs b/tests/compilers/traps.rs index 493090c7e9a..77b5d6e8560 100644 --- a/tests/compilers/traps.rs +++ b/tests/compilers/traps.rs @@ -135,13 +135,9 @@ fn test_trap_stack_overflow(config: crate::Config) -> Result<()> { 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(), "rec_mod"); - assert_eq!(trace[i].func_index(), 0); - assert_eq!(trace[i].function_name(), Some("run")); - } + // We specifically don't check the stack trace here: stack traces after + // stack overflows are not generally possible due to unreliable unwinding + // information. assert!(e.message().contains("call stack exhausted")); Ok(()) diff --git a/tests/ignores.txt b/tests/ignores.txt index ba4976b9a03..a7b67a9a6d3 100644 --- a/tests/ignores.txt +++ b/tests/ignores.txt @@ -36,16 +36,10 @@ aarch64 traps::start_trap_pretty singlepass multi_value_imports::dylib # Singlepass doesn't support multivalue singlepass multi_value_imports::dynamic # Singlepass doesn't support multivalue -# TODO: We need to fix this in ARM. The issue is caused by libunwind overflowing -# the stack while creating the stacktrace. -# https://github.com/rust-lang/backtrace-rs/issues/356 # Also neither LLVM nor Cranelift currently implement stack probing on AArch64. # https://github.com/wasmerio/wasmer/issues/2808 cranelift+aarch64 spec::skip_stack_guard_page llvm+aarch64 spec::skip_stack_guard_page -# TODO: Needs more investigation -cranelift+macos spec::skip_stack_guard_page -llvm+macos spec::skip_stack_guard_page # Some SIMD opperations are not yet supported by Cranelift # Cranelift just added support for most of those recently, it might be easy to update From 742c20a340983f21ea1482ef1b542cc20da2adb0 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 14 Mar 2022 22:02:50 +0000 Subject: [PATCH 12/13] Update corosensei to 0.1.2 --- Cargo.lock | 5 +++-- lib/vm/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d003d151b1..ceacce62484 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -421,13 +421,14 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "corosensei" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85789bf244cdbe2736a4a6912117c0ff010aba1680d81a90abaf96725e0570bc" +checksum = "ab4b310cff9117ec16d05970743c20df3eaddafd461829f2758e76a8de2863a9" dependencies = [ "autocfg", "cfg-if 1.0.0", "libc", + "scopeguard", "windows-sys", ] diff --git a/lib/vm/Cargo.toml b/lib/vm/Cargo.toml index 6d9453e1507..d86a031fce7 100644 --- a/lib/vm/Cargo.toml +++ b/lib/vm/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0", features = ["derive", "rc"] } rkyv = { version = "0.7.20", optional = true } loupe = { version = "0.1", features = ["enable-indexmap"] } enum-iterator = "0.7.0" -corosensei = { version = "0.1.1" } +corosensei = { version = "0.1.2" } scopeguard = "1.1.0" [target.'cfg(target_os = "windows")'.dependencies] From c4e1c5f9ff781dd46c2a68a836db4a35a21e8e06 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 14 Mar 2022 22:03:40 +0000 Subject: [PATCH 13/13] Add back workaround for LLDB debugging on macOS --- Cargo.lock | 1 + lib/vm/Cargo.toml | 3 +++ lib/vm/src/trap/traphandlers.rs | 37 +++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ceacce62484..a6524b9d616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3152,6 +3152,7 @@ dependencies = [ "indexmap", "libc", "loupe", + "mach", "memoffset", "more-asserts", "region", diff --git a/lib/vm/Cargo.toml b/lib/vm/Cargo.toml index d86a031fce7..ef5a60b8788 100644 --- a/lib/vm/Cargo.toml +++ b/lib/vm/Cargo.toml @@ -27,6 +27,9 @@ enum-iterator = "0.7.0" corosensei = { version = "0.1.2" } scopeguard = "1.1.0" +[target.'cfg(target_vendor = "apple")'.dependencies] +mach = "0.3.2" + [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["winbase", "memoryapi", "errhandlingapi"] } diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index 7fd6db45182..a7376a832f4 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -94,6 +94,43 @@ cfg_if::cfg_if! { if cfg!(target_arch = "arm") || cfg!(target_vendor = "apple") { register(&mut PREV_SIGBUS, libc::SIGBUS); } + + // This is necessary to support debugging under LLDB on Darwin. + // For more details see https://github.com/mono/mono/commit/8e75f5a28e6537e56ad70bf870b86e22539c2fb7 + #[cfg(target_vendor = "apple")] + { + use mach::exception_types::*; + use mach::kern_return::*; + use mach::port::*; + use mach::thread_status::*; + use mach::traps::*; + use mach::mach_types::*; + + extern "C" { + fn task_set_exception_ports( + task: task_t, + exception_mask: exception_mask_t, + new_port: mach_port_t, + behavior: exception_behavior_t, + new_flavor: thread_state_flavor_t, + ) -> kern_return_t; + } + + #[allow(non_snake_case)] + #[cfg(target_arch = "x86_64")] + let MACHINE_THREAD_STATE = x86_THREAD_STATE64; + #[allow(non_snake_case)] + #[cfg(target_arch = "aarch64")] + let MACHINE_THREAD_STATE = 6; + + task_set_exception_ports( + mach_task_self(), + EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC, + MACH_PORT_NULL, + EXCEPTION_STATE_IDENTITY as exception_behavior_t, + MACHINE_THREAD_STATE, + ); + } } unsafe extern "C" fn trap_handler(