|
1 | 1 | #include "node_sea.h" |
2 | 2 |
|
| 3 | +#include "blob_serializer_deserializer-inl.h" |
3 | 4 | #include "debug_utils-inl.h" |
4 | 5 | #include "env-inl.h" |
5 | 6 | #include "json_parser.h" |
@@ -34,16 +35,6 @@ namespace node { |
34 | 35 | namespace sea { |
35 | 36 |
|
36 | 37 | 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 | | -}; |
47 | 38 |
|
48 | 39 | SeaFlags operator|(SeaFlags x, SeaFlags y) { |
49 | 40 | return static_cast<SeaFlags>(static_cast<uint32_t>(x) | |
@@ -59,47 +50,100 @@ SeaFlags operator|=(/* NOLINT (runtime/references) */ SeaFlags& x, SeaFlags y) { |
59 | 50 | return x = x | y; |
60 | 51 | } |
61 | 52 |
|
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); |
66 | 63 | }; |
67 | 64 |
|
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() { |
69 | 112 | CHECK(IsSingleExecutable()); |
70 | | - static const SeaResource sea_resource = []() -> SeaResource { |
| 113 | + static const std::string_view result = []() -> std::string_view { |
71 | 114 | size_t size; |
72 | 115 | #ifdef __APPLE__ |
73 | 116 | postject_options options; |
74 | 117 | postject_options_init(&options); |
75 | 118 | options.macho_segment_name = "NODE_SEA"; |
76 | | - const char* code = static_cast<const char*>( |
| 119 | + const char* blob = static_cast<const char*>( |
77 | 120 | postject_find_resource("NODE_SEA_BLOB", &size, &options)); |
78 | 121 | #else |
79 | | - const char* code = static_cast<const char*>( |
| 122 | + const char* blob = static_cast<const char*>( |
80 | 123 | postject_find_resource("NODE_SEA_BLOB", &size, nullptr)); |
81 | 124 | #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}; |
94 | 126 | }(); |
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; |
96 | 132 | } |
97 | 133 |
|
98 | | -} // namespace |
| 134 | +} // anonymous namespace |
99 | 135 |
|
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; |
103 | 147 | } |
104 | 148 |
|
105 | 149 | bool IsSingleExecutable() { |
@@ -194,51 +238,46 @@ std::optional<SeaConfig> ParseSingleExecutableConfig( |
194 | 238 | return result; |
195 | 239 | } |
196 | 240 |
|
197 | | -bool GenerateSingleExecutableBlob(const SeaConfig& config) { |
| 241 | +ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) { |
198 | 242 | std::string main_script; |
199 | 243 | // TODO(joyeecheung): unify the file utils. |
200 | 244 | int r = ReadFileSync(&main_script, config.main_path.c_str()); |
201 | 245 | if (r != 0) { |
202 | 246 | const char* err = uv_strerror(r); |
203 | 247 | FPrintF(stderr, "Cannot read main script %s:%s\n", config.main_path, err); |
204 | | - return false; |
| 248 | + return ExitCode::kGenericUserError; |
205 | 249 | } |
206 | 250 |
|
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()); |
218 | 257 | r = WriteFileSync(config.output_path.c_str(), buf); |
219 | 258 | if (r != 0) { |
220 | 259 | const char* err = uv_strerror(r); |
221 | 260 | FPrintF(stderr, "Cannot write output to %s:%s\n", config.output_path, err); |
222 | | - return false; |
| 261 | + return ExitCode::kGenericUserError; |
223 | 262 | } |
224 | 263 |
|
225 | 264 | FPrintF(stderr, |
226 | 265 | "Wrote single executable preparation blob to %s\n", |
227 | 266 | config.output_path); |
228 | | - return true; |
| 267 | + return ExitCode::kNoFailure; |
229 | 268 | } |
230 | 269 |
|
231 | 270 | } // anonymous namespace |
232 | 271 |
|
233 | 272 | ExitCode BuildSingleExecutableBlob(const std::string& config_path) { |
234 | 273 | std::optional<SeaConfig> config_opt = |
235 | 274 | 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; |
239 | 278 | } |
240 | 279 |
|
241 | | - return ExitCode::kNoFailure; |
| 280 | + return ExitCode::kGenericUserError; |
242 | 281 | } |
243 | 282 |
|
244 | 283 | void Initialize(Local<Object> target, |
|