Skip to content

Commit a63e452

Browse files
committed
erts: Allow erlang:load_nif/2 to load a NIF from an in-memory binary
Add support for passing `#{memory => NifSO::binary(), filename => string():binary()}` as the first argument to erlang:load_nif/2. When a map is given with the 'memory' key/value present, the NIF shared object is loaded directly from the binary image in memory instead of from the file system. If the map only contains the 'filename' key/value it'll revert to the default behavior of loading the SO from file. This addition is needed so that escripts can package SO dependencies inside and be distributed as a single executable. Implementation -------------- * erts_dlopen_mem() — new static helper in erl_nif.c (Linux/POSIX only). On Linux >= 3.17 it uses memfd_create(2) to create an anonymous in-memory file, writes the SO image into it, and calls dlopen(3) via /proc/self/fd/<fd>. On older Linux kernels and other POSIX systems it falls back to shm_open(3) + /dev/shm/<name>. The implementation is derived from the https://github.com/saleyn/memfd_create proof of concept. * erts_load_nif() — extended argument parsing: - Plain filename() continues to work unchanged. - #{memory => Binary, filename => Filename} map: Binary is extracted via erts_get_aligned_binary_bytes(), passed to erts_dlopen_mem(), and the resulting dlopen handle is used directly. The Filename string serves only as a label inside the memory-backed file descriptor. - The open-from-memory step is performed before the existing else-if validation chain (version checks, module-name check, create_lib) so both code paths share the same validation and bookkeeping. - erts_sys_ddll_open() is skipped (via !load_from_mem guard) when a handle was already obtained from memory. * The feature is guarded by #if defined(HAVE_DLOPEN) && defined(__unix__). On unsupported platforms load_nif/2 returns {error,{load_failed,…}}. Testing ------- * nif_SUITE: new test case load_nif_from_mem - Happy path: reads nif_mod.1.so into a binary, calls erlang:load_nif(#{filename => Path, memory => Binary}, []) via nif_mod:load_nif_lib_from_mem/3, and asserts lib_version() == 1. - Error path: non-binary second element returns {error,{bad_lib,_}}. * nif_mod.erl: new exported helper load_nif_lib_from_mem/3 so that the load_nif call site lives inside the NIF module (required by the BIF).
1 parent 6a54781 commit a63e452

6 files changed

Lines changed: 444 additions & 44 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ JAVADOC-GENERATED
213213
/erts/emulator/test/*_native_SUITE.erl
214214
/erts/emulator/test/*_SUITE_data/Makefile
215215
/erts/emulator/test/*_stripped_types_SUITE.erl
216+
/erts/emulator/test/nif_SUITE_data/*.so
216217
/erts/test/install_SUITE_data/install_bin
217218
/erts/test/autoimport_SUITE_data/erlang.xml
218219
/erts/emulator/make_test_dir/

erts/emulator/beam/atom.names

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ atom extra
311311
atom false
312312
atom fcgi
313313
atom fd
314+
atom filename
314315
atom first
315316
atom firstline
316317
atom flags

erts/emulator/beam/erl_nif.c

Lines changed: 259 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
#endif
4545

4646
#include "erl_nif.h"
47-
4847
#include "sys.h"
4948
#include "global.h"
5049
#include "erl_binary.h"
@@ -69,11 +68,161 @@
6968
#endif
7069
#include "jit/beam_asm.h"
7170
#include "erl_global_literals.h"
71+
7272
#include "erl_iolist.h"
73+
#include "erl_atom_table.h"
7374

7475
#include <limits.h>
7576
#include <stddef.h> /* offsetof */
7677

