Skip to content

Commit e2caafa

Browse files
authored
src: use Blob{Des|S}erializer for SEA blobs
PR-URL: #47962 Reviewed-By: Darshan Sen <[email protected]>
1 parent 300f68e commit e2caafa

8 files changed

+206
-93
lines changed

src/blob_serializer_deserializer-inl.h

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
#include "debug_utils-inl.h"
1515

16-
// This is related to the blob that is used in snapshots and has nothing to do
17-
// with `node_blob.h`.
16+
// This is related to the blob that is used in snapshots and single executable
17+
// applications and has nothing to do with `node_blob.h`.
1818

1919
namespace node {
2020

@@ -130,22 +130,22 @@ std::vector<T> BlobDeserializer<Impl>::ReadVector() {
130130

131131
template <typename Impl>
132132
std::string BlobDeserializer<Impl>::ReadString() {
133-
size_t length = ReadArithmetic<size_t>();
134-
135-
if (is_debug) {
136-
Debug("ReadString(), length=%d: ", length);
137-
}
133+
std::string_view view = ReadStringView(StringLogMode::kAddressAndContent);
134+
return std::string(view);
135+
}
138136

139-
CHECK_GT(length, 0); // There should be no empty strings.
140-
MallocedBuffer<char> buf(length + 1);
141-
memcpy(buf.data, sink.data() + read_total, length + 1);
142-
std::string result(buf.data, length); // This creates a copy of buf.data.
137+
template <typename Impl>
138+
std::string_view BlobDeserializer<Impl>::ReadStringView(StringLogMode mode) {
139+
size_t length = ReadArithmetic<size_t>();
140+
Debug("ReadStringView(), length=%zu: ", length);
143141

144-
if (is_debug) {
145-
Debug("\"%s\", read %zu bytes\n", result.c_str(), length + 1);
142+
std::string_view result(sink.data() + read_total, length);
143+
Debug("%p, read %zu bytes\n", result.data(), result.size());
144+
if (mode == StringLogMode::kAddressAndContent) {
145+
Debug("%s", result);
146146
}
147147

148-
read_total += length + 1;
148+
read_total += length;
149149
return result;
150150
}
151151

@@ -262,26 +262,28 @@ size_t BlobSerializer<Impl>::WriteVector(const std::vector<T>& data) {
262262
// [ 4/8 bytes ] length
263263
// [ |length| bytes ] contents
264264
template <typename Impl>
265-
size_t BlobSerializer<Impl>::WriteString(const std::string& data) {
266-
CHECK_GT(data.size(), 0); // No empty strings should be written.
265+
size_t BlobSerializer<Impl>::WriteStringView(std::string_view data,
266+
StringLogMode mode) {
267+
Debug("WriteStringView(), length=%zu: %p\n", data.size(), data.data());
267268
size_t written_total = WriteArithmetic<size_t>(data.size());
268-
if (is_debug) {
269-
std::string str = ToStr(data);
270-
Debug("WriteString(), length=%zu: \"%s\"\n", data.size(), data.c_str());
271-
}
272269

273-
// Write the null-terminated string.
274-
size_t length = data.size() + 1;
275-
sink.insert(sink.end(), data.c_str(), data.c_str() + length);
270+
size_t length = data.size();
271+
sink.insert(sink.end(), data.data(), data.data() + length);
276272
written_total += length;
277273

278-
if (is_debug) {
279-
Debug("WriteString() wrote %zu bytes\n", written_total);
274+
Debug("WriteStringView() wrote %zu bytes\n", written_total);
275+
if (mode == StringLogMode::kAddressAndContent) {
276+
Debug("%s", data);
280277
}
281278

282279
return written_total;
283280
}
284281

282+
template <typename Impl>
283+
size_t BlobSerializer<Impl>::WriteString(const std::string& data) {
284+
return WriteStringView(data, StringLogMode::kAddressAndContent);
285+
}
286+
285287
// Helper for writing an array of numeric types.
286288
template <typename Impl>
287289
template <typename T>

src/blob_serializer_deserializer.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
88

9-
// This is related to the blob that is used in snapshots and has nothing to do
10-
// with `node_blob.h`.
9+
// This is related to the blob that is used in snapshots and single executable
10+
// applications and has nothing to do with `node_blob.h`.
1111

1212
namespace node {
1313

@@ -27,6 +27,11 @@ class BlobSerializerDeserializer {
2727
bool is_debug = false;
2828
};
2929

30+
enum class StringLogMode {
31+
kAddressOnly, // Can be used when the string contains binary content.
32+
kAddressAndContent,
33+
};
34+
3035
// Child classes are expected to implement T Read<T>() where
3136
// !std::is_arithmetic_v<T> && !std::is_same_v<T, std::string>
3237
template <typename Impl>
@@ -52,7 +57,9 @@ class BlobDeserializer : public BlobSerializerDeserializer {
5257
template <typename T>
5358
std::vector<T> ReadVector();
5459

60+
// ReadString() creates a copy of the data. ReadStringView() doesn't.
5561
std::string ReadString();
62+
std::string_view ReadStringView(StringLogMode mode);
5663

5764
// Helper for reading an array of numeric types.
5865
template <typename T>
@@ -77,11 +84,7 @@ template <typename Impl>
7784
class BlobSerializer : public BlobSerializerDeserializer {
7885
public:
7986
explicit BlobSerializer(bool is_debug_v)
80-
: BlobSerializerDeserializer(is_debug_v) {
81-
// Currently the snapshot blob built with an empty script is around 4MB.
82-
// So use that as the default sink size.
83-
sink.reserve(4 * 1024 * 1024);
84-
}
87+
: BlobSerializerDeserializer(is_debug_v) {}
8588
~BlobSerializer() {}
8689

8790
Impl* impl() { return static_cast<Impl*>(this); }
@@ -102,6 +105,7 @@ class BlobSerializer : public BlobSerializerDeserializer {
102105
// The layout of a written string:
103106
// [ 4/8 bytes ] length
104107
// [ |length| bytes ] contents
108+
size_t WriteStringView(std::string_view data, StringLogMode mode);
105109
size_t WriteString(const std::string& data);
106110

107111
// Helper for writing an array of numeric types.

src/debug_utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str);
4848
V(INSPECTOR_PROFILER) \
4949
V(CODE_CACHE) \
5050
V(NGTCP2_DEBUG) \
51+
V(SEA) \
5152
V(WASI) \
5253
V(MKSNAPSHOT)
5354

src/node_main_instance.cc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,16 @@ ExitCode NodeMainInstance::Run() {
8787

8888
void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) {
8989
if (*exit_code == ExitCode::kNoFailure) {
90-
bool is_sea = false;
90+
bool runs_sea_code = false;
9191
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
9292
if (sea::IsSingleExecutable()) {
93-
is_sea = true;
94-
LoadEnvironment(env, sea::FindSingleExecutableCode());
93+
runs_sea_code = true;
94+
sea::SeaResource sea = sea::FindSingleExecutableResource();
95+
std::string_view code = sea.code;
96+
LoadEnvironment(env, code);
9597
}
9698
#endif
97-
if (!is_sea) {
99+
if (!runs_sea_code) {
98100
LoadEnvironment(env, StartExecutionCallback{});
99101
}
100102

src/node_sea.cc

Lines changed: 93 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "node_sea.h"
22

3+
#include "blob_serializer_deserializer-inl.h"
34
#include "debug_utils-inl.h"
45
#include "env-inl.h"
56
#include "json_parser.h"
@@ -34,16 +35,6 @@ namespace node {
3435
namespace sea {
3536

3637
namespace {
37-
// A special number that will appear at the beginning of the single executable
38-
// preparation blobs ready to be injected into the binary. We use this to check
39-
// that the data given to us are intended for building single executable
40-
// applications.
41-
const uint32_t kMagic = 0x143da20;
42-
43-
enum class SeaFlags : uint32_t {
44-
kDefault = 0,
45-
kDisableExperimentalSeaWarning = 1 << 0,
46-
};
4738

4839
SeaFlags operator|(SeaFlags x, SeaFlags y) {
4940
return static_cast<SeaFlags>(static_cast<uint32_t>(x) |
@@ -59,47 +50,100 @@ SeaFlags operator|=(/* NOLINT (runtime/references) */ SeaFlags& x, SeaFlags y) {
5950
return x = x | y;
6051
}
6152

62-
struct SeaResource {
63-
SeaFlags flags = SeaFlags::kDefault;
64-
std::string_view code;
65-
static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags);
53+
class SeaSerializer : public BlobSerializer<SeaSerializer> {
54+
public:
55+
SeaSerializer()
56+
: BlobSerializer<SeaSerializer>(
57+
per_process::enabled_debug_list.enabled(DebugCategory::SEA)) {}
58+
59+
template <typename T,
60+
std::enable_if_t<!std::is_same<T, std::string>::value>* = nullptr,
61+
std::enable_if_t<!std::is_arithmetic<T>::value>* = nullptr>
62+
size_t Write(const T& data);
6663
};
6764

68-
SeaResource FindSingleExecutableResource() {
65+
template <>
66+
size_t SeaSerializer::Write(const SeaResource& sea) {
67+
sink.reserve(SeaResource::kHeaderSize + sea.code.size());
68+
69+
Debug("Write SEA magic %x\n", kMagic);
70+
size_t written_total = WriteArithmetic<uint32_t>(kMagic);
71+
72+
uint32_t flags = static_cast<uint32_t>(sea.flags);
73+
Debug("Write SEA flags %x\n", flags);
74+
written_total += WriteArithmetic<uint32_t>(flags);
75+
DCHECK_EQ(written_total, SeaResource::kHeaderSize);
76+
77+
Debug("Write SEA resource code %p, size=%zu\n",
78+
sea.code.data(),
79+
sea.code.size());
80+
written_total += WriteStringView(sea.code, StringLogMode::kAddressAndContent);
81+
return written_total;
82+
}
83+
84+
class SeaDeserializer : public BlobDeserializer<SeaDeserializer> {
85+
public:
86+
explicit SeaDeserializer(std::string_view v)
87+
: BlobDeserializer<SeaDeserializer>(
88+
per_process::enabled_debug_list.enabled(DebugCategory::SEA), v) {}
89+
90+
template <typename T,
91+
std::enable_if_t<!std::is_same<T, std::string>::value>* = nullptr,
92+
std::enable_if_t<!std::is_arithmetic<T>::value>* = nullptr>
93+
T Read();
94+
};
95+
96+
template <>
97+
SeaResource SeaDeserializer::Read() {
98+
uint32_t magic = ReadArithmetic<uint32_t>();
99+
Debug("Read SEA magic %x\n", magic);
100+
101+
CHECK_EQ(magic, kMagic);
102+
SeaFlags flags(static_cast<SeaFlags>(ReadArithmetic<uint32_t>()));
103+
Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags));
104+
CHECK_EQ(read_total, SeaResource::kHeaderSize);
105+
106+
std::string_view code = ReadStringView(StringLogMode::kAddressAndContent);
107+
Debug("Read SEA resource code %p, size=%zu\n", code.data(), code.size());
108+
return {flags, code};
109+
}
110+
111+
std::string_view FindSingleExecutableBlob() {
69112
CHECK(IsSingleExecutable());
70-
static const SeaResource sea_resource = []() -> SeaResource {
113+
static const std::string_view result = []() -> std::string_view {
71114
size_t size;
72115
#ifdef __APPLE__
73116
postject_options options;
74117
postject_options_init(&options);
75118
options.macho_segment_name = "NODE_SEA";
76-
const char* code = static_cast<const char*>(
119+
const char* blob = static_cast<const char*>(
77120
postject_find_resource("NODE_SEA_BLOB", &size, &options));
78121
#else
79-
const char* code = static_cast<const char*>(
122+
const char* blob = static_cast<const char*>(
80123
postject_find_resource("NODE_SEA_BLOB", &size, nullptr));
81124
#endif
82-
uint32_t first_word = reinterpret_cast<const uint32_t*>(code)[0];
83-
CHECK_EQ(first_word, kMagic);
84-
SeaFlags flags{
85-
reinterpret_cast<const SeaFlags*>(code + sizeof(first_word))[0]};
86-
// TODO(joyeecheung): do more checks here e.g. matching the versions.
87-
return {
88-
flags,
89-
{
90-
code + SeaResource::kHeaderSize,
91-
size - SeaResource::kHeaderSize,
92-
},
93-
};
125+
return {blob, size};
94126
}();
95-
return sea_resource;
127+
per_process::Debug(DebugCategory::SEA,
128+
"Found SEA blob %p, size=%zu\n",
129+
result.data(),
130+
result.size());
131+
return result;
96132
}
97133

98-
} // namespace
134+
} // anonymous namespace
99135

100-
std::string_view FindSingleExecutableCode() {
101-
SeaResource sea_resource = FindSingleExecutableResource();
102-
return sea_resource.code;
136+
SeaResource FindSingleExecutableResource() {
137+
static const SeaResource sea_resource = []() -> SeaResource {
138+
std::string_view blob = FindSingleExecutableBlob();
139+
per_process::Debug(DebugCategory::SEA,
140+
"Found SEA resource %p, size=%zu\n",
141+
blob.data(),
142+
blob.size());
143+
SeaDeserializer deserializer(blob);
144+
return deserializer.Read<SeaResource>();
145+
}();
146+
return sea_resource;
103147
}
104148

105149
bool IsSingleExecutable() {
@@ -194,51 +238,46 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
194238
return result;
195239
}
196240

197-
bool GenerateSingleExecutableBlob(const SeaConfig& config) {
241+
ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
198242
std::string main_script;
199243
// TODO(joyeecheung): unify the file utils.
200244
int r = ReadFileSync(&main_script, config.main_path.c_str());
201245
if (r != 0) {
202246
const char* err = uv_strerror(r);
203247
FPrintF(stderr, "Cannot read main script %s:%s\n", config.main_path, err);
204-
return false;
248+
return ExitCode::kGenericUserError;
205249
}
206250

207-
std::vector<char> sink;
208-
// TODO(joyeecheung): reuse the SnapshotSerializerDeserializer for this.
209-
sink.reserve(SeaResource::kHeaderSize + main_script.size());
210-
const char* pos = reinterpret_cast<const char*>(&kMagic);
211-
sink.insert(sink.end(), pos, pos + sizeof(kMagic));
212-
pos = reinterpret_cast<const char*>(&(config.flags));
213-
sink.insert(sink.end(), pos, pos + sizeof(SeaFlags));
214-
sink.insert(
215-
sink.end(), main_script.data(), main_script.data() + main_script.size());
216-
217-
uv_buf_t buf = uv_buf_init(sink.data(), sink.size());
251+
SeaResource sea{config.flags, main_script};
252+
253+
SeaSerializer serializer;
254+
serializer.Write(sea);
255+
256+
uv_buf_t buf = uv_buf_init(serializer.sink.data(), serializer.sink.size());
218257
r = WriteFileSync(config.output_path.c_str(), buf);
219258
if (r != 0) {
220259
const char* err = uv_strerror(r);
221260
FPrintF(stderr, "Cannot write output to %s:%s\n", config.output_path, err);
222-
return false;
261+
return ExitCode::kGenericUserError;
223262
}
224263

225264
FPrintF(stderr,
226265
"Wrote single executable preparation blob to %s\n",
227266
config.output_path);
228-
return true;
267+
return ExitCode::kNoFailure;
229268
}
230269

231270
} // anonymous namespace
232271

233272
ExitCode BuildSingleExecutableBlob(const std::string& config_path) {
234273
std::optional<SeaConfig> config_opt =
235274
ParseSingleExecutableConfig(config_path);
236-
if (!config_opt.has_value() ||
237-
!GenerateSingleExecutableBlob(config_opt.value())) {
238-
return ExitCode::kGenericUserError;
275+
if (config_opt.has_value()) {
276+
ExitCode code = GenerateSingleExecutableBlob(config_opt.value());
277+
return code;
239278
}
240279

241-
return ExitCode::kNoFailure;
280+
return ExitCode::kGenericUserError;
242281
}
243282

244283
void Initialize(Local<Object> target,

0 commit comments

Comments
 (0)