-
Notifications
You must be signed in to change notification settings - Fork 30k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
Showing
5 changed files
with
215 additions
and
1 deletion.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
doc/contributing/maintaining-single-executable-application-support.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# 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 on 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 projects believes are important to support: | ||
|
||
* Compile with node into executable (boxnode approach) | ||
* Bundled onto exising Node.js exe (pkg approach) | ||
|
||
### Compile with node into executable | ||
|
||
No specific 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. | ||
|
||
### Bundled onto Existing Node.js | ||
|
||
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 enabling | ||
bundling with the shipping Node.js binaries. This includes: | ||
|
||
* Looking for a segment within the executable that holds bundled code. | ||
* Running the bundled code when such a segement is found. | ||
|
||
It is left up to external tools/solutions to: | ||
|
||
* Bundled code into a single script that can be executed with -e on | ||
the command line. | ||
* Generating a command line with appropriate options, including -e to | ||
run the bundle script. | ||
* adding a segment to the existing Node.js executable which contains | ||
the command line and appropriate headers. | ||
* re-generating or removing signatures on the resulting executable | ||
* providing a virtual file system, and hooking it in if needed to | ||
support native modules or reading file contents. | ||
|
||
## Maintaining | ||
|
||
### Compile with node into executable | ||
|
||
There are not special consideration needed. | ||
|
||
### Bundled onto Existing Node.js | ||
|
||
The following header must be included in a segment in order to have it run | ||
as a single executable application: | ||
|
||
JSCODEVVVVVVVVFFFFFFFFF | ||
|
||
where: | ||
|
||
* VVVVVVVV string representing the version to be used to interpret the section, | ||
for example `00000001`. | ||
* FFFFFFFF string representing 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 | ||
between the characters "0" and "F" such that they can each be converted to | ||
a 32 bit 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 was 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 onto existing Node.js binaries is maintained | ||
in `src/node_single_binary.*`. | ||
|
||
Currently only POSIX compliant platform are suppported. The goal | ||
is to expand this to include Windows and MacOS as well. | ||
|
||
If a breaking change is required to the content after the header, 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` are a set of flags that are 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 \[LIFE}(<https://github.com/lief-project/LIEF>) can | ||
be used to add a section in the required format. The following is a | ||
simple example for doing so 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") | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
#include "node_single_binary.h" | ||
#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__) | ||
#include <link.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <wordexp.h> | ||
#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<char*>(info->dlpi_addr + | ||
info->dlpi_phdr[index].p_vaddr); | ||
if (strncmp(MAGIC_HEADER, | ||
content, | ||
strlen(MAGIC_HEADER)) == 0) { | ||
*(static_cast<char**>(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<void*>(&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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |