Skip to content

Commit 5f22375

Browse files
ezequielgarciarefack
authored andcommitted
src: add support to pass flags to dlopen
* add constants for dlopen flags, which are needed for dlopen's flag passing. * introduce an optional parameter for process.dlopen(), allowing to pass dlopen flags (using values from os.constants.dlopen). If no flags are passed, the default behavior is to load the library with RTLD_LAZY (perform lazy binding) and RTLD_LOCAL (symbols are available only locally). PR-URL: #12794 Refs: #4105 Refs: libuv/libuv#1331 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]>
1 parent aa76ce9 commit 5f22375

File tree

10 files changed

+290
-16
lines changed

10 files changed

+290
-16
lines changed

doc/api/os.md

+37
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,43 @@ The following error codes are specific to the Windows operating system:
11701170
</tr>
11711171
</table>
11721172

1173+
### dlopen Constants
1174+
1175+
If available on the operating system, the following constants
1176+
are exported in `os.constants.dlopen`. See dlopen(3) for detailed
1177+
information.
1178+
1179+
<table>
1180+
<tr>
1181+
<th>Constant</th>
1182+
<th>Description</th>
1183+
</tr>
1184+
<tr>
1185+
<td><code>RTLD_LAZY</code></td>
1186+
<td>Perform lazy binding. Node.js sets this flag by default.</td>
1187+
</tr>
1188+
<tr>
1189+
<td><code>RTLD_NOW</code></td>
1190+
<td>Resolve all undefined symbols in the library before dlopen(3)
1191+
returns.</td>
1192+
</tr>
1193+
<tr>
1194+
<td><code>RTLD_GLOBAL</code></td>
1195+
<td>Symbols defined by the library will be made available for symbol
1196+
resolution of subsequently loaded libraries.</td>
1197+
</tr>
1198+
<tr>
1199+
<td><code>RTLD_LOCAL</code></td>
1200+
<td>The converse of RTLD_GLOBAL. This is the default behavior if neither
1201+
flag is specified.</td>
1202+
</tr>
1203+
<tr>
1204+
<td><code>RTLD_DEEPBIND</code></td>
1205+
<td>Make a self-contained library use its own symbols in preference to
1206+
symbols from previously loaded libraries.</td>
1207+
</tr>
1208+
</table>
1209+
11731210
### libuv Constants
11741211

11751212
<table>

doc/api/process.md

+45
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,48 @@ process's [`ChildProcess.disconnect()`][].
638638
If the Node.js process was not spawned with an IPC channel,
639639
`process.disconnect()` will be `undefined`.
640640