78+
#undef SHMOPEN_USE_LINUX_MEMFD
79+
#undef SHMOPEN_USE_SHM_ANON
80+
#undef SHMOPEN_USE_SHM_MKSTEMP
81+
#undef SHMOPEN_USE_POSIX
82+
#undef USE_ERTS_DLOPEN_MEM
83+
84+
#ifdef HAVE_DLFCN_H
85+
#include <dlfcn.h>
86+
#endif
87+
#ifdef HAVE_FCNTL_H
88+
#include <fcntl.h>
89+
#endif
90+
#ifdef HAVE_SYS_MMAN_H
91+
#include <sys/mman.h>
92+
#endif
93+
#ifdef HAVE_SYS_STAT_H
94+
#include <sys/stat.h>
95+
#endif
96+
#ifdef HAVE_SYS_TIME_H
97+
#include <sys/time.h>
98+
#endif
99+
#ifdef HAVE_UNISTD_H
100+
#include <unistd.h>
101+
#endif
102+
#if defined(__linux__)
103+
# if defined(__linux__)
104+
# include <sys/syscall.h>
105+
# endif
106+
# include <sys/utsname.h>
107+
# ifdef SYS_memfd_create
108+
# define SHMOPEN_USE_LINUX_MEMFD
109+
# else
110+
# define SHMOPEN_USE_SHM_POSIX
111+
# endif
112+
# define USE_ERTS_DLOPEN_MEM
113+
#elif defined(__FreeBSD__)
114+
# define SHMOPEN_USE_SHM_ANON
115+
# define USE_ERTS_DLOPEN_MEM
116+
#elif defined(__OpenBSD__)
117+
# define SHMOPEN_USE_SHM_MKSTEMP
118+
# define USE_ERTS_DLOPEN_MEM
119+
#else
120+
# if defined(__APPLE__) || defined(__MACH__) || defined(__DARWIN__) || \
121+
defined(__NetBSD__) || defined(__DragonFly__) || defined(__HAIKU__) || \
122+
defined(__sun)
123+
# define SHMOPEN_USE_POSIX
124+
# define USE_ERTS_DLOPEN_MEM
125+
# endif
126+
#endif
127+
128+
#define ERTS_NIF_MEM_NAME_MAX 64
129+
#define ERTS_MIN_KERNEL_VSN 317
130+
131+
#ifdef USE_ERTS_DLOPEN_MEM
132+
/* Always declare erts_dlopen_mem for all platforms */
133+
static void *erts_dlopen_mem(const char *filename, const void *mem, size_t size);
134+
135+
static char* erts_shm_name(const char *pfx, const char *sfx, char* buf, size_t buf_len)
136+
{
137+
/* Generate a unique name for the shared memory object. */
138+
struct timeval tv;
139+
gettimeofday(&tv, NULL);
140+
enif_snprintf(buf, buf_len, "%s-%-06ld%s", pfx, (long)tv.tv_usec, sfx);
141+
return buf;
142+
}
143+
144+
/*
145+
* Load a shared object from memory using memfd_create (Linux >= 3.17) or
146+
* shm_open (other POSIX systems). Returns a dlopen(3) handle on success,
147+
* or NULL on failure.
148+
*
149+
* filename - a label associated with the in-memory file (may be NULL)
150+
* mem - pointer to the SO image in memory
151+
* size - byte length of the SO image
152+
*/
153+
static void *erts_dlopen_mem(const char *filename, const void *mem, size_t size)
154+
{
155+
char path[PATH_MAX];
156+
char shm_name[NAME_MAX];
157+
int shm_fd = -1;
158+
void *handle = NULL;
159+
#if defined(SHMOPEN_USE_SHM_MKSTEMP) || defined(SHMOPEN_USE_POSIX)
160+
int need_unlink = 0;
161+
#endif
162+
163+
path[0] = '\0';
164+
shm_name[0] = '\0';
165+
166+
#if defined(SHMOPEN_USE_LINUX_MEMFD)
167+
if (!filename)
168+
filename = erts_shm_name("erl-nif-mem-", ".so", shm_name, sizeof(shm_name));
169+
shm_fd = memfd_create(filename, MFD_CLOEXEC);
170+
if (shm_fd < 0)
171+
return NULL;
172+
enif_snprintf(path, sizeof(path), "/proc/self/fd/%d", shm_fd);
173+
174+
#elif defined(SHMOPEN_USE_SHM_ANON)
175+
if (filename)
176+
return NULL;
177+
shm_fd = shm_open(SHM_ANON, O_RDWR, 0);
178+
if (shm_fd < 0)
179+
return NULL;
180+
# if defined(__APPLE__)
181+
if (fcntl(shm_fd, F_GETPATH, path) < 0)
182+
goto err;
183+
# else
184+
enif_snprintf(path, sizeof(path), "/dev/fd/%d", shm_fd);
185+
# endif
186+
187+
#elif defined(SHMOPEN_USE_SHM_MKSTEMP)
188+
enif_snprintf(path, sizeof(path), "/tmp/%s-XXXXXX", filename ? filename : "enif-shm");
189+
shm_fd = shm_mkstemp(path);
190+
if (shm_fd < 0)
191+
return NULL;
192+
need_unlink = 1;
193+
194+
#else /* SHMOPEN_USE_POSIX */
195+
if (filename) {
196+
enif_snprintf(shm_name, sizeof(shm_name), "%s", filename);
197+
} else {
198+
erts_shm_name("enif-shm-", ".so", shm_name, sizeof(shm_name));
199+
}
200+
enif_snprintf(path, sizeof(path), "/dev/shm/%s", shm_name);
201+
shm_fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
202+
if (shm_fd < 0)
203+
return NULL;
204+
need_unlink = 1;
205+
#endif
206+
207+
if (ftruncate(shm_fd, size) || write(shm_fd, mem, size) != (ssize_t)size)
208+
goto err;
209+
210+
handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
211+
212+
err:
213+
# if defined(SHMOPEN_USE_SHM_MKSTEMP)
214+
if (need_unlink)
215+
unlink(path);
216+
# elif defined(SHMOPEN_USE_POSIX)
217+
if (need_unlink)
218+
shm_unlink(path);
219+
# endif
220+
if (shm_fd >= 0)
221+
close(shm_fd);
222+
return handle;
223+
}
224+
#endif /* USE_ERTS_DLOPEN_MEM */
225+
77226
#define ERTS_NIF_HALT_INFO_FLAG_BLOCK (1 << 0)
78227
#define ERTS_NIF_HALT_INFO_FLAG_HALTING (1 << 1)
79228
#define ERTS_NIF_HALT_INFO_FLAG_WAITING (1 << 2)
@@ -4721,6 +4870,8 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47214870
Eterm ret = am_ok;
47224871
int veto;
47234872
int is_static = 0;
4873+
int load_from_mem = 0; /* non-zero when loading from in-memory binary */
4874+
Eterm nif_binary = THE_NON_VALUE;
47244875
struct erl_module_nif* lib = NULL;
47254876
struct erl_module_instance* this_mi;
47264877
struct erl_module_instance* prev_mi;
@@ -4731,13 +4882,45 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47314882
/* since lib_name is used in error messages */
47324883
encoding = ERL_FILENAME_UTF8;
47334884
}
4885+
4886+
/*
4887+
* Accept either:
4888+
* Filename::string()|binary() -- load from file (existing behaviour)
4889+
* #{filename := string()|binary()} -- load from file (map form, no memory)
4890+
* #{memory := NifSO} -- load from memory, auto-generated filename
4891+
* #{memory := NifSO, filename := Filename} -- load from memory with explicit filename
4892+
*
4893+
* If the map contains only 'filename', it falls back to the string|binary filename implementation.
4894+
*/
4895+
if (is_map(filename)) {
4896+
const Eterm *fnm_val = erts_maps_get(am_filename, filename);
4897+
const Eterm *mem_val = erts_maps_get(am_memory, filename);
4898+
if (fnm_val == NULL && mem_val == NULL) {
4899+
return load_nif_error(c_p, "bad_lib",
4900+
"load_nif/2: map argument must contain a 'memory' or 'filename' key");
4901+
}
4902+
if (mem_val) {
4903+
if (!is_bitstring(*mem_val) || TAIL_BITS(bitstring_size(*mem_val)) != 0) {
4904+
return load_nif_error(c_p, "bad_lib",
4905+
"load_nif/2: 'memory' value must be a list or binary");
4906+
}
4907+
nif_binary = *mem_val;
4908+
load_from_mem = 1;
4909+
}
4910+
if (fnm_val) {
4911+
filename = *fnm_val; /* fall through to filename decoding from argument */
4912+
} else {
4913+
lib_name = NULL; /* anonymous: erts_dlopen_mem will generate a name */
4914+
goto after_lib_name;
4915+
}
4916+
}
47344917
lib_name = erts_convert_filename_to_encoding(filename, NULL, 0,
47354918
ERTS_ALC_T_TMP, 1, 0, encoding,
4736-
NULL, 0);
4737-
if (!lib_name) {
4919+
NULL, 0);
4920+
if (!lib_name)
47384921
return THE_NON_VALUE;
4739-
}
47404922

