diff --git a/doc/api/_toc.markdown b/doc/api/_toc.markdown index c838ab383e2ed7..e704bacfea8c37 100644 --- a/doc/api/_toc.markdown +++ b/doc/api/_toc.markdown @@ -10,6 +10,7 @@ * [Console](console.html) * [Crypto](crypto.html) * [Debugger](debugger.html) +* [Dlopen](dlopen.html) * [DNS](dns.html) * [Domain](domain.html) * [Errors](errors.html) diff --git a/doc/api/dlopen.markdown b/doc/api/dlopen.markdown new file mode 100644 index 00000000000000..01539493834fcb --- /dev/null +++ b/doc/api/dlopen.markdown @@ -0,0 +1,92 @@ +# dlopen + + Stability: 1 - Experimental + +Dynamic library loader module. + +Use `require('dlopen')` to access this module. + +## dlopen.extension + +* String + +File extension used for dynamic libraries. + +For example, on Mac OS X: + + console.log(dlopen.extension); + // '.dylib' + +## dlopen.dlopen(name) + +* `name` String or `null` - library filename +* Return: Buffer - backing store for the `uv_lib_t` instance + +Load and link a dynamic library with filename `name`. +If `null` is given as the name, then the current node process is +dynamically loaded instead (i.e. you can load symbols already +loaded into memory). + +Example: + + var libc = dlopen.dlopen('libc.so'); + // + + // null for the current process' memory + var currentProcess = dlopen.dlopen(null); + // + + // error is thrown if something goes wrong + dlopen.dlopen('libdoesnotexist.so') + // Error: dlopen(libdoesnotexist.so, 1): image not found + // at Object.dlopen (dlopen.js:9:11) + +## dlopen.dlclose(lib) + +* `lib` Buffer - the buffer previously returned from `dlopen()` + +Closes dynamic library `lib`. + +Example: + + dlopen.dlclose(libc); + +## dlopen.dlsym(lib, namem) + +* `lib` Buffer - the buffer previously returned from `dlopen()` +* `name` String - name of the symbol to retrieve from `lib` +* Return: Buffer - a pointer-sized buffer containing the address of `name` + +Get the memory address of symbol `name` from dynamic library `lib`. +A new Buffer instance is returned containing the memory address of +the loaded symbol. + +Almost always, you will call one of the Buffer `readPointer*()` +functions on the returned buffer in order to interact with the symbol +further. + +Example: + + var absSymPtr = dlopen.dlsym(libc, 'abs'); + // + + // error is thrown if symbol does not exist + dlopen.dlsym(libc, 'doesnotexist') + // Error: dlsym(0x7fff6ad9f898, doesnotexist): symbol not found + // at Object.dlsym (dlopen.js:24:11) + +## dlopen.dlerror(lib) + +* `lib` Buffer - the buffer previously returned from `dlopen()` +* Return: String - most recent error that has occured on `lib` + +Get previous error message from dynamic library `lib`. + +You most likely won't need to use this function, since `dlopen()` +and `dlsym()` use them internally when something goes wrong. + + +Example: + + dlopen.dlerror(libc) + // 'no error' diff --git a/lib/dlopen.js b/lib/dlopen.js new file mode 100644 index 00000000000000..8270c3c2a9c7cd --- /dev/null +++ b/lib/dlopen.js @@ -0,0 +1,42 @@ +'use strict'; + +const binding = process.binding('dlopen'); + +exports.dlopen = function dlopen(name) { + var lib = new Buffer(binding.sizeof_uv_lib_t); + + if (binding.dlopen(name, lib) !== 0) { + throw new Error(exports.dlerror(lib)); + } + + return lib; +}; + +exports.dlclose = function dlclose(lib) { + return binding.dlclose(lib); +}; + +exports.dlsym = function dlsym(lib, name) { + // TODO: use `sizeof.pointer` for buffer size when nodejs/io.js#1759 is merged + var sym = new Buffer(8); + + if (binding.dlsym(lib, name, sym) !== 0) { + throw new Error(exports.dlerror(lib)); + } + + return sym; +}; + +exports.dlerror = function dlerror(lib) { + return binding.dlerror(lib); +}; + +exports.extension = { + linux: '.so', + sunos: '.so', + solaris: '.so', + freebsd: '.so', + openbsd: '.so', + darwin: '.dylib', + win32: '.dll' +}[process.platform]; diff --git a/lib/repl.js b/lib/repl.js index 40496559bf452f..4aaa87a5d190ec 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -59,10 +59,10 @@ function hasOwnProperty(obj, prop) { exports.writer = util.inspect; exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster', - 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net', - 'os', 'path', 'punycode', 'querystring', 'readline', 'stream', - 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib', - 'smalloc']; + 'crypto', 'dgram', 'dlopen', 'dns', 'domain', 'events', 'fs', 'http', + 'https', 'net', 'os', 'path', 'punycode', 'querystring', 'readline', + 'stream', 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', + 'zlib', 'smalloc']; const BLOCK_SCOPED_ERROR = 'Block-scoped declarations (let, ' + diff --git a/node.gyp b/node.gyp index 70c9841a89a176..9f0d28737a67e3 100644 --- a/node.gyp +++ b/node.gyp @@ -26,6 +26,7 @@ 'lib/crypto.js', 'lib/cluster.js', 'lib/dgram.js', + 'lib/dlopen.js', 'lib/dns.js', 'lib/domain.js', 'lib/events.js', @@ -108,6 +109,7 @@ 'src/node_buffer.cc', 'src/node_constants.cc', 'src/node_contextify.cc', + 'src/node_dlopen.cc', 'src/node_file.cc', 'src/node_http_parser.cc', 'src/node_javascript.cc', @@ -649,6 +651,16 @@ 'sources': [ 'test/cctest/util.cc', ], - } + }, + + # "libtest" dynamic library for "dlopen" tests + { + 'target_name': 'test', + 'type': 'shared_library', + 'product_prefix': 'lib', + 'sources': [ + 'test/libtest/libtest.c' + ], + }, ] # end targets } diff --git a/src/node_dlopen.cc b/src/node_dlopen.cc new file mode 100644 index 00000000000000..82f11c6001ec84 --- /dev/null +++ b/src/node_dlopen.cc @@ -0,0 +1,117 @@ +#include "node.h" +#include "node_buffer.h" +#include "v8.h" +#include "env.h" +#include "env-inl.h" + +namespace node { +namespace dlopen { + +using v8::Boolean; +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::Handle; +using v8::Integer; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; +using v8::Uint32; + + +static void Dlopen(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + const char* filename; + if (args[0]->IsNull()) { + filename = nullptr; + } else if (args[0]->IsString()) { + node::Utf8Value name(env->isolate(), args[0]); + filename = *name; + } else { + return env->ThrowTypeError( + "expected a string filename or null as first argument"); + } + + if (!Buffer::HasInstance(args[1])) + return env->ThrowTypeError("expected a Buffer instance as second argument"); + + Local buf = args[1].As(); + uv_lib_t* lib = reinterpret_cast(Buffer::Data(buf)); + + int r = uv_dlopen(filename, lib); + + args.GetReturnValue().Set(r); +} + + +static void Dlclose(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (!Buffer::HasInstance(args[0])) + return env->ThrowTypeError("expected a Buffer instance as first argument"); + + Local buf = args[0].As(); + uv_lib_t* lib = reinterpret_cast(Buffer::Data(buf)); + + uv_dlclose(lib); +} + + +static void Dlsym(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (!Buffer::HasInstance(args[0])) + return env->ThrowTypeError("expected a Buffer instance as first argument"); + if (!args[1]->IsString()) + return env->ThrowTypeError("expected a string as second argument"); + if (!Buffer::HasInstance(args[2])) + return env->ThrowTypeError("expected a Buffer instance as third argument"); + + Local buf = args[0].As(); + uv_lib_t* lib = reinterpret_cast(Buffer::Data(buf)); + + void* sym; + node::Utf8Value name(env->isolate(), args[1]); + int r = uv_dlsym(lib, *name, &sym); + + Local sym_buf = args[2].As(); + + memcpy(Buffer::Data(sym_buf), &sym, sizeof(sym)); + + args.GetReturnValue().Set(r); +} + + +static void Dlerror(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (!Buffer::HasInstance(args[0])) + return env->ThrowTypeError("expected a Buffer instance as first argument"); + + Local buf = args[0].As(); + uv_lib_t* lib = reinterpret_cast(Buffer::Data(buf)); + + args.GetReturnValue().Set(OneByteString(env->isolate(), uv_dlerror(lib))); +} + + +void Initialize(Handle target, + Handle unused, + Handle context) { + Environment* env = Environment::GetCurrent(context); + env->SetMethod(target, "dlopen", Dlopen); + env->SetMethod(target, "dlclose", Dlclose); + env->SetMethod(target, "dlsym", Dlsym); + env->SetMethod(target, "dlerror", Dlerror); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "sizeof_uv_lib_t"), + Uint32::NewFromUnsigned(env->isolate(), + static_cast(sizeof(uv_lib_t)))); +} + +} // namespace dlopen +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(dlopen, node::dlopen::Initialize) diff --git a/test/libtest/libtest.c b/test/libtest/libtest.c new file mode 100644 index 00000000000000..144e6e1594b1ab --- /dev/null +++ b/test/libtest/libtest.c @@ -0,0 +1,27 @@ +#include +#include + +#if defined(WIN32) || defined(_WIN32) +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +EXPORT int six = 6; + +EXPORT void* n = NULL; + +EXPORT char str[] = "hello world"; + +EXPORT uint64_t factorial(int max) { + int i = max; + uint64_t result = 1; + + while (i >= 2) { + result *= i--; + } + + return result; +} + +EXPORT uintptr_t factorial_addr = (uintptr_t)factorial; diff --git a/test/parallel/test-dlopen.js b/test/parallel/test-dlopen.js new file mode 100644 index 00000000000000..095501ee3149b3 --- /dev/null +++ b/test/parallel/test-dlopen.js @@ -0,0 +1,49 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); +var path = require('path'); +var endianness = require('os').endianness(); + +var dl = require('dlopen'); + +var root = path.join(__dirname, '..', '..'); +var libPath = path.join(root, 'out', 'Release', 'libtest' + dl.extension); +console.log(libPath); + +var libtest = dl.dlopen(libPath); +console.log(libtest); + +// EXPORT int six = 6 +var sixSymPtr = dl.dlsym(libtest, 'six'); +// TODO: use `sizeof.int` +var sixSym = sixSymPtr['readPointer' + endianness](0, 4); +assert.equal(6, sixSym['readInt' + (4 * 8) + endianness](0)); + +// EXPORT void* n = NULL; +var nSymPtr = dl.dlsym(libtest, 'n'); +// TODO: use `sizeof.pointer` +var nSym = nSymPtr['readPointer' + endianness](0, 8); +assert.strictEqual(null, nSym['readPointer' + endianness](0)); + +// EXPORT char str[] = "hello world"; +var strSymPtr = dl.dlsym(libtest, 'str'); +// XXX: We need a way to read a null-terminated array :( :( :( +var strSym = strSymPtr['readPointer' + endianness](0, 12); +assert.equal('hello world', strSym.toString('ascii', 0, 11)); +assert.equal(0, strSym[11]); + +// EXPORT uint64_t factorial(int max) +var factorialSymPtr = dl.dlsym(libtest, 'factorial'); +// TODO: use `sizeof.pointer` +var factorialSym = factorialSymPtr['readPointer' + endianness](0, 0); + +// EXPORT intptr_t factorial_addr = (intptr_t)factorial; +var factorialAddrSymPtr = dl.dlsym(libtest, 'factorial_addr'); +var factorialAddrSym = factorialAddrSymPtr['readPointer' + endianness](0, 8); +var factorialSym2 = factorialAddrSym['readPointer' + endianness](0, 0); + +assert.equal(factorialSym.address(), factorialSym2.address()); + + +// we're done ☺ +dl.dlclose(libtest);