diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc80f8fe..058834963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added ### Changed +* Support overriding the guest log level when building or restoring initialized snapshots. Persisted snapshots use ABI version 3 and must be regenerated. * `Snapshot::save` now writes the guest memory blob sparsely, skipping all-zero blocks instead of writing them. A guest memory image is mostly untouched pages, so this cuts the bytes actually written by roughly the proportion of diff --git a/Cargo.lock b/Cargo.lock index e0fda2830..d869fdedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,6 +163,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bindgen" version = "0.72.1" @@ -278,9 +284,9 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", @@ -295,9 +301,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cairo-rs" -version = "0.22.0" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" +checksum = "df683f1d30b457964673a5541a4603208ef95ab6873d2a55871895cf310f3b56" dependencies = [ "bitflags 2.13.2", "cairo-sys-rs", @@ -307,9 +313,9 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.22.0" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" +checksum = "ee548131103ad8f698c6725669647b9c757b3b66992dd6a026037596417bee21" dependencies = [ "glib-sys", "libc", @@ -343,9 +349,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" dependencies = [ "find-msvc-tools", "jobserver", @@ -448,9 +454,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -458,9 +464,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", @@ -470,9 +476,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.4" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", @@ -482,9 +488,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "colorchoice" @@ -597,9 +603,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] @@ -650,9 +656,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -660,9 +666,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] @@ -678,9 +684,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crunchy" @@ -996,12 +1002,13 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.9.1", + "zlib-rs", ] [[package]] @@ -1243,9 +1250,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded" +checksum = "b399f4650bb52c051f3fdf49a234e8a27ab5725c8cd774556c55eee64b2c0350" dependencies = [ "futures-channel", "futures-core", @@ -1260,9 +1267,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5" +checksum = "6c28739f914c15b87a9856000bed9cbd5b3a8afbca5e982d9a299e1601422cb5" dependencies = [ "glib-sys", "gobject-sys", @@ -1285,9 +1292,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" +checksum = "18b9b8d350db41f690ec1b87109f202782748bbd8ab2f2ede17cb40d29e2838c" dependencies = [ "bitflags 2.13.2", "futures-channel", @@ -1305,21 +1312,21 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.22.6" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" +checksum = "c9597c68fa7bdf4154a3079642cd21c4dccac0b0bdd2897d82861523bf91e7b9" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] name = "glib-sys" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233" +checksum = "99b38907e67e40dec9b60f858bcada952598719cb512a13eb13d6cdcd16f8295" dependencies = [ "libc", "system-deps", @@ -1346,9 +1353,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.22.6" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" +checksum = "07595c9ba696bd9819cb6a8df39f08078f3dd679508fe052dd558492c2053834" dependencies = [ "glib-sys", "libc", @@ -1475,18 +1482,18 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -1522,7 +1529,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -1916,9 +1923,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -1928,9 +1935,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is_terminal_polyfill" @@ -1973,9 +1980,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" dependencies = [ "defmt", "jiff-core", @@ -1988,18 +1995,19 @@ dependencies = [ [[package]] name = "jiff-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ "defmt", + "log", ] [[package]] name = "jiff-static" -version = "0.2.35" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" dependencies = [ "jiff-core", "proc-macro2", @@ -2019,9 +2027,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -2149,9 +2157,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7955dfc218a8afb29dfeffd540e3a6e96baeb94fe7138228dd7cc6937fbbf96" +checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a" dependencies = [ "libc", ] @@ -2303,7 +2311,7 @@ version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" dependencies = [ - "base64", + "base64 0.22.1", "evmap", "indexmap", "metrics", @@ -2365,11 +2373,21 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi", @@ -2626,9 +2644,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c" +checksum = "0b2022dbbd82e1c42bd950a6f1df9b98c796e725dce5ec275e03cbf9772e4efa" dependencies = [ "gio", "glib", @@ -2637,9 +2655,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.22.0" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" +checksum = "ca02d64761b74d56cdd4b437db6d73dc95e6c0d40f27e1c707952c0f09052948" dependencies = [ "glib-sys", "gobject-sys", @@ -2649,9 +2667,9 @@ dependencies = [ [[package]] name = "pangocairo" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738a2ef690f51487814ff7172e0417c8e4fa2d7567571e5eb299d38b47b543ab" +checksum = "bd199a0fef965b3ebda91a929a1ab5e7729538882c68e8de38e1904d2e094757" dependencies = [ "cairo-rs", "glib", @@ -2661,9 +2679,9 @@ dependencies = [ [[package]] name = "pangocairo-sys" -version = "0.22.0" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95cb73468373b9e568abb1afbaf5b42fe6ab9128fc41b5f2adbf69451c3c77f" +checksum = "a649a616d67ae68539bc85f00fb45d36fba742ed20fc20e6712a15d8ee52bd3f" dependencies = [ "cairo-sys-rs", "glib-sys", @@ -2703,9 +2721,9 @@ checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" [[package]] name = "pe-unwind-info" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97f6fccfd2d9d2df765ca23ff85fe5cc437fb0e6d3e164e4d3cbe09d14780c93" +checksum = "e33c6dbf1a8fb7f71742cd70f5e9f0986e60b2d19dc0b28d9ca0d1323259274a" dependencies = [ "arrayvec", "bitflags 2.13.2", @@ -2877,7 +2895,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.8.9", ] [[package]] @@ -2888,9 +2906,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] @@ -3232,11 +3250,11 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64", + "base64 0.23.1", "bytes", "futures-channel", "futures-core", @@ -3391,13 +3409,13 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +checksum = "e1a36a382ed65dbcc0ab47fd5e9a94112417ccd34560a392ef3b7b0f0ec39148" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -3554,9 +3572,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" [[package]] name = "socket2" @@ -3646,25 +3664,25 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] name = "system-deps" -version = "7.0.8" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" +checksum = "8a0dae2cbca1f0ff93795713cfe0a4c02f760e3c0b8ae2ab4ec4045808ff429f" dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml 1.1.4+spec-1.1.0", + "toml 1.1.6+spec-1.1.0", "version-compare", ] @@ -3815,9 +3833,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.4+spec-1.1.0" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ "indexmap", "serde_core", @@ -3848,9 +3866,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.15+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", @@ -3880,7 +3898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "bytes", "http", "http-body", @@ -4268,9 +4286,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -4281,9 +4299,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -4291,9 +4309,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4301,22 +4319,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] @@ -4372,7 +4390,7 @@ version = "0.259.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f7c12eac7bb587801590f6a67ff0bc84d0748513864b71108e4b311cc3df694" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "indexmap", "semver", ] @@ -4401,9 +4419,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -4671,30 +4689,30 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" dependencies = [ "proc-macro2", "quote", @@ -4712,13 +4730,13 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "synstructure", ] @@ -4755,6 +4773,12 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.23" diff --git a/Justfile b/Justfile index c5524bab6..3f65c0a08 100644 --- a/Justfile +++ b/Justfile @@ -243,6 +243,8 @@ test-isolated target=default-target features="" : {{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::uninitialized::tests::test_log_trace --exact --ignored {{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::outb::tests::test_log_outb_log --exact --ignored {{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::initialized_multi_use::tests::from_snapshot::max_guest_log_level_is_honored_from_snapshot --exact --ignored + {{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::initialized_multi_use::tests::from_snapshot::max_guest_log_level_overrides_initialized_snapshot --exact --ignored + {{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::initialized_multi_use::tests::from_snapshot::max_guest_log_level_setter_survives_restore --exact --ignored {{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --test integration_test -- log_message --exact --ignored @# CPU vendor check, gated to known CI runner hardware {{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::snapshot::file::config::tests::cpu_vendor_current_is_recognized --exact --ignored @@ -528,6 +530,8 @@ coverage-run hypervisor="kvm": ensure-cargo-llvm-cov cargo +nightly test -p hyperlight-host --lib -- sandbox::uninitialized::tests::test_log_trace --exact --ignored cargo +nightly test -p hyperlight-host --lib -- sandbox::outb::tests::test_log_outb_log --exact --ignored cargo +nightly test -p hyperlight-host --lib -- sandbox::initialized_multi_use::tests::from_snapshot::max_guest_log_level_is_honored_from_snapshot --exact --ignored + cargo +nightly test -p hyperlight-host --lib -- sandbox::initialized_multi_use::tests::from_snapshot::max_guest_log_level_overrides_initialized_snapshot --exact --ignored + cargo +nightly test -p hyperlight-host --lib -- sandbox::initialized_multi_use::tests::from_snapshot::max_guest_log_level_setter_survives_restore --exact --ignored cargo +nightly test -p hyperlight-host --test integration_test -- log_message --exact --ignored cargo +nightly test -p hyperlight-host --no-default-features -F function_call_metrics,{{ if hypervisor == "mshv3" { "mshv3" } else { "kvm" } }} --lib -- metrics::tests::test_metrics_are_emitted --exact diff --git a/docs/hyperlight-metrics-logs-and-traces.md b/docs/hyperlight-metrics-logs-and-traces.md index 8120e2d44..3a18dab7e 100644 --- a/docs/hyperlight-metrics-logs-and-traces.md +++ b/docs/hyperlight-metrics-logs-and-traces.md @@ -145,6 +145,10 @@ trait from `tracing_core` that allows defining a subscriber for the `tracing` cr This custom subscriber stores the spans and events in a buffer initialized only when tracing is enabled. For each new span and event, a method is called on the custom subscriber which not only stores the data, but also keeps track of the hierarchy and dependencies between the other spans/events. **NOTE**: The spans/events attributes are truncated to fit in the allocated buffer. +The guest log level can be configured when building a sandbox and overridden +for an initialized sandbox with `MultiUseSandbox::log_level`. The override +applies to later guest calls and is reapplied after snapshot restore. + When the storage space is filled, the guest triggers a VM Exit that sends the guest pointers to the host. The host can access the guest memory, get the data and parse it to create the `spans` and `events` using the `opentelemetry` crate which allows specifying the starting and ending timestamps which are captured in the guest using the `TSC`. diff --git a/src/hyperlight_common/src/layout.rs b/src/hyperlight_common/src/layout.rs index 28a03eb9e..616126299 100644 --- a/src/hyperlight_common/src/layout.rs +++ b/src/hyperlight_common/src/layout.rs @@ -15,7 +15,8 @@ pub const SCRATCH_TOP_ALLOCATOR_OFFSET: u64 = 0x10; pub const SCRATCH_TOP_SNAPSHOT_PT_GPA_BASE_OFFSET: u64 = 0x18; pub const SCRATCH_TOP_SNAPSHOT_GENERATION_OFFSET: u64 = 0x20; pub const SCRATCH_TOP_LIBC_RNG_SEED_OFFSET: u64 = 0x28; -pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x30; +pub const SCRATCH_TOP_GUEST_LOG_LEVEL_OFFSET: u64 = 0x30; +pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x40; pub fn scratch_base_gpa(size: usize) -> u64 { (SCRATCH_TOP_GPA - size + 1) as u64 diff --git a/src/hyperlight_common/src/log_level.rs b/src/hyperlight_common/src/log_level.rs index 15b25fef0..cd76104f3 100644 --- a/src/hyperlight_common/src/log_level.rs +++ b/src/hyperlight_common/src/log_level.rs @@ -20,6 +20,9 @@ pub enum GuestLogFilter { Trace, } +/// Marks a pending runtime guest log-level update in scratch memory. +pub const GUEST_LOG_FILTER_UPDATE_PENDING: u64 = 1 << 63; + impl From for tracing_core::LevelFilter { fn from(filter: GuestLogFilter) -> Self { match filter { diff --git a/src/hyperlight_guest/src/guest_handle/host_comm.rs b/src/hyperlight_guest/src/guest_handle/host_comm.rs index 31e160dba..2776ed8c5 100644 --- a/src/hyperlight_guest/src/guest_handle/host_comm.rs +++ b/src/hyperlight_guest/src/guest_handle/host_comm.rs @@ -173,7 +173,9 @@ impl GuestHandle { }; #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))] - if hyperlight_guest_tracing::is_trace_enabled() { + if hyperlight_guest_tracing::is_trace_enabled() + && hyperlight_guest_tracing::accepts_trace_events() + { // If the "trace_guest" feature is enabled and tracing is initialized, log using tracing tracing::trace!( event = message, diff --git a/src/hyperlight_guest/src/layout.rs b/src/hyperlight_guest/src/layout.rs index ecb8f9d43..ecaad560e 100644 --- a/src/hyperlight_guest/src/layout.rs +++ b/src/hyperlight_guest/src/layout.rs @@ -26,4 +26,8 @@ pub fn libc_rng_seed_gva() -> *mut u64 { use hyperlight_common::layout::{SCRATCH_TOP_GVA, SCRATCH_TOP_LIBC_RNG_SEED_OFFSET}; (SCRATCH_TOP_GVA as u64 - SCRATCH_TOP_LIBC_RNG_SEED_OFFSET + 1) as *mut u64 } +pub fn guest_log_level_gva() -> *mut u64 { + use hyperlight_common::layout::{SCRATCH_TOP_GUEST_LOG_LEVEL_OFFSET, SCRATCH_TOP_GVA}; + (SCRATCH_TOP_GVA as u64 - SCRATCH_TOP_GUEST_LOG_LEVEL_OFFSET + 1) as *mut u64 +} pub use arch::{scratch_base_gpa, scratch_base_gva}; diff --git a/src/hyperlight_guest_bin/src/guest_function/call.rs b/src/hyperlight_guest_bin/src/guest_function/call.rs index 0fdb268f1..8ae69bfeb 100644 --- a/src/hyperlight_guest_bin/src/guest_function/call.rs +++ b/src/hyperlight_guest_bin/src/guest_function/call.rs @@ -73,6 +73,8 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result } pub(crate) fn internal_dispatch_function() { + crate::refresh_guest_log_level(); + // Read the current TSC to report it to the host with the spans/events // This helps calculating the timestamps relative to the guest call #[cfg(all(feature = "trace_guest", target_arch = "x86_64"))] diff --git a/src/hyperlight_guest_bin/src/lib.rs b/src/hyperlight_guest_bin/src/lib.rs index 1bd765797..6e71421b9 100644 --- a/src/hyperlight_guest_bin/src/lib.rs +++ b/src/hyperlight_guest_bin/src/lib.rs @@ -12,7 +12,7 @@ use buddy_system_allocator::LockedHeap; use guest_function::register::GuestFunctionRegister; use guest_logger::init_logger; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_common::log_level::GuestLogFilter; +use hyperlight_common::log_level::{GUEST_LOG_FILTER_UPDATE_PENDING, GuestLogFilter}; use hyperlight_common::mem::HyperlightPEB; #[cfg(feature = "mem_profile")] use hyperlight_common::outb::OutBAction; @@ -214,6 +214,32 @@ pub(crate) fn refresh_libc_rng() { } } +pub(crate) fn refresh_guest_log_level() { + let level_ptr = hyperlight_guest::layout::guest_log_level_gva(); + // SAFETY: The host maps this aligned u64 scratch slot for the guest's + // lifetime and writes it only while the guest is stopped. + let request = unsafe { level_ptr.read_volatile() }; + if request & GUEST_LOG_FILTER_UPDATE_PENDING == 0 { + return; + } + + let Ok(filter) = GuestLogFilter::try_from(request & !GUEST_LOG_FILTER_UPDATE_PENDING) else { + // SAFETY: The scratch slot has the validity and exclusivity described above. + unsafe { level_ptr.write_volatile(0) }; + return; + }; + + log::set_max_level(filter.into()); + #[cfg(feature = "trace_guest")] + hyperlight_guest_tracing::update_guest_tracing( + hyperlight_guest_tracing::invariant_tsc::read_tsc(), + filter.into(), + ); + + // SAFETY: The scratch slot has the validity and exclusivity described above. + unsafe { level_ptr.write_volatile(0) }; +} + #[tracing::instrument(skip_all, parent = tracing::Span::current(), level= "Trace")] extern "C" fn hyperlight_main_default() { // no-op diff --git a/src/hyperlight_guest_tracing/src/lib.rs b/src/hyperlight_guest_tracing/src/lib.rs index 2371b3e26..0fa8f0c3f 100644 --- a/src/hyperlight_guest_tracing/src/lib.rs +++ b/src/hyperlight_guest_tracing/src/lib.rs @@ -25,7 +25,8 @@ mod visitor; pub use state::TraceBatchInfo; #[cfg(feature = "trace")] pub use trace::{ - end_trace, flush, init_guest_tracing, is_trace_enabled, new_call, reset, serialized_data, + accepts_trace_events, end_trace, flush, init_guest_tracing, is_trace_enabled, new_call, reset, + serialized_data, update_guest_tracing, }; /// This module is gated because some of these types are also used on the host, but we want @@ -48,6 +49,7 @@ mod trace { /// /// The mutex ensures safe access to the state from both places. static GUEST_STATE: spin::Once>> = spin::Once::new(); + static GUEST_SUBSCRIBER: spin::Once> = spin::Once::new(); /// Initialize the guest tracing subscriber as global default. pub fn init_guest_tracing(guest_start_tsc: u64, max_log_level: LevelFilter) { @@ -55,15 +57,34 @@ mod trace { if tracing_core::dispatcher::has_been_set() { return; } - let sub = GuestSubscriber::new(guest_start_tsc, max_log_level); + let sub = Arc::new(GuestSubscriber::new(guest_start_tsc, max_log_level)); let state = sub.state(); // Store state Weak to use later at runtime GUEST_STATE.call_once(|| Arc::downgrade(state)); + GUEST_SUBSCRIBER.call_once(|| Arc::downgrade(&sub)); // Set global dispatcher let _ = tracing_core::dispatcher::set_global_default(tracing_core::Dispatch::new(sub)); } + /// Update the maximum log level for an existing guest tracing subscriber. + pub fn update_guest_tracing(guest_start_tsc: u64, max_log_level: LevelFilter) { + if let Some(w) = GUEST_SUBSCRIBER.get() + && let Some(subscriber) = w.upgrade() + { + subscriber.set_max_log_level(max_log_level); + } else if max_log_level != LevelFilter::OFF { + init_guest_tracing(guest_start_tsc, max_log_level); + } + } + + pub fn accepts_trace_events() -> bool { + GUEST_SUBSCRIBER + .get() + .and_then(Weak::upgrade) + .is_some_and(|subscriber| subscriber.accepts_trace_events()) + } + /// Ends the current trace by ending all active spans in the /// internal state and storing the end timestamps. /// diff --git a/src/hyperlight_guest_tracing/src/subscriber.rs b/src/hyperlight_guest_tracing/src/subscriber.rs index 4a6c36fdf..95fece9a8 100644 --- a/src/hyperlight_guest_tracing/src/subscriber.rs +++ b/src/hyperlight_guest_tracing/src/subscriber.rs @@ -3,11 +3,13 @@ extern crate alloc; use alloc::sync::Arc; +use core::sync::atomic::{AtomicU64, Ordering}; +use hyperlight_common::log_level::GuestLogFilter; use spin::Mutex; use tracing_core::span::{Attributes, Id, Record}; use tracing_core::subscriber::Subscriber; -use tracing_core::{Event, LevelFilter, Metadata}; +use tracing_core::{Event, Interest, LevelFilter, Metadata}; use crate::state::GuestState; @@ -19,7 +21,7 @@ pub(crate) struct GuestSubscriber { /// so it can be accessed from the guest tracing API state: Arc>, /// Maximum log level to record - max_log_level: LevelFilter, + max_log_level: AtomicU64, } impl GuestSubscriber { @@ -27,9 +29,18 @@ impl GuestSubscriber { pub(crate) fn new(guest_start_tsc: u64, filter: LevelFilter) -> Self { Self { state: Arc::new(Mutex::new(GuestState::new(guest_start_tsc))), - max_log_level: filter, + max_log_level: AtomicU64::new(u64::from(GuestLogFilter::from(filter))), } } + + pub(crate) fn set_max_log_level(&self, filter: LevelFilter) { + self.max_log_level + .store(u64::from(GuestLogFilter::from(filter)), Ordering::Relaxed); + } + + pub(crate) fn accepts_trace_events(&self) -> bool { + self.max_log_level.load(Ordering::Relaxed) == u64::from(GuestLogFilter::Trace) + } /// Returns a reference to the internal state of the subscriber /// This is used to access the spans and events collected by the subscriber pub(crate) fn state(&self) -> &Arc> { @@ -38,9 +49,16 @@ impl GuestSubscriber { } impl Subscriber for GuestSubscriber { + fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest { + Interest::sometimes() + } + fn enabled(&self, md: &Metadata<'_>) -> bool { - // Check if the metadata level is less than or equal to the maximum log level filter - md.level() <= &self.max_log_level + let Ok(filter) = GuestLogFilter::try_from(self.max_log_level.load(Ordering::Relaxed)) + else { + return false; + }; + md.level() <= &LevelFilter::from(filter) } fn new_span(&self, attrs: &Attributes<'_>) -> Id { @@ -62,7 +80,6 @@ impl Subscriber for GuestSubscriber { state.new_span(attrs) } - fn record(&self, id: &Id, values: &Record<'_>) { // We want to protect against re-entrancy issues produced by tracing code that locks // the state and then causes an exception that tries to lock the state again. @@ -167,3 +184,55 @@ impl Subscriber for GuestSubscriber { // no-op: we don't track follows-from relationships } } + +#[cfg(test)] +mod tests { + use tracing_core::metadata::LevelFilter; + use tracing_core::subscriber::Subscriber; + + use super::GuestSubscriber; + + #[test] + fn filter_can_be_updated() { + let subscriber = GuestSubscriber::new(0, LevelFilter::ERROR); + let metadata = tracing_core::metadata! { + name: "event", + target: "test", + level: tracing_core::Level::INFO, + fields: &[], + callsite: &CALLSITE, + kind: tracing_core::metadata::Kind::EVENT, + }; + + assert!(!subscriber.enabled(&metadata)); + subscriber.set_max_log_level(LevelFilter::INFO); + assert!(subscriber.enabled(&metadata)); + } + + #[test] + fn callsite_interest_is_rechecked_after_filter_updates() { + let subscriber = GuestSubscriber::new(0, LevelFilter::ERROR); + assert!( + subscriber + .register_callsite(&CALLSITE_METADATA) + .is_sometimes() + ); + + subscriber.set_max_log_level(LevelFilter::TRACE); + assert!(subscriber.enabled(&CALLSITE_METADATA)); + + subscriber.set_max_log_level(LevelFilter::ERROR); + assert!(!subscriber.enabled(&CALLSITE_METADATA)); + } + + static CALLSITE: tracing_core::callsite::DefaultCallsite = + tracing_core::callsite::DefaultCallsite::new(&CALLSITE_METADATA); + static CALLSITE_METADATA: tracing_core::Metadata<'static> = tracing_core::metadata! { + name: "event", + target: "test", + level: tracing_core::Level::INFO, + fields: &[], + callsite: &CALLSITE, + kind: tracing_core::metadata::Kind::EVENT, + }; +} diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 520ade313..92b5ea34d 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -8,6 +8,7 @@ use hyperlight_common::flatbuffer_wrappers::function_call::{ use hyperlight_common::flatbuffer_wrappers::function_types::FunctionCallResult; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails; +use hyperlight_common::log_level::GuestLogFilter; use hyperlight_common::vmem::{self, PAGE_TABLE_SIZE}; #[cfg(crashdump)] use hyperlight_common::vmem::{BasicMapping, MappingKind}; @@ -536,6 +537,17 @@ impl SandboxMemoryManager { ) } + pub(crate) fn request_guest_log_level_update( + &mut self, + log_level: tracing_core::LevelFilter, + ) -> Result<()> { + self.update_scratch_bookkeeping_item( + hyperlight_common::layout::SCRATCH_TOP_GUEST_LOG_LEVEL_OFFSET, + (hyperlight_common::log_level::GUEST_LOG_FILTER_UPDATE_PENDING) + | u64::from(GuestLogFilter::from(log_level)), + ) + } + fn update_scratch_bookkeeping(&mut self) -> Result<()> { use hyperlight_common::layout::*; let scratch_size = self.scratch_mem.mem_size(); diff --git a/src/hyperlight_host/src/sandbox/builder.rs b/src/hyperlight_host/src/sandbox/builder.rs index 7ac5a4412..9f295e4d0 100644 --- a/src/hyperlight_host/src/sandbox/builder.rs +++ b/src/hyperlight_host/src/sandbox/builder.rs @@ -130,12 +130,11 @@ impl SandboxBuilder { /// # Errors /// /// When building from a snapshot, returns an error if [`Self::init_data`] - /// or [`Self::guest_log_level`] are set. The snapshot already carries - /// both, so they have no effect there. + /// is set because the snapshot already contains it. pub fn build(self) -> Result { let Self { source, - cfg, + mut cfg, host_funcs, init_data, mapped_file_cow, @@ -174,10 +173,8 @@ impl SandboxBuilder { )); } - if guest_log_level.is_some() { - return Err(new_error!( - "guest_log_level has no effect when building from a snapshot, as the snapshot already contains it" - )); + if let Some(log_level) = guest_log_level { + cfg.set_max_guest_log_level(log_level); } let mut sandbox = Sandbox::from_snapshot(snapshot, host_funcs, Some(cfg))?; @@ -243,8 +240,8 @@ impl SandboxBuilder { /// If not set, the log level is determined by the `RUST_LOG` environment variable, /// defaulting to [`LevelFilter::ERROR`] if unset. /// - /// Note: [`Self::build`] errors if this setting is set and the builder's - /// source is a snapshot, as the log level is already captured in the snapshot. + /// When building from a snapshot, this overrides the level captured in the + /// snapshot for subsequent guest calls. pub fn guest_log_level(mut self, level: LevelFilter) -> Self { self.guest_log_level = Some(level); self @@ -474,7 +471,7 @@ mod tests { } #[test] - fn build_from_snapshot_errors_on_ignored_settings() { + fn build_from_snapshot_rejects_init_data_and_accepts_guest_log_level() { let path = simple_guest_as_string().unwrap(); let mut sandbox = SandboxBuilder::from_file(path).build().unwrap(); let snapshot = sandbox.snapshot().unwrap(); @@ -490,7 +487,7 @@ mod tests { SandboxBuilder::from_snapshot(snapshot) .guest_log_level(LevelFilter::INFO) .build() - .is_err() + .is_ok() ); } } diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 55b6908db..46491e161 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -13,6 +13,7 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ }; use hyperlight_common::flatbuffer_wrappers::util::estimate_flatbuffer_capacity; use tracing::{Span, instrument}; +use tracing_core::LevelFilter; use super::Callable; use super::file_mapping::prepare_file_cow; @@ -91,6 +92,8 @@ pub struct MultiUseSandbox { /// Given (snapshot_mem, scratch_mem, cr3), returns a list of root GPAs. /// If not set, only CR3 is used as the single root. pt_root_finder: Option, + /// Runtime guest log-level override reapplied after snapshot restores. + max_guest_log_level: Option, } /// Callback for discovering page table roots from guest memory. @@ -138,9 +141,20 @@ impl MultiUseSandbox { vm, snapshot: None, pt_root_finder: None, + max_guest_log_level: None, } } + /// Sets the maximum log level used by future guest calls. + /// + /// The setting is reapplied after restoring a snapshot. + pub fn log_level(&mut self, log_level: LevelFilter) -> Result<()> { + self.check_ready()?; + self.mem_mgr.request_guest_log_level_update(log_level)?; + self.max_guest_log_level = Some(log_level); + Ok(()) + } + /// Set a callback that discovers page table roots from guest memory. /// The callback receives (snapshot_mem, scratch_mem, cr3) and returns /// the list of root GPAs to walk during snapshot creation. @@ -175,12 +189,11 @@ impl MultiUseSandbox { /// or the load fails with an MSR mismatch. /// /// [`SandboxConfiguration::set_max_guest_log_level`](crate::sandbox::SandboxConfiguration::set_max_guest_log_level) - /// sets the maximum log level passed to the guest. This only takes effect - /// for snapshots that still need their guest entrypoint run - /// (`NextAction::Initialise`). For a snapshot taken from an - /// already-initialized guest, the level was baked into the captured memory - /// when the guest first ran, so a configured value has no effect and a - /// warning is logged. + /// or [`Self::log_level`] sets the maximum log level used by the restored guest. For snapshots + /// captured before guest initialization, the level is passed to the guest + /// during initialization. For snapshots captured after guest + /// initialization, the level is requested through guest memory before the + /// next guest call. /// /// # Examples /// @@ -295,22 +308,8 @@ impl MultiUseSandbox { }; let peb_addr = RawPtr::from(u64::try_from(hshm.layout.peb_address())?); - // `max_guest_log_level` is consumed by `initialise` when it runs the - // guest entrypoint, which only happens for a preinitialised - // (`NextAction::Initialise`) snapshot. A `Call` snapshot already ran - // its entrypoint and baked the log level into the captured memory, so - // warn instead of silently ignoring the configured value. - if max_guest_log_level.is_some() - && matches!(snapshot.next_action(), super::snapshot::NextAction::Call(_)) - { - tracing::warn!( - "max_guest_log_level was configured for from_snapshot, but the snapshot is \ - an already-initialized (Call) snapshot; the log level is baked into the \ - snapshot's memory and the configured value has no effect" - ); - } - - // noop for NextAction::Call + // For NextAction::Call, initialise is a no-op. Runtime overrides are + // delivered through the scratch-memory request below. vm.initialise(peb_addr, seed, &mut hshm, &host_funcs, max_guest_log_level) .map_err(crate::hypervisor::hyperlight_vm::HyperlightVmError::Initialize)?; @@ -345,7 +344,10 @@ impl MultiUseSandbox { })?; } - let sbox = MultiUseSandbox::from_uninit(host_funcs, hshm, vm); + let mut sbox = MultiUseSandbox::from_uninit(host_funcs, hshm, vm); + if let Some(log_level) = max_guest_log_level { + sbox.log_level(log_level)?; + } Ok(sbox) } @@ -627,6 +629,9 @@ impl MultiUseSandbox { self.mem_mgr .request_libc_rng_reseed(rand::random::())?; + if let Some(log_level) = self.max_guest_log_level { + self.mem_mgr.request_guest_log_level_update(log_level)?; + } // The restored snapshot is now our most current snapshot self.snapshot = Some(snapshot.clone()); @@ -4752,6 +4757,138 @@ mod tests { ); } + /// A configured log level overrides the level captured by an initialized snapshot. + /// + /// Ignored because it installs a process-global `log` logger; run + /// in isolation via the `test-isolated` Justfile recipe. + #[test] + #[ignore] + fn max_guest_log_level_overrides_initialized_snapshot() { + use hyperlight_common::log_level::GuestLogFilter; + use hyperlight_testing::logger::{LOGGER, Logger}; + use tracing_core::LevelFilter; + + Logger::initialize_test_logger(); + LOGGER.set_max_level(log::LevelFilter::Trace); + + let initialized_snapshot = |captured_level: LevelFilter| { + SandboxBuilder::from_file(simple_guest_as_pathbuf()) + .guest_log_level(captured_level) + .build() + .unwrap() + .snapshot() + .unwrap() + }; + + let count_guest_logs = + |snapshot: Arc, override_level: Option| -> usize { + let config = override_level.map(|level| { + let mut config = SandboxConfiguration::default(); + config.set_max_guest_log_level(level); + config + }); + let mut sandbox = + MultiUseSandbox::from_snapshot(snapshot, HostFunctions::default(), config) + .unwrap(); + + LOGGER.clear_log_calls(); + for level in [ + LevelFilter::TRACE, + LevelFilter::DEBUG, + LevelFilter::INFO, + LevelFilter::WARN, + LevelFilter::ERROR, + ] { + let encoded: u64 = GuestLogFilter::from(level).into(); + sandbox + .call::<()>("LogMessage", ("hello".to_string(), encoded as i32)) + .unwrap(); + } + + let count = (0..LOGGER.num_log_calls()) + .filter_map(|i| LOGGER.get_log_call(i)) + .filter(|call| call.target == "hyperlight_guest") + .count(); + LOGGER.clear_log_calls(); + count + }; + + let trace_snapshot = initialized_snapshot(LevelFilter::TRACE); + let error_snapshot = initialized_snapshot(LevelFilter::ERROR); + + let captured_trace = count_guest_logs(trace_snapshot.clone(), None); + let overridden_trace = count_guest_logs(trace_snapshot, Some(LevelFilter::ERROR)); + let captured_error = count_guest_logs(error_snapshot.clone(), None); + let overridden_error = count_guest_logs(error_snapshot, Some(LevelFilter::TRACE)); + + assert!( + captured_trace > overridden_trace, + "ERROR override should reduce TRACE snapshot logs (trace={}, error={})", + captured_trace, + overridden_trace + ); + assert!( + overridden_error > captured_error, + "TRACE override should increase ERROR snapshot logs (trace={}, error={})", + overridden_error, + captured_error + ); + } + + /// A runtime log-level override set on a MultiUseSandbox survives restore. + /// + /// Ignored because it installs a process-global `log` logger; run + /// in isolation via the `test-isolated` Justfile recipe. + #[test] + #[ignore] + fn max_guest_log_level_setter_survives_restore() { + use hyperlight_common::log_level::GuestLogFilter; + use hyperlight_testing::logger::{LOGGER, Logger}; + use tracing_core::LevelFilter; + + Logger::initialize_test_logger(); + LOGGER.set_max_level(log::LevelFilter::Trace); + + let mut source = SandboxBuilder::from_file(simple_guest_as_pathbuf()) + .guest_log_level(LevelFilter::TRACE) + .build() + .unwrap(); + let snapshot = source.snapshot().unwrap(); + let mut sandbox = SandboxBuilder::from_snapshot(snapshot.clone()) + .build() + .unwrap(); + sandbox.log_level(LevelFilter::ERROR).unwrap(); + + let count_guest_logs = |sandbox: &mut MultiUseSandbox| { + LOGGER.clear_log_calls(); + for level in [ + LevelFilter::TRACE, + LevelFilter::DEBUG, + LevelFilter::INFO, + LevelFilter::WARN, + LevelFilter::ERROR, + ] { + let encoded: u64 = GuestLogFilter::from(level).into(); + sandbox + .call::<()>("LogMessage", ("hello".to_string(), encoded as i32)) + .unwrap(); + } + let count = (0..LOGGER.num_log_calls()) + .filter_map(|i| LOGGER.get_log_call(i)) + .filter(|call| call.target == "hyperlight_guest") + .count(); + LOGGER.clear_log_calls(); + count + }; + + let before_restore = count_guest_logs(&mut sandbox); + sandbox.restore(snapshot).unwrap(); + let after_restore = count_guest_logs(&mut sandbox); + + assert_eq!(before_restore, 1); + assert_eq!(before_restore, after_restore); + } + /// Two sandboxes built from clones of one `Arc` can /// each `restore` back to it, and stay memory-isolated from /// each other in between. diff --git a/src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs b/src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs index 0f664edbc..7fc997c2e 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs @@ -14,7 +14,7 @@ pub(in crate::sandbox::snapshot) const MT_SNAPSHOT_CURRENT: &str = MT_SNAPSHOT_V /// ABI version for the snapshot memory blob. Bumped when the /// host-guest contract for the snapshot bytes changes. See /// docs/snapshot-versioning.md. -pub(in crate::sandbox::snapshot) const SNAPSHOT_ABI_VERSION: u32 = 2; +pub(in crate::sandbox::snapshot) const SNAPSHOT_ABI_VERSION: u32 = 3; /// OCI standard annotation key for a manifest's tag inside an image /// index. Set on the manifest descriptor in `index.json`, not on the diff --git a/src/hyperlight_host/src/sandbox/snapshot/tripwires.rs b/src/hyperlight_host/src/sandbox/snapshot/tripwires.rs index c6dde9df1..b1ebff975 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/tripwires.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/tripwires.rs @@ -15,7 +15,7 @@ use super::file::{ MT_CONFIG_CURRENT, MT_SNAPSHOT_CURRENT, OCI_LAYOUT_VERSION, SNAPSHOT_ABI_VERSION, }; -const EXPECTED_ABI_VERSION: u32 = 2; +const EXPECTED_ABI_VERSION: u32 = 3; const EXPECTED_MT_CONFIG: &str = "application/vnd.hyperlight.snapshot.config.v1+json"; const EXPECTED_MT_SNAPSHOT: &str = "application/vnd.hyperlight.snapshot.memory.v1"; const EXPECTED_OCI_LAYOUT_VERSION: &str = "1.0.0"; diff --git a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs index f04a7ad60..291e809d0 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs @@ -86,7 +86,11 @@ pub(super) fn evolve_impl_multi_use(u_sbox: UninitializedSandbox) -> Result