Implment RTDL_LOCAL and RTDL_NOW/RTDL_LAZY dlopen flags
There are two changed here in the implementation of dlopen:
1. Avoid calling updateGOT when RTDL_LOCAL is set.
2. Check for undefined stub functions during postInstantiate.
The reason we can't just avoid the stub functions completely here
is the a module could both import and export a given symbol.
diff --git a/src/library_dylink.js b/src/library_dylink.js
index 711f0ae..2299b06 100644
--- a/src/library_dylink.js
+++ b/src/library_dylink.js
@@ -127,8 +127,7 @@
// Applies relocations to exported things.
$relocateExports__internal: true,
- $relocateExports__deps: ['$updateGOT'],
- $relocateExports: function(exports, memoryBase, replace) {
+ $relocateExports: function(exports, memoryBase) {
var relocated = {};
for (var e in exports) {
@@ -150,7 +149,6 @@
}
relocated[e] = value;
}
- updateGOT(relocated, replace);
return relocated;
},
@@ -379,7 +377,7 @@
},
// Module.symbols <- libModule.symbols (flags.global handler)
- $mergeLibSymbols__deps: ['$asmjsMangle'],
+ $mergeLibSymbols__deps: ['$asmjsMangle', '$updateGOT'],
$mergeLibSymbols: function(exports, libName) {
// add symbols into global namespace TODO: weak linking etc.
for (var sym in exports) {
@@ -412,6 +410,7 @@
Module[module_sym] = exports[sym];
}
}
+ updateGOT(exports);
},
// Loads a side module from binary data or compiled Module. Returns the module's exports or a
@@ -471,9 +470,6 @@
if (!resolved) {
resolved = moduleExports[sym];
}
-#if ASSERTIONS
- assert(resolved, 'undefined symbol `' + sym + '`. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment');
-#endif
return resolved;
}
@@ -506,14 +502,18 @@
if (!(prop in stubs)) {
var resolved;
stubs[prop] = function() {
- if (!resolved) resolved = resolveSymbol(prop, true);
+ if (!resolved) resolved = resolveSymbol(prop);
+#if ASSERTIONS
+ assert(resolved, 'undefined symbol `' + prop + '`. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment');
+#endif
return resolved.apply(null, arguments);
};
}
return stubs[prop];
}
};
- var proxy = new Proxy({}, proxyHandler);
+ var stubs = {}
+ var proxy = new Proxy(stubs, proxyHandler);
var info = {
'GOT.mem': new Proxy({}, GOTHandler),
'GOT.func': new Proxy({}, GOTHandler),
@@ -526,10 +526,18 @@
// the table should be unchanged
assert(wasmTable === originalTable);
#endif
+
// add new entries to functionsInTableMap
updateTableMap(tableBase, metadata.tableSize);
moduleExports = relocateExports(instance.exports, memoryBase);
if (!flags.allowUndefined) {
+ if (!flags.lazy) {
+ for (var symbol in stubs) {
+ if (!resolveSymbol(symbol)) {
+ throw Error('undefined symbol: ' + symbol);
+ }
+ }
+ }
reportUndefinedSymbols();
}
#if STACK_OVERFLOW_CHECK >= 2
@@ -710,10 +718,10 @@
// module for lib is loaded - update the dso & global namespace
function moduleLoaded(libModule) {
- if (dso.global) {
- mergeLibSymbols(libModule, lib);
- }
dso.module = libModule;
+ if (dso.global) {
+ mergeLibSymbols(dso.module, lib);
+ }
}
if (flags.loadAsync) {
@@ -794,10 +802,10 @@
err('dlopenInternal: ' + filename);
#endif
- // We don't care about RTLD_NOW and RTLD_LAZY.
var combinedFlags = {
global: Boolean(flags & {{{ cDefine('RTLD_GLOBAL') }}}),
nodelete: Boolean(flags & {{{ cDefine('RTLD_NODELETE') }}}),
+ lazy: Boolean(flags & {{{ cDefine('RTLD_LAZY') }}}),
loadAsync: jsflags.loadAsync,
fs: jsflags.fs,
}
diff --git a/src/library_pthread.js b/src/library_pthread.js
index 9c85705..e80bd36 100644
--- a/src/library_pthread.js
+++ b/src/library_pthread.js
@@ -496,6 +496,9 @@
}
},
+#if RELOCATABLE
+ $registerTlsInit__deps: ['$updateGOT'],
+#endif
$registerTlsInit: function(tlsInitFunc, moduleExports, metadata) {
#if DYLINK_DEBUG
out("registerTlsInit: " + tlsInitFunc);
@@ -519,7 +522,8 @@
for (var sym in metadata.tlsExports) {
metadata.tlsExports[sym] = moduleExports[sym];
}
- relocateExports(metadata.tlsExports, __tls_base, /*replace=*/true);
+ exports = relocateExports(metadata.tlsExports, __tls_base);
+ updateGOT(exports, /*replace=*/true);
}
// Register this function so that its gets called for each thread on
diff --git a/tests/test_core.py b/tests/test_core.py
index 9329433..1260503 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -2612,7 +2612,7 @@
self.clear_setting('MAIN_MODULE')
self.set_setting('SIDE_MODULE')
outfile = self.build(filename, js_outfile=not self.is_wasm())
- shutil.move(outfile, 'liblib.so')
+ shutil.move(outfile, shared.unsuffixed_basename(filename) + '.so')
@needs_dylink
def test_dlfcn_missing(self):
@@ -3431,7 +3431,7 @@
def indir(name):
return os.path.join(dirname, name)
- create_file('a.cpp', r'''
+ create_file('liba.cpp', r'''
#include <stdio.h>
static class A {
@@ -3442,7 +3442,7 @@
} _;
''')
- create_file('b.cpp', r'''
+ create_file('libb.cpp', r'''
#include <stdio.h>
static class B {
@@ -3453,10 +3453,8 @@
} _;
''')
- self.build_dlfcn_lib('a.cpp')
- shutil.move(indir('liblib.so'), indir('liba.so'))
- self.build_dlfcn_lib('b.cpp')
- shutil.move(indir('liblib.so'), indir('libb.so'))
+ self.build_dlfcn_lib('liba.cpp')
+ self.build_dlfcn_lib('libb.cpp')
self.set_setting('MAIN_MODULE')
self.set_setting('NODERAWFS')
@@ -3528,6 +3526,58 @@
'''
self.do_run(src, 'float: 42.\n')
+ @needs_dylink
+ def test_dlfcn_rtld_local(self):
+ create_file('liblib.c', r'''
+ int foo() { return 42; }
+ ''')
+ self.build_dlfcn_lib('liblib.c')
+
+ self.set_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0)
+ create_file('libbar.c', r'''
+ extern int foo();
+ int bar() { return foo(); }
+ ''')
+ self.build_dlfcn_lib('libbar.c')
+
+ self.prep_dlfcn_main()
+ src = r'''
+ #include <assert.h>
+ #include <dlfcn.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+
+ typedef int (*func_t)();
+
+ int main() {
+ void *lib_handle = dlopen("liblib.so", RTLD_LOCAL|RTLD_NOW);
+ if (!lib_handle) {
+ puts(dlerror());
+ abort();
+ }
+ func_t foo = (func_t)dlsym(lib_handle, "foo");
+ if (!foo) {
+ puts(dlerror());
+ abort();
+ }
+ printf("foo: %d\n", foo());
+
+ // Verify that "foo" is not visible in the global
+ // namespace.
+ foo = (func_t)dlsym(RTLD_DEFAULT, "foo");
+ assert(foo == NULL);
+
+ // libbar.so should not be loadable since it depends on the symbol
+ // `foo` which should not be in the global namespace.
+ void *libbar_handle = dlopen("libbar.so", RTLD_NOW);
+ printf("libbar_handle: %p\n", libbar_handle);
+ assert(libbar_handle == NULL);
+ puts(dlerror());
+ return 0;
+ }
+ '''
+ self.do_run(src, ['foo: 42', 'Error: undefined symbol: foo'], assert_all=True)
+
def dylink_test(self, main, side, expected=None, header=None, force_c=False,
main_module=2, **kwargs):
# Same as dylink_testf but take source code in string form