Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/libc/dynlink.c
6162 views
1
/*
2
* Copyright 2021 The Emscripten Authors. All rights reserved.
3
* Emscripten is available under two separate licenses, the MIT license and the
4
* University of Illinois/NCSA Open Source License. Both these licenses can be
5
* found in the LICENSE file.
6
*
7
* Emscripten-specific version dlopen and associated functions. Some code is
8
* shared with musl's ldso/dynlink.c.
9
*/
10
11
#define _GNU_SOURCE
12
#include <assert.h>
13
#include <dlfcn.h>
14
#include <fcntl.h>
15
#include <pthread.h>
16
#include <threads.h>
17
#include <stdarg.h>
18
#include <stdbool.h>
19
#include <stdio.h>
20
#include <stdlib.h>
21
#include <string.h>
22
#include <sys/stat.h>
23
#include <unistd.h>
24
25
#include <emscripten/console.h>
26
#include <emscripten/threading.h>
27
#include <emscripten/promise.h>
28
#include <emscripten/proxying.h>
29
30
#include "dynlink.h"
31
#include "pthread_impl.h"
32
#include "emscripten_internal.h"
33
34
//#define DYLINK_DEBUG
35
36
#ifdef DYLINK_DEBUG
37
#define dbg(fmt, ...) emscripten_dbgf(fmt, ##__VA_ARGS__)
38
#else
39
#define dbg(fmt, ...)
40
#endif
41
42
struct async_data {
43
em_dlopen_callback onsuccess;
44
em_arg_callback_func onerror;
45
void* user_data;
46
};
47
48
void __dl_vseterr(const char*, va_list);
49
50
// We maintain a list of all dlopen and dlsym events linked list.
51
// In multi-threaded builds this is used to keep all the threads in sync
52
// with each other.
53
// In single-threaded builds its only used to keep track of valid DSO handles.
54
struct dlevent {
55
struct dlevent *next, *prev;
56
// Symbol index resulting from dlsym call. -1 means this is a dso event.
57
int sym_index;
58
// dso handler resulting from dlopen call. Only valid when sym_index is -1.
59
struct dso* dso;
60
#ifdef DYLINK_DEBUG
61
int id;
62
#endif
63
};
64
65
// Handle to "main" dso, needed for dlopen(NULL,..)
66
static struct dso main_dso = {
67
.name = "__main__",
68
.flags = 0,
69
};
70
71
static struct dlevent main_event = {
72
.prev = NULL,
73
.next = NULL,
74
.sym_index = -1,
75
.dso = &main_dso,
76
};
77
78
static struct dlevent* _Atomic head = &main_event;
79
static struct dlevent* _Atomic tail = &main_event;
80
81
#ifdef _REENTRANT
82
static thread_local struct dlevent* thread_local_tail = &main_event;
83
static pthread_mutex_t write_lock = PTHREAD_MUTEX_INITIALIZER;
84
static thread_local bool skip_dlsync = false;
85
86
static void do_write_lock() {
87
// Once we have the lock we want to avoid automatic code sync as that would
88
// result in a deadlock.
89
skip_dlsync = true;
90
pthread_mutex_lock(&write_lock);
91
}
92
93
static void do_write_unlock() {
94
pthread_mutex_unlock(&write_lock);
95
skip_dlsync = false;
96
}
97
#else // _REENTRANT
98
#define do_write_unlock()
99
#define do_write_lock()
100
#endif
101
102
static void error(const char* fmt, ...) {
103
va_list ap;
104
va_start(ap, fmt);
105
__dl_vseterr(fmt, ap);
106
va_end(ap);
107
#ifdef DYLINK_DEBUG
108
va_start(ap, fmt);
109
vfprintf(stderr, fmt, ap);
110
va_end(ap);
111
#endif
112
}
113
114
int __dl_invalid_handle(void* h) {
115
struct dlevent* p;
116
for (p = head; p; p = p->next)
117
if (p->sym_index == -1 && p->dso == h)
118
return 0;
119
dbg("__dl_invalid_handle %p", h);
120
error("Invalid library handle %p", (void*)h);
121
return 1;
122
}
123
124
void new_dlevent(struct dso* p, int sym_index) {
125
struct dlevent* ev = calloc(1, sizeof(struct dlevent));
126
127
ev->dso = p;
128
ev->sym_index = sym_index;
129
if (p) p->event = ev;
130
131
// insert into linked list
132
ev->prev = tail;
133
if (tail) {
134
tail->next = ev;
135
#ifdef DYLINK_DEBUG
136
ev->id = tail->id + 1;
137
#endif
138
}
139
dbg("new_dlevent: ev=%p id=%d %s dso=%p sym_index=%d",
140
ev,
141
ev->id,
142
p ? p->name : "RTLD_DEFAULT",
143
p,
144
sym_index);
145
tail = ev;
146
#if _REENTRANT
147
thread_local_tail = ev;
148
#endif
149
}
150
151
static void load_library_done(struct dso* p) {
152
dbg("load_library_done: dso=%p mem_addr=%p mem_size=%zu "
153
"table_addr=%p table_size=%zu",
154
p,
155
p->mem_addr,
156
p->mem_size,
157
p->table_addr,
158
p->table_size);
159
new_dlevent(p, -1);
160
#ifdef _REENTRANT
161
// Block until all other threads have loaded this module.
162
_emscripten_dlsync_threads();
163
#endif
164
// TODO: figure out some way to tell when its safe to free p->file_data. Its
165
// not safe to do here because some threads could have been asleep then when
166
// the "dlsync" occurred and those threads will synchronize when they wake,
167
// which could be an arbitrarily long time in the future.
168
}
169
170
static struct dso* load_library_start(const char* name, int flags) {
171
if (!(flags & (RTLD_LAZY | RTLD_NOW))) {
172
error("invalid mode for dlopen(): Either RTLD_LAZY or RTLD_NOW is required");
173
return NULL;
174
}
175
176
struct dso* p;
177
size_t alloc_size = sizeof *p + strlen(name) + 1;
178
p = calloc(1, alloc_size);
179
p->flags = flags;
180
strcpy(p->name, name);
181
182
// If the file exists in the filesystem, load it here into linear memory which
183
// makes the data available to JS, and to other threads. This data gets
184
// free'd later once all threads have loaded the DSO.
185
struct stat statbuf;
186
if (stat(name, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
187
int fd = open(name, O_RDONLY);
188
if (fd >= 0) {
189
off_t size = lseek(fd, 0, SEEK_END);
190
if (size != (off_t)-1) {
191
lseek(fd, 0, SEEK_SET);
192
p->file_data = malloc(size);
193
if (p->file_data) {
194
if (read(fd, p->file_data, size) == size) {
195
p->file_data_size = size;
196
} else {
197
free(p->file_data);
198
}
199
}
200
}
201
close(fd);
202
}
203
}
204
205
return p;
206
}
207
208
#ifdef _REENTRANT
209
// When we are attempting to synchronize loaded libraries between threads we
210
// currently abort, rather than rejecting the promises. We could reject the
211
// promises, and attempt to return an error from the original dlopen() but we
212
// would have to also unwind the state on all the threads that were able to load
213
// the module.
214
#define ABORT_ON_SYNC_FAILURE 1
215
216
static void dlsync_next(struct dlevent* dlevent, em_promise_t promise);
217
218
static void sync_one_onsuccess(struct dso* dso, void* user_data) {
219
em_promise_t promise = (em_promise_t)user_data;
220
dbg("sync_one_onsuccess dso=%p event=%p promise=%p", dso, dso->event, promise);
221
// Load the next dso in the list
222
thread_local_tail = dso->event;
223
dlsync_next(thread_local_tail->next, promise);
224
}
225
226
static void sync_one_onerror(struct dso* dso, void* user_data) {
227
#if ABORT_ON_SYNC_FAILURE
228
abort();
229
#else
230
em_promise_t promise = (em_promise_t)user_data;
231
emscripten_promise_reject(promise);
232
#endif
233
}
234
235
// Called on the main thread to asynchronously "catch up" with all the DSOs
236
// that are currently loaded.
237
static void dlsync_next(struct dlevent* dlevent, em_promise_t promise) {
238
dbg("dlsync_next event=%p promise=%p", dlevent, promise);
239
240
// Process any dlsym events synchronously until we find a dlopen event
241
while (dlevent && dlevent->sym_index != -1) {
242
dbg("calling _dlsym_catchup_js ....");
243
void* success = _dlsym_catchup_js(dlevent->dso, dlevent->sym_index);
244
if (!success) {
245
emscripten_errf("_dlsym_catchup_js failed: %s", dlerror());
246
sync_one_onerror(dlevent->dso, promise);
247
return;
248
}
249
dlevent = dlevent->next;
250
}
251
252
if (!dlevent) {
253
// All dso loaded
254
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
255
return;
256
}
257
258
dbg("dlsync_next calling _emscripten_dlopen_js: dso=%p", dlevent->dso);
259
_emscripten_dlopen_js(
260
dlevent->dso, sync_one_onsuccess, sync_one_onerror, promise);
261
}
262
263
void _emscripten_dlsync_self_async(em_promise_t promise) {
264
dbg("_emscripten_dlsync_self_async promise=%p", promise);
265
// Unlock happens once all DSO have been loaded, or one of them fails
266
// with sync_one_onerror.
267
dlsync_next(thread_local_tail->next, promise);
268
}
269
270
// Called on background threads to synchronously "catch up" with all the DSOs
271
// that are currently loaded.
272
bool _emscripten_dlsync_self() {
273
// Should only ever be called from a background thread.
274
assert(!emscripten_is_main_runtime_thread());
275
if (thread_local_tail == tail) {
276
dbg("_emscripten_dlsync_self: already in sync");
277
return true;
278
}
279
dbg("_emscripten_dlsync_self: catching up %p %p", thread_local_tail, tail);
280
while (thread_local_tail->next) {
281
struct dlevent* p = thread_local_tail->next;
282
if (p->sym_index != -1) {
283
dbg("_emscripten_dlsync_self: id=%d %s sym_index=%d",
284
p->id,
285
p->dso->name,
286
p->sym_index);
287
void* success = _dlsym_catchup_js(p->dso, p->sym_index);
288
if (!success) {
289
emscripten_errf("_dlsym_catchup_js failed: %s", dlerror());
290
return false;
291
}
292
} else {
293
dbg("_emscripten_dlsync_self: id=%d %s mem_addr=%p "
294
"mem_size=%zu table_addr=%p table_size=%zu",
295
p->id,
296
p->dso->name,
297
p->dso->mem_addr,
298
p->dso->mem_size,
299
p->dso->table_addr,
300
p->dso->table_size);
301
void* success = _dlopen_js(p->dso);
302
if (!success) {
303
// If any on the libraries fails to load here then we give up.
304
// TODO(sbc): Ideally this would never happen and we could/should
305
// abort, but on the main thread (where we don't have sync xhr) its
306
// often not possible to synchronously load side module.
307
emscripten_errf("_dlopen_js failed: %s", dlerror());
308
return false;
309
}
310
}
311
thread_local_tail = p;
312
}
313
dbg("_emscripten_dlsync_self: done");
314
return true;
315
}
316
317
struct promise_result {
318
em_promise_t promise;
319
bool result;
320
};
321
322
static void do_thread_sync(void* arg) {
323
dbg("do_thread_sync");
324
struct promise_result* info = arg;
325
info->result = _emscripten_dlsync_self();
326
}
327
328
static void do_thread_sync_out(void* arg) {
329
dbg("do_thread_sync_out");
330
int* result = (int*)arg;
331
*result = _emscripten_dlsync_self();
332
}
333
334
// Called when a thread exists prior to being able to completely sync operation.
335
// We can just ignore this case and report success.
336
static void thread_sync_cancelled(void* arg) {
337
struct promise_result* info = arg;
338
dbg("thread_sync_cancelled: promise=%p result=%i", info->promise, info->result);
339
emscripten_promise_resolve(info->promise, EM_PROMISE_FULFILL, NULL);
340
emscripten_promise_destroy(info->promise);
341
free(info);
342
}
343
344
// Called once do_thread_sync completes
345
static void thread_sync_done(void* arg) {
346
struct promise_result* info = arg;
347
em_promise_t promise = info->promise;
348
dbg("thread_sync_done: promise=%p result=%i", promise, info->result);
349
if (info->result) {
350
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
351
} else {
352
#if ABORT_ON_SYNC_FAILURE
353
abort();
354
#else
355
emscripten_promise_reject(promise);
356
#endif
357
}
358
emscripten_promise_destroy(promise);
359
free(info);
360
}
361
362
// Proxying queue specially for handling code loading (dlopen) events.
363
// Initialized by the main thread on the first call to
364
// `_emscripten_proxy_dlsync` below, and processed by background threads
365
// that call `_emscripten_process_dlopen_queue` during futex_wait (i.e. whenever
366
// they block).
367
static em_proxying_queue * _Atomic dlopen_proxying_queue = NULL;
368
static thread_local bool processing_queue = false;
369
370
void _emscripten_process_dlopen_queue() {
371
if (dlopen_proxying_queue && !processing_queue) {
372
assert(!emscripten_is_main_runtime_thread());
373
processing_queue = true;
374
emscripten_proxy_execute_queue(dlopen_proxying_queue);
375
processing_queue = false;
376
}
377
}
378
379
// Asynchronously runs _emscripten_dlsync_self on the target then and
380
// resolves (or rejects) the given promise once it is complete.
381
// This function should only ever be called by the main runtime thread which
382
// manages the worker pool.
383
int _emscripten_proxy_dlsync_async(pthread_t target_thread, em_promise_t promise) {
384
assert(emscripten_is_main_runtime_thread());
385
if (!dlopen_proxying_queue) {
386
dlopen_proxying_queue = em_proxying_queue_create();
387
}
388
389
struct promise_result* info = malloc(sizeof(struct promise_result));
390
if (!info) {
391
return false;
392
}
393
*info = (struct promise_result){
394
.promise = promise,
395
.result = false,
396
};
397
int rtn = emscripten_proxy_callback(dlopen_proxying_queue,
398
target_thread,
399
do_thread_sync,
400
thread_sync_done,
401
thread_sync_cancelled,
402
info);
403
if (!rtn) {
404
// If we failed to proxy, then the target thread is no longer alive and no
405
// longer needs to be caught up, so we can resolve the promise early.
406
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
407
emscripten_promise_destroy(promise);
408
free(info);
409
} else if (target_thread->sleeping) {
410
// If the target thread is in the sleeping state (and this check is
411
// performed after the enqueuing of the async work) then we know its safe to
412
// resolve the promise early, since the thread will process our event as
413
// soon as it wakes up.
414
emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL);
415
return 0;
416
}
417
return rtn;
418
}
419
420
int _emscripten_proxy_dlsync(pthread_t target_thread) {
421
assert(emscripten_is_main_runtime_thread());
422
if (!dlopen_proxying_queue) {
423
dlopen_proxying_queue = em_proxying_queue_create();
424
}
425
int result;
426
if (!emscripten_proxy_sync(
427
dlopen_proxying_queue, target_thread, do_thread_sync_out, &result)) {
428
return 0;
429
}
430
return result;
431
}
432
#endif // _REENTRANT
433
434
static void dlopen_onsuccess(struct dso* dso, void* user_data) {
435
struct async_data* data = (struct async_data*)user_data;
436
dbg("dlopen_js_onsuccess: dso=%p mem_addr=%p mem_size=%zu",
437
dso,
438
dso->mem_addr,
439
dso->mem_size);
440
load_library_done(dso);
441
do_write_unlock();
442
data->onsuccess(data->user_data, dso);
443
free(data);
444
}
445
446
static void dlopen_onerror(struct dso* dso, void* user_data) {
447
struct async_data* data = (struct async_data*)user_data;
448
dbg("dlopen_js_onerror: dso=%p", dso);
449
do_write_unlock();
450
data->onerror(data->user_data);
451
free(dso);
452
free(data);
453
}
454
455
// Modified version of path_open from musl/ldso/dynlink.c
456
static int path_find(const char *name, const char *s, char *buf, size_t buf_size) {
457
if (s == NULL) {
458
return -1;
459
}
460
size_t l;
461
int fd;
462
for (;;) {
463
s += strspn(s, ":\n");
464
l = strcspn(s, ":\n");
465
if (l-1 >= INT_MAX) return -1;
466
if (snprintf(buf, buf_size, "%.*s/%s", (int)l, s, name) < buf_size) {
467
dbg("dlopen: path_find: %s", buf);
468
struct stat statbuf;
469
if (stat(buf, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
470
return 0;
471
}
472
switch (errno) {
473
case ENOENT:
474
case ENOTDIR:
475
case EACCES:
476
case ENAMETOOLONG:
477
break;
478
default:
479
dbg("dlopen: path_find failed: %s", strerror(errno));
480
/* Any negative value but -1 will inhibit
481
* further path search. */
482
return -2;
483
}
484
}
485
s += l;
486
}
487
}
488
489
// Resolve filename using LD_LIBRARY_PATH
490
const char* _emscripten_find_dylib(char* buf, const char* rpath, const char* file, size_t buflen) {
491
if (strchr(file, '/')) {
492
// Absolute path, leave it alone
493
return NULL;
494
}
495
const char* env_path = getenv("LD_LIBRARY_PATH");
496
if (path_find(file, env_path, buf, buflen) == 0) {
497
dbg("dlopen: found in LD_LIBRARY_PATH: %s", buf);
498
return buf;
499
}
500
if (path_find(file, rpath, buf, buflen) == 0) {
501
dbg("dlopen: found in RPATH: %s", buf);
502
return buf;
503
}
504
return NULL;
505
}
506
507
static const char* find_dylib(char* buf, const char* file, size_t buflen) {
508
const char* res = _emscripten_find_dylib(buf, NULL, file, buflen);
509
if (res) {
510
return res;
511
}
512
return file;
513
}
514
515
// Search for library name to see if it's already loaded
516
static struct dso* find_existing(const char* file) {
517
for (struct dlevent* e = head; e; e = e->next) {
518
if (e->sym_index == -1 && !strcmp(e->dso->name, file)) {
519
dbg("dlopen: already opened: %p", e->dso);
520
return e->dso;
521
}
522
}
523
return NULL;
524
}
525
526
// Internal version of dlopen with typed return value.
527
// Without this, the compiler won't tell us if we have the wrong return type.
528
static struct dso* _dlopen(const char* file, int flags) {
529
if (!file) {
530
// If a null pointer is passed in path, dlopen() returns a handle equivalent
531
// to RTLD_DEFAULT.
532
dbg("dlopen: NULL -> %p", head->dso);
533
return head->dso;
534
}
535
dbg("dlopen: %s [%d]", file, flags);
536
537
int cs;
538
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
539
do_write_lock();
540
541
char buf[2*NAME_MAX+2];
542
file = find_dylib(buf, file, sizeof buf);
543
544
struct dso* p = find_existing(file);
545
if (p) {
546
goto end;
547
}
548
549
p = load_library_start(file, flags);
550
if (!p) {
551
goto end;
552
}
553
void* success = _dlopen_js(p);
554
if (!success) {
555
dbg("dlopen_js: failed: %p", p);
556
free(p);
557
p = NULL;
558
goto end;
559
}
560
dbg("dlopen_js: success: %p", p);
561
load_library_done(p);
562
end:
563
dbg("dlopen(%s): done: %p", file, p);
564
do_write_unlock();
565
pthread_setcancelstate(cs, 0);
566
return p;
567
}
568
569
void* dlopen(const char* file, int flags) {
570
return _dlopen(file, flags);
571
}
572
573
void emscripten_dlopen(const char* filename, int flags, void* user_data,
574
em_dlopen_callback onsuccess, em_arg_callback_func onerror) {
575
dbg("emscripten_dlopen: %s", filename);
576
if (!filename) {
577
onsuccess(user_data, head->dso);
578
return;
579
}
580
do_write_lock();
581
char buf[2*NAME_MAX+2];
582
filename = find_dylib(buf, filename, sizeof buf);
583
struct dso* p = find_existing(filename);
584
if (p) {
585
onsuccess(user_data, p);
586
return;
587
}
588
p = load_library_start(filename, flags);
589
if (!p) {
590
do_write_unlock();
591
onerror(user_data);
592
return;
593
}
594
595
// For async mode
596
struct async_data* d = malloc(sizeof(struct async_data));
597
d->user_data = user_data;
598
d->onsuccess = onsuccess;
599
d->onerror = onerror;
600
601
dbg("calling emscripten_dlopen_js %p", p);
602
// Unlock happens in dlopen_onsuccess/dlopen_onerror
603
_emscripten_dlopen_js(p, dlopen_onsuccess, dlopen_onerror, d);
604
}
605
606
static void promise_onsuccess(void* user_data, void* handle) {
607
em_promise_t p = (em_promise_t)user_data;
608
dbg("promise_onsuccess: %p", p);
609
emscripten_promise_resolve(p, EM_PROMISE_FULFILL, handle);
610
emscripten_promise_destroy(p);
611
}
612
613
static void promise_onerror(void* user_data) {
614
em_promise_t p = (em_promise_t)user_data;
615
dbg("promise_onerror: %p", p);
616
emscripten_promise_resolve(p, EM_PROMISE_REJECT, NULL);
617
emscripten_promise_destroy(p);
618
}
619
620
// emscripten_dlopen_promise is currently implemented on top of the callback
621
// based API (emscripten_dlopen).
622
// TODO(sbc): Consider inverting this and perhaps deprecating/removing
623
// the old API.
624
em_promise_t emscripten_dlopen_promise(const char* filename, int flags) {
625
// Create a promise that is resolved (and destroyed) once the operation
626
// succeeds.
627
em_promise_t p = emscripten_promise_create();
628
emscripten_dlopen(filename, flags, p, promise_onsuccess, promise_onerror);
629
630
// Create a second promise bound the first one to return the caller. It's
631
// then up to the caller to destroy this promise.
632
em_promise_t ret = emscripten_promise_create();
633
emscripten_promise_resolve(ret, EM_PROMISE_MATCH, p);
634
return ret;
635
}
636
637
void* __dlsym(void* restrict p, const char* restrict s, void* restrict ra) {
638
dbg("__dlsym dso:%p sym:%s", p, s);
639
if (p != RTLD_DEFAULT && p != RTLD_NEXT && __dl_invalid_handle(p)) {
640
return 0;
641
}
642
// The first "dso" is always the default one which is equivalent to
643
// RTLD_DEFAULT. This is what is returned from `dlopen(NULL, ...)`.
644
if (p == head->dso) {
645
p = RTLD_DEFAULT;
646
}
647
void* res;
648
int sym_index = -1;
649
do_write_lock();
650
res = _dlsym_js(p, s, &sym_index);
651
if (sym_index != -1) {
652
new_dlevent(p, sym_index);
653
#ifdef _REENTRANT
654
// Block until all other threads have loaded this module.
655
_emscripten_dlsync_threads();
656
#endif
657
}
658
dbg("__dlsym done dso:%p res:%p", p, res);
659
do_write_unlock();
660
return res;
661
}
662
663