Permalink
Browse files

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 <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>
  • Loading branch information...
ezequielgarcia authored and refack committed Apr 29, 2017
1 parent aa76ce9 commit 5f223759229a112af41508c470d683152523c6e3
View
@@ -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>
View
@@ -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
View
@@ -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,
View
@@ -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,36 +2507,85 @@ 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
// when two contexts try to load the same shared object. Maybe have a shadow
// 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
// module per object is supported.
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;
}
View
@@ -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,18 +1293,24 @@ 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);
DefineSystemConstants(fs_constants);
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);
@@ -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
Oops, something went wrong.

0 comments on commit 5f22375

Please sign in to comment.