Skip to content

Commit 052c468

Browse files
committed
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 <mdawson@devrus.com>
1 parent 72e83fc commit 052c468

File tree

5 files changed

+215
-1
lines changed

5 files changed

+215
-1
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Maintaining Single Executable Applications support
2+
3+
Support for [single executable applications](https://github.com/nodejs/node/blob/master/doc/contributing/technical-priorities.md#single-executable-applications)
4+
is on of the key technical priorities identified for the success of Node.js.
5+
6+
## High level strategy
7+
8+
From the [next-10 discussions](https://github.com/nodejs/next-10/blob/main/meetings/summit-nov-2021.md#single-executable-applications)
9+
there are 2 approaches the projects believes are important to support:
10+
11+
* Compile with node into executable (boxnode approach)
12+
* Bundled onto exising Node.js exe (pkg approach)
13+
14+
### Compile with node into executable
15+
16+
No specific code within the Node.js project is needed to support the
17+
option of compiling a bundled application along with Node.js into a single
18+
executable application.
19+
20+
### Bundled onto Existing Node.js
21+
22+
The project does not plan to provide the complete solution but instead the key
23+
elements which are required in the Node.js executable in order to enabling
24+
bundling with the shipping Node.js binaries. This includes:
25+
26+
* Looking for a segment within the executable that holds bundled code.
27+
* Running the bundled code when such a segement is found.
28+
29+
It is left up to external tools/solutions to:
30+
31+
* Bundled code into a single script that can be executed with -e on
32+
the command line.
33+
* Generating a command line with appropriate options, including -e to
34+
run the bundle script.
35+
* adding a segment to the existing Node.js executable which contains
36+
the command line and appropriate headers.
37+
* re-generating or removing signatures on the resulting executable
38+
* providing a virtual file system, and hooking it in if needed to
39+
support native modules or reading file contents.
40+
41+
## Maintaining
42+
43+
### Compile with node into executable
44+
45+
There are not special consideration needed.
46+
47+
### Bundled onto Existing Node.js
48+
49+
The following header must be included in a segment in order to have it run
50+
as a single executable application:
51+
52+
JSCODEVVVVVVVVFFFFFFFFF
53+
54+
where:
55+
56+
* VVVVVVVV string representing the version to be used to interpret the section,
57+
for example `00000001`.
58+
* FFFFFFFF string representing the flags to be used in the process of starting
59+
the bundled application. Currently this must be `00000000` to indicate that
60+
no flags are set.
61+
62+
The characters in both `VVVVVVVV` and `FFFFFFFF` are restricted to being
63+
between the characters "0" and "F" such that they can each be converted to
64+
a 32 bit integer.
65+
66+
The string following the header is treated as a set of command line options
67+
that are used as a prefix to any additional command line options passed when
68+
the executable was started. For example, for a simple single hello world
69+
for version `00000001`could be:
70+
71+
```text
72+
JSCODE0000000100000000-e \"console.log('Hello from single binary');\"
73+
```
74+
75+
Support for bundling onto existing Node.js binaries is maintained
76+
in `src/node_single_binary.*`.
77+
78+
Currently only POSIX compliant platform are suppported. The goal
79+
is to expand this to include Windows and MacOS as well.
80+
81+
If a breaking change is required to the content after the header, the version
82+
`VVVVVVVV` should be incremented. Support for a new format
83+
may be introduced as a SemVer minor provided that older versions
84+
are still supported. Removing support for a version is SemVer major.
85+
86+
The `FFFFFFFF` are a set of flags that are used to control the
87+
process of starting the application. For example they might indicate
88+
that some set of arguments should be suppressed on the command line.
89+
Currently no flags are in use.
90+
91+
For test purposes \[LIFE}(<https://github.com/lief-project/LIEF>) can
92+
be used to add a section in the required format. The following is a
93+
simple example for doing so on Linux. It can be improved as it
94+
currently replaces an existing section instead of adding a new
95+
one:
96+
97+
```text
98+
#!/usr/bin/env python
99+
import lief
100+
binary = lief.parse('node')
101+
102+
segment = lief.ELF.Segment()
103+
segment.type = lief.ELF.SEGMENT_TYPES.LOAD
104+
segment.flags = lief.ELF.SEGMENT_FLAGS.R
105+
stringContent = "JSCODE0000000100000000-e \"console.log('Hello from single binary');\""
106+
segment.content = bytearray(stringContent.encode())
107+
segment = binary.replace(segment, binary[lief.ELF.SEGMENT_TYPES.NOTE])
108+
109+
binary.write("hello")
110+
```

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@
506506
'src/node_report_module.cc',
507507
'src/node_report_utils.cc',
508508
'src/node_serdes.cc',
509+
'src/node_single_binary.cc',
509510
'src/node_snapshotable.cc',
510511
'src/node_sockaddr.cc',
511512
'src/node_stat_watcher.cc',

src/node.cc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "node_revert.h"
4141
#include "node_v8_platform-inl.h"
4242
#include "node_version.h"
43+
#include "node_single_binary.h"
4344

4445
#if HAVE_OPENSSL
4546
#include "allocated_buffer-inl.h" // Inlined functions needed by node_crypto.h
@@ -1148,7 +1149,15 @@ void TearDownOncePerProcess() {
11481149
}
11491150

11501151
int Start(int argc, char** argv) {
1151-
InitializationResult result = InitializeOncePerProcess(argc, argv);
1152+
node::single_binary::NewArgs* newArgs =
1153+
node::single_binary::checkForSingleBinary(argc, argv);
1154+
1155+
InitializationResult result;
1156+
if (!newArgs->singleBinary) {
1157+
result = InitializeOncePerProcess(argc, argv);
1158+
} else {
1159+
result = InitializeOncePerProcess(newArgs->argc, newArgs->argv);
1160+
}
11521161
if (result.early_return) {
11531162
return result.exit_code;
11541163
}

src/node_single_binary.cc

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include "node_single_binary.h"
2+
#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__)
3+
#include <link.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
#include <wordexp.h>
7+
#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__)
8+
9+
namespace node {
10+
namespace single_binary {
11+
12+
#define MAGIC_HEADER "JSCODE"
13+
#define VERSION_CHARS "00000001"
14+
#define FLAG_CHARS "00000000"
15+
16+
#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__)
17+
static int
18+
callback(struct dl_phdr_info* info, size_t size, void* data) {
19+
// look for the segment with the magic number
20+
for (int index = 0; index < info->dlpi_phnum; index++) {
21+
if (info->dlpi_phdr[index].p_type == PT_LOAD) {
22+
char* content =
23+
reinterpret_cast<char*>(info->dlpi_addr +
24+
info->dlpi_phdr[index].p_vaddr);
25+
if (strncmp(MAGIC_HEADER,
26+
content,
27+
strlen(MAGIC_HEADER)) == 0) {
28+
*(static_cast<char**>(data)) = content;
29+
break;
30+
}
31+
}
32+
}
33+
return 0;
34+
}
35+
#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__)
36+
37+
struct NewArgs* checkForSingleBinary(int argc, char** argv) {
38+
struct NewArgs* newArgs = new NewArgs;
39+
newArgs->singleBinary = false;
40+
41+
#if defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__)
42+
char* singleBinaryData = nullptr;
43+
dl_iterate_phdr(callback, static_cast<void*>(&singleBinaryData));
44+
if (singleBinaryData != nullptr) {
45+
wordexp_t parsedArgs;
46+
wordexp(&(singleBinaryData[strlen(MAGIC_HEADER) +
47+
strlen(VERSION_CHARS) +
48+
strlen(FLAG_CHARS)]),
49+
&parsedArgs, WRDE_NOCMD);
50+
newArgs->argc = 0;
51+
while (parsedArgs.we_wordv[newArgs->argc] != nullptr)
52+
newArgs->argc++;
53+
newArgs->argc = newArgs->argc + argc;
54+
newArgs->argv = new char*[newArgs->argc];
55+
newArgs->argv[0] = argv[0];
56+
int index = 1;
57+
while (parsedArgs.we_wordv[index-1] != nullptr) {
58+
newArgs->argv[index] = parsedArgs.we_wordv[index-1];
59+
index++;
60+
}
61+
for (int i = 1; i < argc; i++) {
62+
newArgs->argv[index++] = argv[i];
63+
}
64+
65+
newArgs->singleBinary = true;
66+
}
67+
#endif // defined(__POSIX__) && !defined(_AIX) && !defined(__APPLE__)
68+
return newArgs;
69+
}
70+
71+
} // namespace single_binary
72+
} // namespace node

src/node_single_binary.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef SRC_NODE_SINGLE_BINARY_H_
2+
#define SRC_NODE_SINGLE_BINARY_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
namespace node {
7+
namespace single_binary {
8+
9+
struct NewArgs {
10+
bool singleBinary;
11+
int argc;
12+
char** argv;
13+
};
14+
15+
NewArgs* checkForSingleBinary(int argc, char** argv);
16+
17+
} // namespace single_binary
18+
} // namespace node
19+
20+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
21+
22+
#endif // SRC_NODE_SINGLE_BINARY_H_

0 commit comments

Comments
 (0)