641+
## process.dlopen(module, filename[, flags])
642+
<!-- YAML
643+
added: v0.1.16
644+
changes:
645+
- version: REPLACEME
646+
pr-url: https://github.com/nodejs/node/pull/12794
647+
description: Added support for the `flags` argument.
648+
-->
649+
650+
* `module` {Object}
651+
* `filename` {string}
652+
* `flags` {os.constants.dlopen}. Defaults to `os.constants.dlopen.RTLD_LAZY`.
653+
654+
The `process.dlopen()` method allows to dynamically load shared
655+
objects. It is primarily used by `require()` to load
656+
C++ Addons, and should not be used directly, except in special
657+
cases. In other words, [`require()`][] should be preferred over
658+
`process.dlopen()`, unless there are specific reasons.
659+
660+
The `flags` argument is an integer that allows to specify dlopen
661+
behavior. See the [`os.constants.dlopen`][] documentation for details.
662+
663+
If there are specific reasons to use `process.dlopen()` (for instance,
664+
to specify dlopen flags), it's often useful to use [`require.resolve()`][]
665+
to look up the module's path.
666+
667+
*Note*: An important drawback when calling `process.dlopen()` is that the
668+
`module` instance must be passed. Functions exported by the C++ Addon will
669+
be accessible via `module.exports`.
670+
671+
The example below shows how to load a C++ Addon, named as `binding`,
672+
that exports a `foo` function. All the symbols will be loaded before
673+
the call returns, by passing the `RTLD_NOW` constant. In this example
674+
the constant is assumed to be available.
675+
676+
```js
677+
const os = require('os');
678+
process.dlopen(module, require.resolve('binding'),
679+
os.constants.dlopen.RTLD_NOW);
680+
module.exports.foo();
681+
```
682+
641683
## process.emitWarning(warning[, options])
642684
<!-- YAML
643685
added: 8.0.0
@@ -1841,13 +1883,16 @@ cases:
18411883
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
18421884
[`net.Server`]: net.html#net_class_net_server
18431885
[`net.Socket`]: net.html#net_class_net_socket
1886+
[`os.constants.dlopen`]: os.html#os_dlopen_constants
18441887
[`process.argv`]: #process_process_argv
18451888
[`process.execPath`]: #process_process_execpath
18461889
[`process.exit()`]: #process_process_exit_code
18471890
[`process.exitCode`]: #process_process_exitcode
18481891
[`process.kill()`]: #process_process_kill_pid_signal
18491892
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
1893+
[`require()`]: globals.html#globals_require
18501894
[`require.main`]: modules.html#modules_accessing_the_main_module
1895+
[`require.resolve()`]: globals.html#globals_require_resolve
18511896
[`setTimeout(fn, 0)`]: timers.html#timers_settimeout_callback_delay_args
18521897
[Child Process]: child_process.html
18531898
[Cluster]: cluster.html

