diff --git a/tests/rtdl/dl_iterate_phdr/test.c b/tests/rtdl/dl_iterate_phdr/test.c index 88e9bbc9..5d48a41b 100644 --- a/tests/rtdl/dl_iterate_phdr/test.c +++ b/tests/rtdl/dl_iterate_phdr/test.c @@ -4,7 +4,6 @@ #include #include - #ifdef USE_HOST_LIBC #define LDSO_PATTERN "ld-linux-" #define LIBFOO "libnative-foo.so" @@ -59,33 +58,32 @@ static int callback(struct dl_phdr_info *info, size_t size, void *data) { int main() { struct result found = { 0 }; - printf("---\n"); assert(!dl_iterate_phdr(callback, &found)); assert(found.found_ldso == 1); assert(found.found_self == 1); assert(found.found_foo == 0); assert(found.found_bar == 0); + printf("---\n"); memset(&found, 0, sizeof(found)); void *bar = dlopen(LIBBAR, RTLD_LOCAL | RTLD_NOW); assert(bar); - printf("---\n"); assert(!dl_iterate_phdr(callback, &found)); assert(found.found_ldso == 1); assert(found.found_self == 1); - assert(found.found_foo == 1); // Since bar depends on foo. assert(found.found_bar == 1); + assert(found.found_foo == 1); // Since bar depends on foo. + printf("---\n"); memset(&found, 0, sizeof(found)); void *foo = dlopen(LIBFOO, RTLD_GLOBAL | RTLD_NOW); assert(foo); - printf("---\n"); assert(!dl_iterate_phdr(callback, &found)); assert(found.found_ldso == 1); assert(found.found_self == 1); assert(found.found_foo == 1); - assert(found.found_foo > 0); assert(found.found_bar == 1); + printf("---\n"); dlclose(bar); dlclose(foo); diff --git a/tests/rtdl/meson.build b/tests/rtdl/meson.build index f0c68513..94f1529d 100644 --- a/tests/rtdl/meson.build +++ b/tests/rtdl/meson.build @@ -1,8 +1,13 @@ rtdl_test_cases = [ 'dl_iterate_phdr', + 'noload-promote', 'rtld_next', 'soname', 'preinit', + 'scope1', + 'scope2', + 'scope3', + 'scope4', ] foreach test_name : rtdl_test_cases diff --git a/tests/rtdl/noload-promote/libfoo.c b/tests/rtdl/noload-promote/libfoo.c new file mode 100644 index 00000000..85e6cd8c --- /dev/null +++ b/tests/rtdl/noload-promote/libfoo.c @@ -0,0 +1 @@ +void foo() {} diff --git a/tests/rtdl/noload-promote/meson.build b/tests/rtdl/noload-promote/meson.build new file mode 100644 index 00000000..4ae6bb3b --- /dev/null +++ b/tests/rtdl/noload-promote/meson.build @@ -0,0 +1,5 @@ +libfoo = shared_library('foo', 'libfoo.c') +test_depends = [libfoo] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +test_native_depends = [libfoo_native] diff --git a/tests/rtdl/noload-promote/test.c b/tests/rtdl/noload-promote/test.c new file mode 100644 index 00000000..0a6c55c5 --- /dev/null +++ b/tests/rtdl/noload-promote/test.c @@ -0,0 +1,22 @@ +#include +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +int main() { + void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo); + + assert(dlsym(RTLD_DEFAULT, "foo") == NULL); + + // Opening a library with RTLD_NOLOAD | RTLD_GLOBAL should promote it to the global scope. + assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW) == foo); + assert(dlsym(RTLD_DEFAULT, "foo") != NULL); + + assert(dlopen("does-not-exist.so.1337", RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW) == NULL); +} diff --git a/tests/rtdl/scope1/libbar.c b/tests/rtdl/scope1/libbar.c new file mode 100644 index 00000000..ecf043ec --- /dev/null +++ b/tests/rtdl/scope1/libbar.c @@ -0,0 +1,14 @@ +char *foo(void); +char *foo_global(void); + +char *bar() { + return "bar"; +} + +char *bar_calls_foo() { + return foo(); +} + +char *bar_calls_foo_global() { + return foo_global(); +} diff --git a/tests/rtdl/scope1/libfoo.c b/tests/rtdl/scope1/libfoo.c new file mode 100644 index 00000000..b4e1b8ca --- /dev/null +++ b/tests/rtdl/scope1/libfoo.c @@ -0,0 +1,9 @@ +char *foo() { + return "foo"; +} + +char global[] = "foo global"; + +char *foo_global() { + return global; +} diff --git a/tests/rtdl/scope1/meson.build b/tests/rtdl/scope1/meson.build new file mode 100644 index 00000000..acb679e7 --- /dev/null +++ b/tests/rtdl/scope1/meson.build @@ -0,0 +1,7 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +test_depends = [libfoo, libbar] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +test_native_depends = [libfoo_native, libbar_native] diff --git a/tests/rtdl/scope1/test.c b/tests/rtdl/scope1/test.c new file mode 100644 index 00000000..c19915db --- /dev/null +++ b/tests/rtdl/scope1/test.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#define LIBBAR "libnative-bar.so" +#else +#define LIBFOO "libfoo.so" +#define LIBBAR "libbar.so" +#endif + +typedef char *strfn(void); + +int main() { + // We haven't dlopen'd these libs yet, so symbol resolution should fail. + assert(dlsym(RTLD_DEFAULT, "foo") == NULL); + assert(dlsym(RTLD_DEFAULT, "bar") == NULL); + + assert(!dlopen(LIBFOO, RTLD_NOLOAD)); + assert(!dlopen(LIBBAR, RTLD_NOLOAD)); + + void *foo_handle = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo_handle); + assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_NOW)); + + strfn *foo_sym = dlsym(foo_handle, "foo"); + assert(foo_sym); + assert(foo_sym()); + assert(!strcmp(foo_sym(), "foo")); + + strfn *foo_global_sym = dlsym(foo_handle, "foo_global"); + assert(foo_global_sym); + assert(foo_global_sym()); + assert(!strcmp(foo_global_sym(), "foo global")); + + assert(dlsym(foo_handle, "doesnotexist") == NULL); + + // Nested opening should work + assert(dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW) == foo_handle); + assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_NOW)); + + // Since we've loaded the same library twice, the addresses should be the same + assert(dlsym(foo_handle, "foo") == foo_sym); + assert(dlsym(foo_handle, "foo_global") == foo_global_sym); + + // libfoo was opened with RTLD_LOCAL, so we shouldn't be able to lookup + // its symbols in the global namespace. + assert(dlsym(RTLD_DEFAULT, "foo") == NULL); + + { + void *bar_handle = dlopen(LIBBAR, RTLD_GLOBAL | RTLD_NOW); + assert(bar_handle); + assert(dlopen(LIBBAR, RTLD_NOLOAD | RTLD_NOW)); + + strfn *bar_sym = dlsym(bar_handle, "bar"); + assert(bar_sym); + assert(bar_sym()); + assert(!strcmp(bar_sym(), "bar")); + + strfn *bar_calls_foo_sym = dlsym(bar_handle, "bar_calls_foo"); + assert(bar_calls_foo_sym); + assert(bar_calls_foo_sym()); + assert(!strcmp(bar_calls_foo_sym(), "foo")); + + strfn *bar_calls_foo_global_sym = dlsym(bar_handle, "bar_calls_foo_global"); + assert(bar_calls_foo_global_sym); + assert(bar_calls_foo_global_sym()); + assert(!strcmp(bar_calls_foo_global_sym(), "foo global")); + + // libbar was opened with RTLD_GLOBAL, so we can find symbols by + // searching in the global scope. + strfn *new_bar_sym = dlsym(RTLD_DEFAULT, "bar"); + assert(new_bar_sym); + assert(new_bar_sym == bar_sym); + + // Note that we loaded libbar with RTLD_GLOBAL, which should pull + // in libfoo's symbols globally too. + strfn *new_foo_sym = dlsym(RTLD_DEFAULT, "foo"); + assert(new_foo_sym); + assert(new_foo_sym == foo_sym); + + assert(dlclose(bar_handle) == 0); + } + + assert(dlclose(foo_handle) == 0); + assert(dlclose(foo_handle) == 0); +} diff --git a/tests/rtdl/scope2/libbar.c b/tests/rtdl/scope2/libbar.c new file mode 100644 index 00000000..4783c581 --- /dev/null +++ b/tests/rtdl/scope2/libbar.c @@ -0,0 +1,5 @@ +char *foo_baz_conflict(void); + +char *bar_calls_foo_baz_conflict() { + return foo_baz_conflict(); +} diff --git a/tests/rtdl/scope2/libbaz.c b/tests/rtdl/scope2/libbaz.c new file mode 100644 index 00000000..fc73adcd --- /dev/null +++ b/tests/rtdl/scope2/libbaz.c @@ -0,0 +1,3 @@ +char *foo_baz_conflict() { + return "resolved to baz"; +} diff --git a/tests/rtdl/scope2/libfoo.c b/tests/rtdl/scope2/libfoo.c new file mode 100644 index 00000000..9f7b881f --- /dev/null +++ b/tests/rtdl/scope2/libfoo.c @@ -0,0 +1,3 @@ +char *foo_baz_conflict() { + return "resolved to foo"; +} diff --git a/tests/rtdl/scope2/meson.build b/tests/rtdl/scope2/meson.build new file mode 100644 index 00000000..938272c5 --- /dev/null +++ b/tests/rtdl/scope2/meson.build @@ -0,0 +1,9 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +libbaz = shared_library('baz', 'libbaz.c') +test_depends = [libfoo, libbar, libbaz] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +libbaz_native = shared_library('native-baz', 'libbaz.c', native: true) +test_native_depends = [libfoo_native, libbar_native, libbaz_native] diff --git a/tests/rtdl/scope2/test.c b/tests/rtdl/scope2/test.c new file mode 100644 index 00000000..f4f42dc5 --- /dev/null +++ b/tests/rtdl/scope2/test.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBBAR "libnative-bar.so" +#define LIBBAZ "libnative-baz.so" +#else +#define LIBBAR "libbar.so" +#define LIBBAZ "libbaz.so" +#endif + +typedef char *strfn(void); + +int main() { + void *baz = dlopen(LIBBAZ, RTLD_LAZY | RTLD_GLOBAL); + assert(baz); + + // At this point, baz is loaded in the global scope. When we load bar locally, + // there is a relocation to `foo_baz_conflict` which is defined in both + // foo (which is a dependency of bar), and baz. In this case baz should win + // since we search the global scope first. + + void *bar = dlopen(LIBBAR, RTLD_LAZY | RTLD_LOCAL); + assert(bar); + + strfn *bfn = dlsym(bar, "bar_calls_foo_baz_conflict"); + assert(!strcmp(bfn(), "resolved to baz")); + + // TODO: Test RTLD_DEEPBIND and DT_SYMBOLIC once we implement it. + + dlclose(bar); + dlclose(baz); +} diff --git a/tests/rtdl/scope3/libbar.c b/tests/rtdl/scope3/libbar.c new file mode 100644 index 00000000..dc377b6f --- /dev/null +++ b/tests/rtdl/scope3/libbar.c @@ -0,0 +1,8 @@ +int g = 1; + +int call_foo(); + +int call_bar() { + return call_foo(); +} + diff --git a/tests/rtdl/scope3/libbaz.c b/tests/rtdl/scope3/libbaz.c new file mode 100644 index 00000000..32524cc3 --- /dev/null +++ b/tests/rtdl/scope3/libbaz.c @@ -0,0 +1,7 @@ +int g = 2; + +int call_foo(); + +int call_baz() { + return call_foo(); +} diff --git a/tests/rtdl/scope3/libfoo.c b/tests/rtdl/scope3/libfoo.c new file mode 100644 index 00000000..bc863192 --- /dev/null +++ b/tests/rtdl/scope3/libfoo.c @@ -0,0 +1,5 @@ +int g = 0; + +int call_foo() { + return g; +} diff --git a/tests/rtdl/scope3/meson.build b/tests/rtdl/scope3/meson.build new file mode 100644 index 00000000..0c985836 --- /dev/null +++ b/tests/rtdl/scope3/meson.build @@ -0,0 +1,9 @@ +libfoo = shared_library('foo', 'libfoo.c') +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +libbaz = shared_library('baz', 'libbaz.c', build_rpath: test_rpath, link_with: libfoo) +test_depends = [libfoo, libbar, libbaz] + +libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +libbaz_native = shared_library('native-baz', 'libbaz.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +test_native_depends = [libfoo_native, libbar_native, libbaz_native] diff --git a/tests/rtdl/scope3/test.c b/tests/rtdl/scope3/test.c new file mode 100644 index 00000000..30fc662b --- /dev/null +++ b/tests/rtdl/scope3/test.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBBAR "libnative-bar.so" +#define LIBBAZ "libnative-baz.so" +#else +#define LIBBAR "libbar.so" +#define LIBBAZ "libbaz.so" +#endif + +int main() { + // In this test, we have bar -> foo and baz -> foo (where -> means 'depends on'). + // All three objects contain a definition of a symbol g. Bar calls into foo to retrieve + // what foo thinks g is, but since bar appears earlier in the scope than foo, bar's copy + // of g wins. + // + // Next, we load baz, which is identical to bar. When baz calls into foo to retrieve g, + // foo still sees bar's definition of g, so bar's copy of g wins. + // + // Swapping the load order of bar and baz should therefore change the value of g which + // foo sees. This behaviour is why dlmopen exists. If we ever implement that, we should + // write a similar test and assert that the calls return different results. + + void *libbar = dlopen(LIBBAR, RTLD_LAZY | RTLD_LOCAL); + int (*call_bar)(void) = dlsym(libbar, "call_bar"); + printf("call_bar: %d\n", call_bar()); + assert(call_bar() == 1); + + void *libbaz = dlopen(LIBBAZ, RTLD_LAZY | RTLD_LOCAL); + int (*call_baz)(void) = dlsym(libbaz, "call_baz"); + printf("call_baz: %d\n", call_baz()); + assert(call_baz() == 1); + + + dlclose(libbar); + dlclose(libbaz); +} diff --git a/tests/rtdl/scope4/libbar.c b/tests/rtdl/scope4/libbar.c new file mode 100644 index 00000000..514e4564 --- /dev/null +++ b/tests/rtdl/scope4/libbar.c @@ -0,0 +1,3 @@ +// Bar needs to have a relocation against foo in order to set DT_NEEDED. +void foo(void); +void bar() { foo(); } diff --git a/tests/rtdl/scope4/libbaz.c b/tests/rtdl/scope4/libbaz.c new file mode 100644 index 00000000..256a0e36 --- /dev/null +++ b/tests/rtdl/scope4/libbaz.c @@ -0,0 +1 @@ +void baz() {} diff --git a/tests/rtdl/scope4/libfoo.c b/tests/rtdl/scope4/libfoo.c new file mode 100644 index 00000000..6710db71 --- /dev/null +++ b/tests/rtdl/scope4/libfoo.c @@ -0,0 +1,3 @@ +// Foo needs to have a relocation against baz in order to set DT_NEEDED. +void baz(void); +void foo() { baz(); } diff --git a/tests/rtdl/scope4/meson.build b/tests/rtdl/scope4/meson.build new file mode 100644 index 00000000..804a40cd --- /dev/null +++ b/tests/rtdl/scope4/meson.build @@ -0,0 +1,9 @@ +libbaz = shared_library('baz', 'libbaz.c') +libfoo = shared_library('foo', 'libfoo.c', build_rpath: test_rpath, link_with: libbaz) +libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo) +test_depends = [libfoo, libbar, libbaz] + +libbaz_native = shared_library('native-baz', 'libbaz.c', native: true) +libfoo_native = shared_library('native-foo', 'libfoo.c', build_rpath: test_rpath, link_with: libbaz_native, native: true) +libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true) +test_native_depends = [libfoo_native, libbar_native, libbaz_native] diff --git a/tests/rtdl/scope4/test.c b/tests/rtdl/scope4/test.c new file mode 100644 index 00000000..2365e266 --- /dev/null +++ b/tests/rtdl/scope4/test.c @@ -0,0 +1,36 @@ +#include +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#define LIBBAR "libnative-bar.so" +#define LIBBAZ "libnative-baz.so" +#else +#define LIBFOO "libfoo.so" +#define LIBBAR "libbar.so" +#define LIBBAZ "libbaz.so" +#endif + +int main() { + // In this test, we have foo -> baz, bar -> foo (where '->' means 'depends on'). + // We first load foo with RTLD_LOCAL, and then load bar with RTLD_GLOBAL. + // This should bring foo and bar into the global scope. + + void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(foo); + assert(dlsym(foo, "foo")); + assert(dlsym(foo, "baz")); + assert(!dlsym(RTLD_DEFAULT, "foo")); + assert(!dlsym(RTLD_DEFAULT, "baz")); + + void *bar = dlopen(LIBBAR, RTLD_GLOBAL | RTLD_NOW); + assert(bar); + assert(dlsym(bar, "bar")); + assert(dlsym(RTLD_DEFAULT, "bar")); + assert(dlsym(RTLD_DEFAULT, "foo")); + assert(dlsym(RTLD_DEFAULT, "baz")); + + dlclose(foo); + dlclose(bar); +}