From 175ad8614fe1d4882cf43bf8293e55ab2c5975da Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Sat, 12 Mar 2022 11:20:44 -0500 Subject: [PATCH 01/10] src,doc: experimental support for SEA - add strategy as discussed in next-10 mini-summit - https://github.com/nodejs/next-10/blob/main/meetings/summit-nov-2021.md#single-executable-applications - add initial support single executable application support for linux Signed-off-by: Michael Dawson --- ...g-single-executable-application-support.md | 111 ++++++++++++++++++ node.gyp | 1 + src/node.cc | 11 +- src/node_single_binary.cc | 72 ++++++++++++ src/node_single_binary.h | 22 ++++ 5 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 doc/contributing/maintaining-single-executable-application-support.md create mode 100644 src/node_single_binary.cc create mode 100644 src/node_single_binary.h diff --git a/doc/contributing/maintaining-single-executable-application-support.md b/doc/contributing/maintaining-single-executable-application-support.md new file mode 100644 index 00000000000000..a47c22fb82b743 --- /dev/null +++ b/doc/contributing/maintaining-single-executable-application-support.md @@ -0,0 +1,111 @@ +# Maintaining Single Executable Applications support + +Support for [single executable applications](https://github.com/nodejs/node/blob/master/doc/contributing/technical-priorities.md#single-executable-applications) +is one of the key technical priorities identified for the success of Node.js. + +## High level strategy + +From the [next-10 discussions](https://github.com/nodejs/next-10/blob/main/meetings/summit-nov-2021.md#single-executable-applications) +there are 2 approaches the project believes are important to support: + +* Compile with Node.js into executable (`boxnode` approach). +* Bundle into existing Node.js executable (`pkg` approach). + +### Compile with node into executable + +No additional code within the Node.js project is needed to support the +option of compiling a bundled application along with Node.js into a single +executable application. + +### Bundle into existing Node.js executable + +The project does not plan to provide the complete solution but instead the key +elements which are required in the Node.js executable in order to enable +bundling with the pre-built Node.js binaries. This includes: + +* Looking for a segment within the executable that holds bundled code. +* Running the bundled code when such a segment is found. + +It is left up to external tools/solutions to: + +* Bundle code into a single script that can be executed with `-e` on + the command line. +* Generate a command line with appropriate options, including `-e` to + run the bundled script. +* Add a segment to an existing Node.js executable which contains + the command line and appropriate headers. +* Re-generate or removing signatures on the resulting executable +* Provide a virtual file system, and hooking it in if needed to + support native modules or reading file contents. + +## Maintaining + +### Compile with node into executable + +The approach of compiling with node into an executable requires that we +maintain a stable [em-bedder API](https://nodejs.org/dist/latest/docs/api/embedding.html). + +### Bundle into existing Node.js executable + +The following header must be included in a segment in order to have it run +as a single executable application: + +JSCODEVVVVVVVVFFFFFFFFF + +where: + +* `VVVVVVVV` represents the version to be used to interpret the section, + for example `00000001`. +* `FFFFFFFF` represents the flags to be used in the process of starting + the bundled application. Currently this must be `00000000` to indicate that + no flags are set. + +The characters in both `VVVVVVVV` and `FFFFFFFF` are restricted to being +hexadecimal characters (`0` through `9` and `A` through `F`) that form +a 32-bit, big endian integer. + +The string following the header is treated as a set of command line options +that are used as a prefix to any additional command line options passed when +the executable is started. For example, for a simple single hello world +for version `00000001` could be: + +```text +JSCODE0000000100000000-e \"console.log('Hello from single binary');\" +``` + +Support for bundling into existing Node.js binaries is maintained +in `src/node_single_binary.*`. + +Currently only POSIX-compliant platforms are supported. The goal +is to expand this to include Windows and macOS as well. + +If a breaking change to the content after the header is required, the version +`VVVVVVVV` should be incremented. Support for a new format +may be introduced as a semver-minor provided that older versions +are still supported. Removing support for a version is semver-major. + +The `FFFFFFFF` is a set of flags that is used to control the +process of starting the application. For example they might indicate +that some set of arguments should be suppressed on the command line. +Currently no flags are in use. + +For test purposes [LIEF](https://github.com/lief-project/LIEF) can +be used to add a section in the required format. The following is a +simple example for using LIEF on Linux. It can be improved as it +currently replaces an existing section instead of adding a new +one: + +```text +#!/usr/bin/env python +import lief +binary = lief.parse('node') + +segment = lief.ELF.Segment() +segment.type = lief.ELF.SEGMENT_TYPES.LOAD +segment.flags = lief.ELF.SEGMENT_FLAGS.R +stringContent = "JSCODE0000000100000000-e \"console.log('Hello from single binary');\"" +segment.content = bytearray(stringContent.encode()) +segment = binary.replace(segment, binary[lief.ELF.SEGMENT_TYPES.NOTE]) + +binary.write("hello") +``` diff --git a/node.gyp b/node.gyp index d063a6408072ac..b439814f5d6aa3 100644 --- a/node.gyp +++ b/node.gyp @@ -523,6 +523,7 @@ 'src/node_report_utils.cc', 'src/node_serdes.cc', 'src/node_shadow_realm.cc', + 'src/node_single_binary.cc', 'src/node_snapshotable.cc', 'src/node_sockaddr.cc', 'src/node_stat_watcher.cc', diff --git a/src/node.cc b/src/node.cc index 78e93c74d3c3c4..93d630b2dda50d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -41,6 +41,7 @@ #include "node_snapshot_builder.h" #include "node_v8_platform-inl.h" #include "node_version.h" +#include "node_single_binary.h" #if HAVE_OPENSSL #include "node_crypto.h" @@ -1182,7 +1183,15 @@ void TearDownOncePerProcess() { } int Start(int argc, char** argv) { - InitializationResult result = InitializeOncePerProcess(argc, argv); + node::single_binary::NewArgs* newArgs = + node::single_binary::checkForSingleBinary(argc, argv); + + InitializationResult result; + if (!newArgs->singleBinary) { + result = InitializeOncePerProcess(argc, argv); + } else { + result = InitializeOncePerProcess(newArgs->argc, newArgs->argv); + } if (result.early_return) { return result.exit_code; } diff --git a/src/node_single_binary.cc b/src/node_single_binary.cc new file mode 100644 index 00000000000000..cb2a27277b1e3e --- /dev/null +++ b/src/node_single_binary.cc @@ -0,0 +1,72 @@ +#include "node_single_binary.h" +#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) +#include +#include +#include +#include +#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + +namespace node { +namespace single_binary { + +#define MAGIC_HEADER "JSCODE" +#define VERSION_CHARS "00000001" +#define FLAG_CHARS "00000000" + +#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) +static int +callback(struct dl_phdr_info* info, size_t size, void* data) { + // look for the segment with the magic number + for (int index = 0; index < info->dlpi_phnum; index++) { + if (info->dlpi_phdr[index].p_type == PT_LOAD) { + char* content = + reinterpret_cast(info->dlpi_addr + + info->dlpi_phdr[index].p_vaddr); + if (strncmp(MAGIC_HEADER, + content, + strlen(MAGIC_HEADER)) == 0) { + *(static_cast(data)) = content; + break; + } + } + } + return 0; +} +#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + +struct NewArgs* checkForSingleBinary(int argc, char** argv) { + struct NewArgs* newArgs = new NewArgs; + newArgs->singleBinary = false; + +#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + char* singleBinaryData = nullptr; + dl_iterate_phdr(callback, static_cast(&singleBinaryData)); + if (singleBinaryData != nullptr) { + wordexp_t parsedArgs; + wordexp(&(singleBinaryData[strlen(MAGIC_HEADER) + + strlen(VERSION_CHARS) + + strlen(FLAG_CHARS)]), + &parsedArgs, WRDE_NOCMD); + newArgs->argc = 0; + while (parsedArgs.we_wordv[newArgs->argc] != nullptr) + newArgs->argc++; + newArgs->argc = newArgs->argc + argc; + newArgs->argv = new char*[newArgs->argc]; + newArgs->argv[0] = argv[0]; + int index = 1; + while (parsedArgs.we_wordv[index-1] != nullptr) { + newArgs->argv[index] = parsedArgs.we_wordv[index-1]; + index++; + } + for (int i = 1; i < argc; i++) { + newArgs->argv[index++] = argv[i]; + } + + newArgs->singleBinary = true; + } +#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + return newArgs; +} + +} // namespace single_binary +} // namespace node diff --git a/src/node_single_binary.h b/src/node_single_binary.h new file mode 100644 index 00000000000000..67aa1384f23c43 --- /dev/null +++ b/src/node_single_binary.h @@ -0,0 +1,22 @@ +#ifndef SRC_NODE_SINGLE_BINARY_H_ +#define SRC_NODE_SINGLE_BINARY_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +namespace node { +namespace single_binary { + +struct NewArgs { + bool singleBinary; + int argc; + char** argv; +}; + +NewArgs* checkForSingleBinary(int argc, char** argv); + +} // namespace single_binary +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_SINGLE_BINARY_H_ From b5ed34ff126efb3efd2c2052c343df34a3900f32 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 30 Jun 2022 15:44:24 -0400 Subject: [PATCH 02/10] squash: address some of the naming suggestions Signed-off-by: Michael Dawson --- node.gyp | 2 +- src/node.cc | 11 +-- src/node_single_binary.cc | 72 ------------------- src/node_single_executable_application.cc | 69 ++++++++++++++++++ ...h => node_single_executable_application.h} | 10 +-- 5 files changed, 81 insertions(+), 83 deletions(-) delete mode 100644 src/node_single_binary.cc create mode 100644 src/node_single_executable_application.cc rename src/{node_single_binary.h => node_single_executable_application.h} (54%) diff --git a/node.gyp b/node.gyp index b439814f5d6aa3..62dff272045c94 100644 --- a/node.gyp +++ b/node.gyp @@ -523,7 +523,7 @@ 'src/node_report_utils.cc', 'src/node_serdes.cc', 'src/node_shadow_realm.cc', - 'src/node_single_binary.cc', + 'src/node_single_executable_application.cc', 'src/node_snapshotable.cc', 'src/node_sockaddr.cc', 'src/node_stat_watcher.cc', diff --git a/src/node.cc b/src/node.cc index 93d630b2dda50d..107d40fbaf40b5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -38,10 +38,10 @@ #include "node_process-inl.h" #include "node_report.h" #include "node_revert.h" +#include "node_single_executable_application.h" #include "node_snapshot_builder.h" #include "node_v8_platform-inl.h" #include "node_version.h" -#include "node_single_binary.h" #if HAVE_OPENSSL #include "node_crypto.h" @@ -1183,14 +1183,15 @@ void TearDownOncePerProcess() { } int Start(int argc, char** argv) { - node::single_binary::NewArgs* newArgs = - node::single_binary::checkForSingleBinary(argc, argv); + node::single_executable_application::single_executable_replacement_args* + new_args = + node::single_executable_application::CheckForSingleBinary(argc, argv); InitializationResult result; - if (!newArgs->singleBinary) { + if (!new_args->single_executable_application) { result = InitializeOncePerProcess(argc, argv); } else { - result = InitializeOncePerProcess(newArgs->argc, newArgs->argv); + result = InitializeOncePerProcess(new_args->argc, new_args->argv); } if (result.early_return) { return result.exit_code; diff --git a/src/node_single_binary.cc b/src/node_single_binary.cc deleted file mode 100644 index cb2a27277b1e3e..00000000000000 --- a/src/node_single_binary.cc +++ /dev/null @@ -1,72 +0,0 @@ -#include "node_single_binary.h" -#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) -#include -#include -#include -#include -#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) - -namespace node { -namespace single_binary { - -#define MAGIC_HEADER "JSCODE" -#define VERSION_CHARS "00000001" -#define FLAG_CHARS "00000000" - -#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) -static int -callback(struct dl_phdr_info* info, size_t size, void* data) { - // look for the segment with the magic number - for (int index = 0; index < info->dlpi_phnum; index++) { - if (info->dlpi_phdr[index].p_type == PT_LOAD) { - char* content = - reinterpret_cast(info->dlpi_addr + - info->dlpi_phdr[index].p_vaddr); - if (strncmp(MAGIC_HEADER, - content, - strlen(MAGIC_HEADER)) == 0) { - *(static_cast(data)) = content; - break; - } - } - } - return 0; -} -#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) - -struct NewArgs* checkForSingleBinary(int argc, char** argv) { - struct NewArgs* newArgs = new NewArgs; - newArgs->singleBinary = false; - -#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) - char* singleBinaryData = nullptr; - dl_iterate_phdr(callback, static_cast(&singleBinaryData)); - if (singleBinaryData != nullptr) { - wordexp_t parsedArgs; - wordexp(&(singleBinaryData[strlen(MAGIC_HEADER) + - strlen(VERSION_CHARS) + - strlen(FLAG_CHARS)]), - &parsedArgs, WRDE_NOCMD); - newArgs->argc = 0; - while (parsedArgs.we_wordv[newArgs->argc] != nullptr) - newArgs->argc++; - newArgs->argc = newArgs->argc + argc; - newArgs->argv = new char*[newArgs->argc]; - newArgs->argv[0] = argv[0]; - int index = 1; - while (parsedArgs.we_wordv[index-1] != nullptr) { - newArgs->argv[index] = parsedArgs.we_wordv[index-1]; - index++; - } - for (int i = 1; i < argc; i++) { - newArgs->argv[index++] = argv[i]; - } - - newArgs->singleBinary = true; - } -#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) - return newArgs; -} - -} // namespace single_binary -} // namespace node diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc new file mode 100644 index 00000000000000..ab7171eaa7b4c8 --- /dev/null +++ b/src/node_single_executable_application.cc @@ -0,0 +1,69 @@ +#include "node_single_executable_application.h" +#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) +#include +#include +#include +#include +#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + +namespace node { +namespace single_executable_application { + +#define MAGIC_HEADER "JSCODE" +#define VERSION_CHARS "00000001" +#define FLAG_CHARS "00000000" + +#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) +static int callback(struct dl_phdr_info* info, size_t size, void* data) { + // look for the segment with the magic number + for (int index = 0; index < info->dlpi_phnum; index++) { + if (info->dlpi_phdr[index].p_type == PT_LOAD) { + char* content = reinterpret_cast(info->dlpi_addr + + info->dlpi_phdr[index].p_vaddr); + if (strncmp(MAGIC_HEADER, content, strlen(MAGIC_HEADER)) == 0) { + *(static_cast(data)) = content; + break; + } + } + } + return 0; +} +#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + +struct single_executable_replacement_args* CheckForSingleBinary(int argc, + char** argv) { + struct single_executable_replacement_args* new_args = + new single_executable_replacement_args; + new_args->single_executable_application = false; + +#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + char* singleBinaryData = nullptr; + dl_iterate_phdr(callback, static_cast(&singleBinaryData)); + if (singleBinaryData != nullptr) { + wordexp_t parsedArgs; + wordexp(&(singleBinaryData[strlen(MAGIC_HEADER) + strlen(VERSION_CHARS) + + strlen(FLAG_CHARS)]), + &parsedArgs, + WRDE_NOCMD); + new_args->argc = 0; + while (parsedArgs.we_wordv[new_args->argc] != nullptr) new_args->argc++; + new_args->argc = new_args->argc + argc; + new_args->argv = new char*[new_args->argc]; + new_args->argv[0] = argv[0]; + int index = 1; + while (parsedArgs.we_wordv[index - 1] != nullptr) { + new_args->argv[index] = parsedArgs.we_wordv[index - 1]; + index++; + } + for (int i = 1; i < argc; i++) { + new_args->argv[index++] = argv[i]; + } + + new_args->single_executable_application = true; + } +#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + return new_args; +} + +} // namespace single_executable_application +} // namespace node diff --git a/src/node_single_binary.h b/src/node_single_executable_application.h similarity index 54% rename from src/node_single_binary.h rename to src/node_single_executable_application.h index 67aa1384f23c43..d5227c4572f736 100644 --- a/src/node_single_binary.h +++ b/src/node_single_executable_application.h @@ -4,17 +4,17 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS namespace node { -namespace single_binary { +namespace single_executable_application { -struct NewArgs { - bool singleBinary; +struct single_executable_replacement_args { + bool single_executable_application; int argc; char** argv; }; -NewArgs* checkForSingleBinary(int argc, char** argv); +single_executable_replacement_args* CheckForSingleBinary(int argc, char** argv); -} // namespace single_binary +} // namespace single_executable_application } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS From ae342ce22e72aad409a3bde5e9786df248252cc5 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 30 Jun 2022 17:48:32 -0400 Subject: [PATCH 03/10] squash: simplify argument passing - incorporate suggestion to simplify how we parse arguments Signed-off-by: Michael Dawson --- ...g-single-executable-application-support.md | 22 +++++----- src/node_single_executable_application.cc | 44 ++++++++++++------- src/node_single_executable_application.h | 6 +-- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/doc/contributing/maintaining-single-executable-application-support.md b/doc/contributing/maintaining-single-executable-application-support.md index a47c22fb82b743..bd9d8547e2eadb 100644 --- a/doc/contributing/maintaining-single-executable-application-support.md +++ b/doc/contributing/maintaining-single-executable-application-support.md @@ -50,7 +50,7 @@ maintain a stable [em-bedder API](https://nodejs.org/dist/latest/docs/api/embedd The following header must be included in a segment in order to have it run as a single executable application: -JSCODEVVVVVVVVFFFFFFFFF +JSCODEVVVVVVVVFFFFFFFFFAAAAAAAA where: @@ -59,18 +59,20 @@ where: * `FFFFFFFF` represents the flags to be used in the process of starting the bundled application. Currently this must be `00000000` to indicate that no flags are set. +* `AAAAAAAA` is the number of arguments being provided -The characters in both `VVVVVVVV` and `FFFFFFFF` are restricted to being -hexadecimal characters (`0` through `9` and `A` through `F`) that form -a 32-bit, big endian integer. +The characters in both `VVVVVVVV`, `FFFFFFFF` and `AAAAAAAA` are +restricted to being hexadecimal characters (`0` through `9` and +`A` through `F`) that form a 32-bit, big endian integer. -The string following the header is treated as a set of command line options -that are used as a prefix to any additional command line options passed when -the executable is started. For example, for a simple single hello world -for version `00000001` could be: +Following the header are AAAAAAAA strings, each terminated for 0x00 +one for each of the parameters passed. These parameters are is treated +as a set of command line options that are used as a prefix to any +additional command line options passed when the executable is started. +For example, for a simple single hello world for version `00000001` could be: ```text -JSCODE0000000100000000-e \"console.log('Hello from single binary');\" +JSCODE000000010000000000000002-e\0console.log('Hello from single binary')\0 ``` Support for bundling into existing Node.js binaries is maintained @@ -103,7 +105,7 @@ binary = lief.parse('node') segment = lief.ELF.Segment() segment.type = lief.ELF.SEGMENT_TYPES.LOAD segment.flags = lief.ELF.SEGMENT_FLAGS.R -stringContent = "JSCODE0000000100000000-e \"console.log('Hello from single binary');\"" +stringContent = "JSCODE000000010000000000000002-e\0console.log('Hello from single binary')\0" segment.content = bytearray(stringContent.encode()) segment = binary.replace(segment, binary[lief.ELF.SEGMENT_TYPES.NOTE]) diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc index ab7171eaa7b4c8..9837dcd34084f3 100644 --- a/src/node_single_executable_application.cc +++ b/src/node_single_executable_application.cc @@ -3,7 +3,7 @@ #include #include #include -#include +#include #endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) namespace node { @@ -12,6 +12,9 @@ namespace single_executable_application { #define MAGIC_HEADER "JSCODE" #define VERSION_CHARS "00000001" #define FLAG_CHARS "00000000" +#define ARGC_OFFSET \ + strlen(MAGIC_HEADER) + strlen(VERSION_CHARS) + strlen(FLAG_CHARS) +#define ARGC_LENGTH 8 #if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) static int callback(struct dl_phdr_info* info, size_t size, void* data) { @@ -36,32 +39,43 @@ struct single_executable_replacement_args* CheckForSingleBinary(int argc, new single_executable_replacement_args; new_args->single_executable_application = false; + char* single_executable_data = nullptr; #if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) - char* singleBinaryData = nullptr; - dl_iterate_phdr(callback, static_cast(&singleBinaryData)); - if (singleBinaryData != nullptr) { - wordexp_t parsedArgs; - wordexp(&(singleBinaryData[strlen(MAGIC_HEADER) + strlen(VERSION_CHARS) + - strlen(FLAG_CHARS)]), - &parsedArgs, - WRDE_NOCMD); + dl_iterate_phdr(callback, static_cast(&single_executable_data)); +#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + if (single_executable_data != nullptr) { + // get the new arguments info + std::string argc_string( + static_cast(&single_executable_data[ARGC_OFFSET]), ARGC_LENGTH); + int argument_count = std::stoi(argc_string, 0, 16); + char* arguments = &(single_executable_data[ARGC_OFFSET + ARGC_LENGTH]); + + // set up new argc count and space for new argv new_args->argc = 0; - while (parsedArgs.we_wordv[new_args->argc] != nullptr) new_args->argc++; - new_args->argc = new_args->argc + argc; + new_args->argc = argc + argument_count; new_args->argv = new char*[new_args->argc]; new_args->argv[0] = argv[0]; int index = 1; - while (parsedArgs.we_wordv[index - 1] != nullptr) { - new_args->argv[index] = parsedArgs.we_wordv[index - 1]; - index++; + + // copy over the new arguments + for (int i = 0; i < argument_count; i++) { + new_args->argv[index++] = arguments; + int length = strlen(arguments); + // TODO(mhdawson): add check that we don't overrun the segment + arguments = arguments + length + 1; } + + // TODO(mhdawson): remaining data after arguments in binary data + // that can be used by the single executable applicaiton. + // Add a way for the application to get that data. + + // copy over the arguments passed when the executable was started for (int i = 1; i < argc; i++) { new_args->argv[index++] = argv[i]; } new_args->single_executable_application = true; } -#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) return new_args; } diff --git a/src/node_single_executable_application.h b/src/node_single_executable_application.h index d5227c4572f736..e74d66b1817680 100644 --- a/src/node_single_executable_application.h +++ b/src/node_single_executable_application.h @@ -1,5 +1,5 @@ -#ifndef SRC_NODE_SINGLE_BINARY_H_ -#define SRC_NODE_SINGLE_BINARY_H_ +#ifndef SRC_NODE_SINGLE_EXECUTABLE_APPLICATION_H_ +#define SRC_NODE_SINGLE_EXECUTABLE_APPLICATION_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS @@ -19,4 +19,4 @@ single_executable_replacement_args* CheckForSingleBinary(int argc, char** argv); #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_NODE_SINGLE_BINARY_H_ +#endif // SRC_NODE_SINGLE_EXECUTABLE_APPLICATION_H_ From 6fffb4e85fdc6efa5e9785e776c15b5fcf405f8a Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Mon, 4 Jul 2022 14:52:14 -0400 Subject: [PATCH 04/10] squash: update value of magic header - update value of magic header based on review comments Signed-off-by: Michael Dawson --- .../maintaining-single-executable-application-support.md | 6 +++--- src/node_single_executable_application.cc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/contributing/maintaining-single-executable-application-support.md b/doc/contributing/maintaining-single-executable-application-support.md index bd9d8547e2eadb..9d488421457ca7 100644 --- a/doc/contributing/maintaining-single-executable-application-support.md +++ b/doc/contributing/maintaining-single-executable-application-support.md @@ -50,7 +50,7 @@ maintain a stable [em-bedder API](https://nodejs.org/dist/latest/docs/api/embedd The following header must be included in a segment in order to have it run as a single executable application: -JSCODEVVVVVVVVFFFFFFFFFAAAAAAAA +NODEJSSEAVVVVVVVVFFFFFFFFFAAAAAAAA where: @@ -72,7 +72,7 @@ additional command line options passed when the executable is started. For example, for a simple single hello world for version `00000001` could be: ```text -JSCODE000000010000000000000002-e\0console.log('Hello from single binary')\0 +NODEJSSEA000000010000000000000002-e\0console.log('Hello from single binary')\0 ``` Support for bundling into existing Node.js binaries is maintained @@ -105,7 +105,7 @@ binary = lief.parse('node') segment = lief.ELF.Segment() segment.type = lief.ELF.SEGMENT_TYPES.LOAD segment.flags = lief.ELF.SEGMENT_FLAGS.R -stringContent = "JSCODE000000010000000000000002-e\0console.log('Hello from single binary')\0" +stringContent = "NODEJSSEA000000010000000000000002-e\0console.log('Hello from single binary')\0" segment.content = bytearray(stringContent.encode()) segment = binary.replace(segment, binary[lief.ELF.SEGMENT_TYPES.NOTE]) diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc index 9837dcd34084f3..530b93247d2f6e 100644 --- a/src/node_single_executable_application.cc +++ b/src/node_single_executable_application.cc @@ -9,7 +9,7 @@ namespace node { namespace single_executable_application { -#define MAGIC_HEADER "JSCODE" +#define MAGIC_HEADER "NODEJSSEA" #define VERSION_CHARS "00000001" #define FLAG_CHARS "00000000" #define ARGC_OFFSET \ From 073bc889d389c5eb87e5a2b20f6a0050ced6784e Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Mon, 4 Jul 2022 17:38:08 -0400 Subject: [PATCH 05/10] squash: avoid running in repl like environment - address comment that we should not be running in repl like environment Signed-off-by: Michael Dawson --- .../main/single_executable_application.js | 26 +++++++++++++++++++ src/node.cc | 8 ++++++ 2 files changed, 34 insertions(+) create mode 100644 lib/internal/main/single_executable_application.js diff --git a/lib/internal/main/single_executable_application.js b/lib/internal/main/single_executable_application.js new file mode 100644 index 00000000000000..f6f84f869e55ab --- /dev/null +++ b/lib/internal/main/single_executable_application.js @@ -0,0 +1,26 @@ +'use strict'; + +const { + prepareMainThreadExecution +} = require('internal/bootstrap/pre_execution'); + +const { getOptionValue } = require('internal/options'); + +const { + evalModule, + evalScript, + readStdin +} = require('internal/process/execution'); + +prepareMainThreadExecution(); +markBootstrapComplete(); + +const source = getOptionValue('--eval'); +const print = getOptionValue('--print'); +if (getOptionValue('--input-type') === 'module') + evalModule(source, print); +else + evalScript('[eval]', + source, + getOptionValue('--inspect-brk'), + print); diff --git a/src/node.cc b/src/node.cc index 107d40fbaf40b5..a2d936ca8006dc 100644 --- a/src/node.cc +++ b/src/node.cc @@ -161,6 +161,8 @@ PVOID old_vectored_exception_handler; // node_v8_platform-inl.h struct V8Platform v8_platform; + +bool single_executable_application = false; } // namespace per_process // The section in the OpenSSL configuration file to be loaded. @@ -520,6 +522,11 @@ MaybeLocal StartExecution(Environment* env, StartExecutionCallback cb) { return StartExecution(env, "internal/main/prof_process"); } + if (env->options()->has_eval_string && + per_process::single_executable_application) { + return StartExecution(env, "internal/main/single_executable_application"); + } + // -e/--eval without -i/--interactive if (env->options()->has_eval_string && !env->options()->force_repl) { return StartExecution(env, "internal/main/eval_string"); @@ -1191,6 +1198,7 @@ int Start(int argc, char** argv) { if (!new_args->single_executable_application) { result = InitializeOncePerProcess(argc, argv); } else { + per_process::single_executable_application = true; result = InitializeOncePerProcess(new_args->argc, new_args->argv); } if (result.early_return) { From e0b8b2dc497e4f85046d0bc0be4fd28e66dd6aaf Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Mon, 4 Jul 2022 18:53:03 -0400 Subject: [PATCH 06/10] squash: cleanup updates of arguments Cleanup up process of updating arguments. Signed-off-by: Michael Dawson --- src/node.cc | 19 +++++++---------- src/node_single_executable_application.cc | 25 +++++++++-------------- src/node_single_executable_application.h | 13 ++++++------ 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/node.cc b/src/node.cc index a2d936ca8006dc..1034e5d9f5f93b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1034,7 +1034,12 @@ InitializationResult InitializeOncePerProcess( argv = uv_setup_args(argc, argv); InitializationResult result; - result.args = std::vector(argv, argv + argc); + if (single_executable_application::CheckForSingleBinary( + argc, argv, &(result.args))) { + per_process::single_executable_application = true; + } else { + result.args = std::vector(argv, argv + argc); + } std::vector errors; // This needs to run *before* V8::Initialize(). @@ -1190,17 +1195,7 @@ void TearDownOncePerProcess() { } int Start(int argc, char** argv) { - node::single_executable_application::single_executable_replacement_args* - new_args = - node::single_executable_application::CheckForSingleBinary(argc, argv); - - InitializationResult result; - if (!new_args->single_executable_application) { - result = InitializeOncePerProcess(argc, argv); - } else { - per_process::single_executable_application = true; - result = InitializeOncePerProcess(new_args->argc, new_args->argv); - } + InitializationResult result = InitializeOncePerProcess(argc, argv); if (result.early_return) { return result.exit_code; } diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc index 530b93247d2f6e..b340b7410b1822 100644 --- a/src/node_single_executable_application.cc +++ b/src/node_single_executable_application.cc @@ -33,11 +33,10 @@ static int callback(struct dl_phdr_info* info, size_t size, void* data) { } #endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) -struct single_executable_replacement_args* CheckForSingleBinary(int argc, - char** argv) { - struct single_executable_replacement_args* new_args = - new single_executable_replacement_args; - new_args->single_executable_application = false; +bool CheckForSingleBinary(int argc, + char** argv, + std::vector* new_argv) { + bool single_executable_application = false; char* single_executable_data = nullptr; #if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) @@ -50,16 +49,12 @@ struct single_executable_replacement_args* CheckForSingleBinary(int argc, int argument_count = std::stoi(argc_string, 0, 16); char* arguments = &(single_executable_data[ARGC_OFFSET + ARGC_LENGTH]); - // set up new argc count and space for new argv - new_args->argc = 0; - new_args->argc = argc + argument_count; - new_args->argv = new char*[new_args->argc]; - new_args->argv[0] = argv[0]; - int index = 1; + // copy over the first argument which needs to stay in place + new_argv->push_back(argv[0]); // copy over the new arguments for (int i = 0; i < argument_count; i++) { - new_args->argv[index++] = arguments; + new_argv->push_back(arguments); int length = strlen(arguments); // TODO(mhdawson): add check that we don't overrun the segment arguments = arguments + length + 1; @@ -71,12 +66,12 @@ struct single_executable_replacement_args* CheckForSingleBinary(int argc, // copy over the arguments passed when the executable was started for (int i = 1; i < argc; i++) { - new_args->argv[index++] = argv[i]; + new_argv->push_back(argv[i]); } - new_args->single_executable_application = true; + single_executable_application = true; } - return new_args; + return single_executable_application; } } // namespace single_executable_application diff --git a/src/node_single_executable_application.h b/src/node_single_executable_application.h index e74d66b1817680..4efd1fa012a989 100644 --- a/src/node_single_executable_application.h +++ b/src/node_single_executable_application.h @@ -3,16 +3,15 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include +#include + namespace node { namespace single_executable_application { -struct single_executable_replacement_args { - bool single_executable_application; - int argc; - char** argv; -}; - -single_executable_replacement_args* CheckForSingleBinary(int argc, char** argv); +bool CheckForSingleBinary(int argc, + char** argv, + std::vector* new_argv); } // namespace single_executable_application } // namespace node From 94d46b3c9bc566990c16b88785c127cbbed363e9 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 7 Jul 2022 17:46:42 -0400 Subject: [PATCH 07/10] src: add simple fuse - add simple fuse so SEA is only enabled when it is set - extra logic to get pointer to SEA data to its own function Signed-off-by: Michael Dawson --- src/node_single_executable_application.cc | 71 ++++++++++++++--------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc index b340b7410b1822..b9318a5b27187c 100644 --- a/src/node_single_executable_application.cc +++ b/src/node_single_executable_application.cc @@ -9,6 +9,9 @@ namespace node { namespace single_executable_application { +const char* sea_fuse = "AE249F4D38193B9BEFE654DF3AFD7065:00"; +#define FUSE_SENTINAL_LENGTH 33 + #define MAGIC_HEADER "NODEJSSEA" #define VERSION_CHARS "00000001" #define FLAG_CHARS "00000000" @@ -33,43 +36,57 @@ static int callback(struct dl_phdr_info* info, size_t size, void* data) { } #endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) -bool CheckForSingleBinary(int argc, - char** argv, - std::vector* new_argv) { - bool single_executable_application = false; +bool CheckFuse(void) { + return (strncmp(sea_fuse + FUSE_SENTINAL_LENGTH, + "01", + FUSE_SENTINAL_LENGTH + 2) == 0); +} +char* GetSEAData() { char* single_executable_data = nullptr; #if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) dl_iterate_phdr(callback, static_cast(&single_executable_data)); #endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) - if (single_executable_data != nullptr) { - // get the new arguments info - std::string argc_string( - static_cast(&single_executable_data[ARGC_OFFSET]), ARGC_LENGTH); - int argument_count = std::stoi(argc_string, 0, 16); - char* arguments = &(single_executable_data[ARGC_OFFSET + ARGC_LENGTH]); + return single_executable_data; +} + +bool CheckForSingleBinary(int argc, + char** argv, + std::vector* new_argv) { + bool single_executable_application = false; - // copy over the first argument which needs to stay in place - new_argv->push_back(argv[0]); + if (CheckFuse()) { + char* single_executable_data = GetSEAData(); + if (single_executable_data != nullptr) { + // get the new arguments info + std::string argc_string( + static_cast(&single_executable_data[ARGC_OFFSET]), + ARGC_LENGTH); + int argument_count = std::stoi(argc_string, 0, 16); + char* arguments = &(single_executable_data[ARGC_OFFSET + ARGC_LENGTH]); - // copy over the new arguments - for (int i = 0; i < argument_count; i++) { - new_argv->push_back(arguments); - int length = strlen(arguments); - // TODO(mhdawson): add check that we don't overrun the segment - arguments = arguments + length + 1; - } + // copy over the first argument which needs to stay in place + new_argv->push_back(argv[0]); - // TODO(mhdawson): remaining data after arguments in binary data - // that can be used by the single executable applicaiton. - // Add a way for the application to get that data. + // copy over the new arguments + for (int i = 0; i < argument_count; i++) { + new_argv->push_back(arguments); + int length = strlen(arguments); + // TODO(mhdawson): add check that we don't overrun the segment + arguments = arguments + length + 1; + } - // copy over the arguments passed when the executable was started - for (int i = 1; i < argc; i++) { - new_argv->push_back(argv[i]); - } + // TODO(mhdawson): remaining data after arguments in binary data + // that can be used by the single executable applicaiton. + // Add a way for the application to get that data. + + // copy over the arguments passed when the executable was started + for (int i = 1; i < argc; i++) { + new_argv->push_back(argv[i]); + } - single_executable_application = true; + single_executable_application = true; + } } return single_executable_application; } From 3ecb114577d895a5d77bba3a4c08691a95b92f75 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 13 Jul 2022 09:47:10 -0400 Subject: [PATCH 08/10] squash: add support for append as well Signed-off-by: Michael Dawson --- src/node_single_executable_application.cc | 88 +++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc index b9318a5b27187c..5f3fd1ac589222 100644 --- a/src/node_single_executable_application.cc +++ b/src/node_single_executable_application.cc @@ -5,6 +5,9 @@ #include #include #endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) +#include + +#include "env-inl.h" namespace node { namespace single_executable_application { @@ -42,11 +45,96 @@ bool CheckFuse(void) { FUSE_SENTINAL_LENGTH + 2) == 0); } +// from Jesec's version +// 4096 chars should be more than enough to deal with +// header + node options + script size +// but definitely not elegant to have this limit +#define SEA_BUF_SIZE 4096 +char sea_buf[SEA_BUF_SIZE]; +std::string executable_path() { + char exec_path_buf[2 * PATH_MAX]; + size_t exec_path_len = sizeof(exec_path_buf); + + if (uv_exepath(exec_path_buf, &exec_path_len) == 0) { + return std::string(exec_path_buf, exec_path_len); + } + + return ""; +} + +char* search_single_binary_data() { + auto exec = executable_path(); + if (exec.empty()) { + return nullptr; + } + + auto f = new std::ifstream(exec); + if (!f->is_open() || !f->good() || f->eof()) { + delete f; + return nullptr; + } + + std::string needle; + needle += MAGIC_HEADER; + needle += VERSION_CHARS; + + constexpr auto buf_size = 1 << 20; + + auto buf = new char[buf_size]; + auto buf_view = std::string_view(buf, buf_size); + auto buf_pos = buf_view.npos; + + size_t f_pos = 0; + + // first read + f->read(buf, buf_size); + f_pos += f->gcount(); + buf_pos = buf_view.find(needle); + if (buf_pos != buf_view.npos) { + f_pos = f_pos - f->gcount() + buf_pos; + + f->clear(); + f->seekg(f_pos, std::ios::beg); + + delete[] buf; + f->read(sea_buf, SEA_BUF_SIZE); + return (sea_buf); + } + + // subsequent reads, moving window + while (!f->eof()) { + std::memcpy(buf, buf + buf_size - needle.size(), needle.size()); + f->read(buf + needle.size(), buf_size - needle.size()); + f_pos += f->gcount(); + buf_pos = buf_view.find(needle); + if (buf_pos != buf_view.npos) { + f_pos = f_pos - f->gcount() - needle.size() + buf_pos; + + f->clear(); + f->seekg(f_pos, std::ios::beg); + + delete[] buf; + f->read(sea_buf, SEA_BUF_SIZE); + return (sea_buf); + } + } + + delete[] buf; + delete f; + return nullptr; +} + char* GetSEAData() { char* single_executable_data = nullptr; #if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) dl_iterate_phdr(callback, static_cast(&single_executable_data)); #endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) + + if (single_executable_data == nullptr) { + // no special segment so read binary instead + single_executable_data = search_single_binary_data(); + } + return single_executable_data; } From 2c7a43462d32ed965d42c2d17f83ae42a00f5e77 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 13 Jul 2022 11:35:52 -0400 Subject: [PATCH 09/10] squash: cleanup Signed-off-by: Michael Dawson --- src/node_single_executable_application.cc | 46 +++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc index 5f3fd1ac589222..4fc6b399ba86a8 100644 --- a/src/node_single_executable_application.cc +++ b/src/node_single_executable_application.cc @@ -13,14 +13,14 @@ namespace node { namespace single_executable_application { const char* sea_fuse = "AE249F4D38193B9BEFE654DF3AFD7065:00"; -#define FUSE_SENTINAL_LENGTH 33 +constexpr const int fuse_sentinal_length = 33; -#define MAGIC_HEADER "NODEJSSEA" -#define VERSION_CHARS "00000001" -#define FLAG_CHARS "00000000" -#define ARGC_OFFSET \ - strlen(MAGIC_HEADER) + strlen(VERSION_CHARS) + strlen(FLAG_CHARS) -#define ARGC_LENGTH 8 +constexpr const char* magic_header = "NODEJSSEA"; +constexpr const char* version_chars = "00000001"; +constexpr const char* flag_chars = "00000000"; +constexpr const int argc_offset = + strlen(magic_header) + strlen(version_chars) + strlen(flag_chars); +constexpr const int argc_length = 8; #if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) static int callback(struct dl_phdr_info* info, size_t size, void* data) { @@ -29,7 +29,7 @@ static int callback(struct dl_phdr_info* info, size_t size, void* data) { if (info->dlpi_phdr[index].p_type == PT_LOAD) { char* content = reinterpret_cast(info->dlpi_addr + info->dlpi_phdr[index].p_vaddr); - if (strncmp(MAGIC_HEADER, content, strlen(MAGIC_HEADER)) == 0) { + if (strncmp(magic_header, content, strlen(magic_header)) == 0) { *(static_cast(data)) = content; break; } @@ -40,18 +40,18 @@ static int callback(struct dl_phdr_info* info, size_t size, void* data) { #endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) bool CheckFuse(void) { - return (strncmp(sea_fuse + FUSE_SENTINAL_LENGTH, + return (strncmp(sea_fuse + fuse_sentinal_length, "01", - FUSE_SENTINAL_LENGTH + 2) == 0); + fuse_sentinal_length + 2) == 0); } // from Jesec's version // 4096 chars should be more than enough to deal with // header + node options + script size // but definitely not elegant to have this limit -#define SEA_BUF_SIZE 4096 -char sea_buf[SEA_BUF_SIZE]; -std::string executable_path() { +constexpr const int sea_buf_size = 4096; +char sea_buf[sea_buf_size]; +std::string GetExecutablePath() { char exec_path_buf[2 * PATH_MAX]; size_t exec_path_len = sizeof(exec_path_buf); @@ -62,8 +62,8 @@ std::string executable_path() { return ""; } -char* search_single_binary_data() { - auto exec = executable_path(); +char* SearchExecutableForSEAData() { + std::string exec = GetExecutablePath(); if (exec.empty()) { return nullptr; } @@ -75,8 +75,8 @@ char* search_single_binary_data() { } std::string needle; - needle += MAGIC_HEADER; - needle += VERSION_CHARS; + needle += magic_header; + needle += version_chars; constexpr auto buf_size = 1 << 20; @@ -97,7 +97,7 @@ char* search_single_binary_data() { f->seekg(f_pos, std::ios::beg); delete[] buf; - f->read(sea_buf, SEA_BUF_SIZE); + f->read(sea_buf, sea_buf_size); return (sea_buf); } @@ -114,7 +114,7 @@ char* search_single_binary_data() { f->seekg(f_pos, std::ios::beg); delete[] buf; - f->read(sea_buf, SEA_BUF_SIZE); + f->read(sea_buf, sea_buf_size); return (sea_buf); } } @@ -132,7 +132,7 @@ char* GetSEAData() { if (single_executable_data == nullptr) { // no special segment so read binary instead - single_executable_data = search_single_binary_data(); + single_executable_data = SearchExecutableForSEAData(); } return single_executable_data; @@ -148,10 +148,10 @@ bool CheckForSingleBinary(int argc, if (single_executable_data != nullptr) { // get the new arguments info std::string argc_string( - static_cast(&single_executable_data[ARGC_OFFSET]), - ARGC_LENGTH); + static_cast(&single_executable_data[argc_offset]), + argc_length); int argument_count = std::stoi(argc_string, 0, 16); - char* arguments = &(single_executable_data[ARGC_OFFSET + ARGC_LENGTH]); + char* arguments = &(single_executable_data[argc_offset + argc_length]); // copy over the first argument which needs to stay in place new_argv->push_back(argv[0]); From bef9dfcfa940f925b5e994b3f1f4601fe0db7352 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Wed, 13 Jul 2022 13:02:31 -0400 Subject: [PATCH 10/10] squash: add support for binary data Signed-off-by: Michael Dawson --- ...g-single-executable-application-support.md | 7 +++- src/node.cc | 15 ++++++- src/node_single_executable_application.cc | 40 ++++++++++++------- src/node_single_executable_application.h | 7 ++-- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/doc/contributing/maintaining-single-executable-application-support.md b/doc/contributing/maintaining-single-executable-application-support.md index 9d488421457ca7..388198bf0c7952 100644 --- a/doc/contributing/maintaining-single-executable-application-support.md +++ b/doc/contributing/maintaining-single-executable-application-support.md @@ -58,7 +58,8 @@ where: for example `00000001`. * `FFFFFFFF` represents the flags to be used in the process of starting the bundled application. Currently this must be `00000000` to indicate that - no flags are set. + no flags are set or `000000001` to indicate that a binary block + of data is included in addition to the arguments. * `AAAAAAAA` is the number of arguments being provided The characters in both `VVVVVVVV`, `FFFFFFFF` and `AAAAAAAA` are @@ -75,6 +76,10 @@ For example, for a simple single hello world for version `00000001` could be: NODEJSSEA000000010000000000000002-e\0console.log('Hello from single binary')\0 ``` +If the flags are set to `00000001` then there must be at least one charater +of binary data following the argument strings and a pointer to this +data will be exposed through `process.seaBinaryData`. + Support for bundling into existing Node.js binaries is maintained in `src/node_single_binary.*`. diff --git a/src/node.cc b/src/node.cc index 1034e5d9f5f93b..3cdcecc1d1a36b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -163,6 +163,7 @@ PVOID old_vectored_exception_handler; struct V8Platform v8_platform; bool single_executable_application = false; +char* sea_binary_data = nullptr; } // namespace per_process // The section in the OpenSSL configuration file to be loaded. @@ -524,6 +525,16 @@ MaybeLocal StartExecution(Environment* env, StartExecutionCallback cb) { if (env->options()->has_eval_string && per_process::single_executable_application) { + if (per_process::sea_binary_data != nullptr) { + Isolate* isolate = env->isolate(); + Local context = env->context(); + READONLY_PROPERTY( + env->process_object(), + "seaBinaryData", + ToV8Value(context, + reinterpret_cast(per_process::sea_binary_data)) + .ToLocalChecked()); + } return StartExecution(env, "internal/main/single_executable_application"); } @@ -1034,8 +1045,8 @@ InitializationResult InitializeOncePerProcess( argv = uv_setup_args(argc, argv); InitializationResult result; - if (single_executable_application::CheckForSingleBinary( - argc, argv, &(result.args))) { + if (single_executable_application::CheckForSEA( + argc, argv, &(result.args), &per_process::sea_binary_data)) { per_process::single_executable_application = true; } else { result.args = std::vector(argv, argv + argc); diff --git a/src/node_single_executable_application.cc b/src/node_single_executable_application.cc index 4fc6b399ba86a8..b9bb819487552c 100644 --- a/src/node_single_executable_application.cc +++ b/src/node_single_executable_application.cc @@ -17,10 +17,11 @@ constexpr const int fuse_sentinal_length = 33; constexpr const char* magic_header = "NODEJSSEA"; constexpr const char* version_chars = "00000001"; -constexpr const char* flag_chars = "00000000"; -constexpr const int argc_offset = - strlen(magic_header) + strlen(version_chars) + strlen(flag_chars); -constexpr const int argc_length = 8; +constexpr const int kFlagsLength = 8; +constexpr const int flags_offset = strlen(magic_header) + strlen(version_chars); +constexpr const int argc_offset = flags_offset + kFlagsLength; +constexpr const int kArgcLength = 8; +constexpr const int kBinaryDataIncludedFlag = 0x1; #if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) static int callback(struct dl_phdr_info* info, size_t size, void* data) { @@ -49,8 +50,8 @@ bool CheckFuse(void) { // 4096 chars should be more than enough to deal with // header + node options + script size // but definitely not elegant to have this limit -constexpr const int sea_buf_size = 4096; -char sea_buf[sea_buf_size]; +constexpr const int kSeaBufSize = 4096; +char sea_buf[kSeaBufSize]; std::string GetExecutablePath() { char exec_path_buf[2 * PATH_MAX]; size_t exec_path_len = sizeof(exec_path_buf); @@ -97,7 +98,7 @@ char* SearchExecutableForSEAData() { f->seekg(f_pos, std::ios::beg); delete[] buf; - f->read(sea_buf, sea_buf_size); + f->read(sea_buf, kSeaBufSize); return (sea_buf); } @@ -114,7 +115,7 @@ char* SearchExecutableForSEAData() { f->seekg(f_pos, std::ios::beg); delete[] buf; - f->read(sea_buf, sea_buf_size); + f->read(sea_buf, kSeaBufSize); return (sea_buf); } } @@ -138,20 +139,27 @@ char* GetSEAData() { return single_executable_data; } -bool CheckForSingleBinary(int argc, - char** argv, - std::vector* new_argv) { +bool CheckForSEA(int argc, + char** argv, + std::vector* new_argv, + char** sea_binary_data) { bool single_executable_application = false; if (CheckFuse()) { char* single_executable_data = GetSEAData(); if (single_executable_data != nullptr) { + // get the Flags + std::string flags_string( + static_cast(&single_executable_data[flags_offset]), + kFlagsLength); + int flags = std::stoi(flags_string, 0, 16); + // get the new arguments info std::string argc_string( static_cast(&single_executable_data[argc_offset]), - argc_length); + kArgcLength); int argument_count = std::stoi(argc_string, 0, 16); - char* arguments = &(single_executable_data[argc_offset + argc_length]); + char* arguments = &(single_executable_data[argc_offset + kArgcLength]); // copy over the first argument which needs to stay in place new_argv->push_back(argv[0]); @@ -164,9 +172,11 @@ bool CheckForSingleBinary(int argc, arguments = arguments + length + 1; } - // TODO(mhdawson): remaining data after arguments in binary data + // remaining data after arguments in binary data // that can be used by the single executable applicaiton. - // Add a way for the application to get that data. + if (flags & kBinaryDataIncludedFlag) { + *sea_binary_data = arguments; + } // copy over the arguments passed when the executable was started for (int i = 1; i < argc; i++) { diff --git a/src/node_single_executable_application.h b/src/node_single_executable_application.h index 4efd1fa012a989..9d7e8de7d84216 100644 --- a/src/node_single_executable_application.h +++ b/src/node_single_executable_application.h @@ -9,9 +9,10 @@ namespace node { namespace single_executable_application { -bool CheckForSingleBinary(int argc, - char** argv, - std::vector* new_argv); +bool CheckForSEA(int argc, + char** argv, + std::vector* new_argv, + char** sea_binary_data); } // namespace single_executable_application } // namespace node