| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f5fcd49 commit 98fa488
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1171,6 +1171,15 @@ in the current working directory. | |||
| 1171 | 1171 | When used without `--build-snapshot`, `--snapshot-blob` specifies the | |
| 1172 | 1172 | path to the blob that will be used to restore the application state. | |
| 1173 | 1173 | ||
| 1174 | + When loading a snapshot, Node.js checks that: | ||
| 1175 | + | ||
| 1176 | + 1. The version, architecture and platform of the running Node.js binary | ||
| 1177 | + are exactly the same as that of the binary that generates the snapshot. | ||
| 1178 | + 2. The V8 flags and CPU features are compatible with that of the binary | ||
| 1179 | + that generates the snapshot. | ||
| 1180 | + | ||
| 1181 | + If they don't match, Node.js would refuse to load the snapshot and exit with 1. | ||
| 1182 | + | ||
| 1174 | 1183 | ### `--test` | |
| 1175 | 1184 | ||
| 1176 | 1185 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -261,6 +261,21 @@ std::ostream& operator<<(std::ostream& output, | |||
| 261 | 261 | return output; | |
| 262 | 262 | } | |
| 263 | 263 | ||
| 264 | + std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& i) { | ||
| 265 | + output << "{\n" | ||
| 266 | + << " " | ||
| 267 | + << (i.type == SnapshotMetadata::Type::kDefault | ||
| 268 | + ? "SnapshotMetadata::Type::kDefault" | ||
| 269 | + : "SnapshotMetadata::Type::kFullyCustomized") | ||
| 270 | + << ", // type\n" | ||
| 271 | + << " \"" << i.node_version << "\", // node_version\n" | ||
| 272 | + << " \"" << i.node_arch << "\", // node_arch\n" | ||
| 273 | + << " \"" << i.node_platform << "\", // node_platform\n" | ||
| 274 | + << " " << i.v8_cache_version_tag << ", // v8_cache_version_tag\n" | ||
| 275 | + << "}"; | ||
| 276 | + return output; | ||
| 277 | + } | ||
| 278 | + | ||
| 264 | 279 | IsolateDataSerializeInfo IsolateData::Serialize(SnapshotCreator* creator) { | |
| 265 | 280 | Isolate* isolate = creator->GetIsolate(); | |
| 266 | 281 | IsolateDataSerializeInfo info; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -984,6 +984,19 @@ struct EnvSerializeInfo { | |||
| 984 | 984 | friend std::ostream& operator<<(std::ostream& o, const EnvSerializeInfo& i); | |
| 985 | 985 | }; | |
| 986 | 986 | ||
| 987 | + struct SnapshotMetadata { | ||
| 988 | + // For now kFullyCustomized is only built with the --build-snapshot CLI flag. | ||
| 989 | + // We might want to add more types of snapshots in the future. | ||
| 990 | + enum class Type : uint8_t { kDefault, kFullyCustomized }; | ||
| 991 | + | ||
| 992 | + Type type; | ||
| 993 | + std::string node_version; | ||
| 994 | + std::string node_arch; | ||
| 995 | + std::string node_platform; | ||
| 996 | + // Result of v8::ScriptCompiler::CachedDataVersionTag(). | ||
| 997 | + uint32_t v8_cache_version_tag; | ||
| 998 | + }; | ||
| 999 | + | ||
| 987 | 1000 | struct SnapshotData { | |
| 988 | 1001 | enum class DataOwnership { kOwned, kNotOwned }; | |
| 989 | 1002 | ||
@@ -993,6 +1006,8 @@ struct SnapshotData { | |||
| 993 | 1006 | ||
| 994 | 1007 | DataOwnership data_ownership = DataOwnership::kOwned; | |
| 995 | 1008 | ||
| 1009 | + SnapshotMetadata metadata; | ||
| 1010 | + | ||
| 996 | 1011 | // The result of v8::SnapshotCreator::CreateBlob() during the snapshot | |
| 997 | 1012 | // building process. | |
| 998 | 1013 | v8::StartupData v8_snapshot_blob_data{nullptr, 0}; | |
@@ -1009,7 +1024,10 @@ struct SnapshotData { | |||
| 1009 | 1024 | std::vector<builtins::CodeCacheInfo> code_cache; | |
| 1010 | 1025 | ||
| 1011 | 1026 | void ToBlob(FILE* out) const; | |
| 1012 | - static void FromBlob(SnapshotData* out, FILE* in); | ||
| 1027 | + // If returns false, the metadata doesn't match the current Node.js binary, | ||
| 1028 | + // and the caller should not consume the snapshot data. | ||
| 1029 | + bool Check() const; | ||
| 1030 | + static bool FromBlob(SnapshotData* out, FILE* in); | ||
| 1013 | 1031 | ||
| 1014 | 1032 | ~SnapshotData(); | |
| 1015 | 1033 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1269,13 +1269,23 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, | |||
| 1269 | 1269 | return exit_code; | |
| 1270 | 1270 | } | |
| 1271 | 1271 | std::unique_ptr<SnapshotData> read_data = std::make_unique<SnapshotData>(); | |
| 1272 | - SnapshotData::FromBlob(read_data.get(), fp); | ||
| 1272 | + if (!SnapshotData::FromBlob(read_data.get(), fp)) { | ||
| 1273 | + // If we fail to read the customized snapshot, simply exit with 1. | ||
| 1274 | + exit_code = 1; | ||
| 1275 | + return exit_code; | ||
| 1276 | + } | ||
| 1273 | 1277 | *snapshot_data_ptr = read_data.release(); | |
| 1274 | 1278 | fclose(fp); | |
| 1275 | 1279 | } else if (per_process::cli_options->node_snapshot) { | |
| 1276 | 1280 | // If --snapshot-blob is not specified, we are reading the embedded | |
| 1277 | 1281 | // snapshot, but we will skip it if --no-node-snapshot is specified. | |
| 1278 | - *snapshot_data_ptr = SnapshotBuilder::GetEmbeddedSnapshotData(); | ||
| 1282 | + const node::SnapshotData* read_data = | ||
| 1283 | + SnapshotBuilder::GetEmbeddedSnapshotData(); | ||
| 1284 | + if (read_data != nullptr && read_data->Check()) { | ||
| 1285 | + // If we fail to read the embedded snapshot, treat it as if Node.js | ||
| 1286 | + // was built without one. | ||
| 1287 | + *snapshot_data_ptr = read_data; | ||
| 1288 | + } | ||
| 1279 | 1289 | } | |
| 1280 | 1290 | ||
| 1281 | 1291 | if ((*snapshot_data_ptr) != nullptr) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -414,6 +414,7 @@ std::ostream& operator<<(std::ostream& output, | |||
| 414 | 414 | const TickInfo::SerializeInfo& d); | |
| 415 | 415 | std::ostream& operator<<(std::ostream& output, | |
| 416 | 416 | const AsyncHooks::SerializeInfo& d); | |
| 417 | + std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& d); | ||
| 417 | 418 | ||
| 418 | 419 | namespace performance { | |
| 419 | 420 | std::ostream& operator<<(std::ostream& output, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -679,6 +679,57 @@ size_t FileWriter::Write(const EnvSerializeInfo& data) { | |||
| 679 | 679 | return written_total; | |
| 680 | 680 | } | |
| 681 | 681 | ||
| 682 | + // Layout of SnapshotMetadata | ||
| 683 | + // [ 1 byte ] type of the snapshot | ||
| 684 | + // [ 4/8 bytes ] length of the node version string | ||
| 685 | + // [ ... ] |length| bytes of node version | ||
| 686 | + // [ 4/8 bytes ] length of the node arch string | ||
| 687 | + // [ ... ] |length| bytes of node arch | ||
| 688 | + // [ 4/8 bytes ] length of the node platform string | ||
| 689 | + // [ ... ] |length| bytes of node platform | ||
| 690 | + // [ 4 bytes ] v8 cache version tag | ||
| 691 | + template <> | ||
| 692 | + SnapshotMetadata FileReader::Read() { | ||
| 693 | + per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<SnapshotMetadata>()\n"); | ||
| 694 | + | ||
| 695 | + SnapshotMetadata result; | ||
| 696 | + result.type = static_cast<SnapshotMetadata::Type>(Read<uint8_t>()); | ||
| 697 | + result.node_version = ReadString(); | ||
| 698 | + result.node_arch = ReadString(); | ||
| 699 | + result.node_platform = ReadString(); | ||
| 700 | + result.v8_cache_version_tag = Read<uint32_t>(); | ||
| 701 | + | ||
| 702 | + if (is_debug) { | ||
| 703 | + std::string str = ToStr(result); | ||
| 704 | + Debug("Read<SnapshotMetadata>() %s\n", str.c_str()); | ||
| 705 | + } | ||
| 706 | + return result; | ||
| 707 | + } | ||
| 708 | + | ||
| 709 | + template <> | ||
| 710 | + size_t FileWriter::Write(const SnapshotMetadata& data) { | ||
| 711 | + if (is_debug) { | ||
| 712 | + std::string str = ToStr(data); | ||
| 713 | + Debug("\nWrite<SnapshotMetadata>() %s\n", str.c_str()); | ||
| 714 | + } | ||
| 715 | + size_t written_total = 0; | ||
| 716 | + // We need the Node.js version, platform and arch to match because | ||
| 717 | + // Node.js may perform synchronizations that are platform-specific and they | ||
| 718 | + // can be changed in semver-patches. | ||
| 719 | + Debug("Write snapshot type %" PRIu8 "\n", static_cast<uint8_t>(data.type)); | ||
| 720 | + written_total += Write<uint8_t>(static_cast<uint8_t>(data.type)); | ||
| 721 | + Debug("Write Node.js version %s\n", data.node_version.c_str()); | ||
| 722 | + written_total += WriteString(data.node_version); | ||
| 723 | + Debug("Write Node.js arch %s\n", data.node_arch); | ||
| 724 | + written_total += WriteString(data.node_arch); | ||
| 725 | + Debug("Write Node.js platform %s\n", data.node_platform); | ||
| 726 | + written_total += WriteString(data.node_platform); | ||
| 727 | + Debug("Write V8 cached data version tag %" PRIx32 "\n", | ||
| 728 | + data.v8_cache_version_tag); | ||
| 729 | + written_total += Write<uint32_t>(data.v8_cache_version_tag); | ||
| 730 | + return written_total; | ||
| 731 | + } | ||
| 732 | + | ||
| 682 | 733 | // Layout of the snapshot blob | |
| 683 | 734 | // [ 4 bytes ] kMagic | |
| 684 | 735 | // [ 4/8 bytes ] length of Node.js version string | |
@@ -695,13 +746,12 @@ void SnapshotData::ToBlob(FILE* out) const { | |||
| 695 | 746 | w.Debug("SnapshotData::ToBlob()\n"); | |
| 696 | 747 | ||
| 697 | 748 | size_t written_total = 0; | |
| 749 | + | ||
| 698 | 750 | // Metadata | |
| 699 | 751 | w.Debug("Write magic %" PRIx32 "\n", kMagic); | |
| 700 | 752 | written_total += w.Write<uint32_t>(kMagic); | |
| 701 | - w.Debug("Write version %s\n", NODE_VERSION); | ||
| 702 | - written_total += w.WriteString(NODE_VERSION); | ||
| 703 | - w.Debug("Write arch %s\n", NODE_ARCH); | ||
| 704 | - written_total += w.WriteString(NODE_ARCH); | ||
| 753 | + w.Debug("Write metadata\n"); | ||
| 754 | + written_total += w.Write<SnapshotMetadata>(metadata); | ||
| 705 | 755 | ||
| 706 | 756 | written_total += w.Write<v8::StartupData>(v8_snapshot_blob_data); | |
| 707 | 757 | w.Debug("Write isolate_data_indices\n"); | |
@@ -712,22 +762,22 @@ void SnapshotData::ToBlob(FILE* out) const { | |||
| 712 | 762 | w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total); | |
| 713 | 763 | } | |
| 714 | 764 | ||
| 715 | - void SnapshotData::FromBlob(SnapshotData* out, FILE* in) { | ||
| 765 | + bool SnapshotData::FromBlob(SnapshotData* out, FILE* in) { | ||
| 716 | 766 | FileReader r(in); | |
| 717 | 767 | r.Debug("SnapshotData::FromBlob()\n"); | |
| 718 | 768 | ||
| 769 | + DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned); | ||
| 770 | + | ||
| 719 | 771 | // Metadata | |
| 720 | 772 | uint32_t magic = r.Read<uint32_t>(); | |
| 721 | - r.Debug("Read magic %" PRIx64 "\n", magic); | ||
| 773 | + r.Debug("Read magic %" PRIx32 "\n", magic); | ||
| 722 | 774 | CHECK_EQ(magic, kMagic); | |
| 723 | - std::string version = r.ReadString(); | ||
| 724 | - r.Debug("Read version %s\n", version.c_str()); | ||
| 725 | - CHECK_EQ(version, NODE_VERSION); | ||
| 726 | - std::string arch = r.ReadString(); | ||
| 727 | - r.Debug("Read arch %s\n", arch.c_str()); | ||
| 728 | - CHECK_EQ(arch, NODE_ARCH); | ||
| 775 | + out->metadata = r.Read<SnapshotMetadata>(); | ||
| 776 | + r.Debug("Read metadata\n"); | ||
| 777 | + if (!out->Check()) { | ||
| 778 | + return false; | ||
| 779 | + } | ||
| 729 | 780 | ||
| 730 | - DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned); | ||
| 731 | 781 | out->v8_snapshot_blob_data = r.Read<v8::StartupData>(); | |
| 732 | 782 | r.Debug("Read isolate_data_info\n"); | |
| 733 | 783 | out->isolate_data_info = r.Read<IsolateDataSerializeInfo>(); | |
@@ -736,6 +786,54 @@ void SnapshotData::FromBlob(SnapshotData* out, FILE* in) { | |||
| 736 | 786 | out->code_cache = r.ReadVector<builtins::CodeCacheInfo>(); | |
| 737 | 787 | ||
| 738 | 788 | r.Debug("SnapshotData::FromBlob() read %d bytes\n", r.read_total); | |
| 789 | + return true; | ||
| 790 | + } | ||
| 791 | + | ||
| 792 | + bool SnapshotData::Check() const { | ||
| 793 | + if (metadata.node_version != per_process::metadata.versions.node) { | ||
| 794 | + fprintf(stderr, | ||
| 795 | + "Failed to load the startup snapshot because it was built with" | ||
| 796 | + "Node.js version %s and the current Node.js version is %s.\n", | ||
| 797 | + metadata.node_version.c_str(), | ||
| 798 | + NODE_VERSION); | ||
| 799 | + return false; | ||
| 800 | + } | ||
| 801 | + | ||
| 802 | + if (metadata.node_arch != per_process::metadata.arch) { | ||
| 803 | + fprintf(stderr, | ||
| 804 | + "Failed to load the startup snapshot because it was built with" | ||
| 805 | + "architecture %s and the architecture is %s.\n", | ||
| 806 | + metadata.node_arch.c_str(), | ||
| 807 | + NODE_ARCH); | ||
| 808 | + return false; | ||
| 809 | + } | ||
| 810 | + | ||
| 811 | + if (metadata.node_platform != per_process::metadata.platform) { | ||
| 812 | + fprintf(stderr, | ||
| 813 | + "Failed to load the startup snapshot because it was built with" | ||
| 814 | + "platform %s and the current platform is %s.\n", | ||
| 815 | + metadata.node_platform.c_str(), | ||
| 816 | + NODE_PLATFORM); | ||
| 817 | + return false; | ||
| 818 | + } | ||
| 819 | + | ||
| 820 | + uint32_t current_cache_version = v8::ScriptCompiler::CachedDataVersionTag(); | ||
| 821 | + if (metadata.v8_cache_version_tag != current_cache_version && | ||
| 822 | + metadata.type == SnapshotMetadata::Type::kFullyCustomized) { | ||
| 823 | + // For now we only do this check for the customized snapshots - we know | ||
| 824 | + // that the flags we use in the default snapshot are limited and safe | ||
| 825 | + // enough so we can relax the constraints for it. | ||
| 826 | + fprintf(stderr, | ||
| 827 | + "Failed to load the startup snapshot because it was built with " | ||
| 828 | + "a different version of V8 or with different V8 configurations.\n" | ||
| 829 | + "Expected tag %" PRIx32 ", read %" PRIx32 "\n", | ||
| 830 | + current_cache_version, | ||
| 831 | + metadata.v8_cache_version_tag); | ||
| 832 | + return false; | ||
| 833 | + } | ||
| 834 | + | ||
| 835 | + // TODO(joyeecheung): check incompatible Node.js flags. | ||
| 836 | + return true; | ||
| 739 | 837 | } | |
| 740 | 838 | ||
| 741 | 839 | SnapshotData::~SnapshotData() { | |
@@ -822,6 +920,10 @@ static const int v8_snapshot_blob_size = )" | |||
| 822 | 920 | // -- data_ownership begins -- | |
| 823 | 921 | SnapshotData::DataOwnership::kNotOwned, | |
| 824 | 922 | // -- data_ownership ends -- | |
| 923 | + // -- metadata begins -- | ||
| 924 | + )" << data->metadata | ||
| 925 | + << R"(, | ||
| 926 | + // -- metadata ends -- | ||
| 825 | 927 | // -- v8_snapshot_blob_data begins -- | |
| 826 | 928 | { v8_snapshot_blob_data, v8_snapshot_blob_size }, | |
| 827 | 929 | // -- v8_snapshot_blob_data ends -- | |
@@ -908,6 +1010,12 @@ int SnapshotBuilder::Generate(SnapshotData* out, | |||
| 908 | 1010 | per_process::v8_platform.Platform()->UnregisterIsolate(isolate); | |
| 909 | 1011 | }); | |
| 910 | 1012 | ||
| 1013 | + // It's only possible to be kDefault in node_mksnapshot. | ||
| 1014 | + SnapshotMetadata::Type snapshot_type = | ||
| 1015 | + per_process::cli_options->build_snapshot | ||
| 1016 | + ? SnapshotMetadata::Type::kFullyCustomized | ||
| 1017 | + : SnapshotMetadata::Type::kDefault; | ||
| 1018 | + | ||
| 911 | 1019 | { | |
| 912 | 1020 | HandleScope scope(isolate); | |
| 913 | 1021 | TryCatch bootstrapCatch(isolate); | |
@@ -956,7 +1064,7 @@ int SnapshotBuilder::Generate(SnapshotData* out, | |||
| 956 | 1064 | // point (we currently only support this kind of entry point, but we | |
| 957 | 1065 | // could also explore snapshotting other kinds of execution modes | |
| 958 | 1066 | // in the future). | |
| 959 | - if (per_process::cli_options->build_snapshot) { | ||
| 1067 | + if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) { | ||
| 960 | 1068 | #if HAVE_INSPECTOR | |
| 961 | 1069 | // TODO(joyeecheung): move this before RunBootstrapping(). | |
| 962 | 1070 | env->InitializeInspector({}); | |
@@ -1020,6 +1128,12 @@ int SnapshotBuilder::Generate(SnapshotData* out, | |||
| 1020 | 1128 | return SNAPSHOT_ERROR; | |
| 1021 | 1129 | } | |
| 1022 | 1130 | ||
| 1131 | + out->metadata = SnapshotMetadata{snapshot_type, | ||
| 1132 | + per_process::metadata.versions.node, | ||
| 1133 | + per_process::metadata.arch, | ||
| 1134 | + per_process::metadata.platform, | ||
| 1135 | + v8::ScriptCompiler::CachedDataVersionTag()}; | ||
| 1136 | + | ||
| 1023 | 1137 | // We cannot resurrect the handles from the snapshot, so make sure that | |
| 1024 | 1138 | // no handles are left open in the environment after the blob is created | |
| 1025 | 1139 | // (which should trigger a GC and close all handles that can be closed). | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,76 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that Node.js refuses to load snapshots built with incompatible | ||
| 4 | + // V8 configurations. | ||
| 5 | + | ||
| 6 | + require('../common'); | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + const { spawnSync } = require('child_process'); | ||
| 9 | + const tmpdir = require('../common/tmpdir'); | ||
| 10 | + const fixtures = require('../common/fixtures'); | ||
| 11 | + const path = require('path'); | ||
| 12 | + const fs = require('fs'); | ||
| 13 | + | ||
| 14 | + tmpdir.refresh(); | ||
| 15 | + const blobPath = path.join(tmpdir.path, 'snapshot.blob'); | ||
| 16 | + const entry = fixtures.path('empty.js'); | ||
| 17 | + | ||
| 18 | + // The flag used can be any flag that makes a difference in | ||
| 19 | + // v8::ScriptCompiler::CachedDataVersionTag(). --harmony | ||
| 20 | + // is chosen here because it's stable enough and makes a difference. | ||
| 21 | + { | ||
| 22 | + // Build a snapshot with --harmony. | ||
| 23 | + const child = spawnSync(process.execPath, [ | ||
| 24 | + '--harmony', | ||
| 25 | + '--snapshot-blob', | ||
| 26 | + blobPath, | ||
| 27 | + '--build-snapshot', | ||
| 28 | + entry, | ||
| 29 | + ], { | ||
| 30 | + cwd: tmpdir.path | ||
| 31 | + }); | ||
| 32 | + if (child.status !== 0) { | ||
| 33 | + console.log(child.stderr.toString()); | ||
| 34 | + console.log(child.stdout.toString()); | ||
| 35 | + assert.strictEqual(child.status, 0); | ||
| 36 | + } | ||
| 37 | + const stats = fs.statSync(path.join(tmpdir.path, 'snapshot.blob')); | ||
| 38 | + assert(stats.isFile()); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + { | ||
| 42 | + // Now load the snapshot without --harmony, which should fail. | ||
| 43 | + const child = spawnSync(process.execPath, [ | ||
| 44 | + '--snapshot-blob', | ||
| 45 | + blobPath, | ||
| 46 | + ], { | ||
| 47 | + cwd: tmpdir.path, | ||
| 48 | + env: { | ||
| 49 | + ...process.env, | ||
| 50 | + } | ||
| 51 | + }); | ||
| 52 | + | ||
| 53 | + const stderr = child.stderr.toString().trim(); | ||
| 54 | + assert.match(stderr, /Failed to load the startup snapshot/); | ||
| 55 | + assert.strictEqual(child.status, 1); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + { | ||
| 59 | + // Load it again with --harmony and it should work. | ||
| 60 | + const child = spawnSync(process.execPath, [ | ||
| 61 | + '--harmony', | ||
| 62 | + '--snapshot-blob', | ||
| 63 | + blobPath, | ||
| 64 | + ], { | ||
| 65 | + cwd: tmpdir.path, | ||
| 66 | + env: { | ||
| 67 | + ...process.env, | ||
| 68 | + } | ||
| 69 | + }); | ||
| 70 | + | ||
| 71 | + if (child.status !== 0) { | ||
| 72 | + console.log(child.stderr.toString()); | ||
| 73 | + console.log(child.stdout.toString()); | ||
| 74 | + assert.strictEqual(child.status, 0); | ||
| 75 | + } | ||
| 76 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments