blob: 853fa7b852f1051f5b02b69f55613a798afc7288 [file] [edit]
/*
* Copyright 2021 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*
* Emscripten-specific version dlopen and associated functions. Some code is
* shared with musl's ldso/dynlink.c.
*/
#define _GNU_SOURCE
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
#include <threads.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dynlink.h>
#include <emscripten/console.h>
//#define DYLINK_DEBUG
struct async_data {
em_dlopen_callback onsuccess;
em_arg_callback_func onerror;
void* user_data;
};
typedef void (*dlopen_callback_func)(struct dso*, struct async_data* user_data);
void _dlinit(struct dso* main_dso_handle);
void* _dlopen_js(struct dso* handle);
void* _dlsym_js(struct dso* handle, const char* symbol);
void _emscripten_dlopen_js(struct dso* handle,
dlopen_callback_func onsuccess,
dlopen_callback_func onerror,
struct async_data* user_data);
void __dl_vseterr(const char*, va_list);
static struct dso * _Atomic head, * _Atomic tail;
static thread_local struct dso* thread_local_tail;
static pthread_rwlock_t lock;
static void error(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
__dl_vseterr(fmt, ap);
va_end(ap);
#ifdef DYLINK_DEBUG
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
#endif
}
int __dl_invalid_handle(void* h) {
struct dso* p;
for (p = head; p; p = p->next)
if (h == p)
return 0;
error("Invalid library handle %p", (void*)h);
return 1;
}
static void load_library_done(struct dso* p) {
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: load_library_done: dso=%p mem_addr=%p mem_size=%zu "
"table_addr=%p table_size=%zu",
pthread_self(),
p,
p->mem_addr,
p->mem_size,
p->table_addr,
p->table_size);
#endif
// insert into linked list
p->prev = tail;
if (tail) {
tail->next = p;
}
tail = p;
thread_local_tail = p;
if (!head) {
head = p;
}
}
static struct dso* load_library_start(const char* name, int flags) {
if (!(flags & (RTLD_LAZY | RTLD_NOW))) {
error("invalid mode for dlopen(): Either RTLD_LAZY or RTLD_NOW is required");
return NULL;
}
struct dso* p;
size_t alloc_size = sizeof *p + strlen(name) + 1;
p = calloc(1, alloc_size);
p->flags = flags;
strcpy(p->name, name);
return p;
}
static void dlopen_js_onsuccess(struct dso* dso, struct async_data* data) {
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: dlopen_js_onsuccess: dso=%p mem_addr=%p mem_size=%zu",
pthread_self(),
dso,
dso->mem_addr,
dso->mem_size);
#endif
load_library_done(dso);
pthread_rwlock_unlock(&lock);
data->onsuccess(data->user_data, dso);
free(data);
}
static void dlopen_js_onerror(struct dso* dso, struct async_data* data) {
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: dlopen_js_onerror: dso=%p", pthread_self(), dso);
#endif
pthread_rwlock_unlock(&lock);
data->onerror(data->user_data);
free(dso);
free(data);
}
// This function is called at the start of all entry points so that the dso
// list gets initialized on first use.
static void ensure_init() {
if (head) {
return;
}
// Initialize the dso list. This happens on first run.
pthread_rwlock_wrlock(&lock);
if (!head) {
// Flags are not important since the main module is already loaded.
struct dso* p = load_library_start("__main__", RTLD_NOW|RTLD_GLOBAL);
assert(p);
_dlinit(p);
load_library_done(p);
assert(head);
}
pthread_rwlock_unlock(&lock);
}
void* dlopen(const char* file, int flags) {
ensure_init();
if (!file) {
return head;
}
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: dlopen: %s [%d]", pthread_self(), file, flags);
#endif
struct dso* p;
int cs;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
pthread_rwlock_wrlock(&lock);
/* Search for the name to see if it's already loaded */
for (p = head; p; p = p->next) {
if (!strcmp(p->name, file)) {
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: dlopen: already opened: %p", pthread_self(), p);
#endif
goto end;
}
}
p = load_library_start(file, flags);
if (!p) {
goto end;
}
void* success = _dlopen_js(p);
if (!success) {
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: dlopen_js: failed: %p", pthread_self(), p);
#endif
free(p);
p = NULL;
goto end;
}
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: dlopen_js: success: %p", pthread_self(), p);
#endif
load_library_done(p);
end:
pthread_rwlock_unlock(&lock);
pthread_setcancelstate(cs, 0);
return p;
}
void emscripten_dlopen(const char* filename, int flags, void* user_data,
em_dlopen_callback onsuccess, em_arg_callback_func onerror) {
ensure_init();
if (!filename) {
onsuccess(user_data, head);
return;
}
pthread_rwlock_wrlock(&lock);
struct dso* p = load_library_start(filename, flags);
if (!p) {
pthread_rwlock_unlock(&lock);
onerror(user_data);
return;
}
// For async mode
struct async_data* d = malloc(sizeof(struct async_data));
d->user_data = user_data;
d->onsuccess = onsuccess;
d->onerror = onerror;
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: calling emscripten_dlopen_js %p", pthread_self(), p);
#endif
// Unlock happens in dlopen_js_onsuccess/dlopen_js_onerror
_emscripten_dlopen_js(p, dlopen_js_onsuccess, dlopen_js_onerror, d);
}
void* __dlsym(void* restrict p, const char* restrict s, void* restrict ra) {
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: __dlsym dso:%p sym:%s", pthread_self(), p, s);
#endif
if (p != RTLD_DEFAULT && p != RTLD_NEXT && __dl_invalid_handle(p)) {
return 0;
}
void* res;
pthread_rwlock_rdlock(&lock);
res = _dlsym_js(p, s);
pthread_rwlock_unlock(&lock);
return res;
}
int dladdr(const void* addr, Dl_info* info) {
// report all function pointers as coming from this program itself XXX not
// really correct in any way
info->dli_fname = "unknown";
info->dli_fbase = NULL;
info->dli_sname = NULL;
info->dli_saddr = NULL;
return 1;
}
#ifdef _REENTRANT
void _emscripten_thread_sync_code() {
// This function is called from emscripten_yeild which itself is called
// whenever we block on a futex. We need to check to avoid infinite
// recursion when taking the lock below.
static thread_local bool syncing = false;
if (syncing) {
return;
}
syncing = true;
ensure_init();
if (thread_local_tail == tail) {
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: emscripten_thread_sync_code: already in sync", pthread_self());
#endif
goto done;
}
pthread_rwlock_rdlock(&lock);
if (!thread_local_tail) {
thread_local_tail = head;
}
while (thread_local_tail->next) {
struct dso* p = thread_local_tail->next;
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: emscripten_thread_sync_code: %s mem_addr=%p "
"mem_size=%zu table_addr=%p table_size=%zu",
pthread_self(),
p->name,
p->mem_addr,
p->mem_size,
p->table_addr,
p->table_size);
#endif
void* success = _dlopen_js(p);
if (!success) {
// If any on the libraries fails to load here then we give up.
// TODO(sbc): Ideally this would never happen and we could/should
// abort, but on the main thread (where we don't have sync xhr) its
// often not possible to syncronously load side module.
_emscripten_errf("emscripten_thread_sync_code failed: %s", dlerror());
break;
}
thread_local_tail = p;
}
pthread_rwlock_unlock(&lock);
#ifdef DYLINK_DEBUG
_emscripten_errf("%p: emscripten_thread_sync_code done", pthread_self());
#endif
done:
syncing = false;
}
#endif