#include "node_snapshotable.h" #include #include #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 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 #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& SnapshotBuilder::CollectExternalReferences() { static auto registry = std::make_unique(); return registry->external_references(); } void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data, Isolate::CreateParams* params) { params->external_references = CollectExternalReferences().data(); params->snapshot_blob = const_cast(&(data->blob)); } void SnapshotBuilder::Generate(SnapshotData* out, const std::vector args, const std::vector 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 main_instance; std::string result; { const std::vector& 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); out->isolate_data_indices = main_instance->isolate_data()->Serialize(&creator); HandleScope scope(isolate); // The default context with only things created by V8. creator.SetDefaultContext(Context::New(isolate)); auto CreateBaseContext = [&]() { TryCatch bootstrapCatch(isolate); // Run the per-context scripts. Local base_context = NewContext(isolate); if (bootstrapCatch.HasCaught()) { PrintCaughtException(isolate, base_context, bootstrapCatch); abort(); } return base_context; }; // The Node.js-specific context with primodials, can be used by workers // TODO(joyeecheung): investigate if this can be used by vm contexts // without breaking compatibility. { size_t index = creator.AddContext(CreateBaseContext()); CHECK_EQ(index, SnapshotBuilder::kNodeBaseContextIndex); } // The main instance context. { Local main_context = CreateBaseContext(); Context::Scope context_scope(main_context); TryCatch bootstrapCatch(isolate); // Create the environment. env = new Environment(main_instance->isolate_data(), main_context, args, exec_args, nullptr, node::EnvironmentFlags::kDefaultFlags, {}); // Run scripts in lib/internal/bootstrap/ MaybeLocal result = env->RunBootstrapping(); if (bootstrapCatch.HasCaught()) { // TODO(joyeecheung): fail by exiting with a non-zero exit code. PrintCaughtException(isolate, main_context, bootstrapCatch); abort(); } 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 // TODO(joyeecheung): we could use the result for something special, // like setting up initializers that should be invoked at snapshot // dehydration. MaybeLocal result = LoadEnvironment(env, StartExecutionCallback{}); if (bootstrapCatch.HasCaught()) { // TODO(joyeecheung): fail by exiting with a non-zero exit code. PrintCaughtException(isolate, main_context, bootstrapCatch); abort(); } 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()) { // TODO(joyeecheung): fail by exiting with a non-zero exit code. PrintCaughtException(isolate, main_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( main_context, {SerializeNodeContextInternalFields, env}); CHECK_EQ(index, SnapshotBuilder::kNodeMainContextIndex); } } // 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 args, const std::vector 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 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 holder, int index, StartupData payload, void* env) { per_process::Debug(DebugCategory::MKSNAPSHOT, "Deserialize internal field %d of %p, size=%d\n", static_cast(index), (*holder), static_cast(payload.raw_size)); if (payload.raw_size == 0) { holder->SetAlignedPointerInInternalField(index, nullptr); return; } Environment* env_ptr = static_cast(env); const InternalFieldInfo* info = reinterpret_cast(payload.data); 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 holder, int index, void* env) { per_process::Debug(DebugCategory::MKSNAPSHOT, "Serialize internal field, index=%d, holder=%p\n", static_cast(index), *holder); void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot); if (ptr == nullptr) { return StartupData{nullptr, 0}; } DCHECK(static_cast(ptr)->is_snapshotable()); SnapshotableObject* obj = static_cast(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(info->length)); return StartupData{reinterpret_cast(info), static_cast(info->length)}; } void SerializeBindingData(Environment* env, SnapshotCreator* creator, EnvSerializeInfo* info) { size_t i = 0; env->ForEachBindingData([&](FastStringKey key, BaseObjectPtr binding) { per_process::Debug(DebugCategory::MKSNAPSHOT, "Serialize binding %i, %p, type=%s\n", static_cast(i), *(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(index)); info->bindings.push_back({key.c_str(), i, index}); SnapshotableObject* ptr = static_cast(binding.get()); ptr->PrepareForSerialization(env->context(), creator); } else { UNREACHABLE(); } i++; }); } namespace mksnapshot { static void CompileSnapshotMain(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Local filename = args[0].As(); Local source = args[1].As(); Isolate* isolate = args.GetIsolate(); Local 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> 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 fn; if (ScriptCompiler::CompileFunctionInContext(context, &script_source, parameters.size(), parameters.data(), 0, nullptr, ScriptCompiler::kEagerCompile) .ToLocal(&fn)) { args.GetReturnValue().Set(fn); } } static void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = context->GetIsolate(); env->SetMethod(target, "compileSnapshotMain", CompileSnapshotMain); target ->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cleanups"), v8::Array::New(isolate)) .Check(); } static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(CompileSnapshotMain); registry->Register(MarkBootstrapComplete); } } // namespace mksnapshot } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(mksnapshot, node::mksnapshot::Initialize) NODE_MODULE_EXTERNAL_REFERENCE(mksnapshot, node::mksnapshot::RegisterExternalReferences)