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: https://github.com/nodejs/node/pull/12794 Refs: https://github.com/nodejs/node/pull/4105 Refs: https://github.com/libuv/libuv/pull/1331 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
This commit is contained in:
parent
aa76ce943b
commit
5f22375922
@ -1170,6 +1170,43 @@ The following error codes are specific to the Windows operating system:
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### dlopen Constants
|
||||
|
||||
If available on the operating system, the following constants
|
||||
are exported in `os.constants.dlopen`. See dlopen(3) for detailed
|
||||
information.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Constant</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>RTLD_LAZY</code></td>
|
||||
<td>Perform lazy binding. Node.js sets this flag by default.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>RTLD_NOW</code></td>
|
||||
<td>Resolve all undefined symbols in the library before dlopen(3)
|
||||
returns.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>RTLD_GLOBAL</code></td>
|
||||
<td>Symbols defined by the library will be made available for symbol
|
||||
resolution of subsequently loaded libraries.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>RTLD_LOCAL</code></td>
|
||||
<td>The converse of RTLD_GLOBAL. This is the default behavior if neither
|
||||
flag is specified.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>RTLD_DEEPBIND</code></td>
|
||||
<td>Make a self-contained library use its own symbols in preference to
|
||||
symbols from previously loaded libraries.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### libuv Constants
|
||||
|
||||
<table>
|
||||
|
@ -638,6 +638,48 @@ process's [`ChildProcess.disconnect()`][].
|
||||
If the Node.js process was not spawned with an IPC channel,
|
||||
`process.disconnect()` will be `undefined`.
|
||||
|
||||
## process.dlopen(module, filename[, flags])
|
||||
<!-- YAML
|
||||
added: v0.1.16
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/12794
|
||||
description: Added support for the `flags` argument.
|
||||
-->
|
||||
|
||||
* `module` {Object}
|
||||
* `filename` {string}
|
||||
* `flags` {os.constants.dlopen}. Defaults to `os.constants.dlopen.RTLD_LAZY`.
|
||||
|
||||
The `process.dlopen()` method allows to dynamically load shared
|
||||
objects. It is primarily used by `require()` to load
|
||||
C++ Addons, and should not be used directly, except in special
|
||||
cases. In other words, [`require()`][] should be preferred over
|
||||
`process.dlopen()`, unless there are specific reasons.
|
||||
|
||||
The `flags` argument is an integer that allows to specify dlopen
|
||||
behavior. See the [`os.constants.dlopen`][] documentation for details.
|
||||
|
||||
If there are specific reasons to use `process.dlopen()` (for instance,
|
||||
to specify dlopen flags), it's often useful to use [`require.resolve()`][]
|
||||
to look up the module's path.
|
||||
|
||||
*Note*: An important drawback when calling `process.dlopen()` is that the
|
||||
`module` instance must be passed. Functions exported by the C++ Addon will
|
||||
be accessible via `module.exports`.
|
||||
|
||||
The example below shows how to load a C++ Addon, named as `binding`,
|
||||
that exports a `foo` function. All the symbols will be loaded before
|
||||
the call returns, by passing the `RTLD_NOW` constant. In this example
|
||||
the constant is assumed to be available.
|
||||
|
||||
```js
|
||||
const os = require('os');
|
||||
process.dlopen(module, require.resolve('binding'),
|
||||
os.constants.dlopen.RTLD_NOW);
|
||||
module.exports.foo();
|
||||
```
|
||||
|
||||
## process.emitWarning(warning[, options])
|
||||
<!-- YAML
|
||||
added: 8.0.0
|
||||
@ -1841,13 +1883,16 @@ cases:
|
||||
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
|
||||
[`net.Server`]: net.html#net_class_net_server
|
||||
[`net.Socket`]: net.html#net_class_net_socket
|
||||
[`os.constants.dlopen`]: os.html#os_dlopen_constants
|
||||
[`process.argv`]: #process_process_argv
|
||||
[`process.execPath`]: #process_process_execpath
|
||||
[`process.exit()`]: #process_process_exit_code
|
||||
[`process.exitCode`]: #process_process_exitcode
|
||||
[`process.kill()`]: #process_process_kill_pid_signal
|
||||
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
|
||||
[`require()`]: globals.html#globals_require
|
||||
[`require.main`]: modules.html#modules_accessing_the_main_module
|
||||
[`require.resolve()`]: globals.html#globals_require_resolve
|
||||
[`setTimeout(fn, 0)`]: timers.html#timers_settimeout_callback_delay_args
|
||||
[Child Process]: child_process.html
|
||||
[Cluster]: cluster.html
|
||||
|
@ -27,6 +27,7 @@
|
||||
// Deprecation Code: DEP0008
|
||||
const constants = process.binding('constants');
|
||||
Object.assign(exports,
|
||||
constants.os.dlopen,
|
||||
constants.os.errno,
|
||||
constants.os.signals,
|
||||
constants.fs,
|
||||
|
81
src/node.cc
81
src/node.cc
@ -117,6 +117,10 @@ typedef int mode_t;
|
||||
#include <grp.h> // getgrnam()
|
||||
#endif
|
||||
|
||||
#if defined(__POSIX__)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <crt_externs.h>
|
||||
#define environ (*_NSGetEnviron())
|
||||
@ -2503,7 +2507,49 @@ struct node_module* get_linked_module(const char* name) {
|
||||
return mp;
|
||||
}
|
||||
|
||||
// DLOpen is process.dlopen(module, filename).
|
||||
struct DLib {
|
||||
std::string filename_;
|
||||
std::string errmsg_;
|
||||
void* handle_;
|
||||
int flags_;
|
||||
|
||||
#ifdef __POSIX__
|
||||
static const int kDefaultFlags = RTLD_LAZY;
|
||||
|
||||
bool Open() {
|
||||
handle_ = dlopen(filename_.c_str(), flags_);
|
||||
if (handle_ != nullptr)
|
||||
return true;
|
||||
errmsg_ = dlerror();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
if (handle_ != nullptr)
|
||||
dlclose(handle_);
|
||||
}
|
||||
#else // !__POSIX__
|
||||
static const int kDefaultFlags = 0;
|
||||
uv_lib_t lib_;
|
||||
|
||||
bool Open() {
|
||||
int ret = uv_dlopen(filename_.c_str(), &lib_);
|
||||
if (ret == 0) {
|
||||
handle_ = static_cast<void*>(lib_.handle);
|
||||
return true;
|
||||
}
|
||||
errmsg_ = uv_dlerror(&lib_);
|
||||
uv_dlclose(&lib_);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
uv_dlclose(&lib_);
|
||||
}
|
||||
#endif // !__POSIX__
|
||||
};
|
||||
|
||||
// DLOpen is process.dlopen(module, filename, flags).
|
||||
// Used to load 'module.node' dynamically shared objects.
|
||||
//
|
||||
// FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict
|
||||
@ -2511,18 +2557,25 @@ struct node_module* get_linked_module(const char* name) {
|
||||
// cache that's a plain C list or hash table that's shared across contexts?
|
||||
static void DLOpen(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
uv_lib_t lib;
|
||||
|
||||
CHECK_EQ(modpending, nullptr);
|
||||
|
||||
if (args.Length() != 2) {
|
||||
env->ThrowError("process.dlopen takes exactly 2 arguments.");
|
||||
if (args.Length() < 2) {
|
||||
env->ThrowError("process.dlopen needs at least 2 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t flags = DLib::kDefaultFlags;
|
||||
if (args.Length() > 2 && !args[2]->Int32Value(env->context()).To(&flags)) {
|
||||
return env->ThrowTypeError("flag argument must be an integer.");
|
||||
}
|
||||
|
||||
Local<Object> module = args[0]->ToObject(env->isolate()); // Cast
|
||||
node::Utf8Value filename(env->isolate(), args[1]); // Cast
|
||||
const bool is_dlopen_error = uv_dlopen(*filename, &lib);
|
||||
DLib dlib;
|
||||
dlib.filename_ = *filename;
|
||||
dlib.flags_ = flags;
|
||||
bool is_opened = dlib.Open();
|
||||
|
||||
// Objects containing v14 or later modules will have registered themselves
|
||||
// on the pending list. Activate all of them now. At present, only one
|
||||
@ -2530,9 +2583,9 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
|
||||
node_module* const mp = modpending;
|
||||
modpending = nullptr;
|
||||
|
||||
if (is_dlopen_error) {
|
||||
Local<String> errmsg = OneByteString(env->isolate(), uv_dlerror(&lib));
|
||||
uv_dlclose(&lib);
|
||||
if (!is_opened) {
|
||||
Local<String> errmsg = OneByteString(env->isolate(), dlib.errmsg_.c_str());
|
||||
dlib.Close();
|
||||
#ifdef _WIN32
|
||||
// Windows needs to add the filename into the error message
|
||||
errmsg = String::Concat(errmsg, args[1]->ToString(env->isolate()));
|
||||
@ -2542,7 +2595,7 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
if (mp == nullptr) {
|
||||
uv_dlclose(&lib);
|
||||
dlib.Close();
|
||||
env->ThrowError("Module did not self-register.");
|
||||
return;
|
||||
}
|
||||
@ -2569,18 +2622,18 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
// NOTE: `mp` is allocated inside of the shared library's memory, calling
|
||||
// `uv_dlclose` will deallocate it
|
||||
uv_dlclose(&lib);
|
||||
// `dlclose` will deallocate it
|
||||
dlib.Close();
|
||||
env->ThrowError(errmsg);
|
||||
return;
|
||||
}
|
||||
if (mp->nm_flags & NM_F_BUILTIN) {
|
||||
uv_dlclose(&lib);
|
||||
dlib.Close();
|
||||
env->ThrowError("Built-in module self-registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
mp->nm_dso_handle = lib.handle;
|
||||
mp->nm_dso_handle = dlib.handle_;
|
||||
mp->nm_link = modlist_addon;
|
||||
modlist_addon = mp;
|
||||
|
||||
@ -2592,7 +2645,7 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
|
||||
} else if (mp->nm_register_func != nullptr) {
|
||||
mp->nm_register_func(exports, module, mp->nm_priv);
|
||||
} else {
|
||||
uv_dlclose(&lib);
|
||||
dlib.Close();
|
||||
env->ThrowError("Module has no declared entry point.");
|
||||
return;
|
||||
}
|
||||
|
@ -44,6 +44,10 @@
|
||||
# endif // !OPENSSL_NO_ENGINE
|
||||
#endif
|
||||
|
||||
#if defined(__POSIX__)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::Local;
|
||||
@ -1238,6 +1242,28 @@ void DefineZlibConstants(Local<Object> target) {
|
||||
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_LEVEL);
|
||||
}
|
||||
|
||||
void DefineDLOpenConstants(Local<Object> target) {
|
||||
#ifdef RTLD_LAZY
|
||||
NODE_DEFINE_CONSTANT(target, RTLD_LAZY);
|
||||
#endif
|
||||
|
||||
#ifdef RTLD_NOW
|
||||
NODE_DEFINE_CONSTANT(target, RTLD_NOW);
|
||||
#endif
|
||||
|
||||
#ifdef RTLD_GLOBAL
|
||||
NODE_DEFINE_CONSTANT(target, RTLD_GLOBAL);
|
||||
#endif
|
||||
|
||||
#ifdef RTLD_LOCAL
|
||||
NODE_DEFINE_CONSTANT(target, RTLD_LOCAL);
|
||||
#endif
|
||||
|
||||
#ifdef RTLD_DEEPBIND
|
||||
NODE_DEFINE_CONSTANT(target, RTLD_DEEPBIND);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
|
||||
@ -1267,6 +1293,10 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
|
||||
CHECK(zlib_constants->SetPrototype(env->context(),
|
||||
Null(env->isolate())).FromJust());
|
||||
|
||||
Local<Object> dlopen_constants = Object::New(isolate);
|
||||
CHECK(dlopen_constants->SetPrototype(env->context(),
|
||||
Null(env->isolate())).FromJust());
|
||||
|
||||
DefineErrnoConstants(err_constants);
|
||||
DefineWindowsErrorConstants(err_constants);
|
||||
DefineSignalConstants(sig_constants);
|
||||
@ -1274,11 +1304,13 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
|
||||
DefineOpenSSLConstants(crypto_constants);
|
||||
DefineCryptoConstants(crypto_constants);
|
||||
DefineZlibConstants(zlib_constants);
|
||||
DefineDLOpenConstants(dlopen_constants);
|
||||
|
||||
// Define libuv constants.
|
||||
NODE_DEFINE_CONSTANT(os_constants, UV_UDP_REUSEADDR);
|
||||
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_EXCL);
|
||||
|
||||
os_constants->Set(OneByteString(isolate, "dlopen"), dlopen_constants);
|
||||
os_constants->Set(OneByteString(isolate, "errno"), err_constants);
|
||||
os_constants->Set(OneByteString(isolate, "signals"), sig_constants);
|
||||
target->Set(OneByteString(isolate, "os"), os_constants);
|
||||
|
48
test/addons/dlopen-ping-pong/binding.cc
Normal file
48
test/addons/dlopen-ping-pong/binding.cc
Normal file
@ -0,0 +1,48 @@
|
||||
#include <node.h>
|
||||
#include <v8.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
extern "C" const char* dlopen_pong(void) {
|
||||
return "pong";
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::Object;
|
||||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
typedef const char* (*ping)(void);
|
||||
|
||||
static ping ping_func;
|
||||
|
||||
void LoadLibrary(const FunctionCallbackInfo<Value>& args) {
|
||||
const String::Utf8Value filename(args[0]);
|
||||
void* handle = dlopen(*filename, RTLD_LAZY);
|
||||
assert(handle != nullptr);
|
||||
ping_func = reinterpret_cast<ping>(dlsym(handle, "dlopen_ping"));
|
||||
assert(ping_func != nullptr);
|
||||
}
|
||||
|
||||
void Ping(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
assert(ping_func != nullptr);
|
||||
args.GetReturnValue().Set(String::NewFromUtf8(isolate, ping_func()));
|
||||
}
|
||||
|
||||
void init(Local<Object> exports) {
|
||||
NODE_SET_METHOD(exports, "load", LoadLibrary);
|
||||
NODE_SET_METHOD(exports, "ping", Ping);
|
||||
}
|
||||
|
||||
NODE_MODULE(binding, init)
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#endif // _WIN32
|
25
test/addons/dlopen-ping-pong/binding.gyp
Normal file
25
test/addons/dlopen-ping-pong/binding.gyp
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'ping',
|
||||
'product_extension': 'so',
|
||||
'type': 'shared_library',
|
||||
'sources': [ 'ping.c' ],
|
||||
'conditions': [
|
||||
['OS=="mac"', {
|
||||
'xcode_settings': {
|
||||
'OTHER_LDFLAGS': [ '-Wl,-undefined', '-Wl,dynamic_lookup' ]
|
||||
}}],
|
||||
# Pass erok flag to the linker, to prevent unresolved symbols
|
||||
# from failing. Still, the test won't pass, so we'll skip it on AIX.
|
||||
['OS=="aix"', {
|
||||
'ldflags': [ '-Wl,-berok' ]
|
||||
}]],
|
||||
},
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
|
||||
'sources': [ 'binding.cc' ],
|
||||
}
|
||||
]
|
||||
}
|
9
test/addons/dlopen-ping-pong/ping.c
Normal file
9
test/addons/dlopen-ping-pong/ping.c
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef _WIN32
|
||||
|
||||
const char* dlopen_pong(void);
|
||||
|
||||
const char* dlopen_ping(void) {
|
||||
return dlopen_pong();
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
23
test/addons/dlopen-ping-pong/test.js
Normal file
23
test/addons/dlopen-ping-pong/test.js
Normal file
@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
|
||||
if (common.isWindows)
|
||||
common.skip('dlopen global symbol loading is not supported on this os.');
|
||||
|
||||
if (common.isAIX)
|
||||
common.skip('this test does not pass on AIX.');
|
||||
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
|
||||
process.dlopen(module, bindingPath,
|
||||
os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL);
|
||||
module.exports.load(path.dirname(bindingPath) + '/ping.so');
|
||||
assert.strictEqual(module.exports.ping(), 'pong');
|
||||
|
||||
// Check that after the addon is loaded with
|
||||
// process.dlopen() a require() call fails.
|
||||
const re = /^Error: Module did not self-register\.$/;
|
||||
assert.throws(() => require(`./build/${common.buildType}/binding`), re);
|
@ -9,7 +9,8 @@ assert.deepStrictEqual(
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
Object.keys(constants.os).sort(), ['UV_UDP_REUSEADDR', 'errno', 'signals']
|
||||
Object.keys(constants.os).sort(), ['UV_UDP_REUSEADDR', 'dlopen', 'errno',
|
||||
'signals']
|
||||
);
|
||||
|
||||
// Make sure all the constants objects don't inherit from Object.prototype
|
||||
@ -26,5 +27,5 @@ function test(obj) {
|
||||
|
||||
[
|
||||
constants, constants.crypto, constants.fs, constants.os, constants.zlib,
|
||||
constants.os.errno, constants.os.signals
|
||||
constants.os.dlopen, constants.os.errno, constants.os.signals
|
||||
].forEach(test);
|
||||
|
Loading…
x
Reference in New Issue
Block a user