Add ocInit_MachO
[ghc.git] / rts / linker / LoadArchive.c
1 #include <string.h>
2 #include <stddef.h>
3
4 #include <Rts.h>
5 #include "PathUtils.h"
6
7 #include "sm/Storage.h"
8 #include "sm/OSMem.h"
9 #include "RtsUtils.h"
10 #include "LinkerInternals.h"
11 #include "linker/M32Alloc.h"
12
13 /* Platform specific headers */
14 #if defined(OBJFORMAT_PEi386)
15 # include "linker/PEi386.h"
16 #elif defined(darwin_HOST_OS) || defined(ios_HOST_OS)
17 # include "linker/MachO.h"
18 # include <regex.h>
19 # include <mach/machine.h>
20 # include <mach-o/fat.h>
21 #endif
22
23 #include <ctype.h>
24
25 #define FAIL(...) do {\
26 errorBelch("loadArchive: "__VA_ARGS__); \
27 goto fail;\
28 } while (0)
29
30 #define DEBUG_LOG(...) IF_DEBUG(linker, debugBelch("loadArchive: " __VA_ARGS__))
31
32 #if defined(darwin_HOST_OS) || defined(ios_HOST_OS)
33 /* Read 4 bytes and convert to host byte order */
34 static uint32_t read4Bytes(const char buf[static 4])
35 {
36 return ntohl(*(uint32_t*)buf);
37 }
38
39 static StgBool loadFatArchive(char tmp[static 20], FILE* f, pathchar* path)
40 {
41 uint32_t nfat_arch, nfat_offset, cputype, cpusubtype;
42 #if defined(i386_HOST_ARCH)
43 const uint32_t mycputype = CPU_TYPE_X86;
44 const uint32_t mycpusubtype = CPU_SUBTYPE_X86_ALL;
45 #elif defined(x86_64_HOST_ARCH)
46 const uint32_t mycputype = CPU_TYPE_X86_64;
47 const uint32_t mycpusubtype = CPU_SUBTYPE_X86_64_ALL;
48 #elif defined(powerpc_HOST_ARCH)
49 const uint32_t mycputype = CPU_TYPE_POWERPC;
50 const uint32_t mycpusubtype = CPU_SUBTYPE_POWERPC_ALL;
51 #elif defined(powerpc64_HOST_ARCH)
52 const uint32_t mycputype = CPU_TYPE_POWERPC64;
53 const uint32_t mycpusubtype = CPU_SUBTYPE_POWERPC_ALL;
54 #elif defined(aarch64_HOST_ARCH)
55 const uint32_t mycputype = CPU_TYPE_ARM64;
56 const uint32_t mycpusubtype = CPU_SUBTYPE_ARM64_ALL;
57 #else
58 #error Unknown Darwin architecture
59 #endif
60
61 nfat_arch = read4Bytes(tmp + 4);
62 DEBUG_LOG("found a fat archive containing %d architectures\n", nfat_arch);
63 nfat_offset = 0;
64 for (uint32_t i = 0; i < nfat_arch; i++) {
65 /* search for the right arch */
66 int n = fread(tmp, 1, 12, f);
67 if (n != 12) {
68 errorBelch("Failed reading arch from `%" PATH_FMT "'", path);
69 return false;
70 }
71 cputype = read4Bytes(tmp);
72 cpusubtype = read4Bytes(tmp + 4);
73 if (cputype == mycputype && cpusubtype == mycpusubtype) {
74 DEBUG_LOG("found my archive in a fat archive\n");
75 nfat_offset = read4Bytes(tmp + 8);
76 break;
77 }
78 }
79 if (nfat_offset == 0) {
80 errorBelch("Fat archive contains %d architectures, "
81 "but none of them are compatible with the host",
82 (int)nfat_arch);
83 return false;
84 } else {
85 /* Seek to the correct architecture */
86 int n = fseek(f, nfat_offset, SEEK_SET);
87 if (n != 0) {
88 errorBelch("Failed to seek to arch in `%" PATH_FMT "'", path);
89 return false;
90 }
91
92 /* Read the header */
93 n = fread(tmp, 1, 8, f);
94 if (n != 8) {
95 errorBelch("Failed reading header from `%" PATH_FMT "'", path);
96 return false;
97 }
98
99 /* Check the magic number */
100 if (strncmp(tmp, "!<arch>\n", 8) != 0) {
101 errorBelch("couldn't find archive in `%" PATH_FMT "'"
102 "at offset %d", path, nfat_offset);
103 return false;
104 }
105 }
106 return true;
107 }
108 #endif
109
110 static StgBool readThinArchiveMember(int n, int memberSize, pathchar* path,
111 char* fileName, char* image)
112 {
113 StgBool has_succeeded = false;
114 FILE* member = NULL;
115 pathchar *pathCopy, *dirName, *memberPath, *objFileName;
116 memberPath = NULL;
117 /* Allocate and setup the dirname of the archive. We'll need
118 this to locate the thin member */
119 pathCopy = pathdup(path); // Convert the char* to a pathchar*
120 dirName = pathdir(pathCopy);
121 /* Append the relative member name to the dirname. This should be
122 be the full path to the actual thin member. */
123 int memberLen = pathlen(dirName) + 1 + strlen(fileName) + 1;
124 memberPath = stgMallocBytes(pathsize * memberLen, "loadArchive(file)");
125 objFileName = mkPath(fileName);
126 pathprintf(memberPath, memberLen, WSTR("%" PATH_FMT "%" PATH_FMT), dirName,
127 objFileName);
128 stgFree(objFileName);
129 stgFree(dirName);
130 member = pathopen(memberPath, WSTR("rb"));
131 if (!member) {
132 errorBelch("loadObj: can't read thin archive `%" PATH_FMT "'",
133 memberPath);
134 goto inner_fail;
135 }
136 n = fread(image, 1, memberSize, member);
137 if (n != memberSize) {
138 errorBelch("loadArchive: error whilst reading `%s'",
139 fileName);
140 goto inner_fail;
141 }
142 has_succeeded = true;
143
144 inner_fail:
145 fclose(member);
146 stgFree(memberPath);
147 stgFree(pathCopy);
148 return has_succeeded;
149 }
150
151 static StgBool checkFatArchive(char magic[static 20], FILE* f, pathchar* path)
152 {
153 StgBool success;
154 success = false;
155 #if defined(darwin_HOST_OS) || defined(ios_HOST_OS)
156 /* Not a standard archive, look for a fat archive magic number: */
157 if (read4Bytes(magic) == FAT_MAGIC)
158 success = loadFatArchive(magic, f, path);
159 else
160 errorBelch("loadArchive: Neither an archive, nor a fat archive: "
161 "`%" PATH_FMT "'", path);
162 #else
163 (void)magic;
164 (void)f;
165 errorBelch("loadArchive: Not an archive: `%" PATH_FMT "'", path);
166 #endif
167 return success;
168 }
169
170 /**
171 * Look up the filename in the GNU-variant index file pointed to by
172 * gnuFileIndex.
173 * @param fileName_ a pointer to a pointer to the file name to be looked up.
174 * The file name must have been allocated with `StgMallocBytes`, and will
175 * be reallocated on return; the old value is now _invalid_.
176 * @param gnuFileIndexSize The size of the index.
177 */
178 static StgBool
179 lookupGNUArchiveIndex(int gnuFileIndexSize, char **fileName_,
180 char* gnuFileIndex, pathchar* path, size_t* thisFileNameSize,
181 size_t* fileNameSize)
182 {
183 int n;
184 char *fileName = *fileName_;
185 if (isdigit(fileName[1])) {
186 int i;
187 for (n = 2; isdigit(fileName[n]); n++)
188 ;
189
190 fileName[n] = '\0';
191 n = atoi(fileName + 1);
192 if (gnuFileIndex == NULL) {
193 errorBelch("loadArchive: GNU-variant filename "
194 "without an index while reading from `%" PATH_FMT "'",
195 path);
196 return false;
197 }
198 if (n < 0 || n > gnuFileIndexSize) {
199 errorBelch("loadArchive: GNU-variant filename "
200 "offset %d out of range [0..%d] "
201 "while reading filename from `%" PATH_FMT "'",
202 n, gnuFileIndexSize, path);
203 return false;
204 }
205 if (n != 0 && gnuFileIndex[n - 1] != '\n') {
206 errorBelch("loadArchive: GNU-variant filename offset "
207 "%d invalid (range [0..%d]) while reading "
208 "filename from `%" PATH_FMT "'",
209 n, gnuFileIndexSize, path);
210 return false;
211 }
212 for (i = n; gnuFileIndex[i] != '\n'; i++)
213 ;
214
215 size_t FileNameSize = i - n - 1;
216 if (FileNameSize >= *fileNameSize) {
217 /* Double it to avoid potentially continually
218 increasing it by 1 */
219 *fileNameSize = FileNameSize * 2;
220 *fileName_ = fileName = stgReallocBytes(fileName, *fileNameSize,
221 "loadArchive(fileName)");
222 }
223 memcpy(fileName, gnuFileIndex + n, FileNameSize);
224 fileName[FileNameSize] = '\0';
225 *thisFileNameSize = FileNameSize;
226 }
227 /* Skip 32-bit symbol table ("/" + 15 blank characters)
228 and 64-bit symbol table ("/SYM64/" + 9 blank characters) */
229 else if (0 == strncmp(fileName + 1, " ", 15) ||
230 0 == strncmp(fileName + 1, "SYM64/ ", 15)) {
231 fileName[0] = '\0';
232 *thisFileNameSize = 0;
233 }
234 else {
235 errorBelch("loadArchive: invalid GNU-variant filename `%.16s' "
236 "while reading filename from `%" PATH_FMT "'",
237 fileName, path);
238 return false;
239 }
240
241 return true;
242 }
243
244 static HsInt loadArchive_ (pathchar *path)
245 {
246 ObjectCode* oc = NULL;
247 char *image = NULL;
248 HsInt retcode = 0;
249 int memberSize;
250 FILE *f = NULL;
251 int n;
252 size_t thisFileNameSize = (size_t)-1; /* shut up bogus GCC warning */
253 char *fileName;
254 size_t fileNameSize;
255 int isObject, isGnuIndex, isThin, isImportLib;
256 char tmp[20];
257 char *gnuFileIndex;
258 int gnuFileIndexSize;
259 int misalignment = 0;
260
261 DEBUG_LOG("start\n");
262 DEBUG_LOG("Loading archive `%" PATH_FMT" '\n", path);
263
264 /* Check that we haven't already loaded this archive.
265 Ignore requests to load multiple times */
266 if (isAlreadyLoaded(path)) {
267 IF_DEBUG(linker,
268 debugBelch("ignoring repeated load of %" PATH_FMT "\n", path));
269 return 1; /* success */
270 }
271
272 gnuFileIndex = NULL;
273 gnuFileIndexSize = 0;
274
275 fileNameSize = 32;
276 fileName = stgMallocBytes(fileNameSize, "loadArchive(fileName)");
277
278 isThin = 0;
279 isImportLib = 0;
280
281 f = pathopen(path, WSTR("rb"));
282 if (!f)
283 FAIL("loadObj: can't read `%" PATH_FMT "'", path);
284
285 /* Check if this is an archive by looking for the magic "!<arch>\n"
286 * string. Usually, if this fails, we belch an error and return. On
287 * Darwin however, we may have a fat archive, which contains archives for
288 * more than one architecture. Fat archives start with the magic number
289 * 0xcafebabe, always stored big endian. If we find a fat_header, we scan
290 * through the fat_arch structs, searching through for one for our host
291 * architecture. If a matching struct is found, we read the offset
292 * of our archive data (nfat_offset) and seek forward nfat_offset bytes
293 * from the start of the file.
294 *
295 * A subtlety is that all of the members of the fat_header and fat_arch
296 * structs are stored big endian, so we need to call byte order
297 * conversion functions.
298 *
299 * If we find the appropriate architecture in a fat archive, we gobble
300 * its magic "!<arch>\n" string and continue processing just as if
301 * we had a single architecture archive.
302 */
303
304 n = fread ( tmp, 1, 8, f );
305 if (n != 8) {
306 FAIL("Failed reading header from `%" PATH_FMT "'", path);
307 }
308 if (strncmp(tmp, "!<arch>\n", 8) == 0) {}
309 /* Check if this is a thin archive by looking for the magic string "!<thin>\n"
310 *
311 * ar thin libraries have the exact same format as normal archives except they
312 * have a different magic string and they don't copy the object files into the
313 * archive.
314 *
315 * Instead each header entry points to the location of the object file on disk.
316 * This is useful when a library is only created to satisfy a compile time dependency
317 * instead of to be distributed. This saves the time required for copying.
318 *
319 * Thin archives are always flattened. They always only contain simple headers
320 * pointing to the object file and so we need not allocate more memory than needed
321 * to find the object file.
322 *
323 */
324 else if (strncmp(tmp, "!<thin>\n", 8) == 0) {
325 isThin = 1;
326 }
327 else {
328 StgBool success = checkFatArchive(tmp, f, path);
329 if (!success)
330 goto fail;
331 }
332 DEBUG_LOG("loading archive contents\n");
333
334 while (1) {
335 DEBUG_LOG("reading at %ld\n", ftell(f));
336 n = fread ( fileName, 1, 16, f );
337 if (n != 16) {
338 if (feof(f)) {
339 DEBUG_LOG("EOF while reading from '%" PATH_FMT "'\n", path);
340 break;
341 }
342 else {
343 FAIL("Failed reading file name from `%" PATH_FMT "'", path);
344 }
345 }
346
347 #if defined(darwin_HOST_OS) || defined(ios_HOST_OS)
348 if (strncmp(fileName, "!<arch>\n", 8) == 0) {
349 DEBUG_LOG("found the start of another archive, breaking\n");
350 break;
351 }
352 #endif
353
354 n = fread ( tmp, 1, 12, f );
355 if (n != 12)
356 FAIL("Failed reading mod time from `%" PATH_FMT "'", path);
357 n = fread ( tmp, 1, 6, f );
358 if (n != 6)
359 FAIL("Failed reading owner from `%" PATH_FMT "'", path);
360 n = fread ( tmp, 1, 6, f );
361 if (n != 6)
362 FAIL("Failed reading group from `%" PATH_FMT "'", path);
363 n = fread ( tmp, 1, 8, f );
364 if (n != 8)
365 FAIL("Failed reading mode from `%" PATH_FMT "'", path);
366 n = fread ( tmp, 1, 10, f );
367 if (n != 10)
368 FAIL("Failed reading size from `%" PATH_FMT "'", path);
369 tmp[10] = '\0';
370 for (n = 0; isdigit(tmp[n]); n++);
371 tmp[n] = '\0';
372 memberSize = atoi(tmp);
373
374 DEBUG_LOG("size of this archive member is %d\n", memberSize);
375 n = fread ( tmp, 1, 2, f );
376 if (n != 2)
377 FAIL("Failed reading magic from `%" PATH_FMT "'", path);
378 if (strncmp(tmp, "\x60\x0A", 2) != 0)
379 FAIL("Failed reading magic from `%" PATH_FMT "' at %ld. Got %c%c",
380 path, ftell(f), tmp[0], tmp[1]);
381
382 isGnuIndex = 0;
383 /* Check for BSD-variant large filenames */
384 if (0 == strncmp(fileName, "#1/", 3)) {
385 size_t n = 0;
386 fileName[16] = '\0';
387 if (isdigit(fileName[3])) {
388 for (n = 4; isdigit(fileName[n]); n++)
389 ;
390
391 fileName[n] = '\0';
392 thisFileNameSize = atoi(fileName + 3);
393 memberSize -= thisFileNameSize;
394 if (thisFileNameSize >= fileNameSize) {
395 /* Double it to avoid potentially continually
396 increasing it by 1 */
397 fileNameSize = thisFileNameSize * 2;
398 fileName = stgReallocBytes(fileName, fileNameSize,
399 "loadArchive(fileName)");
400 }
401 n = fread(fileName, 1, thisFileNameSize, f);
402 if (n != thisFileNameSize) {
403 errorBelch("Failed reading filename from `%" PATH_FMT "'",
404 path);
405 goto fail;
406 }
407 fileName[thisFileNameSize] = 0;
408 /* On OS X at least, thisFileNameSize is the size of the
409 fileName field, not the length of the fileName
410 itself. */
411 thisFileNameSize = strlen(fileName);
412 } else {
413 errorBelch("BSD-variant filename size not found "
414 "while reading filename from `%" PATH_FMT "'", path);
415 goto fail;
416 }
417 }
418 /* Check for GNU file index file */
419 else if (0 == strncmp(fileName, "//", 2)) {
420 fileName[0] = '\0';
421 thisFileNameSize = 0;
422 isGnuIndex = 1;
423 }
424 /* Check for a file in the GNU file index */
425 else if (fileName[0] == '/') {
426 if (!lookupGNUArchiveIndex(gnuFileIndexSize, &fileName,
427 gnuFileIndex, path, &thisFileNameSize, &fileNameSize)) {
428 goto fail;
429 }
430 }
431 /* Finally, the case where the filename field actually contains
432 the filename */
433 else {
434 /* GNU ar terminates filenames with a '/', this allowing
435 spaces in filenames. So first look to see if there is a
436 terminating '/'. */
437 for (thisFileNameSize = 0;
438 thisFileNameSize < 16;
439 thisFileNameSize++) {
440 if (fileName[thisFileNameSize] == '/') {
441 fileName[thisFileNameSize] = '\0';
442 break;
443 }
444 }
445 /* If we didn't find a '/', then a space teminates the
446 filename. Note that if we don't find one, then
447 thisFileNameSize ends up as 16, and we already have the
448 '\0' at the end. */
449 if (thisFileNameSize == 16) {
450 for (thisFileNameSize = 0;
451 thisFileNameSize < 16;
452 thisFileNameSize++) {
453 if (fileName[thisFileNameSize] == ' ') {
454 fileName[thisFileNameSize] = '\0';
455 break;
456 }
457 }
458 }
459 }
460
461 DEBUG_LOG("Found member file `%s'\n", fileName);
462
463 /* TODO: Stop relying on file extensions to determine input formats.
464 Instead try to match file headers. See Trac #13103. */
465 isObject = (thisFileNameSize >= 2 && strncmp(fileName + thisFileNameSize - 2, ".o" , 2) == 0)
466 || (thisFileNameSize >= 4 && strncmp(fileName + thisFileNameSize - 4, ".p_o", 4) == 0)
467 || (thisFileNameSize >= 4 && strncmp(fileName + thisFileNameSize - 4, ".obj", 4) == 0);
468
469 #if defined(OBJFORMAT_PEi386)
470 /*
471 * Note [MSVC import files (ext .lib)]
472 * MSVC compilers store the object files in
473 * the import libraries with extension .dll
474 * so on Windows we should look for those too.
475 * The PE COFF format doesn't specify any specific file name
476 * for sections. So on windows, just try to load it all.
477 *
478 * Linker members (e.g. filename / are skipped since they are not needed)
479 */
480 isImportLib = thisFileNameSize >= 4 && strncmp(fileName + thisFileNameSize - 4, ".dll", 4) == 0;
481
482 /*
483 * Note [GCC import files (ext .dll.a)]
484 * GCC stores import information in the same binary format
485 * as the object file normally has. The only difference is that
486 * all the information are put in .idata sections. The only real
487 * way to tell if we're dealing with an import lib is by looking
488 * at the file extension.
489 */
490 isImportLib = isImportLib || endsWithPath(path, WSTR(".dll.a"));
491 #endif // windows
492
493 DEBUG_LOG("\tthisFileNameSize = %d\n", (int)thisFileNameSize);
494 DEBUG_LOG("\tisObject = %d\n", isObject);
495
496 if (isObject) {
497 char *archiveMemberName;
498
499 DEBUG_LOG("Member is an object file...loading...\n");
500
501 #if defined(mingw32_HOST_OS)
502 // TODO: We would like to use allocateExec here, but allocateExec
503 // cannot currently allocate blocks large enough.
504 image = allocateImageAndTrampolines(path, fileName, f, memberSize,
505 isThin);
506 #elif defined(darwin_HOST_OS) || defined(ios_HOST_OS)
507 if (RTS_LINKER_USE_MMAP)
508 image = mmapForLinker(memberSize, MAP_ANONYMOUS, -1, 0);
509 else {
510 /* See loadObj() */
511 misalignment = machoGetMisalignment(f);
512 image = stgMallocBytes(memberSize + misalignment,
513 "loadArchive(image)");
514 image += misalignment;
515 }
516
517 #else // not windows or darwin
518 image = stgMallocBytes(memberSize, "loadArchive(image)");
519 #endif
520 if (isThin) {
521 if (!readThinArchiveMember(n, memberSize, path,
522 fileName, image)) {
523 goto fail;
524 }
525 }
526 else
527 {
528 n = fread ( image, 1, memberSize, f );
529 if (n != memberSize) {
530 FAIL("error whilst reading `%" PATH_FMT "'", path);
531 }
532 }
533
534 archiveMemberName = stgMallocBytes(pathlen(path) + thisFileNameSize + 3,
535 "loadArchive(file)");
536 sprintf(archiveMemberName, "%" PATH_FMT "(%.*s)",
537 path, (int)thisFileNameSize, fileName);
538
539 oc = mkOc(path, image, memberSize, false, archiveMemberName
540 , misalignment);
541 #ifdef OBJFORMAT_MACHO
542 ocInit_MachO( oc );
543 #endif
544
545 stgFree(archiveMemberName);
546
547 if (0 == loadOc(oc)) {
548 stgFree(fileName);
549 fclose(f);
550 return 0;
551 } else {
552 #if defined(OBJFORMAT_PEi386)
553 if (isImportLib)
554 {
555 findAndLoadImportLibrary(oc);
556 stgFree(oc);
557 oc = NULL;
558 break;
559 } else {
560 #endif
561 oc->next = objects;
562 objects = oc;
563 #if defined(OBJFORMAT_PEi386)
564 }
565 #endif
566 }
567 }
568 else if (isGnuIndex) {
569 if (gnuFileIndex != NULL) {
570 FAIL("GNU-variant index found, but already have an index, \
571 while reading filename from `%" PATH_FMT "'", path);
572 }
573 DEBUG_LOG("Found GNU-variant file index\n");
574 #if RTS_LINKER_USE_MMAP
575 gnuFileIndex = mmapForLinker(memberSize + 1, MAP_ANONYMOUS, -1, 0);
576 #else
577 gnuFileIndex = stgMallocBytes(memberSize + 1, "loadArchive(image)");
578 #endif
579 n = fread ( gnuFileIndex, 1, memberSize, f );
580 if (n != memberSize) {
581 FAIL("error whilst reading `%" PATH_FMT "'", path);
582 }
583 gnuFileIndex[memberSize] = '/';
584 gnuFileIndexSize = memberSize;
585 }
586 else if (isImportLib) {
587 #if defined(OBJFORMAT_PEi386)
588 if (checkAndLoadImportLibrary(path, fileName, f)) {
589 DEBUG_LOG("Member is an import file section... "
590 "Corresponding DLL has been loaded...\n");
591 }
592 else {
593 DEBUG_LOG("Member is not a valid import file section... "
594 "Skipping...\n");
595 n = fseek(f, memberSize, SEEK_CUR);
596 if (n != 0)
597 FAIL("error whilst seeking by %d in `%" PATH_FMT "'",
598 memberSize, path);
599 }
600 #endif
601 }
602 else {
603 DEBUG_LOG("`%s' does not appear to be an object file\n",
604 fileName);
605 if (!isThin || thisFileNameSize == 0) {
606 n = fseek(f, memberSize, SEEK_CUR);
607 if (n != 0)
608 FAIL("error whilst seeking by %d in `%" PATH_FMT "'",
609 memberSize, path);
610 }
611 }
612
613 /* .ar files are 2-byte aligned */
614 if (!(isThin && thisFileNameSize > 0) && memberSize % 2) {
615 DEBUG_LOG("trying to read one pad byte\n");
616 n = fread ( tmp, 1, 1, f );
617 if (n != 1) {
618 if (feof(f)) {
619 DEBUG_LOG("found EOF while reading one pad byte\n");
620 break;
621 }
622 else {
623 FAIL("Failed reading padding from `%" PATH_FMT "'", path);
624 }
625 }
626 DEBUG_LOG("successfully read one pad byte\n");
627 }
628 DEBUG_LOG("reached end of archive loading while loop\n");
629 }
630 retcode = 1;
631 fail:
632 if (f != NULL)
633 fclose(f);
634
635 if (fileName != NULL)
636 stgFree(fileName);
637 if (gnuFileIndex != NULL) {
638 #if RTS_LINKER_USE_MMAP
639 munmap(gnuFileIndex, gnuFileIndexSize + 1);
640 #else
641 stgFree(gnuFileIndex);
642 #endif
643 }
644
645 if (RTS_LINKER_USE_MMAP)
646 m32_allocator_flush();
647
648 DEBUG_LOG("done\n");
649 return retcode;
650 }
651
652 HsInt loadArchive (pathchar *path)
653 {
654 ACQUIRE_LOCK(&linker_mutex);
655 HsInt r = loadArchive_(path);
656 RELEASE_LOCK(&linker_mutex);
657 return r;
658 }