1 /** 2 * Invoke the linker as a separate process. 3 * 4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved 5 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright) 6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/link.d, _link.d) 8 * Documentation: https://dlang.org/phobos/dmd_link.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/link.d 10 */ 11 12 module dmd.link; 13 14 import core.stdc.ctype; 15 import core.stdc.stdio; 16 import core.stdc.string; 17 import core.sys.posix.stdio; 18 import core.sys.posix.stdlib; 19 import core.sys.posix.unistd; 20 import core.sys.windows.winbase; 21 import core.sys.windows.windef; 22 import dmd.env; 23 import dmd.errors; 24 import dmd.globals; 25 import dmd.mars; 26 import dmd.root.file; 27 import dmd.root.filename; 28 import dmd.root.outbuffer; 29 import dmd.root.rmem; 30 import dmd.root.string; 31 import dmd.utils; 32 import dmd.vsoptions; 33 34 version (Posix) extern (C) int pipe(int*); 35 version (Windows) extern (C) int spawnlp(int, const char*, const char*, const char*, const char*); 36 version (Windows) extern (C) int spawnl(int, const char*, const char*, const char*, const char*); 37 version (Windows) extern (C) int spawnv(int, const char*, const char**); 38 // Workaround lack of 'vfork' in older druntime binding for non-Glibc 39 version (Posix) extern(C) pid_t vfork(); 40 version (CRuntime_Microsoft) 41 { 42 // until the new windows bindings are available when building dmd. 43 static if(!is(STARTUPINFOA)) 44 { 45 alias STARTUPINFOA = STARTUPINFO; 46 47 // dwCreationFlags for CreateProcess() and CreateProcessAsUser() 48 enum : DWORD { 49 DEBUG_PROCESS = 0x00000001, 50 DEBUG_ONLY_THIS_PROCESS = 0x00000002, 51 CREATE_SUSPENDED = 0x00000004, 52 DETACHED_PROCESS = 0x00000008, 53 CREATE_NEW_CONSOLE = 0x00000010, 54 NORMAL_PRIORITY_CLASS = 0x00000020, 55 IDLE_PRIORITY_CLASS = 0x00000040, 56 HIGH_PRIORITY_CLASS = 0x00000080, 57 REALTIME_PRIORITY_CLASS = 0x00000100, 58 CREATE_NEW_PROCESS_GROUP = 0x00000200, 59 CREATE_UNICODE_ENVIRONMENT = 0x00000400, 60 CREATE_SEPARATE_WOW_VDM = 0x00000800, 61 CREATE_SHARED_WOW_VDM = 0x00001000, 62 CREATE_FORCEDOS = 0x00002000, 63 BELOW_NORMAL_PRIORITY_CLASS = 0x00004000, 64 ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000, 65 CREATE_BREAKAWAY_FROM_JOB = 0x01000000, 66 CREATE_WITH_USERPROFILE = 0x02000000, 67 CREATE_DEFAULT_ERROR_MODE = 0x04000000, 68 CREATE_NO_WINDOW = 0x08000000, 69 PROFILE_USER = 0x10000000, 70 PROFILE_KERNEL = 0x20000000, 71 PROFILE_SERVER = 0x40000000 72 } 73 } 74 } 75 76 /**************************************** 77 * Write filename to cmdbuf, quoting if necessary. 78 */ 79 private void writeFilename(OutBuffer* buf, const(char)[] filename) 80 { 81 /* Loop and see if we need to quote 82 */ 83 foreach (const char c; filename) 84 { 85 if (isalnum(c) || c == '_') 86 continue; 87 /* Need to quote 88 */ 89 buf.writeByte('"'); 90 buf.writestring(filename); 91 buf.writeByte('"'); 92 return; 93 } 94 /* No quoting necessary 95 */ 96 buf.writestring(filename); 97 } 98 99 private void writeFilename(OutBuffer* buf, const(char)* filename) 100 { 101 writeFilename(buf, filename.toDString()); 102 } 103 104 version (Posix) 105 { 106 /***************************** 107 * As it forwards the linker error message to stderr, checks for the presence 108 * of an error indicating lack of a main function (NME_ERR_MSG). 109 * 110 * Returns: 111 * 1 if there is a no main error 112 * -1 if there is an IO error 113 * 0 otherwise 114 */ 115 private int findNoMainError(int fd) 116 { 117 version (OSX) 118 { 119 static immutable(char*) nmeErrorMessage = "`__Dmain`, referenced from:"; 120 } 121 else 122 { 123 static immutable(char*) nmeErrorMessage = "undefined reference to `_Dmain`"; 124 } 125 FILE* stream = fdopen(fd, "r"); 126 if (stream is null) 127 return -1; 128 const(size_t) len = 64 * 1024 - 1; 129 char[len + 1] buffer; // + '\0' 130 size_t beg = 0, end = len; 131 bool nmeFound = false; 132 for (;;) 133 { 134 // read linker output 135 const(size_t) n = fread(&buffer[beg], 1, len - beg, stream); 136 if (beg + n < len && ferror(stream)) 137 return -1; 138 buffer[(end = beg + n)] = '\0'; 139 // search error message, stop at last complete line 140 const(char)* lastSep = strrchr(buffer.ptr, '\n'); 141 if (lastSep) 142 buffer[(end = lastSep - &buffer[0])] = '\0'; 143 if (strstr(&buffer[0], nmeErrorMessage)) 144 nmeFound = true; 145 if (lastSep) 146 buffer[end++] = '\n'; 147 if (fwrite(&buffer[0], 1, end, stderr) < end) 148 return -1; 149 if (beg + n < len && feof(stream)) 150 break; 151 // copy over truncated last line 152 memcpy(&buffer[0], &buffer[end], (beg = len - end)); 153 } 154 return nmeFound ? 1 : 0; 155 } 156 } 157 158 version (Windows) 159 { 160 private void writeQuotedArgIfNeeded(ref OutBuffer buffer, const(char)* arg) 161 { 162 bool quote = false; 163 for (size_t i = 0; arg[i]; ++i) 164 { 165 if (arg[i] == '"') 166 { 167 quote = false; 168 break; 169 } 170 171 if (arg[i] == ' ') 172 quote = true; 173 } 174 175 if (quote) 176 buffer.writeByte('"'); 177 buffer.writestring(arg); 178 if (quote) 179 buffer.writeByte('"'); 180 } 181 182 unittest 183 { 184 OutBuffer buffer; 185 186 const(char)[] test(string arg) 187 { 188 buffer.reset(); 189 buffer.writeQuotedArgIfNeeded(arg.ptr); 190 return buffer[]; 191 } 192 193 assert(test("arg") == `arg`); 194 assert(test("arg with spaces") == `"arg with spaces"`); 195 assert(test(`"/LIBPATH:dir with spaces"`) == `"/LIBPATH:dir with spaces"`); 196 assert(test(`/LIBPATH:"dir with spaces"`) == `/LIBPATH:"dir with spaces"`); 197 } 198 } 199 200 /***************************** 201 * Run the linker. Return status of execution. 202 */ 203 public int runLINK() 204 { 205 const phobosLibname = global.finalDefaultlibname(); 206 207 void setExeFile() 208 { 209 /* Generate exe file name from first obj name. 210 * No need to add it to cmdbuf because the linker will default to it. 211 */ 212 const char[] n = FileName.name(global.params.objfiles[0].toDString); 213 global.params.exefile = FileName.forceExt(n, "exe"); 214 } 215 216 const(char)[] getMapFilename() 217 { 218 const(char)[] fn = FileName.forceExt(global.params.exefile, "map"); 219 const(char)[] path = FileName.path(global.params.exefile); 220 return path.length ? fn : FileName.combine(global.params.objdir, fn); 221 } 222 223 version (Windows) 224 { 225 if (phobosLibname) 226 global.params.libfiles.push(phobosLibname.xarraydup.ptr); 227 228 if (global.params.mscoff) 229 { 230 OutBuffer cmdbuf; 231 cmdbuf.writestring("/NOLOGO"); 232 for (size_t i = 0; i < global.params.objfiles.length; i++) 233 { 234 cmdbuf.writeByte(' '); 235 const(char)* p = global.params.objfiles[i]; 236 writeFilename(&cmdbuf, p); 237 } 238 if (global.params.resfile) 239 { 240 cmdbuf.writeByte(' '); 241 writeFilename(&cmdbuf, global.params.resfile); 242 } 243 cmdbuf.writeByte(' '); 244 if (global.params.exefile) 245 { 246 cmdbuf.writestring("/OUT:"); 247 writeFilename(&cmdbuf, global.params.exefile); 248 } 249 else 250 { 251 setExeFile(); 252 } 253 // Make sure path to exe file exists 254 ensurePathToNameExists(Loc.initial, global.params.exefile); 255 cmdbuf.writeByte(' '); 256 if (global.params.mapfile) 257 { 258 cmdbuf.writestring("/MAP:"); 259 writeFilename(&cmdbuf, global.params.mapfile); 260 } 261 else if (dmdParams.map) 262 { 263 cmdbuf.writestring("/MAP:"); 264 writeFilename(&cmdbuf, getMapFilename()); 265 } 266 for (size_t i = 0; i < global.params.libfiles.length; i++) 267 { 268 cmdbuf.writeByte(' '); 269 cmdbuf.writestring("/DEFAULTLIB:"); 270 writeFilename(&cmdbuf, global.params.libfiles[i]); 271 } 272 if (global.params.deffile) 273 { 274 cmdbuf.writeByte(' '); 275 cmdbuf.writestring("/DEF:"); 276 writeFilename(&cmdbuf, global.params.deffile); 277 } 278 if (global.params.symdebug) 279 { 280 cmdbuf.writeByte(' '); 281 cmdbuf.writestring("/DEBUG"); 282 // in release mode we need to reactivate /OPT:REF after /DEBUG 283 if (global.params.release) 284 cmdbuf.writestring(" /OPT:REF"); 285 } 286 if (global.params.dll) 287 { 288 cmdbuf.writeByte(' '); 289 cmdbuf.writestring("/DLL"); 290 } 291 for (size_t i = 0; i < global.params.linkswitches.length; i++) 292 { 293 cmdbuf.writeByte(' '); 294 cmdbuf.writeQuotedArgIfNeeded(global.params.linkswitches[i]); 295 } 296 297 VSOptions vsopt; 298 // if a runtime library (msvcrtNNN.lib) from the mingw folder is selected explicitly, do not detect VS and use lld 299 if (global.params.mscrtlib.length <= 6 || 300 global.params.mscrtlib[0..6] != "msvcrt" || !isdigit(global.params.mscrtlib[6])) 301 vsopt.initialize(); 302 303 const(char)* lflags = vsopt.linkOptions(global.params.is64bit); 304 if (lflags) 305 { 306 cmdbuf.writeByte(' '); 307 cmdbuf.writestring(lflags); 308 } 309 310 const(char)* linkcmd = getenv(global.params.is64bit ? "LINKCMD64" : "LINKCMD"); 311 if (!linkcmd) 312 linkcmd = getenv("LINKCMD"); // backward compatible 313 if (!linkcmd) 314 linkcmd = vsopt.linkerPath(global.params.is64bit); 315 316 // object files not SAFESEH compliant, but LLD is more picky than MS link 317 if (!global.params.is64bit) 318 if (FileName.equals(FileName.name(linkcmd), "lld-link.exe")) 319 cmdbuf.writestring(" /SAFESEH:NO"); 320 321 cmdbuf.writeByte(0); // null terminate the buffer 322 char[] p = cmdbuf.extractSlice()[0 .. $-1]; 323 const(char)[] lnkfilename; 324 if (p.length > 7000) 325 { 326 lnkfilename = FileName.forceExt(global.params.exefile, "lnk"); 327 writeFile(Loc.initial, lnkfilename, p); 328 if (lnkfilename.length < p.length) 329 { 330 p[0] = '@'; 331 p[1 .. lnkfilename.length +1] = lnkfilename; 332 p[lnkfilename.length +1] = 0; 333 } 334 } 335 336 const int status = executecmd(linkcmd, p.ptr); 337 if (lnkfilename) 338 { 339 lnkfilename.toCStringThen!(lf => remove(lf.ptr)); 340 FileName.free(lnkfilename.ptr); 341 } 342 return status; 343 } 344 else 345 { 346 OutBuffer cmdbuf; 347 global.params.libfiles.push("user32"); 348 global.params.libfiles.push("kernel32"); 349 for (size_t i = 0; i < global.params.objfiles.length; i++) 350 { 351 if (i) 352 cmdbuf.writeByte('+'); 353 const(char)[] p = global.params.objfiles[i].toDString(); 354 const(char)[] basename = FileName.removeExt(FileName.name(p)); 355 const(char)[] ext = FileName.ext(p); 356 if (ext.length && !strchr(basename.ptr, '.')) 357 { 358 // Write name sans extension (but not if a double extension) 359 writeFilename(&cmdbuf, p[0 .. $ - ext.length - 1]); 360 } 361 else 362 writeFilename(&cmdbuf, p); 363 FileName.free(basename.ptr); 364 } 365 cmdbuf.writeByte(','); 366 if (global.params.exefile) 367 writeFilename(&cmdbuf, global.params.exefile); 368 else 369 { 370 setExeFile(); 371 } 372 // Make sure path to exe file exists 373 ensurePathToNameExists(Loc.initial, global.params.exefile); 374 cmdbuf.writeByte(','); 375 if (global.params.mapfile) 376 writeFilename(&cmdbuf, global.params.mapfile); 377 else if (dmdParams.map) 378 { 379 writeFilename(&cmdbuf, getMapFilename()); 380 } 381 else 382 cmdbuf.writestring("nul"); 383 cmdbuf.writeByte(','); 384 for (size_t i = 0; i < global.params.libfiles.length; i++) 385 { 386 if (i) 387 cmdbuf.writeByte('+'); 388 writeFilename(&cmdbuf, global.params.libfiles[i]); 389 } 390 if (global.params.deffile) 391 { 392 cmdbuf.writeByte(','); 393 writeFilename(&cmdbuf, global.params.deffile); 394 } 395 /* Eliminate unnecessary trailing commas */ 396 while (1) 397 { 398 const size_t i = cmdbuf.length; 399 if (!i || cmdbuf[i - 1] != ',') 400 break; 401 cmdbuf.setsize(cmdbuf.length - 1); 402 } 403 if (global.params.resfile) 404 { 405 cmdbuf.writestring("/RC:"); 406 writeFilename(&cmdbuf, global.params.resfile); 407 } 408 if (dmdParams.map || global.params.mapfile) 409 cmdbuf.writestring("/m"); 410 version (none) 411 { 412 if (debuginfo) 413 cmdbuf.writestring("/li"); 414 if (codeview) 415 { 416 cmdbuf.writestring("/co"); 417 if (codeview3) 418 cmdbuf.writestring(":3"); 419 } 420 } 421 else 422 { 423 if (global.params.symdebug) 424 cmdbuf.writestring("/co"); 425 } 426 cmdbuf.writestring("/noi"); 427 for (size_t i = 0; i < global.params.linkswitches.length; i++) 428 { 429 cmdbuf.writestring(global.params.linkswitches[i]); 430 } 431 cmdbuf.writeByte(';'); 432 cmdbuf.writeByte(0); //null terminate the buffer 433 char[] p = cmdbuf.extractSlice()[0 .. $-1]; 434 const(char)[] lnkfilename; 435 if (p.length > 7000) 436 { 437 lnkfilename = FileName.forceExt(global.params.exefile, "lnk"); 438 writeFile(Loc.initial, lnkfilename, p); 439 if (lnkfilename.length < p.length) 440 { 441 p[0] = '@'; 442 p[1 .. lnkfilename.length +1] = lnkfilename; 443 p[lnkfilename.length +1] = 0; 444 } 445 } 446 const(char)* linkcmd = getenv("LINKCMD"); 447 if (!linkcmd) 448 linkcmd = "optlink"; 449 const int status = executecmd(linkcmd, p.ptr); 450 if (lnkfilename) 451 { 452 lnkfilename.toCStringThen!(lf => remove(lf.ptr)); 453 FileName.free(lnkfilename.ptr); 454 } 455 return status; 456 } 457 } 458 else version (Posix) 459 { 460 pid_t childpid; 461 int status; 462 // Build argv[] 463 Strings argv; 464 const(char)* cc = getenv("CC"); 465 if (!cc) 466 { 467 argv.push("cc"); 468 } 469 else 470 { 471 // Split CC command to support link driver arguments such as -fpie or -flto. 472 char* arg = cast(char*)Mem.check(strdup(cc)); 473 const(char)* tok = strtok(arg, " "); 474 while (tok) 475 { 476 argv.push(mem.xstrdup(tok)); 477 tok = strtok(null, " "); 478 } 479 free(arg); 480 } 481 argv.append(&global.params.objfiles); 482 version (OSX) 483 { 484 // If we are on Mac OS X and linking a dynamic library, 485 // add the "-dynamiclib" flag 486 if (global.params.dll) 487 argv.push("-dynamiclib"); 488 } 489 else version (Posix) 490 { 491 if (global.params.dll) 492 argv.push("-shared"); 493 } 494 // None of that a.out stuff. Use explicit exe file name, or 495 // generate one from name of first source file. 496 argv.push("-o"); 497 if (global.params.exefile) 498 { 499 argv.push(global.params.exefile.xarraydup.ptr); 500 } 501 else if (global.params.run) 502 { 503 version (all) 504 { 505 char[L_tmpnam + 14 + 1] name; 506 strcpy(name.ptr, P_tmpdir); 507 strcat(name.ptr, "/dmd_runXXXXXX"); 508 int fd = mkstemp(name.ptr); 509 if (fd == -1) 510 { 511 error(Loc.initial, "error creating temporary file"); 512 return 1; 513 } 514 else 515 close(fd); 516 global.params.exefile = name.arraydup; 517 argv.push(global.params.exefile.xarraydup.ptr); 518 } 519 else 520 { 521 /* The use of tmpnam raises the issue of "is this a security hole"? 522 * The hole is that after tmpnam and before the file is opened, 523 * the attacker modifies the file system to get control of the 524 * file with that name. I do not know if this is an issue in 525 * this context. 526 * We cannot just replace it with mkstemp, because this name is 527 * passed to the linker that actually opens the file and writes to it. 528 */ 529 char[L_tmpnam + 1] s; 530 char* n = tmpnam(s.ptr); 531 global.params.exefile = mem.xstrdup(n); 532 argv.push(global.params.exefile); 533 } 534 } 535 else 536 { 537 // Generate exe file name from first obj name 538 const(char)[] n = global.params.objfiles[0].toDString(); 539 const(char)[] ex; 540 n = FileName.name(n); 541 if (const e = FileName.ext(n)) 542 { 543 if (global.params.dll) 544 ex = FileName.forceExt(ex, global.dll_ext); 545 else 546 ex = FileName.removeExt(n); 547 } 548 else 549 ex = "a.out"; // no extension, so give up 550 argv.push(ex.ptr); 551 global.params.exefile = ex; 552 } 553 // Make sure path to exe file exists 554 ensurePathToNameExists(Loc.initial, global.params.exefile); 555 if (global.params.symdebug) 556 argv.push("-g"); 557 if (global.params.is64bit) 558 argv.push("-m64"); 559 else 560 argv.push("-m32"); 561 version (OSX) 562 { 563 /* Without this switch, ld generates messages of the form: 564 * ld: warning: could not create compact unwind for __Dmain: offset of saved registers too far to encode 565 * meaning they are further than 255 bytes from the frame register. 566 * ld reverts to the old method instead. 567 * See: https://ghc.haskell.org/trac/ghc/ticket/5019 568 * which gives this tidbit: 569 * "When a C++ (or x86_64 Objective-C) exception is thrown, the runtime must unwind the 570 * stack looking for some function to catch the exception. Traditionally, the unwind 571 * information is stored in the __TEXT/__eh_frame section of each executable as Dwarf 572 * CFI (call frame information). Beginning in Mac OS X 10.6, the unwind information is 573 * also encoded in the __TEXT/__unwind_info section using a two-level lookup table of 574 * compact unwind encodings. 575 * The unwinddump tool displays the content of the __TEXT/__unwind_info section." 576 * 577 * A better fix would be to save the registers next to the frame pointer. 578 */ 579 argv.push("-Xlinker"); 580 argv.push("-no_compact_unwind"); 581 } 582 if (dmdParams.map || global.params.mapfile.length) 583 { 584 argv.push("-Xlinker"); 585 version (OSX) 586 { 587 argv.push("-map"); 588 } 589 else 590 { 591 argv.push("-Map"); 592 } 593 if (!global.params.mapfile.length) 594 { 595 const(char)[] fn = FileName.forceExt(global.params.exefile, "map"); 596 const(char)[] path = FileName.path(global.params.exefile); 597 global.params.mapfile = path.length ? fn : FileName.combine(global.params.objdir, fn); 598 } 599 argv.push("-Xlinker"); 600 argv.push(global.params.mapfile.xarraydup.ptr); 601 } 602 if (0 && global.params.exefile) 603 { 604 /* This switch enables what is known as 'smart linking' 605 * in the Windows world, where unreferenced sections 606 * are removed from the executable. It eliminates unreferenced 607 * functions, essentially making a 'library' out of a module. 608 * Although it is documented to work with ld version 2.13, 609 * in practice it does not, but just seems to be ignored. 610 * Thomas Kuehne has verified that it works with ld 2.16.1. 611 * BUG: disabled because it causes exception handling to fail 612 * because EH sections are "unreferenced" and elided 613 */ 614 argv.push("-Xlinker"); 615 argv.push("--gc-sections"); 616 } 617 618 // return true if flagp should be ordered in with the library flags 619 static bool flagIsLibraryRelated(const char* p) 620 { 621 const flag = p.toDString(); 622 623 return startsWith(p, "-l") || startsWith(p, "-L") 624 || flag == "-(" || flag == "-)" 625 || flag == "--start-group" || flag == "--end-group" 626 || FileName.equalsExt(p, "a") 627 ; 628 } 629 630 /* Add libraries. The order of libraries passed is: 631 * 1. link switches without a -L prefix, 632 e.g. --whole-archive "lib.a" --no-whole-archive (global.params.linkswitches) 633 * 2. static libraries ending with *.a (global.params.libfiles) 634 * 3. link switches with a -L prefix (global.params.linkswitches) 635 * 4. libraries specified by pragma(lib), which were appended 636 * to global.params.libfiles. These are prefixed with "-l" 637 * 5. dynamic libraries passed to the command line (global.params.dllfiles) 638 * 6. standard libraries. 639 */ 640 641 // STEP 1 642 foreach (pi, p; global.params.linkswitches) 643 { 644 if (p && p[0] && !flagIsLibraryRelated(p)) 645 { 646 if (!global.params.linkswitchIsForCC[pi]) 647 argv.push("-Xlinker"); 648 argv.push(p); 649 } 650 } 651 652 // STEP 2 653 foreach (p; global.params.libfiles) 654 { 655 if (FileName.equalsExt(p, "a")) 656 argv.push(p); 657 } 658 659 // STEP 3 660 foreach (pi, p; global.params.linkswitches) 661 { 662 if (p && p[0] && flagIsLibraryRelated(p)) 663 { 664 if (!startsWith(p, "-l") && !startsWith(p, "-L") && !global.params.linkswitchIsForCC[pi]) 665 { 666 // Don't need -Xlinker if switch starts with -l or -L. 667 // Eliding -Xlinker is significant for -L since it allows our paths 668 // to take precedence over gcc defaults. 669 // All other link switches were already added in step 1. 670 argv.push("-Xlinker"); 671 } 672 argv.push(p); 673 } 674 } 675 676 // STEP 4 677 foreach (p; global.params.libfiles) 678 { 679 if (!FileName.equalsExt(p, "a")) 680 { 681 const plen = strlen(p); 682 char* s = cast(char*)mem.xmalloc(plen + 3); 683 s[0] = '-'; 684 s[1] = 'l'; 685 memcpy(s + 2, p, plen + 1); 686 argv.push(s); 687 } 688 } 689 690 // STEP 5 691 foreach (p; global.params.dllfiles) 692 { 693 argv.push(p); 694 } 695 696 // STEP 6 697 /* D runtime libraries must go after user specified libraries 698 * passed with -l. 699 */ 700 const libname = phobosLibname; 701 if (libname.length) 702 { 703 const bufsize = 2 + libname.length + 1; 704 auto buf = (cast(char*) malloc(bufsize))[0 .. bufsize]; 705 if (!buf) 706 Mem.error(); 707 buf[0 .. 2] = "-l"; 708 709 char* getbuf(const(char)[] suffix) 710 { 711 buf[2 .. 2 + suffix.length] = suffix[]; 712 buf[2 + suffix.length] = 0; 713 return buf.ptr; 714 } 715 716 if (libname.length > 3 + 2 && libname[0 .. 3] == "lib") 717 { 718 if (libname[$-2 .. $] == ".a") 719 { 720 argv.push("-Xlinker"); 721 argv.push("-Bstatic"); 722 argv.push(getbuf(libname[3 .. $-2])); 723 argv.push("-Xlinker"); 724 argv.push("-Bdynamic"); 725 } 726 else if (libname[$-3 .. $] == ".so") 727 argv.push(getbuf(libname[3 .. $-3])); 728 else 729 argv.push(getbuf(libname)); 730 } 731 else 732 { 733 argv.push(getbuf(libname)); 734 } 735 } 736 //argv.push("-ldruntime"); 737 argv.push("-lpthread"); 738 argv.push("-lm"); 739 version (linux) 740 { 741 // Changes in ld for Ubuntu 11.10 require this to appear after phobos2 742 argv.push("-lrt"); 743 // Link against libdl for phobos usage of dlopen 744 argv.push("-ldl"); 745 } 746 if (global.params.verbose) 747 { 748 // Print it 749 OutBuffer buf; 750 for (size_t i = 0; i < argv.dim; i++) 751 { 752 buf.writestring(argv[i]); 753 buf.writeByte(' '); 754 } 755 message(buf.peekChars()); 756 } 757 argv.push(null); 758 // set up pipes 759 int[2] fds; 760 if (pipe(fds.ptr) == -1) 761 { 762 perror("unable to create pipe to linker"); 763 return -1; 764 } 765 // vfork instead of fork to avoid https://issues.dlang.org/show_bug.cgi?id=21089 766 childpid = vfork(); 767 if (childpid == 0) 768 { 769 // pipe linker stderr to fds[0] 770 dup2(fds[1], STDERR_FILENO); 771 close(fds[0]); 772 execvp(argv[0], argv.tdata()); 773 perror(argv[0]); // failed to execute 774 _exit(-1); 775 } 776 else if (childpid == -1) 777 { 778 perror("unable to fork"); 779 return -1; 780 } 781 close(fds[1]); 782 const(int) nme = findNoMainError(fds[0]); 783 waitpid(childpid, &status, 0); 784 if (WIFEXITED(status)) 785 { 786 status = WEXITSTATUS(status); 787 if (status) 788 { 789 if (nme == -1) 790 { 791 perror("error with the linker pipe"); 792 return -1; 793 } 794 else 795 { 796 error(Loc.initial, "linker exited with status %d", status); 797 if (nme == 1) 798 error(Loc.initial, "no main function specified"); 799 } 800 } 801 } 802 else if (WIFSIGNALED(status)) 803 { 804 error(Loc.initial, "linker killed by signal %d", WTERMSIG(status)); 805 status = 1; 806 } 807 return status; 808 } 809 else 810 { 811 error(Loc.initial, "linking is not yet supported for this version of DMD."); 812 return -1; 813 } 814 } 815 816 817 /****************************** 818 * Execute a rule. Return the status. 819 * cmd program to run 820 * args arguments to cmd, as a string 821 */ 822 version (Windows) 823 { 824 private int executecmd(const(char)* cmd, const(char)* args) 825 { 826 int status; 827 size_t len; 828 if (global.params.verbose) 829 message("%s %s", cmd, args); 830 if (!global.params.mscoff) 831 { 832 if ((len = strlen(args)) > 255) 833 { 834 status = putenvRestorable("_CMDLINE", args[0 .. len]); 835 if (status == 0) 836 args = "@_CMDLINE"; 837 else 838 error(Loc.initial, "command line length of %llu is too long", cast(ulong) len); 839 } 840 } 841 // Normalize executable path separators 842 // https://issues.dlang.org/show_bug.cgi?id=9330 843 cmd = toWinPath(cmd); 844 version (CRuntime_Microsoft) 845 { 846 // Open scope so dmd doesn't complain about alloca + exception handling 847 { 848 // Use process spawning through the WinAPI to avoid issues with executearg0 and spawnlp 849 OutBuffer cmdbuf; 850 cmdbuf.writestring("\""); 851 cmdbuf.writestring(cmd); 852 cmdbuf.writestring("\" "); 853 cmdbuf.writestring(args); 854 855 STARTUPINFOA startInf; 856 startInf.dwFlags = STARTF_USESTDHANDLES; 857 startInf.hStdInput = GetStdHandle(STD_INPUT_HANDLE); 858 startInf.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 859 startInf.hStdError = GetStdHandle(STD_ERROR_HANDLE); 860 PROCESS_INFORMATION procInf; 861 862 BOOL b = CreateProcessA(null, cmdbuf.peekChars(), null, null, 1, NORMAL_PRIORITY_CLASS, null, null, &startInf, &procInf); 863 if (b) 864 { 865 WaitForSingleObject(procInf.hProcess, INFINITE); 866 DWORD returnCode; 867 GetExitCodeProcess(procInf.hProcess, &returnCode); 868 status = returnCode; 869 CloseHandle(procInf.hProcess); 870 } 871 else 872 { 873 status = -1; 874 } 875 } 876 } 877 else 878 { 879 status = executearg0(cmd, args); 880 if (status == -1) 881 { 882 status = spawnlp(0, cmd, cmd, args, null); 883 } 884 } 885 if (status) 886 { 887 if (status == -1) 888 error(Loc.initial, "can't run '%s', check PATH", cmd); 889 else 890 error(Loc.initial, "linker exited with status %d", status); 891 } 892 return status; 893 } 894 } 895 896 /************************************** 897 * Attempt to find command to execute by first looking in the directory 898 * where DMD was run from. 899 * Returns: 900 * -1 did not find command there 901 * !=-1 exit status from command 902 */ 903 version (Windows) 904 { 905 private int executearg0(const(char)* cmd, const(char)* args) 906 { 907 const argv0 = global.params.argv0; 908 //printf("argv0='%s', cmd='%s', args='%s'\n",argv0,cmd,args); 909 // If cmd is fully qualified, we don't do this 910 if (FileName.absolute(cmd.toDString())) 911 return -1; 912 const file = FileName.replaceName(argv0, cmd.toDString); 913 //printf("spawning '%s'\n",file); 914 // spawnlp returns intptr_t in some systems, not int 915 return spawnl(0, file.ptr, file.ptr, args, null); 916 } 917 } 918 919 /*************************************** 920 * Run the compiled program. 921 * Return exit status. 922 */ 923 public int runProgram() 924 { 925 //printf("runProgram()\n"); 926 if (global.params.verbose) 927 { 928 OutBuffer buf; 929 buf.writestring(global.params.exefile); 930 for (size_t i = 0; i < global.params.runargs.dim; ++i) 931 { 932 buf.writeByte(' '); 933 buf.writestring(global.params.runargs[i]); 934 } 935 message(buf.peekChars()); 936 } 937 // Build argv[] 938 Strings argv; 939 argv.push(global.params.exefile.xarraydup.ptr); 940 for (size_t i = 0; i < global.params.runargs.dim; ++i) 941 { 942 const(char)* a = global.params.runargs[i]; 943 version (Windows) 944 { 945 // BUG: what about " appearing in the string? 946 if (strchr(a, ' ')) 947 { 948 char* b = cast(char*)mem.xmalloc(3 + strlen(a)); 949 sprintf(b, "\"%s\"", a); 950 a = b; 951 } 952 } 953 argv.push(a); 954 } 955 argv.push(null); 956 restoreEnvVars(); 957 version (Windows) 958 { 959 const(char)[] ex = FileName.name(global.params.exefile); 960 if (ex == global.params.exefile) 961 ex = FileName.combine(".", ex); 962 else 963 ex = global.params.exefile; 964 // spawnlp returns intptr_t in some systems, not int 965 return spawnv(0, ex.xarraydup.ptr, argv.tdata()); 966 } 967 else version (Posix) 968 { 969 pid_t childpid; 970 int status; 971 childpid = fork(); 972 if (childpid == 0) 973 { 974 const(char)[] fn = argv[0].toDString(); 975 // Make it "./fn" if needed 976 if (!FileName.absolute(fn)) 977 fn = FileName.combine(".", fn); 978 fn.toCStringThen!((fnp) { 979 execv(fnp.ptr, argv.tdata()); 980 // If execv returns, it failed to execute 981 perror(fnp.ptr); 982 }); 983 return -1; 984 } 985 waitpid(childpid, &status, 0); 986 if (WIFEXITED(status)) 987 { 988 status = WEXITSTATUS(status); 989 //printf("--- errorlevel %d\n", status); 990 } 991 else if (WIFSIGNALED(status)) 992 { 993 error(Loc.initial, "program killed by signal %d", WTERMSIG(status)); 994 status = 1; 995 } 996 return status; 997 } 998 else 999 { 1000 assert(0); 1001 } 1002 }