forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathloader_lib.c
More file actions
510 lines (464 loc) · 19 KB
/
Copy pathloader_lib.c
File metadata and controls
510 lines (464 loc) · 19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
// This file is a part of Julia. License is MIT: https://julialang.org/license
// This file defines an RPATH-style relative path loader for all platforms
#include "loader.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Bring in definitions of symbols exported from libjulia. */
#include "jl_exports.h"
/* Bring in helper functions for windows without libgcc. */
#ifdef _OS_WINDOWS_
#include "loader_win_utils.c"
#include <fileapi.h>
static int win_file_exists(wchar_t* wpath) {
return GetFileAttributesW(wpath) == INVALID_FILE_ATTRIBUTES ? 0 : 1;
}
#endif
// Save DEP_LIBS to a variable that is explicitly sized for expansion
static char dep_libs[1024] = "\0" DEP_LIBS;
JL_DLLEXPORT void jl_loader_print_stderr(const char * msg)
{
fputs(msg, stderr);
}
// I use three arguments a lot.
void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char * msg3)
{
jl_loader_print_stderr(msg1);
jl_loader_print_stderr(msg2);
jl_loader_print_stderr(msg3);
}
/* Wrapper around dlopen(), with extra relative pathing thrown in*/
/* If err, then loads the library successfully or panics.
* If !err, then loads the library or returns null if the file does not exist,
* or panics if opening failed for any other reason. */
/* Currently the only use of this function with !err is in opening libjulia-codegen,
* which the user can delete to save space if generating new code is not necessary.
* However, if it exists and cannot be loaded, that's a problem. So, we alert the user
* and abort the process. */
/* If allow_basename, then any library already loaded with the same basename is treated
* as a duplicate and a handle to the loaded library is returned. Otherwise already-loaded
* libraries are detected by full filepath / inode. */
static void *load_library(const char * rel_path, const char * src_dir, int allow_basename, int err) {
void * handle = NULL;
if (allow_basename) {
// See if a handle is already open to the basename
const char *basename = rel_path + strlen(rel_path);
while (basename-- > rel_path)
if (*basename == PATHSEPSTRING[0] || *basename == '/')
break;
basename++;
#if defined(_OS_WINDOWS_)
if ((handle = GetModuleHandleA(basename)))
return handle;
#else
// if err == 0 the library is optional, so don't allow global lookups to see it
if ((handle = dlopen(basename, RTLD_NOLOAD | RTLD_NOW | (err ? RTLD_GLOBAL : RTLD_LOCAL))))
return handle;
#endif
}
char path[2*JL_PATH_MAX + 1] = {0};
strncat(path, src_dir, sizeof(path) - 1);
strncat(path, PATHSEPSTRING, sizeof(path) - 1);
strncat(path, rel_path, sizeof(path) - 1);
#if defined(_OS_WINDOWS_)
#define PATH_EXISTS() win_file_exists(wpath)
wchar_t *wpath = utf8_to_wchar(path);
if (!wpath) {
jl_loader_print_stderr3("ERROR: Unable to convert path ", path, " to wide string!\n");
exit(1);
}
handle = (void *)LoadLibraryExW(wpath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
#else
#define PATH_EXISTS() !access(path, F_OK)
handle = dlopen(path, RTLD_NOW | (err ? RTLD_GLOBAL : RTLD_LOCAL));
#endif
if (handle != NULL) {
#if defined(_OS_WINDOWS_)
free(wpath);
#endif
}
else {
if (!err && !PATH_EXISTS()) {
#if defined(_OS_WINDOWS_)
free(wpath);
#endif
return NULL;
}
#if defined(_OS_WINDOWS_)
free(wpath);
#endif
#undef PATH_EXISTS
jl_loader_print_stderr3("ERROR: Unable to load dependent library ", path, "\n");
#if defined(_OS_WINDOWS_)
LPWSTR wmsg = TEXT("");
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_MAX_WIDTH_MASK,
NULL, GetLastError(),
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPWSTR)&wmsg, 0, NULL);
char *errmsg = wchar_to_utf8(wmsg);
jl_loader_print_stderr3("Message:", errmsg, "\n");
free(errmsg);
#else
char *dlerr = dlerror();
if (dlerr != NULL) {
jl_loader_print_stderr3("Message:", dlerr, "\n");
}
#endif
exit(1);
}
return handle;
}
// case-insensitive strcmp
static int istrcmp(const char *val, const char *token) {
for (; *token; val++, token++) {
char c = *val;
if (c >= 'A' && c <= 'Z')
c += 'a' - 'A';
if (c != *token)
return 0;
}
return *val == '\0';
}
// intended to match Base.get_bool_env
static int env_var_bool(const char *name, int *value) {
#if defined(_OS_WINDOWS_)
char val[8];
DWORD val_len = GetEnvironmentVariableA(name, val, sizeof(val));
if (val_len == 0 || val_len >= sizeof(val)) /* unset, or too long to be a token */
return 0;
#else
const char *val = getenv(name);
if (val == NULL)
return 0;
#endif
if (istrcmp(val, "t") || istrcmp(val, "true") || istrcmp(val, "y") || istrcmp(val, "yes") || istrcmp(val, "1")) {
*value = 1;
return 1;
}
if (istrcmp(val, "f") || istrcmp(val, "false") || istrcmp(val, "n") || istrcmp(val, "no") || istrcmp(val, "0")) {
*value = 0;
return 1;
}
return 0;
}
static void * lookup_symbol(const void * lib_handle, const char * symbol_name) {
#ifdef _OS_WINDOWS_
return GetProcAddress((HMODULE) lib_handle, symbol_name);
#else
return dlsym((void *)lib_handle, symbol_name);
#endif
}
#if defined(_OS_WINDOWS_)
static char *win32_formatmessage(DWORD code) {
DWORD res;
LPWSTR errmsg;
res = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_MAX_WIDTH_MASK,
NULL, code,
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPWSTR)&errmsg, 0, NULL);
if (!res && (GetLastError() == ERROR_MUI_FILE_NOT_FOUND ||
GetLastError() == ERROR_RESOURCE_TYPE_NOT_FOUND)) {
res = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_MAX_WIDTH_MASK,
NULL, code,
0, (LPWSTR)&errmsg, 0, NULL);
}
char *utf8msg = wchar_to_utf8(errmsg);
LocalFree(errmsg);
return utf8msg;
}
#endif
// Find the location of libjulia.
char *lib_dir = NULL;
JL_DLLEXPORT const char * jl_get_libdir()
{
// Reuse the path if this is not the first call.
if (lib_dir) {
return lib_dir;
}
#if defined(_OS_WINDOWS_)
// On Windows, we use GetModuleFileNameW
HMODULE libjulia = NULL;
// Get a handle to libjulia
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCWSTR)jl_get_libdir, &libjulia)) {
DWORD err = GetLastError();
jl_loader_print_stderr3("ERROR: could not locate library \"", LIBJULIA_NAME, "\"\n");
char *msg = win32_formatmessage(err);
jl_loader_print_stderr(msg);
free(msg);
exit(1);
}
wchar_t *libjulia_path = (wchar_t*)malloc(32768 * sizeof(wchar_t)); // max long path length
if (!GetModuleFileNameW(libjulia, libjulia_path, 32768)) {
jl_loader_print_stderr("ERROR: GetModuleFileName() failed\n");
exit(1);
}
lib_dir = wchar_to_utf8(libjulia_path);
if (!lib_dir) {
jl_loader_print_stderr("ERROR: Unable to convert julia path to UTF-8\n");
exit(1);
}
free(libjulia_path);
#else
// On all other platforms, use dladdr()
Dl_info info;
if (!dladdr(&jl_get_libdir, &info)) {
jl_loader_print_stderr("ERROR: Unable to dladdr(&jl_get_libdir)!\n");
char *dlerr = dlerror();
if (dlerr != NULL) {
jl_loader_print_stderr3("Message:", dlerr, "\n");
}
exit(1);
}
lib_dir = strdup(info.dli_fname);
#endif
// Finally, convert to dirname
const char * new_dir = dirname(lib_dir);
if (new_dir != lib_dir) {
// On some platforms, dirname() mutates. On others, it does not.
memcpy(lib_dir, new_dir, strlen(new_dir)+1);
}
return lib_dir;
}
// On Linux, it can happen that the system has a newer libstdc++ than the one we ship,
// which can break loading of some system libraries: <https://github.com/JuliaLang/julia/issues/34276>.
// As a fix, on linux we probe the system libstdc++ to see if it is newer, and then load it if it is.
// Otherwise, we load the bundled one. This improves compatibility with third party dynamic libs that
// may depend on symbols exported by the system libstdc++.
#ifdef _OS_LINUX_
#ifndef GLIBCXX_LEAST_VERSION_SYMBOL
#warning GLIBCXX_LEAST_VERSION_SYMBOL should always be defined in the makefile.
#define GLIBCXX_LEAST_VERSION_SYMBOL "GLIBCXX_a.b.c" /* Appease the linter */
#endif
#include <link.h>
#include <sys/wait.h>
// Return the path to the libstdcxx to load.
// If the path is found, return it.
// Otherwise, return NULL.
// The path returned must be freed.
static const char *libstdcxxprobe(void)
{
void *handle = dlopen("libstdc++.so.6\0*", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (handle != NULL)
return NULL; // libstdc++ already loaded - nothing we can do
return jl_loader_probe_system_library("libstdc++.so.6", GLIBCXX_LEAST_VERSION_SYMBOL);
}
#endif
void *libjulia_internal = NULL;
void *libjulia_codegen = NULL;
__attribute__((constructor)) void jl_load_libjulia_internal(void) {
#if defined(_OS_LINUX_)
// Julia uses `sigwait()` to handle signals, and all threads are required
// to mask the corresponding handlers so that the signals can be waited on.
// Here, we setup that masking early, so that it is inherited by any threads
// spawned (e.g. by constructors) when loading deps of libjulia-internal.
sigset_t all_signals, prev_mask;
sigfillset(&all_signals);
pthread_sigmask(SIG_BLOCK, &all_signals, &prev_mask);
#endif
// Only initialize this once
if (libjulia_internal != NULL) {
return;
}
// Introspect to find our own path
const char *lib_dir = jl_get_libdir();
// Pre-load libraries that libjulia-internal needs.
char *curr_dep = &dep_libs[1];
// We keep track of "special" libraries names (ones whose name is prefixed with `@`)
// which are libraries that we want to load in some special, custom way.
// The current list is:
// libstdc++
// libjulia-internal
// libjulia-codegen
const int NUM_SPECIAL_LIBRARIES = 3;
int special_idx = 0;
while (1) {
// try to find next colon character; if we can't, break out
char * colon = strchr(curr_dep, ':');
if (colon == NULL)
break;
// If this library name starts with `@`, don't open it here (but mark it as special)
if (curr_dep[0] == '@') {
special_idx += 1;
if (special_idx > NUM_SPECIAL_LIBRARIES) {
jl_loader_print_stderr("ERROR: Too many special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n");
exit(1);
}
}
// Skip to next dep
curr_dep = colon + 1;
}
// Assert that we have exactly the right number of special library names
if (special_idx != NUM_SPECIAL_LIBRARIES) {
jl_loader_print_stderr("ERROR: Too few special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n");
exit(1);
}
// Now that we've asserted that we have the right number of special
// libraries, actually run a loop over the deps loading them in-order.
// If it's a special library, we do slightly different things, especially
// for libstdc++, where we actually probe for a system libstdc++ and
// load that if it's newer.
special_idx = 0;
curr_dep = &dep_libs[1];
while (1) {
// try to find next colon character; if we can't, break out
char * colon = strchr(curr_dep, ':');
if (colon == NULL)
break;
// Chop the string at the colon so it's a valid-ending-string
*colon = '\0';
// If this library name starts with `@`, it's a special library
// and requires special handling:
if (curr_dep[0] == '@') {
// Skip the `@` for future function calls.
curr_dep += 1;
// First special library to be loaded is `libstdc++`; perform probing here.
if (special_idx == 0) {
#if defined(_OS_LINUX_)
int do_probe = 1;
int probe_successful = 0;
// Check to see if the user has disabled libstdc++ probing
env_var_bool("JULIA_PROBE_LIBSTDCXX", &do_probe);
if (do_probe) {
const char *cxxpath = libstdcxxprobe();
if (cxxpath) {
void *cxx_handle = dlopen(cxxpath, RTLD_LAZY);
(void)cxx_handle;
const char *dlr = dlerror();
if (dlr) {
jl_loader_print_stderr("ERROR: Unable to dlopen(cxxpath) in parent!\n");
jl_loader_print_stderr3("Message: ", dlr, "\n");
exit(1);
}
free((void *)cxxpath);
probe_successful = 1;
}
}
// If the probe rejected the system libstdc++ (or didn't find one!)
// just load our bundled libstdc++ as identified by curr_dep;
if (!probe_successful) {
# ifdef RT_STATIC_LIBSTDCXX
// If we have a statically-linked libstdc++, it is ok for
// this to fail.
load_library(curr_dep, lib_dir, /* allow_basename */ 1, /* err */ 0);
# else
load_library(curr_dep, lib_dir, /* allow_basename */ 1, /* err */ 1);
# endif
}
#endif
} else if (special_idx == 1) {
// This special library is `libjulia-internal`
libjulia_internal = load_library(curr_dep, lib_dir, /* allow_basename */ 0, /* err */ 1);
} else if (special_idx == 2) {
// This special library is `libjulia-codegen`
int load_codegen = 1;
env_var_bool("JULIA_LOAD_CODEGEN_LIB", &load_codegen);
if (load_codegen)
libjulia_codegen = load_library(curr_dep, lib_dir, /* allow_basename */ 0, /* err */ 0);
}
special_idx++;
} else {
// Otherwise, just load it as "normal"
load_library(curr_dep, lib_dir, /* allow_basename */ 1, /* err */ 1);
}
// Skip ahead to next dependency
curr_dep = colon + 1;
}
const char * const * codegen_func_names;
const char *codegen_liberr;
if (libjulia_codegen == NULL) {
// if codegen is not available, use fallback implementation in libjulia-internal
libjulia_codegen = libjulia_internal;
codegen_func_names = jl_codegen_fallback_func_names;
codegen_liberr = " from libjulia-internal\n";
}
else {
codegen_func_names = jl_codegen_exported_func_names;
codegen_liberr = " from libjulia-codegen\n";
}
// Once we have libjulia-internal loaded, re-export its symbols:
for (unsigned int symbol_idx=0; jl_runtime_exported_func_names[symbol_idx] != NULL; ++symbol_idx) {
void *addr = lookup_symbol(libjulia_internal, jl_runtime_exported_func_names[symbol_idx]);
if (addr == NULL) {
jl_loader_print_stderr3("ERROR: Unable to load ", jl_runtime_exported_func_names[symbol_idx], " from libjulia-internal\n");
exit(1);
}
(*jl_runtime_exported_func_addrs[symbol_idx]) = addr;
}
// jl_options must be initialized very early, in case an embedder sets some
// values there before calling jl_init
((void (*)(void))jl_init_options_addr)();
for (unsigned int symbol_idx=0; codegen_func_names[symbol_idx] != NULL; ++symbol_idx) {
void *addr = lookup_symbol(libjulia_codegen, codegen_func_names[symbol_idx]);
if (addr == NULL) {
jl_loader_print_stderr3("ERROR: Unable to load ", codegen_func_names[symbol_idx], codegen_liberr);
exit(1);
}
(*jl_codegen_exported_func_addrs[symbol_idx]) = addr;
}
// Next, if we're on Linux/FreeBSD, set up fast TLS.
#if !defined(_OS_WINDOWS_) && !defined(_OS_OPENBSD_)
void (*jl_pgcstack_setkey)(void*, void*(*)(void)) = lookup_symbol(libjulia_internal, "jl_pgcstack_setkey");
if (jl_pgcstack_setkey == NULL) {
jl_loader_print_stderr("ERROR: Cannot find jl_pgcstack_setkey() function within libjulia-internal!\n");
exit(1);
}
void *fptr = lookup_symbol(RTLD_DEFAULT, "jl_get_pgcstack_static");
void *(*key)(void) = lookup_symbol(RTLD_DEFAULT, "jl_pgcstack_addr_static");
_Atomic(char) *semaphore = lookup_symbol(RTLD_DEFAULT, "jl_pgcstack_static_semaphore");
if (fptr != NULL && key != NULL && semaphore != NULL) {
char already_used = 0;
atomic_compare_exchange_strong(semaphore, &already_used, 1);
if (already_used == 0) // RMW succeeded - we have exclusive access
jl_pgcstack_setkey(fptr, key);
}
#endif
// jl_options must be initialized very early, in case an embedder sets some
// values there before calling jl_init
((void (*)(void))jl_init_options_addr)();
#if defined(_OS_LINUX_)
// Restore the original signal mask. `jl_init()` will later setup blocking
// for the specific set of signals we `sigwait()` on, and any threads spawned
// during loading above will still retain their inherited signal mask.
pthread_sigmask(SIG_SETMASK, &prev_mask, NULL);
#endif
}
// Load libjulia and run the REPL with the given arguments (in UTF-8 format)
JL_DLLEXPORT int jl_load_repl(int argc, char * argv[]) {
// Some compilers/platforms are known to have `__attribute__((constructor))` issues,
// so we have a fallback call of `jl_load_libjulia_internal()` here.
if (libjulia_internal == NULL) {
jl_load_libjulia_internal();
if (libjulia_internal == NULL) {
jl_loader_print_stderr("ERROR: libjulia-internal could not be loaded!\n");
exit(1);
}
}
// Load the repl entrypoint symbol and jump into it!
int (*entrypoint)(int, char **) = (int (*)(int, char **))lookup_symbol(libjulia_internal, "jl_repl_entrypoint");
if (entrypoint == NULL) {
jl_loader_print_stderr("ERROR: Unable to find `jl_repl_entrypoint()` within libjulia-internal!\n");
exit(1);
}
return entrypoint(argc, (char **)argv);
}
#ifdef _OS_WINDOWS_
int __stdcall DllMainCRTStartup(void *instance, unsigned reason, void *reserved) {
setup_stdio();
// Because we override DllMainCRTStartup, we have to manually call our constructor methods
jl_load_libjulia_internal();
return 1;
}
#endif
#ifdef __cplusplus
} // extern "C"
#endif