%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
#include "node_snapshotable.h" #include <iostream> #include <sstream> #include "base_object-inl.h" #include "debug_utils-inl.h" #include "env-inl.h" #include "node_blob.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_file.h" #include "node_internals.h" #include "node_main_instance.h" #include "node_process.h" #include "node_snapshot_builder.h" #include "node_v8.h" #include "node_v8_platform-inl.h" #if HAVE_INSPECTOR #include "inspector/worker_inspector.h" // ParentInspectorHandle #endif namespace node { using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::MaybeLocal; using v8::Object; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::SnapshotCreator; using v8::StartupData; using v8::String; using v8::TryCatch; using v8::Value; template <typename T> void WriteVector(std::ostringstream* ss, const T* vec, size_t size) { for (size_t i = 0; i < size; i++) { *ss << std::to_string(vec[i]) << (i == size - 1 ? '\n' : ','); } } std::string FormatBlob(SnapshotData* data) { std::ostringstream ss; ss << R"(#include <cstddef> #include "env.h" #include "node_snapshot_builder.h" #include "v8.h" // This file is generated by tools/snapshot. Do not edit. namespace node { static const char blob_data[] = { )"; WriteVector(&ss, data->blob.data, data->blob.raw_size); ss << R"(}; static const int blob_size = )" << data->blob.raw_size << R"(; SnapshotData snapshot_data { // -- blob begins -- { blob_data, blob_size }, // -- blob ends -- // -- isolate_data_indices begins -- { )"; WriteVector(&ss, data->isolate_data_indices.data(), data->isolate_data_indices.size()); ss << R"(}, // -- isolate_data_indices ends -- // -- env_info begins -- )" << data->env_info << R"( // -- env_info ends -- }; const SnapshotData* SnapshotBuilder::GetEmbeddedSnapshotData() { Mutex::ScopedLock lock(snapshot_data_mutex_); return &snapshot_data; } } // namespace node )"; return ss.str(); } Mutex SnapshotBuilder::snapshot_data_mutex_; const std::vector<intptr_t>& SnapshotBuilder::CollectExternalReferences() { static auto registry = std::make_unique<ExternalReferenceRegistry>(); return registry->external_references(); } void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data, Isolate::CreateParams* params) { params->external_references = CollectExternalReferences().data(); params->snapshot_blob = const_cast<v8::StartupData*>(&(data->blob)); } void SnapshotBuilder::Generate(SnapshotData* out, const std::vector<std::string> args, const std::vector<std::string> exec_args) { Isolate* isolate = Isolate::Allocate(); isolate->SetCaptureStackTraceForUncaughtExceptions( true, 10, v8::StackTrace::StackTraceOptions::kDetailed); per_process::v8_platform.Platform()->RegisterIsolate(isolate, uv_default_loop()); std::unique_ptr<NodeMainInstance> main_instance; std::string result; { const std::vector<intptr_t>& external_references = CollectExternalReferences(); SnapshotCreator creator(isolate, external_references.data()); Environment* env; { main_instance = NodeMainInstance::Create(isolate, uv_default_loop(), per_process::v8_platform.Platform(), args, exec_args); HandleScope scope(isolate); creator.SetDefaultContext(Context::New(isolate)); out->isolate_data_indices = main_instance->isolate_data()->Serialize(&creator); // Run the per-context scripts Local<Context> context; { TryCatch bootstrapCatch(isolate); context = NewContext(isolate); if (bootstrapCatch.HasCaught()) { PrintCaughtException(isolate, context, bootstrapCatch); abort(); } } Context::Scope context_scope(context); // Create the environment env = new Environment(main_instance->isolate_data(), context, args, exec_args, nullptr, node::EnvironmentFlags::kDefaultFlags, {}); // Run scripts in lib/internal/bootstrap/ { TryCatch bootstrapCatch(isolate); MaybeLocal<Value> result = env->RunBootstrapping(); if (bootstrapCatch.HasCaught()) { PrintCaughtException(isolate, context, bootstrapCatch); } result.ToLocalChecked(); } // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be // loaded via LoadEnvironment() to execute process.argv[1] as the entry // point (we currently only support this kind of entry point, but we // could also explore snapshotting other kinds of execution modes // in the future). if (per_process::cli_options->build_snapshot) { #if HAVE_INSPECTOR env->InitializeInspector({}); #endif TryCatch bootstrapCatch(isolate); // TODO(joyeecheung): we could use the result for something special, // like setting up initializers that should be invoked at snapshot // dehydration. MaybeLocal<Value> result = LoadEnvironment(env, StartExecutionCallback{}); if (bootstrapCatch.HasCaught()) { PrintCaughtException(isolate, context, bootstrapCatch); } result.ToLocalChecked(); // FIXME(joyeecheung): right now running the loop in the snapshot // builder seems to introduces inconsistencies in JS land that need to // be synchronized again after snapshot restoration. int exit_code = SpinEventLoop(env).FromMaybe(1); CHECK_EQ(exit_code, 0); if (bootstrapCatch.HasCaught()) { PrintCaughtException(isolate, context, bootstrapCatch); abort(); } } if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) { env->PrintAllBaseObjects(); printf("Environment = %p\n", env); } // Serialize the native states out->env_info = env->Serialize(&creator); // Serialize the context size_t index = creator.AddContext( context, {SerializeNodeContextInternalFields, env}); CHECK_EQ(index, NodeMainInstance::kNodeContextIndex); } // Must be out of HandleScope out->blob = creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear); // We must be able to rehash the blob when we restore it or otherwise // the hash seed would be fixed by V8, introducing a vulnerability. CHECK(out->blob.CanBeRehashed()); // We cannot resurrect the handles from the snapshot, so make sure that // no handles are left open in the environment after the blob is created // (which should trigger a GC and close all handles that can be closed). if (!env->req_wrap_queue()->IsEmpty() || !env->handle_wrap_queue()->IsEmpty() || per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) { PrintLibuvHandleInformation(env->event_loop(), stderr); } CHECK(env->req_wrap_queue()->IsEmpty()); CHECK(env->handle_wrap_queue()->IsEmpty()); // Must be done while the snapshot creator isolate is entered i.e. the // creator is still alive. FreeEnvironment(env); main_instance->Dispose(); } per_process::v8_platform.Platform()->UnregisterIsolate(isolate); } std::string SnapshotBuilder::Generate( const std::vector<std::string> args, const std::vector<std::string> exec_args) { SnapshotData data; Generate(&data, args, exec_args); std::string result = FormatBlob(&data); delete[] data.blob.data; return result; } SnapshotableObject::SnapshotableObject(Environment* env, Local<Object> wrap, EmbedderObjectType type) : BaseObject(env, wrap), type_(type) { } const char* SnapshotableObject::GetTypeNameChars() const { switch (type_) { #define V(PropertyName, NativeTypeName) \ case EmbedderObjectType::k_##PropertyName: { \ return NativeTypeName::type_name.c_str(); \ } SERIALIZABLE_OBJECT_TYPES(V) #undef V default: { UNREACHABLE(); } } } bool IsSnapshotableType(FastStringKey key) { #define V(PropertyName, NativeTypeName) \ if (key == NativeTypeName::type_name) { \ return true; \ } SERIALIZABLE_OBJECT_TYPES(V) #undef V return false; } void DeserializeNodeInternalFields(Local<Object> holder, int index, StartupData payload, void* env) { per_process::Debug(DebugCategory::MKSNAPSHOT, "Deserialize internal field %d of %p, size=%d\n", static_cast<int>(index), (*holder), static_cast<int>(payload.raw_size)); if (payload.raw_size == 0) { holder->SetAlignedPointerInInternalField(index, nullptr); return; } DCHECK_EQ(index, BaseObject::kEmbedderType); Environment* env_ptr = static_cast<Environment*>(env); const InternalFieldInfo* info = reinterpret_cast<const InternalFieldInfo*>(payload.data); // TODO(joyeecheung): we can add a constant kNodeEmbedderId to the // beginning of every InternalFieldInfo to ensure that we don't // step on payloads that were not serialized by Node.js. switch (info->type) { #define V(PropertyName, NativeTypeName) \ case EmbedderObjectType::k_##PropertyName: { \ per_process::Debug(DebugCategory::MKSNAPSHOT, \ "Object %p is %s\n", \ (*holder), \ NativeTypeName::type_name.c_str()); \ env_ptr->EnqueueDeserializeRequest( \ NativeTypeName::Deserialize, holder, index, info->Copy()); \ break; \ } SERIALIZABLE_OBJECT_TYPES(V) #undef V default: { UNREACHABLE(); } } } StartupData SerializeNodeContextInternalFields(Local<Object> holder, int index, void* env) { // We only do one serialization for the kEmbedderType slot, the result // contains everything necessary for deserializing the entire object, // including the fields whose index is bigger than kEmbedderType // (most importantly, BaseObject::kSlot). // For Node.js this design is enough for all the native binding that are // serializable. if (index != BaseObject::kEmbedderType) { return StartupData{nullptr, 0}; } void* type_ptr = holder->GetAlignedPointerFromInternalField(index); if (type_ptr == nullptr) { return StartupData{nullptr, 0}; } uint16_t type = *(static_cast<uint16_t*>(type_ptr)); per_process::Debug(DebugCategory::MKSNAPSHOT, "type = 0x%x\n", type); if (type != kNodeEmbedderId) { return StartupData{nullptr, 0}; } per_process::Debug(DebugCategory::MKSNAPSHOT, "Serialize internal field, index=%d, holder=%p\n", static_cast<int>(index), *holder); void* binding_ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot); per_process::Debug(DebugCategory::MKSNAPSHOT, "binding = %p\n", binding_ptr); DCHECK(static_cast<BaseObject*>(binding_ptr)->is_snapshotable()); SnapshotableObject* obj = static_cast<SnapshotableObject*>(binding_ptr); per_process::Debug(DebugCategory::MKSNAPSHOT, "Object %p is %s, ", *holder, obj->GetTypeNameChars()); InternalFieldInfo* info = obj->Serialize(index); per_process::Debug(DebugCategory::MKSNAPSHOT, "payload size=%d\n", static_cast<int>(info->length)); return StartupData{reinterpret_cast<const char*>(info), static_cast<int>(info->length)}; } void SerializeBindingData(Environment* env, SnapshotCreator* creator, EnvSerializeInfo* info) { size_t i = 0; env->ForEachBindingData([&](FastStringKey key, BaseObjectPtr<BaseObject> binding) { per_process::Debug(DebugCategory::MKSNAPSHOT, "Serialize binding %i (%p), object=%p, type=%s\n", static_cast<int>(i), binding.get(), *(binding->object()), key.c_str()); if (IsSnapshotableType(key)) { size_t index = creator->AddData(env->context(), binding->object()); per_process::Debug(DebugCategory::MKSNAPSHOT, "Serialized with index=%d\n", static_cast<int>(index)); info->bindings.push_back({key.c_str(), i, index}); SnapshotableObject* ptr = static_cast<SnapshotableObject*>(binding.get()); ptr->PrepareForSerialization(env->context(), creator); } else { UNREACHABLE(); } i++; }); } namespace mksnapshot { void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) { CHECK(args[0]->IsString()); Local<String> filename = args[0].As<String>(); Local<String> source = args[1].As<String>(); Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); ScriptOrigin origin(isolate, filename, 0, 0, true); // TODO(joyeecheung): do we need all of these? Maybe we would want a less // internal version of them. std::vector<Local<String>> parameters = { FIXED_ONE_BYTE_STRING(isolate, "require"), FIXED_ONE_BYTE_STRING(isolate, "__filename"), FIXED_ONE_BYTE_STRING(isolate, "__dirname"), }; ScriptCompiler::Source script_source(source, origin); Local<Function> fn; if (ScriptCompiler::CompileFunctionInContext(context, &script_source, parameters.size(), parameters.data(), 0, nullptr, ScriptCompiler::kEagerCompile) .ToLocal(&fn)) { args.GetReturnValue().Set(fn); } } void SetSerializeCallback(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->snapshot_serialize_callback().IsEmpty()); CHECK(args[0]->IsFunction()); env->set_snapshot_serialize_callback(args[0].As<Function>()); } void SetDeserializeCallback(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->snapshot_deserialize_callback().IsEmpty()); CHECK(args[0]->IsFunction()); env->set_snapshot_deserialize_callback(args[0].As<Function>()); } void SetDeserializeMainFunction(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->snapshot_deserialize_main().IsEmpty()); CHECK(args[0]->IsFunction()); env->set_snapshot_deserialize_main(args[0].As<Function>()); } void Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) { Environment* env = Environment::GetCurrent(context); env->SetMethod(target, "compileSerializeMain", CompileSerializeMain); env->SetMethod(target, "markBootstrapComplete", MarkBootstrapComplete); env->SetMethod(target, "setSerializeCallback", SetSerializeCallback); env->SetMethod(target, "setDeserializeCallback", SetDeserializeCallback); env->SetMethod( target, "setDeserializeMainFunction", SetDeserializeMainFunction); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(CompileSerializeMain); registry->Register(MarkBootstrapComplete); registry->Register(SetSerializeCallback); registry->Register(SetDeserializeCallback); registry->Register(SetDeserializeMainFunction); } } // namespace mksnapshot } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(mksnapshot, node::mksnapshot::Initialize) NODE_MODULE_EXTERNAL_REFERENCE(mksnapshot, node::mksnapshot::RegisterExternalReferences)