4923+
after_lib_name:
47414924
/* Find calling module */
47424925
caller = erts_find_function_from_pc(I);
47434926
ASSERT(caller != NULL);
@@ -4756,24 +4939,65 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47564939
this_mi = &module_p->curr;
47574940
prev_mi = &module_p->old;
47584941
if (in_area(caller, module_p->old.code_hdr, module_p->old.code_length)) {
4759-
ret = load_nif_error(c_p, "old_code", "Calling load_nif from old "
4760-
"module '%T' not allowed", mod_atom);
4761-
goto error;
4942+
ret = load_nif_error(c_p, "old_code", "Calling load_nif from old "
4943+
"module '%T' not allowed", mod_atom);
4944+
goto error;
47624945
} else if (module_p->on_load) {
4763-
ASSERT(((module_p->on_load)->code_hdr)->on_load);
4764-
if (module_p->curr.code_hdr) {
4765-
prev_mi = &module_p->curr;
4766-
} else {
4767-
prev_mi = &module_p->old;
4768-
}
4769-
this_mi = module_p->on_load;
4946+
ASSERT(((module_p->on_load)->code_hdr)->on_load);
4947+
if (module_p->curr.code_hdr) {
4948+
prev_mi = &module_p->curr;
4949+
} else {
4950+
prev_mi = &module_p->old;
4951+
}
4952+
this_mi = module_p->on_load;
4953+
}
4954+
4955+
/* If the caller passed #{memory => NifSO::binary(), filename => Filename},
4956+
* open the shared object from memory now so that `handle` is ready for
4957+
* the common else-if chain below (which runs
4958+
* erts_sys_ddll_load_nif_init / call_nif_init, version checks, and
4959+
* create_lib for both file and memory loads). */
4960+
if (!is_static && load_from_mem) {
4961+
#if defined(USE_ERTS_DLOPEN_MEM)
4962+
const byte *bin_bytes = NULL;
4963+
Uint bin_size = 0;
4964+
const byte *tmp_alloc = NULL;
4965+
static const int max_path_len = PATH_MAX - 9; /* space for "/dev/shm/" */
4966+
if (lib_name != NULL) {
4967+
int lib_name_len = sys_strlen(lib_name);
4968+
if (lib_name_len > max_path_len - 1) { /* -1 for null terminator added */
4969+
ret = load_nif_error(c_p, "load_failed",
4970+
"NIF library path too long: %d (max %d bytes)",
4971+
lib_name_len, max_path_len);
4972+
goto error;
4973+
}
4974+
}
4975+
bin_bytes = erts_get_aligned_binary_bytes(nif_binary, &bin_size, &tmp_alloc);
4976+
if (!bin_bytes) {
4977+
ret = load_nif_error(c_p, "load_failed",
4978+
"Failed to access NIF binary data");
4979+
goto error;
4980+
}
4981+
handle = erts_dlopen_mem(lib_name, bin_bytes, (size_t)bin_size);
4982+
erts_free_aligned_binary_bytes(tmp_alloc);
4983+
if (!handle) {
4984+
ret = load_nif_error(c_p, "load_failed",
4985+
"Failed to load NIF library from memory: '%s'",
4986+
dlerror());
4987+
goto error;
4988+
}
4989+
#else
4990+
ret = load_nif_error(c_p, "load_failed",
4991+
"Loading NIF from memory is not supported on this platform");
4992+
goto error;
4993+
#endif
47704994
}
47714995

