%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)