From f1262fb440532cfa8ca184b0d322d6cac357789d Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 25 Feb 2022 16:44:02 +0000 Subject: [PATCH] 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 90a839a3358..4bd6415452f 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 a4d46787852..2d2831bfa56 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