47724996
if (this_mi->nif != NULL) {
47734997
ret = load_nif_error(c_p,"reload","NIF library already loaded"
47744998
" (reload disallowed since OTP 20).");
47754999
}
4776-
else if (!is_static &&
5000+
else if (!is_static && !load_from_mem &&
47775001
(err=erts_sys_ddll_open(lib_name, &handle, &errdesc)) != ERL_DE_NO_ERROR) {
47785002
const char slogan[] = "Failed to load NIF library";
47795003
if (strstr(errdesc.str, lib_name) != NULL) {
@@ -4785,14 +5009,14 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
47855009
}
47865010
else if (!is_static &&
47875011
erts_sys_ddll_load_nif_init(handle, &init_func, &errdesc) != ERL_DE_NO_ERROR) {
4788-
ret = load_nif_error(c_p, bad_lib, "Failed to find library init"
5012+
ret = load_nif_error(c_p, bad_lib, "Failed to find library init"
47895013
" function: '%s'", errdesc.str);
47905014

47915015
}
47925016
else if (!is_static &&
47935017
(erts_add_taint(mod_atom),
47945018
(entry = erts_sys_ddll_call_nif_init(init_func)) == NULL)) {
4795-
ret = load_nif_error(c_p, bad_lib, "Library init-call unsuccessful");
5019+
ret = load_nif_error(c_p, bad_lib, "Library init-call unsuccessful");
47965020
}
47975021
else if (entry->major > ERL_NIF_MAJOR_VERSION
47985022
|| (entry->major == ERL_NIF_MAJOR_VERSION
@@ -4811,12 +5035,12 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
48115035
}
48125036
else if (AT_LEAST_VERSION(entry, 2, 1)
48135037
&& sys_strcmp(entry->vm_variant, ERL_NIF_VM_VARIANT) != 0) {
4814-
ret = load_nif_error(c_p, bad_lib, "Library (%s) not compiled for "
5038+
ret = load_nif_error(c_p, bad_lib, "Library (%s) not compiled for "
48155039
"this vm variant (%s).",
48165040
entry->vm_variant, ERL_NIF_VM_VARIANT);
48175041
}
48185042
else if (!erts_is_atom_str((char*)entry->name, mod_atom, 1)) {
4819-
ret = load_nif_error(c_p, bad_lib, "Library module name '%s' does not"
5043+
ret = load_nif_error(c_p, bad_lib, "Library module name '%s' does not"
48205044
" match calling module '%T'", entry->name, mod_atom);
48215045
}
48225046
else {
@@ -4941,7 +5165,7 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
49415165
}
49425166

49435167
if (ret != am_ok) {
4944-
goto error;
5168+
goto error;
49455169
}
49465170
ASSERT(lib);
49475171
ASSERT(lib->finish->nstubs_hashed == lib->entry.num_of_funcs);
@@ -5016,22 +5240,23 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args)
50165240
}
50175241
else {
50185242
error:
5019-
rollback_opened_resource_types();
5020-
ASSERT(ret != am_ok);
5021-
if (lib != NULL) {
5022-
if (lib->finish != NULL) {
5023-
erase_hashed_stubs(lib->finish);
5024-
erts_free(ERTS_ALC_T_NIF, lib->finish);
5025-
}
5026-
erts_free(ERTS_ALC_T_NIF, lib);
5027-
}
5028-
if (handle != NULL) {
5029-
erts_sys_ddll_close(handle);
5030-
}
5031-
erts_sys_ddll_free_error(&errdesc);
5243+
rollback_opened_resource_types();
5244+
ASSERT(ret != am_ok);
5245+
if (lib != NULL) {
5246+
if (lib->finish != NULL) {
5247+
erase_hashed_stubs(lib->finish);
5248+
erts_free(ERTS_ALC_T_NIF, lib->finish);
5249+
}
5250+
erts_free(ERTS_ALC_T_NIF, lib);
5251+
}
5252+
if (handle != NULL) {
5253+
erts_sys_ddll_close(handle);
5254+
}
5255+
erts_sys_ddll_free_error(&errdesc);
50325256
}
50335257

5034-
erts_free(ERTS_ALC_T_TMP, lib_name);
5258+
if (lib_name)
5259+
erts_free(ERTS_ALC_T_TMP, lib_name);
50355260

50365261
BIF_RET(ret);
50375262
}

0 commit comments

Comments
 (0)