blob: 97e0e83445fcb52ec31cdb538dae5a44447b3a60 [file] [log] [blame] [edit]
#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_builtins.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_metadata.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::Object;
using v8::ScriptCompiler;
using v8::ScriptOrigin;
using v8::SnapshotCreator;
using v8::StartupData;
using v8::String;
using v8::TryCatch;
using v8::Value;
const uint32_t SnapshotData::kMagic;
std::ostream& operator<<(std::ostream& output,
const builtins::CodeCacheInfo& info) {
output << "<builtins::CodeCacheInfo id=" << info.id
<< ", size=" << info.data.size() << ">\n";
return output;
}
std::ostream& operator<<(std::ostream& output,
const std::vector<builtins::CodeCacheInfo>& vec) {
output << "{\n";
for (const auto& info : vec) {
output << info;
}
output << "}\n";
return output;
}
std::ostream& operator<<(std::ostream& output,
const std::vector<uint8_t>& vec) {
output << "{\n";
for (const auto& i : vec) {
output << i << ",";
}
output << "}";
return output;
}
class FileIO {
public:
explicit FileIO(FILE* file)
: f(file),
is_debug(per_process::enabled_debug_list.enabled(
DebugCategory::MKSNAPSHOT)) {}
template <typename... Args>
void Debug(const char* format, Args&&... args) const {
per_process::Debug(
DebugCategory::MKSNAPSHOT, format, std::forward<Args>(args)...);
}
template <typename T>
std::string ToStr(const T& arg) const {
std::stringstream ss;
ss << arg;
return ss.str();
}
template <typename T>
std::string GetName() const {
#define TYPE_LIST(V) \
V(builtins::CodeCacheInfo) \
V(PropInfo) \
V(std::string)
#define V(TypeName) \
if (std::is_same_v<T, TypeName>) { \
return #TypeName; \
}
TYPE_LIST(V)
#undef V
std::string name;
if (std::is_arithmetic_v<T>) {
if (!std::is_signed_v<T>) {
name += "u";
}
name += std::is_integral_v<T> ? "int" : "float";
name += std::to_string(sizeof(T) * 8);
name += "_t";
}
return name;
}
FILE* f = nullptr;
bool is_debug = false;
};
class FileReader : public FileIO {
public:
explicit FileReader(FILE* file) : FileIO(file) {}
~FileReader() {}
// Helper for reading numeric types.
template <typename T>
T Read() {
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
T result;
Read(&result, 1);
return result;
}
// Layout of vectors:
// [ 4/8 bytes ] count
// [ ... ] contents (count * size of individual elements)
template <typename T>
std::vector<T> ReadVector() {
if (is_debug) {
std::string name = GetName<T>();
Debug("\nReadVector<%s>()(%d-byte)\n", name.c_str(), sizeof(T));
}
size_t count = static_cast<size_t>(Read<size_t>());
if (count == 0) {
return std::vector<T>();
}
if (is_debug) {
Debug("Reading %d vector elements...\n", count);
}
std::vector<T> result = ReadVector<T>(count, std::is_arithmetic<T>{});
if (is_debug) {
std::string str = std::is_arithmetic_v<T> ? "" : ToStr(result);
std::string name = GetName<T>();
Debug("ReadVector<%s>() read %s\n", name.c_str(), str.c_str());
}
return result;
}
std::string ReadString() {
size_t length = Read<size_t>();
if (is_debug) {
Debug("ReadString(), length=%d: ", length);
}
CHECK_GT(length, 0); // There should be no empty strings.
MallocedBuffer<char> buf(length + 1);
size_t r = fread(buf.data, 1, length + 1, f);
CHECK_EQ(r, length + 1);
std::string result(buf.data, length); // This creates a copy of buf.data.
if (is_debug) {
Debug("\"%s\", read %d bytes\n", result.c_str(), r);
}
read_total += r;
return result;
}
size_t read_total = 0;
private:
// Helper for reading an array of numeric types.
template <typename T>
void Read(T* out, size_t count) {
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
DCHECK_GT(count, 0); // Should not read contents for vectors of size 0.
if (is_debug) {
std::string name = GetName<T>();
Debug("Read<%s>()(%d-byte), count=%d: ", name.c_str(), sizeof(T), count);
}
size_t r = fread(out, sizeof(T), count, f);
CHECK_EQ(r, count);
if (is_debug) {
std::string str =
"{ " + std::to_string(out[0]) + (count > 1 ? ", ... }" : " }");
Debug("%s, read %d bytes\n", str.c_str(), r);
}
read_total += r;
}
// Helper for reading numeric vectors.
template <typename Number>
std::vector<Number> ReadVector(size_t count, std::true_type) {
static_assert(std::is_arithmetic_v<Number>, "Not an arithmetic type");
DCHECK_GT(count, 0); // Should not read contents for vectors of size 0.
std::vector<Number> result(count);
Read(result.data(), count);
return result;
}
// Helper for reading non-numeric vectors.
template <typename T>
std::vector<T> ReadVector(size_t count, std::false_type) {
static_assert(!std::is_arithmetic_v<T>, "Arithmetic type");
DCHECK_GT(count, 0); // Should not read contents for vectors of size 0.
std::vector<T> result;
result.reserve(count);
bool original_is_debug = is_debug;
is_debug = original_is_debug && !std::is_same_v<T, std::string>;
for (size_t i = 0; i < count; ++i) {
if (is_debug) {
Debug("\n[%d] ", i);
}
result.push_back(Read<T>());
}
is_debug = original_is_debug;
return result;
}
};
class FileWriter : public FileIO {
public:
explicit FileWriter(FILE* file) : FileIO(file) {}
~FileWriter() {}
// Helper for writing numeric types.
template <typename T>
size_t Write(const T& data) {
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
return Write(&data, 1);
}
// Layout of vectors:
// [ 4/8 bytes ] count
// [ ... ] contents (count * size of individual elements)
template <typename T>
size_t WriteVector(const std::vector<T>& data) {
if (is_debug) {
std::string str = std::is_arithmetic_v<T> ? "" : ToStr(data);
std::string name = GetName<T>();
Debug("\nWriteVector<%s>() (%d-byte), count=%d: %s\n",
name.c_str(),
sizeof(T),
data.size(),
str.c_str());
}
size_t written_total = Write<size_t>(data.size());
if (data.size() == 0) {
return written_total;
}
written_total += WriteVector<T>(data, std::is_arithmetic<T>{});
if (is_debug) {
std::string name = GetName<T>();
Debug("WriteVector<%s>() wrote %d bytes\n", name.c_str(), written_total);
}
return written_total;
}
// The layout of a written string:
// [ 4/8 bytes ] length
// [ |length| bytes ] contents
size_t WriteString(const std::string& data) {
CHECK_GT(data.size(), 0); // No empty strings should be written.
size_t written_total = Write<size_t>(data.size());
if (is_debug) {
std::string str = ToStr(data);
Debug("WriteString(), length=%d: \"%s\"\n", data.size(), data.c_str());
}
size_t r = fwrite(data.c_str(), 1, data.size() + 1, f);
CHECK_EQ(r, data.size() + 1);
written_total += r;
if (is_debug) {
Debug("WriteString() wrote %d bytes\n", written_total);
}
return written_total;
}
private:
// Helper for writing an array of numeric types.
template <typename T>
size_t Write(const T* data, size_t count) {
DCHECK_GT(count, 0); // Should not write contents for vectors of size 0.
if (is_debug) {
std::string str =
"{ " + std::to_string(data[0]) + (count > 1 ? ", ... }" : " }");
std::string name = GetName<T>();
Debug("Write<%s>() (%d-byte), count=%d: %s",
name.c_str(),
sizeof(T),
count,
str.c_str());
}
size_t r = fwrite(data, sizeof(T), count, f);
CHECK_EQ(r, count);
if (is_debug) {
Debug(", wrote %d bytes\n", r);
}
return r;
}
// Helper for writing numeric vectors.
template <typename Number>
size_t WriteVector(const std::vector<Number>& data, std::true_type) {
return Write(data.data(), data.size());
}
// Helper for writing non-numeric vectors.
template <typename T>
size_t WriteVector(const std::vector<T>& data, std::false_type) {
DCHECK_GT(data.size(),
0); // Should not write contents for vectors of size 0.
size_t written_total = 0;
bool original_is_debug = is_debug;
is_debug = original_is_debug && !std::is_same_v<T, std::string>;
for (size_t i = 0; i < data.size(); ++i) {
if (is_debug) {
Debug("\n[%d] ", i);
}
written_total += Write<T>(data[i]);
}
is_debug = original_is_debug;
return written_total;
}
};
// Layout of serialized std::string:
// [ 4/8 bytes ] length
// [ |length| bytes ] contents
template <>
std::string FileReader::Read() {
return ReadString();
}
template <>
size_t FileWriter::Write(const std::string& data) {
return WriteString(data);
}
// Layout of v8::StartupData
// [ 4/8 bytes ] raw_size
// [ |raw_size| bytes ] contents
template <>
v8::StartupData FileReader::Read() {
Debug("Read<v8::StartupData>()\n");
int raw_size = Read<int>();
Debug("size=%d\n", raw_size);
CHECK_GT(raw_size, 0); // There should be no startup data of size 0.
// The data pointer of v8::StartupData would be deleted so it must be new'ed.
std::unique_ptr<char> buf = std::unique_ptr<char>(new char[raw_size]);
Read<char>(buf.get(), raw_size);
return v8::StartupData{buf.release(), raw_size};
}
template <>
size_t FileWriter::Write(const v8::StartupData& data) {
Debug("\nWrite<v8::StartupData>() size=%d\n", data.raw_size);
CHECK_GT(data.raw_size, 0); // There should be no startup data of size 0.
size_t written_total = Write<int>(data.raw_size);
written_total += Write<char>(data.data, static_cast<size_t>(data.raw_size));
Debug("Write<v8::StartupData>() wrote %d bytes\n\n", written_total);
return written_total;
}
// Layout of builtins::CodeCacheInfo
// [ 4/8 bytes ] length of the module id string
// [ ... ] |length| bytes of module id
// [ 4/8 bytes ] length of module code cache
// [ ... ] |length| bytes of module code cache
template <>
builtins::CodeCacheInfo FileReader::Read() {
Debug("Read<builtins::CodeCacheInfo>()\n");
builtins::CodeCacheInfo result{ReadString(), ReadVector<uint8_t>()};
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<builtins::CodeCacheInfo>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(const builtins::CodeCacheInfo& data) {
Debug("\nWrite<builtins::CodeCacheInfo>() id = %s"
", size=%d\n",
data.id.c_str(),
data.data.size());
size_t written_total = WriteString(data.id);
written_total += WriteVector<uint8_t>(data.data);
Debug("Write<builtins::CodeCacheInfo>() wrote %d bytes\n", written_total);
return written_total;
}
// Layout of PropInfo
// [ 4/8 bytes ] length of the data name string
// [ ... ] |length| bytes of data name
// [ 4 bytes ] index in the PropInfo vector
// [ 4/8 bytes ] index in the snapshot blob, can be used with
// GetDataFromSnapshotOnce().
template <>
PropInfo FileReader::Read() {
Debug("Read<PropInfo>()\n");
PropInfo result;
result.name = ReadString();
result.id = Read<uint32_t>();
result.index = Read<SnapshotIndex>();
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<PropInfo>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(const PropInfo& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("Write<PropInfo>() %s\n", str.c_str());
}
size_t written_total = WriteString(data.name);
written_total += Write<uint32_t>(data.id);
written_total += Write<SnapshotIndex>(data.index);
Debug("Write<PropInfo>() wrote %d bytes\n", written_total);
return written_total;
}
// Layout of AsyncHooks::SerializeInfo
// [ 4/8 bytes ] snapshot index of async_ids_stack
// [ 4/8 bytes ] snapshot index of fields
// [ 4/8 bytes ] snapshot index of async_id_fields
// [ 4/8 bytes ] snapshot index of js_execution_async_resources
// [ 4/8 bytes ] length of native_execution_async_resources
// [ ... ] snapshot indices of each element in
// native_execution_async_resources
template <>
AsyncHooks::SerializeInfo FileReader::Read() {
Debug("Read<AsyncHooks::SerializeInfo>()\n");
AsyncHooks::SerializeInfo result;
result.async_ids_stack = Read<AliasedBufferIndex>();
result.fields = Read<AliasedBufferIndex>();
result.async_id_fields = Read<AliasedBufferIndex>();
result.js_execution_async_resources = Read<SnapshotIndex>();
result.native_execution_async_resources = ReadVector<SnapshotIndex>();
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<AsyncHooks::SerializeInfo>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(const AsyncHooks::SerializeInfo& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("Write<AsyncHooks::SerializeInfo>() %s\n", str.c_str());
}
size_t written_total = Write<AliasedBufferIndex>(data.async_ids_stack);
written_total += Write<AliasedBufferIndex>(data.fields);
written_total += Write<AliasedBufferIndex>(data.async_id_fields);
written_total += Write<SnapshotIndex>(data.js_execution_async_resources);
written_total +=
WriteVector<SnapshotIndex>(data.native_execution_async_resources);
Debug("Write<AsyncHooks::SerializeInfo>() wrote %d bytes\n", written_total);
return written_total;
}
// Layout of TickInfo::SerializeInfo
// [ 4/8 bytes ] snapshot index of fields
template <>
TickInfo::SerializeInfo FileReader::Read() {
Debug("Read<TickInfo::SerializeInfo>()\n");
TickInfo::SerializeInfo result;
result.fields = Read<AliasedBufferIndex>();
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<TickInfo::SerializeInfo>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(const TickInfo::SerializeInfo& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("Write<TickInfo::SerializeInfo>() %s\n", str.c_str());
}
size_t written_total = Write<AliasedBufferIndex>(data.fields);
Debug("Write<TickInfo::SerializeInfo>() wrote %d bytes\n", written_total);
return written_total;
}
// Layout of TickInfo::SerializeInfo
// [ 4/8 bytes ] snapshot index of fields
template <>
ImmediateInfo::SerializeInfo FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Read<ImmediateInfo::SerializeInfo>()\n");
ImmediateInfo::SerializeInfo result;
result.fields = Read<AliasedBufferIndex>();
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<ImmediateInfo::SerializeInfo>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(const ImmediateInfo::SerializeInfo& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("Write<ImmeidateInfo::SerializeInfo>() %s\n", str.c_str());
}
size_t written_total = Write<AliasedBufferIndex>(data.fields);
Debug("Write<ImmeidateInfo::SerializeInfo>() wrote %d bytes\n",
written_total);
return written_total;
}
// Layout of PerformanceState::SerializeInfo
// [ 4/8 bytes ] snapshot index of root
// [ 4/8 bytes ] snapshot index of milestones
// [ 4/8 bytes ] snapshot index of observers
template <>
performance::PerformanceState::SerializeInfo FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Read<PerformanceState::SerializeInfo>()\n");
performance::PerformanceState::SerializeInfo result;
result.root = Read<AliasedBufferIndex>();
result.milestones = Read<AliasedBufferIndex>();
result.observers = Read<AliasedBufferIndex>();
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<PerformanceState::SerializeInfo>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(
const performance::PerformanceState::SerializeInfo& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("Write<PerformanceState::SerializeInfo>() %s\n", str.c_str());
}
size_t written_total = Write<AliasedBufferIndex>(data.root);
written_total += Write<AliasedBufferIndex>(data.milestones);
written_total += Write<AliasedBufferIndex>(data.observers);
Debug("Write<PerformanceState::SerializeInfo>() wrote %d bytes\n",
written_total);
return written_total;
}
// Layout of IsolateDataSerializeInfo
// [ 4/8 bytes ] length of primitive_values vector
// [ ... ] |length| of primitive_values indices
// [ 4/8 bytes ] length of template_values vector
// [ ... ] |length| of PropInfo data
template <>
IsolateDataSerializeInfo FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Read<IsolateDataSerializeInfo>()\n");
IsolateDataSerializeInfo result;
result.primitive_values = ReadVector<SnapshotIndex>();
result.template_values = ReadVector<PropInfo>();
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<IsolateDataSerializeInfo>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(const IsolateDataSerializeInfo& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("Write<IsolateDataSerializeInfo>() %s\n", str.c_str());
}
size_t written_total = WriteVector<SnapshotIndex>(data.primitive_values);
written_total += WriteVector<PropInfo>(data.template_values);
Debug("Write<IsolateDataSerializeInfo>() wrote %d bytes\n", written_total);
return written_total;
}
template <>
EnvSerializeInfo FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<EnvSerializeInfo>()\n");
EnvSerializeInfo result;
result.bindings = ReadVector<PropInfo>();
result.builtins = ReadVector<std::string>();
result.async_hooks = Read<AsyncHooks::SerializeInfo>();
result.tick_info = Read<TickInfo::SerializeInfo>();
result.immediate_info = Read<ImmediateInfo::SerializeInfo>();
result.performance_state =
Read<performance::PerformanceState::SerializeInfo>();
result.exiting = Read<AliasedBufferIndex>();
result.stream_base_state = Read<AliasedBufferIndex>();
result.should_abort_on_uncaught_toggle = Read<AliasedBufferIndex>();
result.persistent_values = ReadVector<PropInfo>();
result.context = Read<SnapshotIndex>();
return result;
}
template <>
size_t FileWriter::Write(const EnvSerializeInfo& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("\nWrite<EnvSerializeInfo>() %s\n", str.c_str());
}
// Use += here to ensure order of evaluation.
size_t written_total = WriteVector<PropInfo>(data.bindings);
written_total += WriteVector<std::string>(data.builtins);
written_total += Write<AsyncHooks::SerializeInfo>(data.async_hooks);
written_total += Write<TickInfo::SerializeInfo>(data.tick_info);
written_total += Write<ImmediateInfo::SerializeInfo>(data.immediate_info);
written_total += Write<performance::PerformanceState::SerializeInfo>(
data.performance_state);
written_total += Write<AliasedBufferIndex>(data.exiting);
written_total += Write<AliasedBufferIndex>(data.stream_base_state);
written_total +=
Write<AliasedBufferIndex>(data.should_abort_on_uncaught_toggle);
written_total += WriteVector<PropInfo>(data.persistent_values);
written_total += Write<SnapshotIndex>(data.context);
Debug("Write<EnvSerializeInfo>() wrote %d bytes\n", written_total);
return written_total;
}
// Layout of SnapshotMetadata
// [ 1 byte ] type of the snapshot
// [ 4/8 bytes ] length of the node version string
// [ ... ] |length| bytes of node version
// [ 4/8 bytes ] length of the node arch string
// [ ... ] |length| bytes of node arch
// [ 4/8 bytes ] length of the node platform string
// [ ... ] |length| bytes of node platform
// [ 4 bytes ] v8 cache version tag
template <>
SnapshotMetadata FileReader::Read() {
per_process::Debug(DebugCategory::MKSNAPSHOT, "Read<SnapshotMetadata>()\n");
SnapshotMetadata result;
result.type = static_cast<SnapshotMetadata::Type>(Read<uint8_t>());
result.node_version = ReadString();
result.node_arch = ReadString();
result.node_platform = ReadString();
result.v8_cache_version_tag = Read<uint32_t>();
if (is_debug) {
std::string str = ToStr(result);
Debug("Read<SnapshotMetadata>() %s\n", str.c_str());
}
return result;
}
template <>
size_t FileWriter::Write(const SnapshotMetadata& data) {
if (is_debug) {
std::string str = ToStr(data);
Debug("\nWrite<SnapshotMetadata>() %s\n", str.c_str());
}
size_t written_total = 0;
// We need the Node.js version, platform and arch to match because
// Node.js may perform synchronizations that are platform-specific and they
// can be changed in semver-patches.
Debug("Write snapshot type %" PRIu8 "\n", static_cast<uint8_t>(data.type));
written_total += Write<uint8_t>(static_cast<uint8_t>(data.type));
Debug("Write Node.js version %s\n", data.node_version.c_str());
written_total += WriteString(data.node_version);
Debug("Write Node.js arch %s\n", data.node_arch);
written_total += WriteString(data.node_arch);
Debug("Write Node.js platform %s\n", data.node_platform);
written_total += WriteString(data.node_platform);
Debug("Write V8 cached data version tag %" PRIx32 "\n",
data.v8_cache_version_tag);
written_total += Write<uint32_t>(data.v8_cache_version_tag);
return written_total;
}
// Layout of the snapshot blob
// [ 4 bytes ] kMagic
// [ 4/8 bytes ] length of Node.js version string
// [ ... ] contents of Node.js version string
// [ 4/8 bytes ] length of Node.js arch string
// [ ... ] contents of Node.js arch string
// [ ... ] v8_snapshot_blob_data from SnapshotCreator::CreateBlob()
// [ ... ] isolate_data_info
// [ ... ] env_info
// [ ... ] code_cache
void SnapshotData::ToBlob(FILE* out) const {
FileWriter w(out);
w.Debug("SnapshotData::ToBlob()\n");
size_t written_total = 0;
// Metadata
w.Debug("Write magic %" PRIx32 "\n", kMagic);
written_total += w.Write<uint32_t>(kMagic);
w.Debug("Write metadata\n");
written_total += w.Write<SnapshotMetadata>(metadata);
written_total += w.Write<v8::StartupData>(v8_snapshot_blob_data);
w.Debug("Write isolate_data_indices\n");
written_total += w.Write<IsolateDataSerializeInfo>(isolate_data_info);
written_total += w.Write<EnvSerializeInfo>(env_info);
w.Debug("Write code_cache\n");
written_total += w.WriteVector<builtins::CodeCacheInfo>(code_cache);
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
}
bool SnapshotData::FromBlob(SnapshotData* out, FILE* in) {
FileReader r(in);
r.Debug("SnapshotData::FromBlob()\n");
DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned);
// Metadata
uint32_t magic = r.Read<uint32_t>();
r.Debug("Read magic %" PRIx32 "\n", magic);
CHECK_EQ(magic, kMagic);
out->metadata = r.Read<SnapshotMetadata>();
r.Debug("Read metadata\n");
if (!out->Check()) {
return false;
}
out->v8_snapshot_blob_data = r.Read<v8::StartupData>();
r.Debug("Read isolate_data_info\n");
out->isolate_data_info = r.Read<IsolateDataSerializeInfo>();
out->env_info = r.Read<EnvSerializeInfo>();
r.Debug("Read code_cache\n");
out->code_cache = r.ReadVector<builtins::CodeCacheInfo>();
r.Debug("SnapshotData::FromBlob() read %d bytes\n", r.read_total);
return true;
}
bool SnapshotData::Check() const {
if (metadata.node_version != per_process::metadata.versions.node) {
fprintf(stderr,
"Failed to load the startup snapshot because it was built with"
"Node.js version %s and the current Node.js version is %s.\n",
metadata.node_version.c_str(),
NODE_VERSION);
return false;
}
if (metadata.node_arch != per_process::metadata.arch) {
fprintf(stderr,
"Failed to load the startup snapshot because it was built with"
"architecture %s and the architecture is %s.\n",
metadata.node_arch.c_str(),
NODE_ARCH);
return false;
}
if (metadata.node_platform != per_process::metadata.platform) {
fprintf(stderr,
"Failed to load the startup snapshot because it was built with"
"platform %s and the current platform is %s.\n",
metadata.node_platform.c_str(),
NODE_PLATFORM);
return false;
}
uint32_t current_cache_version = v8::ScriptCompiler::CachedDataVersionTag();
if (metadata.v8_cache_version_tag != current_cache_version &&
metadata.type == SnapshotMetadata::Type::kFullyCustomized) {
// For now we only do this check for the customized snapshots - we know
// that the flags we use in the default snapshot are limited and safe
// enough so we can relax the constraints for it.
fprintf(stderr,
"Failed to load the startup snapshot because it was built with "
"a different version of V8 or with different V8 configurations.\n"
"Expected tag %" PRIx32 ", read %" PRIx32 "\n",
current_cache_version,
metadata.v8_cache_version_tag);
return false;
}
// TODO(joyeecheung): check incompatible Node.js flags.
return true;
}
SnapshotData::~SnapshotData() {
if (data_ownership == DataOwnership::kOwned &&
v8_snapshot_blob_data.data != nullptr) {
delete[] v8_snapshot_blob_data.data;
}
}
template <typename T>
void WriteVector(std::ostream* 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' : ',');
}
}
static std::string GetCodeCacheDefName(const std::string& id) {
char buf[64] = {0};
size_t size = id.size();
CHECK_LT(size, sizeof(buf));
for (size_t i = 0; i < size; ++i) {
char ch = id[i];
buf[i] = (ch == '-' || ch == '/') ? '_' : ch;
}
return std::string(buf) + std::string("_cache_data");
}
static std::string FormatSize(size_t size) {
char buf[64] = {0};
if (size < 1024) {
snprintf(buf, sizeof(buf), "%.2fB", static_cast<double>(size));
} else if (size < 1024 * 1024) {
snprintf(buf, sizeof(buf), "%.2fKB", static_cast<double>(size / 1024));
} else {
snprintf(
buf, sizeof(buf), "%.2fMB", static_cast<double>(size / 1024 / 1024));
}
return buf;
}
static void WriteStaticCodeCacheData(std::ostream* ss,
const builtins::CodeCacheInfo& info) {
*ss << "static const uint8_t " << GetCodeCacheDefName(info.id) << "[] = {\n";
WriteVector(ss, info.data.data(), info.data.size());
*ss << "};";
}
static void WriteCodeCacheInitializer(std::ostream* ss, const std::string& id) {
std::string def_name = GetCodeCacheDefName(id);
*ss << " { \"" << id << "\",\n";
*ss << " {" << def_name << ",\n";
*ss << " " << def_name << " + arraysize(" << def_name << "),\n";
*ss << " }\n";
*ss << " },\n";
}
void FormatBlob(std::ostream& ss, SnapshotData* data) {
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 v8_snapshot_blob_data[] = {
)";
WriteVector(&ss,
data->v8_snapshot_blob_data.data,
data->v8_snapshot_blob_data.raw_size);
ss << R"(};
static const int v8_snapshot_blob_size = )"
<< data->v8_snapshot_blob_data.raw_size << ";";
// Windows can't deal with too many large vector initializers.
// Store the data into static arrays first.
for (const auto& item : data->code_cache) {
WriteStaticCodeCacheData(&ss, item);
}
ss << R"(SnapshotData snapshot_data {
// -- data_ownership begins --
SnapshotData::DataOwnership::kNotOwned,
// -- data_ownership ends --
// -- metadata begins --
)" << data->metadata
<< R"(,
// -- metadata ends --
// -- v8_snapshot_blob_data begins --
{ v8_snapshot_blob_data, v8_snapshot_blob_size },
// -- v8_snapshot_blob_data ends --
// -- isolate_data_indices begins --
)" << data->isolate_data_info
<< R"(
// -- isolate_data_indices ends --
,
// -- env_info begins --
)" << data->env_info
<< R"(
// -- env_info ends --
,
// -- code_cache begins --
{)";
for (const auto& item : data->code_cache) {
WriteCodeCacheInitializer(&ss, item.id);
}
ss << R"(
}
// -- code_cache ends --
};
const SnapshotData* SnapshotBuilder::GetEmbeddedSnapshotData() {
Mutex::ScopedLock lock(snapshot_data_mutex_);
return &snapshot_data;
}
} // namespace node
)";
}
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->v8_snapshot_blob_data));
}
// TODO(joyeecheung): share these exit code constants across the code base.
constexpr int UNCAUGHT_EXCEPTION_ERROR = 1;
constexpr int BOOTSTRAP_ERROR = 10;
constexpr int SNAPSHOT_ERROR = 14;
int SnapshotBuilder::Generate(SnapshotData* out,
const std::vector<std::string> args,
const std::vector<std::string> exec_args) {
const std::vector<intptr_t>& external_references =
CollectExternalReferences();
Isolate* isolate = Isolate::Allocate();
// Must be done before the SnapshotCreator creation so that the
// memory reducer can be initialized.
per_process::v8_platform.Platform()->RegisterIsolate(isolate,
uv_default_loop());
SnapshotCreator creator(isolate, external_references.data());
isolate->SetCaptureStackTraceForUncaughtExceptions(
true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
Environment* env = nullptr;
std::unique_ptr<NodeMainInstance> main_instance =
NodeMainInstance::Create(isolate,
uv_default_loop(),
per_process::v8_platform.Platform(),
args,
exec_args);
// The cleanups should be done in case of an early exit due to errors.
auto cleanup = OnScopeLeave([&]() {
// Must be done while the snapshot creator isolate is entered i.e. the
// creator is still alive. The snapshot creator destructor will destroy
// the isolate.
if (env != nullptr) {
FreeEnvironment(env);
}
main_instance->Dispose();
per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
});
// It's only possible to be kDefault in node_mksnapshot.
SnapshotMetadata::Type snapshot_type =
per_process::cli_options->build_snapshot
? SnapshotMetadata::Type::kFullyCustomized
: SnapshotMetadata::Type::kDefault;
{
HandleScope scope(isolate);
TryCatch bootstrapCatch(isolate);
auto print_Exception = OnScopeLeave([&]() {
if (bootstrapCatch.HasCaught()) {
PrintCaughtException(
isolate, isolate->GetCurrentContext(), bootstrapCatch);
}
});
// The default context with only things created by V8.
Local<Context> default_context = Context::New(isolate);
// 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.
Local<Context> base_context = NewContext(isolate);
if (base_context.IsEmpty()) {
return BOOTSTRAP_ERROR;
}
Local<Context> main_context = NewContext(isolate);
if (main_context.IsEmpty()) {
return BOOTSTRAP_ERROR;
}
// Initialize the main instance context.
{
Context::Scope context_scope(main_context);
// 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/
if (env->RunBootstrapping().IsEmpty()) {
return BOOTSTRAP_ERROR;
}
// 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 (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) {
#if HAVE_INSPECTOR
// TODO(joyeecheung): move this before RunBootstrapping().
env->InitializeInspector({});
#endif
if (LoadEnvironment(env, StartExecutionCallback{}).IsEmpty()) {
return UNCAUGHT_EXCEPTION_ERROR;
}
// 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(UNCAUGHT_EXCEPTION_ERROR);
if (exit_code != 0) {
return exit_code;
}
}
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
env->PrintAllBaseObjects();
printf("Environment = %p\n", env);
}
// Serialize the native states
out->isolate_data_info =
main_instance->isolate_data()->Serialize(&creator);
out->env_info = env->Serialize(&creator);
#ifdef NODE_USE_NODE_CODE_CACHE
// Regenerate all the code cache.
if (!builtins::BuiltinLoader::CompileAllBuiltins(main_context)) {
return UNCAUGHT_EXCEPTION_ERROR;
}
builtins::BuiltinLoader::CopyCodeCache(&(out->code_cache));
for (const auto& item : out->code_cache) {
std::string size_str = FormatSize(item.data.size());
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Generated code cache for %d: %s\n",
item.id.c_str(),
size_str.c_str());
}
#endif
}
// Global handles to the contexts can't be disposed before the
// blob is created. So initialize all the contexts before adding them.
// TODO(joyeecheung): figure out how to remove this restriction.
creator.SetDefaultContext(default_context);
size_t index = creator.AddContext(base_context);
CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex);
index = creator.AddContext(main_context,
{SerializeNodeContextInternalFields, env});
CHECK_EQ(index, SnapshotData::kNodeMainContextIndex);
}
// Must be out of HandleScope
out->v8_snapshot_blob_data =
creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kKeep);
// 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.
if (!out->v8_snapshot_blob_data.CanBeRehashed()) {
return SNAPSHOT_ERROR;
}
out->metadata = SnapshotMetadata{snapshot_type,
per_process::metadata.versions.node,
per_process::metadata.arch,
per_process::metadata.platform,
v8::ScriptCompiler::CachedDataVersionTag()};
// 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).
bool queues_are_empty =
env->req_wrap_queue()->IsEmpty() && env->handle_wrap_queue()->IsEmpty();
if (!queues_are_empty ||
per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
PrintLibuvHandleInformation(env->event_loop(), stderr);
}
if (!queues_are_empty) {
return SNAPSHOT_ERROR;
}
return 0;
}
int SnapshotBuilder::Generate(std::ostream& out,
const std::vector<std::string> args,
const std::vector<std::string> exec_args) {
SnapshotData data;
int exit_code = Generate(&data, args, exec_args);
if (exit_code != 0) {
return exit_code;
}
FormatBlob(out, &data);
return exit_code;
}
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) {
if (payload.raw_size == 0) {
holder->SetAlignedPointerInInternalField(index, nullptr);
return;
}
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) {
uint32_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)) {
SnapshotIndex 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::CompileFunction(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) {
SetMethod(context, target, "compileSerializeMain", CompileSerializeMain);
SetMethod(context, target, "setSerializeCallback", SetSerializeCallback);
SetMethod(context, target, "setDeserializeCallback", SetDeserializeCallback);
SetMethod(context,
target,
"setDeserializeMainFunction",
SetDeserializeMainFunction);
}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(CompileSerializeMain);
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)