-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
/
Copy pathloader_lib.c
610 lines (556 loc) · 22 KB
/
loader_lib.c
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
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
// 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. */
static void * load_library(const char * rel_path, const char * src_dir, int err) {
void * handle = NULL;
// 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;
}
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_)
void win32_formatmessage(DWORD code, char *reason, int len) {
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);
}
res = WideCharToMultiByte(CP_UTF8, 0, errmsg, -1, reason, len, NULL, NULL);
reason[len - 1] = '\0';
LocalFree(errmsg);
}
#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[2048];
win32_formatmessage(err, msg, sizeof(msg));
jl_loader_print_stderr(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 libstdxc++.
#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>
// write(), but handle errors and avoid EINTR
static void write_wrapper(int fd, const char *str, size_t len)
{
size_t written_sofar = 0;
while (len) {
ssize_t bytes_written = write(fd, str + written_sofar, len);
if (bytes_written == -1 && errno == EINTR) continue;
if (bytes_written == -1 && errno != EINTR) {
perror("(julia) child libstdcxxprobe write");
_exit(1);
}
len -= bytes_written;
written_sofar += bytes_written;
}
}
// read(), but handle errors and avoid EINTR
static void read_wrapper(int fd, char **ret, size_t *ret_len)
{
// Allocate an initial buffer
size_t len = JL_PATH_MAX;
char *buf = (char *)malloc(len + 1);
if (!buf) {
perror("(julia) malloc");
exit(1);
}
// Read into it, reallocating as necessary
size_t have_read = 0;
while (1) {
ssize_t n = read(fd, buf + have_read, len - have_read);
if (n == 0) break;
if (n == -1 && errno != EINTR) {
perror("(julia) libstdcxxprobe read");
exit(1);
}
if (n == -1 && errno == EINTR) continue;
have_read += n;
if (have_read == len) {
buf = (char *)realloc(buf, 1 + (len *= 2));
if (!buf) {
perror("(julia) realloc");
exit(1);
}
}
}
*ret = buf;
*ret_len = have_read;
}
// Return the path to the libstdcxx to load.
// If the path is found, return it.
// Otherwise, print the error and exit.
// The path returned must be freed.
static char *libstdcxxprobe(void)
{
// Create the pipe and child process.
int fork_pipe[2];
int ret = pipe(fork_pipe);
if (ret == -1) {
perror("(julia) Error during libstdcxxprobe: pipe");
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror("Error during libstdcxxprobe:\nfork");
exit(1);
}
if (pid == (pid_t) 0) { // Child process.
close(fork_pipe[0]);
// Open the first available libstdc++.so.
// If it can't be found, report so by exiting zero.
// The star is there to prevent the compiler from merging constants
// with "\0*libstdc++.so.6", which we string replace inside the .so during
// make install.
void *handle = dlopen("libstdc++.so.6\0*", RTLD_LAZY);
if (!handle) {
_exit(0);
}
// See if the version is compatible
char *dlerr = dlerror(); // clear out dlerror
void *sym = dlsym(handle, GLIBCXX_LEAST_VERSION_SYMBOL);
(void)sym;
dlerr = dlerror();
if (dlerr) {
// We can't use the library that was found, so don't write anything.
// The main process will see that nothing was written,
// then exit the function and return null.
_exit(0);
}
// No error means the symbol was found, we can use this library.
// Get the path to it, and write it to the parent process.
struct link_map *lm;
ret = dlinfo(handle, RTLD_DI_LINKMAP, &lm);
if (ret == -1) {
char *errbuf = dlerror();
char *errdesc = (char*)"Error during libstdcxxprobe in child process:\ndlinfo: ";
write_wrapper(STDERR_FILENO, errdesc, strlen(errdesc));
write_wrapper(STDERR_FILENO, errbuf, strlen(errbuf));
write_wrapper(STDERR_FILENO, "\n", 1);
_exit(1);
}
char *libpath = lm->l_name;
write_wrapper(fork_pipe[1], libpath, strlen(libpath));
_exit(0);
}
else { // Parent process.
close(fork_pipe[1]);
// Read the absolute path to the lib from the child process.
char *path;
size_t pathlen;
read_wrapper(fork_pipe[0], &path, &pathlen);
// Close the read end of the pipe
close(fork_pipe[0]);
// Wait for the child to complete.
while (1) {
int wstatus;
pid_t npid = waitpid(pid, &wstatus, 0);
if (npid == -1) {
if (errno == EINTR) continue;
if (errno != EINTR) {
perror("Error during libstdcxxprobe in parent process:\nwaitpid");
exit(1);
}
}
else if (!WIFEXITED(wstatus)) {
const char *err_str = "Error during libstdcxxprobe in parent process:\n"
"The child process did not exit normally.\n";
size_t err_strlen = strlen(err_str);
write_wrapper(STDERR_FILENO, err_str, err_strlen);
exit(1);
}
else if (WEXITSTATUS(wstatus)) {
// The child has printed an error and exited, so the parent should exit too.
exit(1);
}
break;
}
if (!pathlen) {
free(path);
return NULL;
}
// Ensure that `path` is zero-terminated.
path[pathlen] = '\0';
return path;
}
}
#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
char *probevar = getenv("JULIA_PROBE_LIBSTDCXX");
if (probevar) {
if (strcmp(probevar, "1") == 0 || strcmp(probevar, "yes") == 0)
do_probe = 1;
else if (strcmp(probevar, "0") == 0 || strcmp(probevar, "no") == 0)
do_probe = 0;
}
if (do_probe) {
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(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) {
load_library(curr_dep, lib_dir, 1);
}
#endif
} else if (special_idx == 1) {
// This special library is `libjulia-internal`
libjulia_internal = load_library(curr_dep, lib_dir, 1);
} else if (special_idx == 2) {
// This special library is `libjulia-codegen`
libjulia_codegen = load_library(curr_dep, lib_dir, 0);
}
special_idx++;
} else {
// Otherwise, just load it as "normal"
load_library(curr_dep, lib_dir, 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