lib/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
// Deprecation Code: DEP0008
2828
const constants = process.binding('constants');
2929
Object.assign(exports,
30+
constants.os.dlopen,
3031
constants.os.errno,
3132
constants.os.signals,
3233
constants.fs,

src/node.cc

+67-14
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ typedef int mode_t;
117117
#include <grp.h> // getgrnam()
118118
#endif
119119

120+
#if defined(__POSIX__)
121+
#include <dlfcn.h>
122+
#endif
123+
120124
#ifdef __APPLE__
121125
#include <crt_externs.h>
122126
#define environ (*_NSGetEnviron())
@@ -2503,36 +2507,85 @@ struct node_module* get_linked_module(const char* name) {
25032507
return mp;
25042508
}
25052509

2506-
// DLOpen is process.dlopen(module, filename).
2510+
struct DLib {
2511+
std::string filename_;
2512+
std::string errmsg_;
2513+
void* handle_;
2514+
int flags_;
2515+
2516+
#ifdef __POSIX__
2517+
static const int kDefaultFlags = RTLD_LAZY;
2518+
2519+
bool Open() {
2520+
handle_ = dlopen(filename_.c_str(), flags_);
2521+
if (handle_ != nullptr)
2522+
return true;
2523+
errmsg_ = dlerror();
2524+
return false;
2525+
}
2526+
2527+
void Close() {
2528+
if (handle_ != nullptr)
2529+
dlclose(handle_);
2530+
}
2531+
#else // !__POSIX__
2532+
static const int kDefaultFlags = 0;
2533+
uv_lib_t lib_;
2534+
2535+
bool Open() {
2536+
int ret = uv_dlopen(filename_.c_str(), &lib_);
2537+
if (ret == 0) {
2538+
handle_ = static_cast<void*>(lib_.handle);
2539+
return true;
2540+
}
2541+
errmsg_ = uv_dlerror(&lib_);
2542+
uv_dlclose(&lib_);
2543+
return false;
2544+
}
2545+
2546+
void Close() {
2547+
uv_dlclose(&lib_);
2548+
}
2549+
#endif // !__POSIX__
2550+
};
2551+
2552+
// DLOpen is process.dlopen(module, filename, flags).
25072553
// Used to load 'module.node' dynamically shared objects.
25082554
//
25092555
// FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict
25102556
// when two contexts try to load the same shared object. Maybe have a shadow
25112557
// cache that's a plain C list or hash table that's shared across contexts?
25122558
static void DLOpen(const FunctionCallbackInfo<Value>& args) {
25132559
Environment* env = Environment::GetCurrent(args);
2514-
uv_lib_t lib;
25152560

25162561
CHECK_EQ(modpending, nullptr);
25172562

2518-
if (args.Length() != 2) {
2519-
env->ThrowError("process.dlopen takes exactly 2 arguments.");
2563+
if (args.Length() < 2) {
2564+
env->ThrowError("process.dlopen needs at least 2 arguments.");
25202565
return;
25212566
}
25222567

2568+
int32_t flags = DLib::kDefaultFlags;
2569+
if (args.Length() > 2 && !args[2]->Int32Value(env->context()).To(&flags)) {
2570+
return env->ThrowTypeError("flag argument must be an integer.");
2571+
}
2572+
25232573
Local<Object> module = args[0]->ToObject(env->isolate()); // Cast
25242574
node::Utf8Value filename(env->isolate(), args[1]); // Cast
2525-
const bool is_dlopen_error = uv_dlopen(*filename, &lib);
2575+
DLib dlib;
2576+
dlib.filename_ = *filename;
2577+
dlib.flags_ = flags;
2578+
bool is_opened = dlib.Open();
25262579

25272580
// Objects containing v14 or later modules will have registered themselves
25282581
// on the pending list. Activate all of them now. At present, only one
25292582
// module per object is supported.
25302583
node_module* const mp = modpending;
25312584
modpending = nullptr;
25322585

2533-
if (is_dlopen_error) {
2534-
Local<String> errmsg = OneByteString(env->isolate(), uv_dlerror(&lib));
2535-
uv_dlclose(&lib);
2586+
if (!is_opened) {
2587+
Local<String> errmsg = OneByteString(env->isolate(), dlib.errmsg_.c_str());
2588+
dlib.Close();
25362589
#ifdef _WIN32
25372590
// Windows needs to add the filename into the error message
25382591
errmsg = String::Concat(errmsg, args[1]->ToString(env->isolate()));
@@ -2542,7 +2595,7 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
25422595
}
25432596

25442597
if (mp == nullptr) {
2545-
uv_dlclose(&lib);
2598+
dlib.Close();
25462599
env->ThrowError("Module did not self-register.");
25472600
return;
25482601
}
@@ -2569,18 +2622,18 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
25692622
}
25702623

25712624
// NOTE: `mp` is allocated inside of the shared library's memory, calling
2572-
// `uv_dlclose` will deallocate it
2573-
uv_dlclose(&lib);
2625+
// `dlclose` will deallocate it
2626+
dlib.Close();
25742627
env->ThrowError(errmsg);
25752628
return;
25762629
}
25772630
if (mp->nm_flags & NM_F_BUILTIN) {
2578-
uv_dlclose(&lib);
2631+
dlib.Close();
25792632
env->ThrowError("Built-in module self-registered.");
25802633
return;
25812634
}
25822635

2583-
mp->nm_dso_handle = lib.handle;
2636+
mp->nm_dso_handle = dlib.handle_;
25842637
mp->nm_link = modlist_addon;
25852638
modlist_addon = mp;
25862639

@@ -2592,7 +2645,7 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
25922645
} else if (mp->nm_register_func != nullptr) {
25932646
mp->nm_register_func(exports, module, mp->nm_priv);
25942647
} else {
2595-
uv_dlclose(&lib);
2648+
dlib.Close();
25962649
env->ThrowError("Module has no declared entry point.");
25972650
return;
25982651
}

src/node_constants.cc

+32
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
# endif // !OPENSSL_NO_ENGINE
4545
#endif
4646

47+
#if defined(__POSIX__)
48+
#include <dlfcn.h>
49+
#endif
50+
4751
namespace node {
4852

4953
using v8::Local;
@@ -1238,6 +1242,28 @@ void DefineZlibConstants(Local<Object> target) {
12381242
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_LEVEL);
12391243
}
12401244

1245+
void DefineDLOpenConstants(Local<Object> target) {
1246+
#ifdef RTLD_LAZY
1247+
NODE_DEFINE_CONSTANT(target, RTLD_LAZY);
1248+
#endif
1249+
1250+
#ifdef RTLD_NOW
1251+
NODE_DEFINE_CONSTANT(target, RTLD_NOW);
1252+
#endif
1253+
1254+
#ifdef RTLD_GLOBAL
1255+
NODE_DEFINE_CONSTANT(target, RTLD_GLOBAL);
1256+
#endif
1257+
1258+
#ifdef RTLD_LOCAL
1259+
NODE_DEFINE_CONSTANT(target, RTLD_LOCAL);
1260+
#endif
1261+
1262+
#ifdef RTLD_DEEPBIND
1263+
NODE_DEFINE_CONSTANT(target, RTLD_DEEPBIND);
1264+
#endif
1265+
}
1266+
12411267
} // anonymous namespace
12421268

