| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #define _GNU_SOURCE |
| #include <assert.h> |
| #include <dlfcn.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <threads.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stdatomic.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
|
|
| #include <emscripten/console.h> |
| #include <emscripten/threading.h> |
| #include <emscripten/promise.h> |
| #include <emscripten/proxying.h> |
|
|
| #include "dynlink.h" |
| #include "pthread_impl.h" |
| #include "threading_internal.h" |
| #include "emscripten_internal.h" |
|
|
| |
|
|
| #ifdef DYLINK_DEBUG |
| #define dbg(fmt, ...) emscripten_dbgf(fmt, ##__VA_ARGS__) |
| #else |
| #define dbg(fmt, ...) |
| #endif |
|
|
| struct async_data { |
| em_dlopen_callback onsuccess; |
| em_arg_callback_func onerror; |
| void* user_data; |
| }; |
|
|
| void __dl_vseterr(const char*, va_list); |
|
|
| |
| |
| |
| |
| struct dlevent { |
| struct dlevent *next, *prev; |
| |
| int sym_index; |
| |
| struct dso* dso; |
| #ifdef DYLINK_DEBUG |
| int id; |
| #endif |
| }; |
|
|
| |
| static struct dso main_dso = { |
| .name = "__main__", |
| .flags = 0, |
| }; |
|
|
| static struct dlevent main_event = { |
| .prev = NULL, |
| .next = NULL, |
| .sym_index = -1, |
| .dso = &main_dso, |
| }; |
|
|
| static struct dlevent* _Atomic head = &main_event; |
| static struct dlevent* _Atomic tail = &main_event; |
|
|
| #ifdef _REENTRANT |
| static thread_local struct dlevent* thread_local_tail = &main_event; |
| static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER; |
| static thread_local bool skip_dlsync = false; |
|
|
| static void do_write_lock() { |
| |
| |
| skip_dlsync = true; |
| pthread_mutex_lock(&write_lock); |
| } |
|
|
| static void do_write_unlock() { |
| pthread_mutex_unlock(&write_lock); |
| skip_dlsync = false; |
| } |
| #else |
| #define do_write_unlock() |
| #define do_write_lock() |
| #endif |
|
|
| 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 dlevent* p; |
| for (p = head; p; p = p->next) |
| if (p->sym_index == -1 && p->dso == h) |
| return 0; |
| dbg("__dl_invalid_handle %p", h); |
| error("Invalid library handle %p", (void*)h); |
| return 1; |
| } |
|
|
| void new_dlevent(struct dso* p, int sym_index) { |
| struct dlevent* ev = calloc(1, sizeof(struct dlevent)); |
|
|
| ev->dso = p; |
| ev->sym_index = sym_index; |
| if (p) p->event = ev; |
|
|
| |
| ev->prev = tail; |
| if (tail) { |
| tail->next = ev; |
| #ifdef DYLINK_DEBUG |
| ev->id = tail->id + 1; |
| #endif |
| } |
| dbg("new_dlevent: ev=%p id=%d %s dso=%p sym_index=%d", |
| ev, |
| ev->id, |
| p ? p->name : "RTLD_DEFAULT", |
| p, |
| sym_index); |
| tail = ev; |
| #if _REENTRANT |
| thread_local_tail = ev; |
| #endif |
| } |
|
|
| static void load_library_done(struct dso* p) { |
| dbg("load_library_done: dso=%p mem_addr=%p mem_size=%zu " |
| "table_addr=%p table_size=%zu", |
| p, |
| p->mem_addr, |
| p->mem_size, |
| p->table_addr, |
| p->table_size); |
| new_dlevent(p, -1); |
| #ifdef _REENTRANT |
| |
| _emscripten_dlsync_threads(); |
| #endif |
| |
| |
| |
| |
| } |
|
|
| 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); |
|
|
| |
| |
| |
| struct stat statbuf; |
| if (stat(name, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) { |
| int fd = open(name, O_RDONLY); |
| if (fd >= 0) { |
| off_t size = lseek(fd, 0, SEEK_END); |
| if (size != (off_t)-1) { |
| lseek(fd, 0, SEEK_SET); |
| p->file_data = malloc(size); |
| if (p->file_data) { |
| if (read(fd, p->file_data, size) == size) { |
| p->file_data_size = size; |
| } else { |
| free(p->file_data); |
| } |
| } |
| } |
| close(fd); |
| } |
| } |
|
|
| return p; |
| } |
|
|
| #ifdef _REENTRANT |
| |
| |
| |
| |
| |
| #define ABORT_ON_SYNC_FAILURE 1 |
|
|
| static void dlsync_next(struct dlevent* dlevent, em_promise_t promise); |
|
|
| static void sync_one_onsuccess(struct dso* dso, void* user_data) { |
| em_promise_t promise = (em_promise_t)user_data; |
| dbg("sync_one_onsuccess dso=%p event=%p promise=%p", dso, dso->event, promise); |
| |
| thread_local_tail = dso->event; |
| dlsync_next(thread_local_tail->next, promise); |
| } |
|
|
| static void sync_one_onerror(struct dso* dso, void* user_data) { |
| #if ABORT_ON_SYNC_FAILURE |
| abort(); |
| #else |
| em_promise_t promise = (em_promise_t)user_data; |
| emscripten_promise_reject(promise); |
| #endif |
| } |
|
|
| |
| |
| static void dlsync_next(struct dlevent* dlevent, em_promise_t promise) { |
| dbg("dlsync_next event=%p promise=%p", dlevent, promise); |
|
|
| |
| while (dlevent && dlevent->sym_index != -1) { |
| dbg("calling _dlsym_catchup_js ...."); |
| void* success = _dlsym_catchup_js(dlevent->dso, dlevent->sym_index); |
| if (!success) { |
| emscripten_errf("_dlsym_catchup_js failed: %s", dlerror()); |
| sync_one_onerror(dlevent->dso, promise); |
| return; |
| } |
| dlevent = dlevent->next; |
| } |
|
|
| if (!dlevent) { |
| |
| emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL); |
| return; |
| } |
|
|
| dbg("dlsync_next calling _emscripten_dlopen_js: dso=%p", dlevent->dso); |
| _emscripten_dlopen_js( |
| dlevent->dso, sync_one_onsuccess, sync_one_onerror, promise); |
| } |
|
|
| void _emscripten_dlsync_self_async(em_promise_t promise) { |
| dbg("_emscripten_dlsync_self_async promise=%p", promise); |
| |
| |
| dlsync_next(thread_local_tail->next, promise); |
| } |
|
|
| |
| |
| bool _emscripten_dlsync_self() { |
| |
| assert(!emscripten_is_main_runtime_thread()); |
| if (thread_local_tail == tail) { |
| dbg("_emscripten_dlsync_self: already in sync"); |
| return true; |
| } |
| dbg("_emscripten_dlsync_self: catching up %p %p", thread_local_tail, tail); |
| while (thread_local_tail->next) { |
| struct dlevent* p = thread_local_tail->next; |
| if (p->sym_index != -1) { |
| dbg("_emscripten_dlsync_self: id=%d %s sym_index=%d", |
| p->id, |
| p->dso->name, |
| p->sym_index); |
| void* success = _dlsym_catchup_js(p->dso, p->sym_index); |
| if (!success) { |
| emscripten_errf("_dlsym_catchup_js failed: %s", dlerror()); |
| return false; |
| } |
| } else { |
| dbg("_emscripten_dlsync_self: id=%d %s mem_addr=%p " |
| "mem_size=%zu table_addr=%p table_size=%zu", |
| p->id, |
| p->dso->name, |
| p->dso->mem_addr, |
| p->dso->mem_size, |
| p->dso->table_addr, |
| p->dso->table_size); |
| void* success = _dlopen_js(p->dso); |
| if (!success) { |
| |
| |
| |
| |
| emscripten_errf("_dlopen_js failed: %s", dlerror()); |
| return false; |
| } |
| } |
| thread_local_tail = p; |
| } |
| dbg("_emscripten_dlsync_self: done"); |
| return true; |
| } |
|
|
| struct promise_result { |
| em_promise_t promise; |
| bool result; |
| }; |
|
|
| static void do_thread_sync(void* arg) { |
| dbg("do_thread_sync"); |
| struct promise_result* info = arg; |
| info->result = _emscripten_dlsync_self(); |
| } |
|
|
| static void do_thread_sync_out(void* arg) { |
| dbg("do_thread_sync_out"); |
| int* result = (int*)arg; |
| *result = _emscripten_dlsync_self(); |
| } |
|
|
| |
| |
| static void thread_sync_cancelled(void* arg) { |
| struct promise_result* info = arg; |
| dbg("thread_sync_cancelled: promise=%p result=%i", info->promise, info->result); |
| emscripten_promise_resolve(info->promise, EM_PROMISE_FULFILL, NULL); |
| emscripten_promise_destroy(info->promise); |
| free(info); |
| } |
|
|
| |
| static void thread_sync_done(void* arg) { |
| struct promise_result* info = arg; |
| em_promise_t promise = info->promise; |
| dbg("thread_sync_done: promise=%p result=%i", promise, info->result); |
| if (info->result) { |
| emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL); |
| } else { |
| #if ABORT_ON_SYNC_FAILURE |
| abort(); |
| #else |
| emscripten_promise_reject(promise); |
| #endif |
| } |
| emscripten_promise_destroy(promise); |
| free(info); |
| } |
|
|
| |
| |
| |
| |
| |
| static em_proxying_queue * _Atomic dlopen_proxying_queue = NULL; |
| static thread_local bool processing_queue = false; |
|
|
| void _emscripten_process_dlopen_queue() { |
| if (dlopen_proxying_queue && !processing_queue) { |
| assert(!emscripten_is_main_runtime_thread()); |
| processing_queue = true; |
| emscripten_proxy_execute_queue(dlopen_proxying_queue); |
| processing_queue = false; |
| } |
| } |
|
|
| |
| |
| |
| |
| int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise) { |
| assert(emscripten_is_main_runtime_thread()); |
| if (!dlopen_proxying_queue) { |
| dlopen_proxying_queue = em_proxying_queue_create(); |
| } |
|
|
| struct promise_result* info = malloc(sizeof(struct promise_result)); |
| if (!info) { |
| return false; |
| } |
| *info = (struct promise_result){ |
| .promise = promise, |
| .result = false, |
| }; |
| int rtn = emscripten_proxy_callback(dlopen_proxying_queue, |
| target_thread, |
| do_thread_sync, |
| thread_sync_done, |
| thread_sync_cancelled, |
| info); |
| if (!rtn) { |
| |
| |
| emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL); |
| emscripten_promise_destroy(promise); |
| free(info); |
| } else { |
| |
| _emscripten_thread_notify(target_thread); |
| } |
| return rtn; |
| } |
|
|
| int _emscripten_proxy_dlsync(pthread_t target_thread) { |
| assert(emscripten_is_main_runtime_thread()); |
| if (!dlopen_proxying_queue) { |
| dlopen_proxying_queue = em_proxying_queue_create(); |
| } |
| int result; |
| if (!emscripten_proxy_sync( |
| dlopen_proxying_queue, target_thread, do_thread_sync_out, &result)) { |
| return 0; |
| } |
| return result; |
| } |
| #endif |
|
|
| static void dlopen_onsuccess(struct dso* dso, void* user_data) { |
| struct async_data* data = (struct async_data*)user_data; |
| dbg("dlopen_js_onsuccess: dso=%p mem_addr=%p mem_size=%zu", |
| dso, |
| dso->mem_addr, |
| dso->mem_size); |
| load_library_done(dso); |
| do_write_unlock(); |
| data->onsuccess(data->user_data, dso); |
| free(data); |
| } |
|
|
| static void dlopen_onerror(struct dso* dso, void* user_data) { |
| struct async_data* data = (struct async_data*)user_data; |
| dbg("dlopen_js_onerror: dso=%p", dso); |
| do_write_unlock(); |
| data->onerror(data->user_data); |
| free(dso); |
| free(data); |
| } |
|
|
| |
| static int path_find(const char *name, const char *s, char *buf, size_t buf_size) { |
| if (s == NULL) { |
| return -1; |
| } |
| size_t l; |
| int fd; |
| for (;;) { |
| s += strspn(s, ":\n"); |
| l = strcspn(s, ":\n"); |
| if (l-1 >= INT_MAX) return -1; |
| if (snprintf(buf, buf_size, "%.*s/%s", (int)l, s, name) < buf_size) { |
| dbg("dlopen: path_find: %s", buf); |
| struct stat statbuf; |
| if (stat(buf, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) { |
| return 0; |
| } |
| switch (errno) { |
| case ENOENT: |
| case ENOTDIR: |
| case EACCES: |
| case ENAMETOOLONG: |
| break; |
| default: |
| dbg("dlopen: path_find failed: %s", strerror(errno)); |
| |
| |
| return -2; |
| } |
| } |
| s += l; |
| } |
| } |
|
|
| |
| const char* _emscripten_find_dylib(char* buf, const char* rpath, const char* file, size_t buflen) { |
| if (strchr(file, '/')) { |
| |
| return NULL; |
| } |
| const char* env_path = getenv("LD_LIBRARY_PATH"); |
| if (path_find(file, env_path, buf, buflen) == 0) { |
| dbg("dlopen: found in LD_LIBRARY_PATH: %s", buf); |
| return buf; |
| } |
| if (path_find(file, rpath, buf, buflen) == 0) { |
| dbg("dlopen: found in RPATH: %s", buf); |
| return buf; |
| } |
| return NULL; |
| } |
|
|
| static const char* find_dylib(char* buf, const char* file, size_t buflen) { |
| const char* res = _emscripten_find_dylib(buf, NULL, file, buflen); |
| if (res) { |
| return res; |
| } |
| return file; |
| } |
|
|
| |
| static struct dso* find_existing(const char* file) { |
| for (struct dlevent* e = head; e; e = e->next) { |
| if (e->sym_index == -1 && !strcmp(e->dso->name, file)) { |
| dbg("dlopen: already opened: %p", e->dso); |
| return e->dso; |
| } |
| } |
| return NULL; |
| } |
|
|
| |
| |
| static struct dso* _dlopen(const char* file, int flags) { |
| if (!file) { |
| |
| |
| dbg("dlopen: NULL -> %p", head->dso); |
| return head->dso; |
| } |
| dbg("dlopen: %s [%d]", file, flags); |
|
|
| int cs; |
| pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); |
| do_write_lock(); |
|
|
| char buf[2*NAME_MAX+2]; |
| file = find_dylib(buf, file, sizeof buf); |
|
|
| struct dso* p = find_existing(file); |
| if (p) { |
| goto end; |
| } |
|
|
| p = load_library_start(file, flags); |
| if (!p) { |
| goto end; |
| } |
| void* success = _dlopen_js(p); |
| if (!success) { |
| dbg("dlopen_js: failed: %p", p); |
| free(p); |
| p = NULL; |
| goto end; |
| } |
| dbg("dlopen_js: success: %p", p); |
| load_library_done(p); |
| end: |
| dbg("dlopen(%s): done: %p", file, p); |
| do_write_unlock(); |
| pthread_setcancelstate(cs, 0); |
| return p; |
| } |
|
|
| void* dlopen(const char* file, int flags) { |
| return _dlopen(file, flags); |
| } |
|
|
| void emscripten_dlopen(const char* filename, int flags, void* user_data, |
| em_dlopen_callback onsuccess, em_arg_callback_func onerror) { |
| dbg("emscripten_dlopen: %s", filename); |
| if (!filename) { |
| onsuccess(user_data, head->dso); |
| return; |
| } |
| do_write_lock(); |
| char buf[2*NAME_MAX+2]; |
| filename = find_dylib(buf, filename, sizeof buf); |
| struct dso* p = find_existing(filename); |
| if (p) { |
| onsuccess(user_data, p); |
| return; |
| } |
| p = load_library_start(filename, flags); |
| if (!p) { |
| do_write_unlock(); |
| onerror(user_data); |
| return; |
| } |
|
|
| |
| struct async_data* d = malloc(sizeof(struct async_data)); |
| d->user_data = user_data; |
| d->onsuccess = onsuccess; |
| d->onerror = onerror; |
|
|
| dbg("calling emscripten_dlopen_js %p", p); |
| |
| _emscripten_dlopen_js(p, dlopen_onsuccess, dlopen_onerror, d); |
| } |
|
|
| static void promise_onsuccess(void* user_data, void* handle) { |
| em_promise_t p = (em_promise_t)user_data; |
| dbg("promise_onsuccess: %p", p); |
| emscripten_promise_resolve(p, EM_PROMISE_FULFILL, handle); |
| emscripten_promise_destroy(p); |
| } |
|
|
| static void promise_onerror(void* user_data) { |
| em_promise_t p = (em_promise_t)user_data; |
| dbg("promise_onerror: %p", p); |
| emscripten_promise_resolve(p, EM_PROMISE_REJECT, NULL); |
| emscripten_promise_destroy(p); |
| } |
|
|
| |
| |
| |
| |
| em_promise_t emscripten_dlopen_promise(const char* filename, int flags) { |
| |
| |
| em_promise_t p = emscripten_promise_create(); |
| emscripten_dlopen(filename, flags, p, promise_onsuccess, promise_onerror); |
|
|
| |
| |
| em_promise_t ret = emscripten_promise_create(); |
| emscripten_promise_resolve(ret, EM_PROMISE_MATCH, p); |
| return ret; |
| } |
|
|
| void* __dlsym(void* restrict p, const char* restrict s, void* restrict ra) { |
| dbg("__dlsym dso:%p sym:%s", p, s); |
| if (p != RTLD_DEFAULT && p != RTLD_NEXT && __dl_invalid_handle(p)) { |
| return 0; |
| } |
| |
| |
| if (p == head->dso) { |
| p = RTLD_DEFAULT; |
| } |
| void* res; |
| int sym_index = -1; |
| do_write_lock(); |
| res = _dlsym_js(p, s, &sym_index); |
| if (sym_index != -1) { |
| new_dlevent(p, sym_index); |
| #ifdef _REENTRANT |
| |
| _emscripten_dlsync_threads(); |
| #endif |
| } |
| dbg("__dlsym done dso:%p res:%p", p, res); |
| do_write_unlock(); |
| return res; |
| } |
|
|