Skip to content

Commit

Permalink
src: add support to pass flags to dlopen
Browse files Browse the repository at this point in the history
* 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: nodejs#12794
Refs: nodejs#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]>
  • Loading branch information
ezequielgarcia authored and addaleax committed Sep 13, 2017
1 parent 90fd9f5 commit f1ec902
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 16 deletions.
37 changes: 37 additions & 0 deletions doc/api/os.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
45 changes: 45 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
81 changes: 67 additions & 14 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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()));
Expand All @@ -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;
}
Expand All @@ -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;

Expand All @@ -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;
}
Expand Down
32 changes: 32 additions & 0 deletions src/node_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
# endif // !OPENSSL_NO_ENGINE
#endif

#if defined(__POSIX__)
#include <dlfcn.h>
#endif

namespace node {

using v8::Local;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
48 changes: 48 additions & 0 deletions test/addons/dlopen-ping-pong/binding.cc
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit f1ec902

Please sign in to comment.