12431269
void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
@@ -1267,18 +1293,24 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
12671293
CHECK(zlib_constants->SetPrototype(env->context(),
12681294
Null(env->isolate())).FromJust());
12691295

1296+
Local<Object> dlopen_constants = Object::New(isolate);
1297+
CHECK(dlopen_constants->SetPrototype(env->context(),
1298+
Null(env->isolate())).FromJust());
1299+
12701300
DefineErrnoConstants(err_constants);
12711301
DefineWindowsErrorConstants(err_constants);
12721302
DefineSignalConstants(sig_constants);
12731303
DefineSystemConstants(fs_constants);
12741304
DefineOpenSSLConstants(crypto_constants);
12751305
DefineCryptoConstants(crypto_constants);
12761306
DefineZlibConstants(zlib_constants);
1307+
DefineDLOpenConstants(dlopen_constants);
12771308

12781309
// Define libuv constants.
12791310
NODE_DEFINE_CONSTANT(os_constants, UV_UDP_REUSEADDR);
12801311
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_EXCL);
12811312

1313+
os_constants->Set(OneByteString(isolate, "dlopen"), dlopen_constants);
12821314
os_constants->Set(OneByteString(isolate, "errno"), err_constants);
12831315
os_constants->Set(OneByteString(isolate, "signals"), sig_constants);
12841316
target->Set(OneByteString(isolate, "os"), os_constants);
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <node.h>
2+
#include <v8.h>
3+
4+
#ifndef _WIN32
5+
6+
#include <dlfcn.h>
7+
8+
extern "C" const char* dlopen_pong(void) {
9+
return "pong";
10+
}
11+
12+
namespace {
13+
14+
using v8::FunctionCallbackInfo;
15+
using v8::Isolate;
16+
using v8::Local;
17+
using v8::Object;
18+
using v8::String;
19+
using v8::Value;
20+
21+
typedef const char* (*ping)(void);
22+
23+
static ping ping_func;
24+
25+
void LoadLibrary(const FunctionCallbackInfo<Value>& args) {
26+
const String::Utf8Value filename(args[0]);
27+
void* handle = dlopen(*filename, RTLD_LAZY);
28+
assert(handle != nullptr);
29+
ping_func = reinterpret_cast<ping>(dlsym(handle, "dlopen_ping"));
30+
assert(ping_func != nullptr);
31+
}
32+
33+
void Ping(const FunctionCallbackInfo<Value>& args) {
34+
Isolate* isolate = args.GetIsolate();
35+
assert(ping_func != nullptr);
36+
args.GetReturnValue().Set(String::NewFromUtf8(isolate, ping_func()));
37+
}
38+
39+
void init(Local<Object> exports) {
40+
NODE_SET_METHOD(exports, "load", LoadLibrary);
41+
NODE_SET_METHOD(exports, "ping", Ping);
42+
}
43+
44+
NODE_MODULE(binding, init)
45+
46+
} // anonymous namespace
47+
48+
#endif // _WIN32

0 commit comments

Comments
 (0)