user: implement mlibc as the libc, finally.

It's finally done..

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
This commit is contained in:
kaguya
2026-05-02 03:31:49 -04:00
parent 2fa39ad85a
commit 9a9b91c940
2387 changed files with 152741 additions and 315 deletions
@@ -0,0 +1 @@
# only the test executable is required
@@ -0,0 +1,9 @@
#include <unistd.h>
__attribute__((destructor)) void destroy() {
_exit(0);
}
int main() {
return 1;
}
@@ -0,0 +1 @@
# only the test executable is required
@@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
// Make sure files are flushed on process exit.
int main() {
int pipefd[2];
if (pipe(pipefd) < 0) {
perror("pipe");
return -1;
}
pid_t child = fork();
if (child < 0) {
perror("fork");
return -1;
}
// This should sit in libc's buffer and be flushed on exit.
const char *message = "hello world";
if (child == 0) {
// Child
dup2(pipefd[1], STDOUT_FILENO);
printf("%s", message);
return 0;
}
// Parent
wait(NULL);
char buffer[64];
ssize_t len = read(pipefd[0], buffer, sizeof(buffer));
assert(len > 0);
buffer[len] = 0;
return strcmp(buffer, message) != 0;
}
@@ -0,0 +1,3 @@
// Bar needs to have a relocation against foo in order to set DT_NEEDED.
int foo(void);
int bar() { return foo(); }
@@ -0,0 +1 @@
int foo() { return 0; }
@@ -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]
@@ -0,0 +1,91 @@
#include <assert.h>
#include <link.h>
#include <string.h>
#include <dlfcn.h>
#include <stdio.h>
#ifdef USE_HOST_LIBC
#define LDSO_PATTERN "ld-linux-"
#define LIBFOO "libnative-foo.so"
#define LIBBAR "libnative-bar.so"
#else
#define LDSO_PATTERN "ld.so"
#define LIBFOO "libfoo.so"
#define LIBBAR "libbar.so"
#endif
struct result {
int found_ldso;
int found_self;
int found_foo;
int found_bar;
};
static int ends_with(const char *suffix, const char *s) {
size_t suffix_len = strlen(suffix);
size_t s_len = strlen(s);
if (s_len < suffix_len)
return 0;
else {
return !strcmp(suffix, s + s_len - suffix_len);
}
}
static int contains(const char *pattern, const char *s) {
return !!strstr(s, pattern);
}
static int callback(struct dl_phdr_info *info, size_t size, void *data) {
assert(size == sizeof(struct dl_phdr_info));
struct result *found = (struct result *) data;
printf("%s\n", info->dlpi_name);
fflush(stdout);
if (ends_with("foo.so", info->dlpi_name))
found->found_foo++;
if (ends_with("bar.so", info->dlpi_name))
found->found_bar++;
if (contains(LDSO_PATTERN, info->dlpi_name))
found->found_ldso++;
if (!strcmp("", info->dlpi_name))
found->found_self++;
assert(info->dlpi_phdr);
return 0;
}
int main() {
struct result found = { 0 };
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);
assert(!dl_iterate_phdr(callback, &found));
assert(found.found_ldso == 1);
assert(found.found_self == 1);
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);
assert(!dl_iterate_phdr(callback, &found));
assert(found.found_ldso == 1);
assert(found.found_self == 1);
assert(found.found_foo == 1);
assert(found.found_bar == 1);
printf("---\n");
dlclose(bar);
dlclose(foo);
return 0;
}
@@ -0,0 +1 @@
char foo_global[] = "";
@@ -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]
@@ -0,0 +1,23 @@
#include <dlfcn.h>
#include <assert.h>
#ifdef USE_HOST_LIBC
#define LIBFOO "libnative-foo.so"
#else
#define LIBFOO "libfoo.so"
#endif
int main() {
void *foo_handle = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
assert(foo_handle);
char *foo_global = (char *)dlsym(foo_handle, "foo_global");
assert(foo_global);
Dl_info info;
assert(dladdr((const void *)foo_global, &info) != 0);
assert(dlclose(foo_handle) == 0);
return 0;
}
+27
View File
@@ -0,0 +1,27 @@
import os
import sys
def main():
args = sys.argv[1:]
env = os.environ.copy()
# parse environment variable assignments
i = 0
while i < len(args) and '=' in args[i]:
var, val = args[i].split('=', 1)
env[var] = val
i += 1
if i < len(args):
exe_wrapper = os.environ.get('MESON_EXE_WRAPPER')
if exe_wrapper:
cmd = [exe_wrapper] + args[i:]
else:
cmd = args[i:]
os.execvpe(cmd[0], cmd, env)
else:
print("No command provided to execute.", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,11 @@
#include <stdint.h>
static uint64_t foo_impl(void) {
return 69;
}
static uint64_t (*foo_resolver(void))(void) {
return foo_impl;
}
uint64_t foo(void) __attribute__((ifunc("foo_resolver")));
@@ -0,0 +1,5 @@
libifunc_test = shared_library('ifunc-test-dlopen', 'lib.c', dependencies: libc_dep, install: true)
test_depends = [libifunc_test]
libifunc_test_native = shared_library('native-ifunc-test-dlopen', 'lib.c', native: true)
test_native_depends = [libifunc_test_native]
@@ -0,0 +1,23 @@
#include <assert.h>
#include <dlfcn.h>
int foo(void);
#ifdef USE_HOST_LIBC
#define LIB "libnative-ifunc-test-dlopen.so"
#else
#define LIB "libifunc-test-dlopen.so"
#endif
int main(void) {
void *handle = dlopen(LIB, RTLD_NOW | RTLD_LOCAL);
assert(handle);
int (*func)() = (int (*)()) dlsym(handle, "foo");
assert(func);
int ret = func();
assert(ret == 69);
return 0;
}
+35
View File
@@ -0,0 +1,35 @@
#include <stdint.h>
static uint64_t foo_impl(void) {
return 420;
}
#if defined(__riscv) && !defined(USE_HOST_LIBC)
#include <sys/hwprobe.h>
#elif defined(__riscv)
int __riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, size_t cpusetsize, cpu_set_t *cpus,
unsigned int flags) __attribute__((access(read_write, 1, 2)));
typedef int (*__riscv_hwprobe_t)(struct riscv_hwprobe *pairs, size_t pair_count,
size_t cpusetsize, cpu_set_t *cpus, unsigned int flags) __attribute__((access(read_write, 1, 2)));
#endif
#if defined(__riscv)
#include <stdlib.h>
static uint64_t (*foo_resolver(long long, __riscv_hwprobe_t hwprobe, void *))(void) {
if (!hwprobe)
return NULL;
struct riscv_hwprobe pairs[1] = {
{ .key = 4, .value = 0 }
};
hwprobe(pairs, 1, 0, 0, 0);
return foo_impl;
}
#else
static uint64_t (*foo_resolver(void))(void) {
return foo_impl;
}
#endif
uint64_t foo(void) __attribute__((ifunc("foo_resolver")));
@@ -0,0 +1,5 @@
libifunc_test = shared_library('ifunc-test', 'lib.c', dependencies: libc_dep)
test_link_with = [libifunc_test]
libifunc_test_native = shared_library('native-ifunc-test', 'lib.c', native: true)
test_native_link_with = [libifunc_test_native]
@@ -0,0 +1,10 @@
#include <assert.h>
int foo(void);
int main(void) {
int res = foo();
assert(res == 420);
return 0;
}
@@ -0,0 +1 @@
void foo() {}
@@ -0,0 +1,22 @@
# Remove the RPATH and set the LD_LIBRARY_PATH environment variable
# instead when running tests to make sure the library can be found
# in a directory specified by the user at runtime
test_rpath = '$ORIGIN/'
test_ld_path = meson.global_build_root() / 'tests' / 'rtld' / test_name
test_env += ['LD_LIBRARY_PATH=' + test_ld_path]
test_native_env += ['LD_LIBRARY_PATH=' + test_ld_path]
libfoo = shared_library('foo', 'libfoo.c',
dependencies: libc_dep,
link_args: test_additional_link_args,
)
test_depends = [libfoo]
libfoo_native = shared_library('native-foo', 'libfoo.c',
link_args: ['-ldl'] + test_additional_link_args,
native: true
)
test_native_depends = [libfoo_native]
@@ -0,0 +1,18 @@
#include <dlfcn.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#ifdef USE_HOST_LIBC
#define LIBFOO "libnative-foo.so"
#else
#define LIBFOO "libfoo.so"
#endif
int main() {
void *foo = dlopen(LIBFOO, RTLD_NOW);
assert(foo);
dlclose(foo);
return 0;
}
+106
View File
@@ -0,0 +1,106 @@
rtld_test_cases = [
'dl_iterate_phdr',
'dladdr_local',
'ld_library_path',
'noload-promote',
'rtld_next',
'soname',
'preinit',
'destroy1',
'destroy2',
'scope1',
'scope2',
'scope3',
'scope4',
'scope5',
'search-order',
'tls_align',
'relr',
'symver',
]
if host_machine.cpu_family() != 'm68k'
rtld_test_cases += [
'ifunc',
'ifunc-dlopen',
]
endif
host_libc_rtld_nosan_test_cases = [
'preinit',
]
py = import('python').find_installation('python3')
foreach test_name : rtld_test_cases
test_rpath = meson.global_build_root() / 'tests' / 'rtld' / test_name / ''
test_rpath += ':$ORIGIN/' # Workaround old and buggy qemu-user on CI
test_env = []
test_link_with = []
test_depends = []
test_native_env = []
test_native_link_with = []
test_native_depends = []
test_additional_link_args = []
test_skipped = false
# Build the needed DSOs for the test. This sets the variables above.
subdir(test_name)
if test_skipped
exec = executable('rtld-' + test_name, ['skipped.c', test_sources],
dependencies: libc_dep,
override_options: test_override_options,
c_args: test_c_args,
link_args: test_link_args,
)
test(test_name, exec, suite: 'rtld')
if build_tests_host_libc and not host_libc_excluded_test_cases.contains('rtld' / test_name)
test(test_name, exec, suite: ['host-libc', 'rtld'])
endif
continue
endif
exec = executable('rtld-' + test_name, [test_name / 'test.c', test_sources],
link_with: test_link_with,
dependencies: libc_dep,
build_rpath: test_rpath,
override_options: test_override_options,
c_args: test_c_args,
link_args: test_link_args + test_additional_link_args,
)
args = [
meson.global_source_root() + '/tests/rtld/env.py',
test_env,
exec,
]
# wrap the test so we can control the environment variables without meson interfering
test(test_name, py, args: args, suite: 'rtld', depends: test_depends)
if build_tests_host_libc and not host_libc_excluded_test_cases.contains('rtld' / test_name)
if test_name in host_libc_rtld_nosan_test_cases
host_libc_rtld_sanitize_options = 'b_sanitize=none'
else
# Don't use ASan here, due to a bug that breaks dlopen() + DT_RUNPATH:
# https://bugzilla.redhat.com/show_bug.cgi?id=1449604
host_libc_rtld_sanitize_options = 'b_sanitize=undefined'
endif
exec = executable('host-libc-' + test_name, test_name / 'test.c',
link_with: test_native_link_with,
dependencies: rtlib_deps,
build_rpath: test_rpath,
override_options: host_libc_rtld_sanitize_options,
c_args: ['-D_GNU_SOURCE', '-DUSE_HOST_LIBC'],
link_args: ['-ldl'] + test_additional_link_args,
native: true,
)
args = [
meson.global_source_root() + '/tests/rtld/env.py',
test_native_env,
exec,
]
test(test_name, py, args: args, suite: ['host-libc', 'rtld'], depends: test_native_depends)
endif
endforeach
@@ -0,0 +1 @@
void foo() {}
@@ -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]
@@ -0,0 +1,24 @@
#include <dlfcn.h>
#include <assert.h>
#include <stddef.h>
#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);
return 0;
}
@@ -0,0 +1,18 @@
#include <stdio.h>
#include <assert.h>
int fooDone = 0;
// DSOs do not support pre-initialization functions.
__attribute__((constructor))
void fooInit() {
dprintf(1, "initialization function called in foo\n");
assert(fooDone == 0);
fooDone++;
}
int isFooDone() {
return fooDone;
}
@@ -0,0 +1,19 @@
if host_machine.cpu_family() == 'riscv64'
# gp isn't initialized until after crt1.o runs, so to access
# globals in our pre-initializers we must disable it.
test_additional_link_args = ['-Wl,--no-relax']
endif
libfoo = shared_library('foo', ['libfoo.c'],
dependencies: libc_dep,
override_options: 'b_sanitize=none',
link_args: test_additional_link_args,
)
test_link_with = [libfoo]
libfoo_native = shared_library('native-foo', 'libfoo.c',
link_args: ['-ldl'] + test_additional_link_args,
override_options: 'b_sanitize=none',
native: true
)
test_native_link_with = [libfoo_native]
@@ -0,0 +1,46 @@
#include <stdio.h>
#include <assert.h>
int mainDone = 0;
int isFooDone();
void preInit1() {
// Use dprintf because stdout might not be initialized yet.
dprintf(1, "pre-initialization function 1 called in main executable\n");
assert(isFooDone() == 0);
assert(mainDone == 0);
mainDone++;
}
void preInit2() {
dprintf(1, "pre-initialization function 2 called in main executable\n");
assert(isFooDone() == 0);
assert(mainDone == 1);
mainDone++;
}
__attribute__((constructor))
void mainInit() {
dprintf(1, "initialization function called in main executable\n");
assert(isFooDone() == 1);
assert(mainDone == 2);
mainDone++;
}
// Manually register the pre-initialization functions.
__attribute__((used, section(".preinit_array")))
static void (*preinitFunctions[])(void) = {
&preInit1,
&preInit2,
};
int main() {
assert(isFooDone() == 1);
assert(mainDone == 3);
return 0;
}
@@ -0,0 +1 @@
test_additional_link_args += ['-Wl,-z,pack-relative-relocs']
+24
View File
@@ -0,0 +1,24 @@
#include <stddef.h>
#include <assert.h>
const char* strings[] = {
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
};
int main() {
for(size_t i = 0; i < sizeof(strings) / sizeof(*strings); ++i) {
assert(strings[i][0] == 0);
}
return 0;
}
@@ -0,0 +1,16 @@
#include <dlfcn.h>
typedef char *charFn(void);
__attribute__((weak))
char *definedInBoth() {
return "bar";
}
charFn *barGetDefault() {
return (charFn *)dlsym(RTLD_DEFAULT, "definedInBoth");
}
charFn *barGetNext() {
return (charFn *)dlsym(RTLD_NEXT, "definedInBoth");
}
@@ -0,0 +1,17 @@
#include <dlfcn.h>
typedef char *charFn(void);
__attribute__((weak))
char *definedInBoth() {
return "foo";
}
charFn *fooGetDefault() {
return (charFn *)dlsym(RTLD_DEFAULT, "definedInBoth");
}
charFn *fooGetNext() {
return (charFn *)dlsym(RTLD_NEXT, "definedInBoth");
}
@@ -0,0 +1,27 @@
# Prevent tail calls, as it breaks libc's 'calling object' detection.
no_tail_calls = '-fno-optimize-sibling-calls'
libfoo = shared_library('foo', 'libfoo.c',
dependencies: libc_dep,
c_args: no_tail_calls,
)
libbar = shared_library('bar', 'libbar.c',
dependencies: libc_dep,
c_args: no_tail_calls,
)
test_link_with = [libfoo, libbar] # foo is linked before bar
libfoo_native = shared_library('native-foo', 'libfoo.c',
c_args: [no_tail_calls, '-D_GNU_SOURCE'],
link_args: '-ldl',
override_options: 'b_sanitize=none',
native: true
)
libbar_native = shared_library('native-bar', 'libbar.c',
c_args: [no_tail_calls, '-D_GNU_SOURCE'],
link_args: '-ldl',
override_options: 'b_sanitize=none',
native: true
)
test_native_link_with = [libfoo_native, libbar_native]
@@ -0,0 +1,28 @@
#include <string.h>
#include <assert.h>
typedef char *charFn(void);
charFn *fooGetDefault(void);
charFn *fooGetNext(void);
charFn *barGetDefault(void);
charFn *barGetNext(void);
int main() {
charFn *ret;
ret = fooGetDefault();
assert(ret != NULL);
assert(!strcmp(ret(), "foo"));
ret = fooGetNext();
assert(ret != NULL);
assert(!strcmp(ret(), "bar"));
ret = barGetDefault();
assert(ret != NULL);
assert(!strcmp(ret(), "foo"));
assert(barGetNext() == NULL);
return 0;
}
@@ -0,0 +1,14 @@
char *foo(void);
char *foo_global(void);
char *bar(void) {
return "bar";
}
char *bar_calls_foo(void) {
return foo();
}
char *bar_calls_foo_global(void) {
return foo_global();
}
@@ -0,0 +1,9 @@
char *foo(void) {
return "foo";
}
char global[] = "foo global";
char *foo_global(void) {
return global;
}
@@ -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]
@@ -0,0 +1,92 @@
#include <stddef.h>
#include <dlfcn.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#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);
return 0;
}
@@ -0,0 +1,5 @@
char *foo_baz_conflict(void);
char *bar_calls_foo_baz_conflict(void) {
return foo_baz_conflict();
}
@@ -0,0 +1,3 @@
char *foo_baz_conflict() {
return "resolved to baz";
}
@@ -0,0 +1,3 @@
char *foo_baz_conflict() {
return "resolved to foo";
}
@@ -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]
@@ -0,0 +1,38 @@
#include <stddef.h>
#include <dlfcn.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#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);
return 0;
}
@@ -0,0 +1,8 @@
int g = 1;
int call_foo(void);
int call_bar(void) {
return call_foo();
}
@@ -0,0 +1,7 @@
int g = 2;
int call_foo(void);
int call_baz(void) {
return call_foo();
}
@@ -0,0 +1,5 @@
int g = 0;
int call_foo(void) {
return g;
}
@@ -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]
@@ -0,0 +1,41 @@
#include <dlfcn.h>
#include <stdio.h>
#include <assert.h>
#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);
return 0;
}
@@ -0,0 +1,3 @@
// Bar needs to have a relocation against foo in order to set DT_NEEDED.
void foo(void);
void bar() { foo(); }
@@ -0,0 +1 @@
void baz() {}
@@ -0,0 +1,3 @@
// Foo needs to have a relocation against baz in order to set DT_NEEDED.
void baz(void);
void foo() { baz(); }
@@ -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]
@@ -0,0 +1,38 @@
#include <dlfcn.h>
#include <stdio.h>
#include <assert.h>
#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);
return 0;
}
@@ -0,0 +1 @@
void foo() {}
@@ -0,0 +1,5 @@
libfoo = shared_library('foo', 'libfoo.c')
test_link_with = [libfoo]
libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
test_native_link_with = [libfoo_native]
@@ -0,0 +1,29 @@
#include <dlfcn.h>
#include <stdio.h>
#include <assert.h>
#ifdef USE_HOST_LIBC
#define LIBFOO "libnative-foo.so"
#else
#define LIBFOO "libfoo.so"
#endif
// We need to have a relocation against foo for DT_NEEDED.
void foo();
void bar() { foo(); }
int main() {
// In this test, we have exec -> foo (where '->' means 'depends on').
// This means that foo is in the global scope due to DT_NEEDED.
// We then dlopen it again with RTLD_LOCAL, which should just return
// the already-loaded object, but used to crash in the mlibc linker instead.
void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
assert(foo);
assert(dlsym(foo, "foo"));
assert(dlsym(RTLD_DEFAULT, "foo"));
dlclose(foo);
return 0;
}
@@ -0,0 +1,19 @@
#include <assert.h>
#include <dlfcn.h>
#ifdef USE_HOST_LIBC
#define LIBFOO "libnative-foo.so"
#else
#define LIBFOO "libfoo.so"
#endif
int foo(void);
int bar(void) {
return foo();
}
[[gnu::constructor]] void init(void) {
void *libfoo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
assert(libfoo);
}
@@ -0,0 +1,3 @@
int foo(void) {
return 0xCAFEBABE;
}
@@ -0,0 +1,32 @@
# meson does not have native custom_target, so when cross compiling it gets
# unhappy that we're trying to link a "host" library (which is actually built
# for the build machine) to a build executable. This means we can't patchelf
# native libraries if we want cross compilation to work.
patchelf = find_program('patchelf', required: false)
if patchelf.found()
# Overwrite LD_LIBRARY_PATH to make meson not fix our shenanigans
# Otherwise, meson's LD_LIBRARY_PATH makes this test useless because libfoo
# will be found through it instead of failing when libfoo is not found
test_env += ['LD_LIBRARY_PATH=']
test_native_env += ['LD_LIBRARY_PATH=']
libfoo = shared_library('foo', 'libfoo.c', link_args: test_additional_link_args)
libbar_unpatched = shared_library('bar-unpatched', 'libbar.c',
link_args: test_additional_link_args,
override_options: 'b_sanitize=none',
dependencies: libc_dep,
link_with: libfoo)
libbar = custom_target('patch-libbar',
command: [patchelf,
'--remove-rpath',
'--set-soname', 'libbar.so',
libbar_unpatched,
'--output', '@OUTPUT0@'],
output: ['libbar.so'],
)
test_link_with = [libbar, libfoo]
else
test_skipped = true
endif
@@ -0,0 +1,21 @@
#include <assert.h>
#include <dlfcn.h>
#ifdef USE_HOST_LIBC
#define LIBBAR "libnative-bar.so"
#else
#define LIBBAR "libbar.so"
#endif
int foo();
int bar();
int main() {
void *libbar = dlopen(LIBBAR, RTLD_LOCAL | RTLD_NOW);
assert(libbar);
assert(foo() == (int) 0xCAFEBABE);
assert(bar() == (int) 0xCAFEBABE);
return 0;
}
+3
View File
@@ -0,0 +1,3 @@
int main() {
return 77;
}
@@ -0,0 +1 @@
char *name() { return "bar"; }
@@ -0,0 +1 @@
char *name(void) { return "foo"; }
@@ -0,0 +1,17 @@
# Create two libraries with the same SONAME.
bar_soname = '-Wl,-soname=libbar.so'
libfoo = shared_library('foo', 'libfoo.c', link_args: '-Wl,-soname=libbar.so')
libbar = shared_library('bar', 'libbar.c', link_args: '-Wl,-soname=libbar.so')
test_depends = [libfoo, libbar]
libfoo_native = shared_library('native-foo', 'libfoo.c',
link_args: ['-ldl', '-Wl,-soname=libnative-bar.so'],
native: true
)
libbar_native = shared_library('native-bar', 'libbar.c',
link_args: ['-ldl', '-Wl,-soname=libnative-bar.so'],
native: true
)
test_native_depends = [libfoo_native, libbar_native]
@@ -0,0 +1,35 @@
#include <dlfcn.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#ifdef USE_HOST_LIBC
#define LIBFOO "libnative-foo.so"
#define LIBBAR "libnative-bar.so"
#else
#define LIBFOO "libfoo.so"
#define LIBBAR "libbar.so"
#endif
int main() {
void *foo = dlopen(LIBFOO, RTLD_NOW);
void *bar = dlopen(LIBBAR, RTLD_NOW);
assert(foo);
assert(bar);
// Since these libraries have the same SONAME, they should return the same thing.
assert(foo == bar);
char *(*fooSym)(void) = dlsym(foo, "name");
char *(*barSym)(void) = dlsym(bar, "name");
assert(fooSym && barSym);
assert(fooSym() && barSym());
printf("foo: name() = \"%s\"\n", fooSym());
printf("bar: name() = \"%s\"\n", barSym());
assert(!strcmp(fooSym(), barSym()));
dlclose(foo);
dlclose(bar);
return 0;
}
@@ -0,0 +1,15 @@
int foo_v1(void) {
return 1;
}
int foo_v2(void) {
return 2;
}
int foo_v3(void) {
return 3;
}
asm(".symver foo_v1, foo@FOO_1");
asm(".symver foo_v2, foo@FOO_2");
asm(".symver foo_v3, foo@@FOO_3"); // default version (due to @@ vs @)
@@ -0,0 +1,14 @@
FOO_1 {
global: foo;
local: *;
};
FOO_2 {
global: foo;
local: *;
};
FOO_3 {
global: foo;
local: *;
};
@@ -0,0 +1,11 @@
version_script = meson.current_source_dir() / 'libfoo.ver'
libfoo = shared_library('foo', 'libfoo.c',
link_args : ['-Wl,--version-script', version_script])
test_depends = [libfoo]
test_link_with = [libfoo]
libfoo_native = shared_library('native-foo', 'libfoo.c', native: true,
link_args : ['-Wl,--version-script', version_script])
test_native_depends = [libfoo_native]
test_native_link_with = [libfoo_native]
@@ -0,0 +1,39 @@
#include <assert.h>
#include <stdio.h>
#include <dlfcn.h>
#ifdef USE_HOST_LIBC
#define LIBFOO "libnative-foo.so"
#else
#define LIBFOO "libfoo.so"
#endif
int foo(void);
int main() {
int ver = foo();
fprintf(stderr, "called foo version %d\n", ver);
assert(ver == 3); // version 3 is the default for libfoo.so
void *libfoo_h = dlopen(LIBFOO, RTLD_GLOBAL | RTLD_NOW);
assert(libfoo_h);
int (*foo1)(void) = dlvsym(libfoo_h, "foo", "FOO_1");
assert(foo1);
int (*foo2)(void) = dlvsym(libfoo_h, "foo", "FOO_2");
assert(foo2);
int (*foo3)(void) = dlvsym(libfoo_h, "foo", "FOO_3");
assert(foo3);
int (*foo_def)(void) = dlsym(libfoo_h, "foo");
assert(foo_def);
assert(foo1() == 1);
assert(foo2() == 2);
assert(foo3() == 3);
assert(foo3 == foo_def);
return 0;
}
@@ -0,0 +1 @@
_Thread_local __attribute__((aligned(8))) char bar_thread_local[8] = "Hello!";
@@ -0,0 +1 @@
_Thread_local __attribute__((aligned(16))) char foo_thread_local[8] = "Hello!";
@@ -0,0 +1,7 @@
libfoo = shared_library('foo', 'libfoo.c')
libbar = shared_library('bar', 'libbar.c')
test_link_with = [libfoo, libbar]
libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
libbar_native = shared_library('native-bar', 'libbar.c', native: true)
test_native_link_with = [libfoo_native, libbar_native]
@@ -0,0 +1,12 @@
#include <assert.h>
#include <stdint.h>
extern _Thread_local char foo_thread_local[];
extern _Thread_local char bar_thread_local[];
int main() {
assert(!((uintptr_t)foo_thread_local & (16 - 1)));
assert(!((uintptr_t)bar_thread_local & (8 - 1)));
return 0;
}