1 /** 2 * Invoke the linker as a separate process. 3 * 4 * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved 5 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 6 * License: $(LINK2 https://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.dmdparams; 23 import dmd.errors; 24 import dmd.globals; 25 import dmd.location; 26 import dmd.root.array; 27 import dmd.root.env; 28 import dmd.root.file; 29 import dmd.root.filename; 30 import dmd.common.outbuffer; 31 import dmd.common.string; 32 import dmd.root.rmem; 33 import dmd.root.string; 34 import dmd.utils; 35 import dmd.target; 36 import dmd.vsoptions; 37 38 version (Posix) extern (C) int pipe(int*); 39 40 version (Windows) 41 { 42 /* https://www.digitalmars.com/rtl/process.html#_spawn 43 * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/spawnvp-wspawnvp?view=msvc-170 44 */ 45 extern (C) 46 { 47 int spawnlp(int, const char*, const char*, const char*, const char*); 48 int spawnl(int, const char*, const char*, const char*, const char*); 49 int spawnv(int, const char*, const char**); 50 int spawnvp(int, const char*, const char**); 51 enum _P_WAIT = 0; 52 } 53 } 54 55 // Workaround lack of 'vfork' in older druntime binding for non-Glibc 56 version (Posix) extern(C) pid_t vfork(); 57 version (CRuntime_Microsoft) 58 { 59 // until the new windows bindings are available when building dmd. 60 static if(!is(STARTUPINFOA)) 61 { 62 alias STARTUPINFOA = STARTUPINFO; 63 64 // dwCreationFlags for CreateProcess() and CreateProcessAsUser() 65 enum : DWORD { 66 DEBUG_PROCESS = 0x00000001, 67 DEBUG_ONLY_THIS_PROCESS = 0x00000002, 68 CREATE_SUSPENDED = 0x00000004, 69 DETACHED_PROCESS = 0x00000008, 70 CREATE_NEW_CONSOLE = 0x00000010, 71 NORMAL_PRIORITY_CLASS = 0x00000020, 72 IDLE_PRIORITY_CLASS = 0x00000040, 73 HIGH_PRIORITY_CLASS = 0x00000080, 74 REALTIME_PRIORITY_CLASS = 0x00000100, 75 CREATE_NEW_PROCESS_GROUP = 0x00000200, 76 CREATE_UNICODE_ENVIRONMENT = 0x00000400, 77 CREATE_SEPARATE_WOW_VDM = 0x00000800, 78 CREATE_SHARED_WOW_VDM = 0x00001000, 79 CREATE_FORCEDOS = 0x00002000, 80 BELOW_NORMAL_PRIORITY_CLASS = 0x00004000, 81 ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000, 82 CREATE_BREAKAWAY_FROM_JOB = 0x01000000, 83 CREATE_WITH_USERPROFILE = 0x02000000, 84 CREATE_DEFAULT_ERROR_MODE = 0x04000000, 85 CREATE_NO_WINDOW = 0x08000000, 86 PROFILE_USER = 0x10000000, 87 PROFILE_KERNEL = 0x20000000, 88 PROFILE_SERVER = 0x40000000 89 } 90 } 91 } 92 93 /**************************************** 94 * Write filename to cmdbuf, quoting if necessary. 95 */ 96 private void writeFilename(OutBuffer* buf, const(char)[] filename) @safe 97 { 98 /* Loop and see if we need to quote 99 */ 100 foreach (const char c; filename) 101 { 102 if (isalnum(c) || c == '_') 103 continue; 104 /* Need to quote 105 */ 106 buf.writeByte('"'); 107 buf.writestring(filename); 108 buf.writeByte('"'); 109 return; 110 } 111 /* No quoting necessary 112 */ 113 buf.writestring(filename); 114 } 115 116 private void writeFilename(OutBuffer* buf, const(char)* filename) 117 { 118 writeFilename(buf, filename.toDString()); 119 } 120 121 version (Posix) 122 { 123 /***************************** 124 * As it forwards the linker error message to stderr, checks for the presence 125 * of an error indicating lack of a main function (NME_ERR_MSG). 126 * 127 * Returns: 128 * 1 if there is a no main error 129 * -1 if there is an IO error 130 * 0 otherwise 131 */ 132 private int findNoMainError(int fd) 133 { 134 version (OSX) 135 { 136 static immutable(char*) nmeErrorMessage = "`__Dmain`, referenced from:"; 137 } 138 else 139 { 140 static immutable(char*) nmeErrorMessage = "undefined reference to `_Dmain`"; 141 } 142 FILE* stream = fdopen(fd, "r"); 143 if (stream is null) 144 return -1; 145 const(size_t) len = 64 * 1024 - 1; 146 char[len + 1] buffer; // + '\0' 147 size_t beg = 0, end = len; 148 bool nmeFound = false; 149 for (;;) 150 { 151 // read linker output 152 const(size_t) n = fread(&buffer[beg], 1, len - beg, stream); 153 if (beg + n < len && ferror(stream)) 154 return -1; 155 buffer[(end = beg + n)] = '\0'; 156 // search error message, stop at last complete line 157 const(char)* lastSep = strrchr(buffer.ptr, '\n'); 158 if (lastSep) 159 buffer[(end = lastSep - &buffer[0])] = '\0'; 160 if (strstr(&buffer[0], nmeErrorMessage)) 161 nmeFound = true; 162 if (lastSep) 163 buffer[end++] = '\n'; 164 if (fwrite(&buffer[0], 1, end, stderr) < end) 165 return -1; 166 if (beg + n < len && feof(stream)) 167 break; 168 // copy over truncated last line 169 memcpy(&buffer[0], &buffer[end], (beg = len - end)); 170 } 171 return nmeFound ? 1 : 0; 172 } 173 } 174 175 version (Windows) 176 { 177 private void writeQuotedArgIfNeeded(ref OutBuffer buffer, const(char)* arg) 178 { 179 bool quote = false; 180 for (size_t i = 0; arg[i]; ++i) 181 { 182 if (arg[i] == '"') 183 { 184 quote = false; 185 break; 186 } 187 188 if (arg[i] == ' ') 189 quote = true; 190 } 191 192 if (quote) 193 buffer.writeByte('"'); 194 buffer.writestring(arg); 195 if (quote) 196 buffer.writeByte('"'); 197 } 198 199 unittest 200 { 201 OutBuffer buffer; 202 203 const(char)[] test(string arg) 204 { 205 buffer.reset(); 206 buffer.writeQuotedArgIfNeeded(arg.ptr); 207 return buffer[]; 208 } 209 210 assert(test("arg") == `arg`); 211 assert(test("arg with spaces") == `"arg with spaces"`); 212 assert(test(`"/LIBPATH:dir with spaces"`) == `"/LIBPATH:dir with spaces"`); 213 assert(test(`/LIBPATH:"dir with spaces"`) == `/LIBPATH:"dir with spaces"`); 214 } 215 } 216 217 /***************************** 218 * Run the linker. Return status of execution. 219 */ 220 public int runLINK() 221 { 222 const phobosLibname = finalDefaultlibname(); 223 224 void setExeFile() 225 { 226 /* Generate exe file name from first obj name. 227 * No need to add it to cmdbuf because the linker will default to it. 228 */ 229 const char[] n = FileName.name(global.params.objfiles[0].toDString); 230 global.params.exefile = FileName.forceExt(n, "exe"); 231 } 232 233 const(char)[] getMapFilename() 234 { 235 const(char)[] fn = FileName.forceExt(global.params.exefile, map_ext); 236 const(char)[] path = FileName.path(global.params.exefile); 237 return path.length ? fn : FileName.combine(global.params.objdir, fn); 238 } 239 240 version (Windows) 241 { 242 if (phobosLibname) 243 global.params.libfiles.push(phobosLibname.xarraydup.ptr); 244 245 if (target.objectFormat() == Target.ObjectFormat.coff) 246 { 247 OutBuffer cmdbuf; 248 cmdbuf.writestring("/NOLOGO"); 249 for (size_t i = 0; i < global.params.objfiles.length; i++) 250 { 251 cmdbuf.writeByte(' '); 252 const(char)* p = global.params.objfiles[i]; 253 writeFilename(&cmdbuf, p); 254 } 255 if (global.params.resfile) 256 { 257 cmdbuf.writeByte(' '); 258 writeFilename(&cmdbuf, global.params.resfile); 259 } 260 cmdbuf.writeByte(' '); 261 if (global.params.exefile) 262 { 263 cmdbuf.writestring("/OUT:"); 264 writeFilename(&cmdbuf, global.params.exefile); 265 } 266 else 267 { 268 setExeFile(); 269 } 270 // Make sure path to exe file exists 271 if (!ensurePathToNameExists(Loc.initial, global.params.exefile)) 272 fatal(); 273 cmdbuf.writeByte(' '); 274 if (global.params.mapfile) 275 { 276 cmdbuf.writestring("/MAP:"); 277 writeFilename(&cmdbuf, global.params.mapfile); 278 } 279 else if (driverParams.map) 280 { 281 cmdbuf.writestring("/MAP:"); 282 writeFilename(&cmdbuf, getMapFilename()); 283 } 284 for (size_t i = 0; i < global.params.libfiles.length; i++) 285 { 286 cmdbuf.writeByte(' '); 287 cmdbuf.writestring("/DEFAULTLIB:"); 288 writeFilename(&cmdbuf, global.params.libfiles[i]); 289 } 290 if (global.params.deffile) 291 { 292 cmdbuf.writeByte(' '); 293 cmdbuf.writestring("/DEF:"); 294 writeFilename(&cmdbuf, global.params.deffile); 295 } 296 if (driverParams.symdebug) 297 { 298 cmdbuf.writeByte(' '); 299 cmdbuf.writestring("/DEBUG"); 300 // in release mode we need to reactivate /OPT:REF after /DEBUG 301 if (global.params.release) 302 cmdbuf.writestring(" /OPT:REF"); 303 } 304 if (driverParams.dll) 305 { 306 cmdbuf.writeByte(' '); 307 cmdbuf.writestring("/DLL"); 308 } 309 for (size_t i = 0; i < global.params.linkswitches.length; i++) 310 { 311 cmdbuf.writeByte(' '); 312 cmdbuf.writeQuotedArgIfNeeded(global.params.linkswitches[i]); 313 } 314 315 VSOptions vsopt; 316 // if a runtime library (msvcrtNNN.lib) from the mingw folder is selected explicitly, do not detect VS and use lld 317 if (driverParams.mscrtlib.length <= 6 || 318 driverParams.mscrtlib[0..6] != "msvcrt" || !isdigit(driverParams.mscrtlib[6])) 319 vsopt.initialize(); 320 321 const(char)* linkcmd = getenv(target.isX86_64 ? "LINKCMD64" : "LINKCMD"); 322 if (!linkcmd) 323 linkcmd = getenv("LINKCMD"); // backward compatible 324 if (!linkcmd) 325 linkcmd = vsopt.linkerPath(target.isX86_64); 326 327 if (!target.isX86_64 && FileName.equals(FileName.name(linkcmd), "lld-link.exe")) 328 { 329 // object files not SAFESEH compliant, but LLD is more picky than MS link 330 cmdbuf.writestring(" /SAFESEH:NO"); 331 // if we are using LLD as a fallback, don't link to any VS libs even if 332 // we detected a VS installation and they are present 333 vsopt.uninitialize(); 334 } 335 336 if (const(char)* lflags = vsopt.linkOptions(target.isX86_64)) 337 { 338 cmdbuf.writeByte(' '); 339 cmdbuf.writestring(lflags); 340 } 341 342 cmdbuf.writeByte(0); // null terminate the buffer 343 char[] p = cmdbuf.extractSlice()[0 .. $-1]; 344 const(char)[] lnkfilename; 345 if (p.length > 7000) 346 { 347 lnkfilename = FileName.forceExt(global.params.exefile, "lnk"); 348 if (!writeFile(Loc.initial, lnkfilename, p)) 349 fatal(); 350 if (lnkfilename.length < p.length) 351 { 352 p[0] = '@'; 353 p[1 .. lnkfilename.length +1] = lnkfilename; 354 p[lnkfilename.length +1] = 0; 355 } 356 } 357 358 const int status = executecmd(linkcmd, p.ptr); 359 if (lnkfilename) 360 { 361 lnkfilename.toCStringThen!(lf => remove(lf.ptr)); 362 FileName.free(lnkfilename.ptr); 363 } 364 return status; 365 } 366 else if (target.objectFormat() == Target.ObjectFormat.omf) 367 { 368 OutBuffer cmdbuf; 369 global.params.libfiles.push("user32"); 370 global.params.libfiles.push("kernel32"); 371 for (size_t i = 0; i < global.params.objfiles.length; i++) 372 { 373 if (i) 374 cmdbuf.writeByte('+'); 375 const(char)[] p = global.params.objfiles[i].toDString(); 376 const(char)[] basename = FileName.removeExt(FileName.name(p)); 377 const(char)[] ext = FileName.ext(p); 378 if (ext.length && !strchr(basename.ptr, '.')) 379 { 380 // Write name sans extension (but not if a double extension) 381 writeFilename(&cmdbuf, p[0 .. $ - ext.length - 1]); 382 } 383 else 384 writeFilename(&cmdbuf, p); 385 FileName.free(basename.ptr); 386 } 387 cmdbuf.writeByte(','); 388 if (global.params.exefile) 389 writeFilename(&cmdbuf, global.params.exefile); 390 else 391 { 392 setExeFile(); 393 } 394 // Make sure path to exe file exists 395 if (!ensurePathToNameExists(Loc.initial, global.params.exefile)) 396 fatal(); 397 cmdbuf.writeByte(','); 398 if (global.params.mapfile) 399 writeFilename(&cmdbuf, global.params.mapfile); 400 else if (driverParams.map) 401 { 402 writeFilename(&cmdbuf, getMapFilename()); 403 } 404 else 405 cmdbuf.writestring("nul"); 406 cmdbuf.writeByte(','); 407 for (size_t i = 0; i < global.params.libfiles.length; i++) 408 { 409 if (i) 410 cmdbuf.writeByte('+'); 411 writeFilename(&cmdbuf, global.params.libfiles[i]); 412 } 413 if (global.params.deffile) 414 { 415 cmdbuf.writeByte(','); 416 writeFilename(&cmdbuf, global.params.deffile); 417 } 418 /* Eliminate unnecessary trailing commas */ 419 while (1) 420 { 421 const size_t i = cmdbuf.length; 422 if (!i || cmdbuf[i - 1] != ',') 423 break; 424 cmdbuf.setsize(cmdbuf.length - 1); 425 } 426 if (global.params.resfile) 427 { 428 cmdbuf.writestring("/RC:"); 429 writeFilename(&cmdbuf, global.params.resfile); 430 } 431 if (driverParams.map || global.params.mapfile) 432 cmdbuf.writestring("/m"); 433 version (none) 434 { 435 if (debuginfo) 436 cmdbuf.writestring("/li"); 437 if (codeview) 438 { 439 cmdbuf.writestring("/co"); 440 if (codeview3) 441 cmdbuf.writestring(":3"); 442 } 443 } 444 else 445 { 446 if (driverParams.symdebug) 447 cmdbuf.writestring("/co"); 448 } 449 cmdbuf.writestring("/noi"); 450 for (size_t i = 0; i < global.params.linkswitches.length; i++) 451 { 452 cmdbuf.writestring(global.params.linkswitches[i]); 453 } 454 cmdbuf.writeByte(';'); 455 cmdbuf.writeByte(0); //null terminate the buffer 456 char[] p = cmdbuf.extractSlice()[0 .. $-1]; 457 const(char)[] lnkfilename; 458 if (p.length > 7000) 459 { 460 lnkfilename = FileName.forceExt(global.params.exefile, "lnk"); 461 if (!writeFile(Loc.initial, lnkfilename, p)) 462 fatal(); 463 if (lnkfilename.length < p.length) 464 { 465 p[0] = '@'; 466 p[1 .. lnkfilename.length +1] = lnkfilename; 467 p[lnkfilename.length +1] = 0; 468 } 469 } 470 const(char)* linkcmd = getenv("LINKCMD"); 471 if (!linkcmd) 472 linkcmd = "optlink"; 473 const int status = executecmd(linkcmd, p.ptr); 474 if (lnkfilename) 475 { 476 lnkfilename.toCStringThen!(lf => remove(lf.ptr)); 477 FileName.free(lnkfilename.ptr); 478 } 479 return status; 480 } 481 else 482 { 483 assert(0); 484 } 485 } 486 else version (Posix) 487 { 488 pid_t childpid; 489 int status; 490 // Build argv[] 491 Strings argv; 492 const(char)* cc = getenv("CC"); 493 if (!cc) 494 { 495 argv.push("cc"); 496 } 497 else 498 { 499 // Split CC command to support link driver arguments such as -fpie or -flto. 500 char* arg = cast(char*)Mem.check(strdup(cc)); 501 const(char)* tok = strtok(arg, " "); 502 while (tok) 503 { 504 argv.push(mem.xstrdup(tok)); 505 tok = strtok(null, " "); 506 } 507 free(arg); 508 } 509 argv.append(&global.params.objfiles); 510 version (OSX) 511 { 512 // If we are on Mac OS X and linking a dynamic library, 513 // add the "-dynamiclib" flag 514 if (driverParams.dll) 515 argv.push("-dynamiclib"); 516 } 517 else version (Posix) 518 { 519 if (driverParams.dll) 520 argv.push("-shared"); 521 } 522 // None of that a.out stuff. Use explicit exe file name, or 523 // generate one from name of first source file. 524 argv.push("-o"); 525 if (global.params.exefile) 526 { 527 argv.push(global.params.exefile.xarraydup.ptr); 528 } 529 else if (global.params.run) 530 { 531 version (all) 532 { 533 char[L_tmpnam + 14 + 1] name; 534 strcpy(name.ptr, P_tmpdir); 535 strcat(name.ptr, "/dmd_runXXXXXX"); 536 int fd = mkstemp(name.ptr); 537 if (fd == -1) 538 { 539 error(Loc.initial, "error creating temporary file"); 540 return 1; 541 } 542 else 543 close(fd); 544 global.params.exefile = name.arraydup; 545 argv.push(global.params.exefile.xarraydup.ptr); 546 } 547 else 548 { 549 /* The use of tmpnam raises the issue of "is this a security hole"? 550 * The hole is that after tmpnam and before the file is opened, 551 * the attacker modifies the file system to get control of the 552 * file with that name. I do not know if this is an issue in 553 * this context. 554 * We cannot just replace it with mkstemp, because this name is 555 * passed to the linker that actually opens the file and writes to it. 556 */ 557 char[L_tmpnam + 1] s; 558 char* n = tmpnam(s.ptr); 559 global.params.exefile = mem.xstrdup(n); 560 argv.push(global.params.exefile); 561 } 562 } 563 else 564 { 565 // Generate exe file name from first obj name 566 const(char)[] n = global.params.objfiles[0].toDString(); 567 const(char)[] ex; 568 n = FileName.name(n); 569 if (const e = FileName.ext(n)) 570 { 571 if (driverParams.dll) 572 ex = FileName.forceExt(ex, target.dll_ext); 573 else 574 ex = FileName.removeExt(n); 575 } 576 else 577 ex = "a.out"; // no extension, so give up 578 argv.push(ex.ptr); 579 global.params.exefile = ex; 580 } 581 // Make sure path to exe file exists 582 if (!ensurePathToNameExists(Loc.initial, global.params.exefile)) 583 fatal(); 584 if (driverParams.symdebug) 585 argv.push("-g"); 586 if (target.isX86_64) 587 argv.push("-m64"); 588 else 589 argv.push("-m32"); 590 version (OSX) 591 { 592 /* Without this switch, ld generates messages of the form: 593 * ld: warning: could not create compact unwind for __Dmain: offset of saved registers too far to encode 594 * meaning they are further than 255 bytes from the frame register. 595 * ld reverts to the old method instead. 596 * See: https://ghc.haskell.org/trac/ghc/ticket/5019 597 * which gives this tidbit: 598 * "When a C++ (or x86_64 Objective-C) exception is thrown, the runtime must unwind the 599 * stack looking for some function to catch the exception. Traditionally, the unwind 600 * information is stored in the __TEXT/__eh_frame section of each executable as Dwarf 601 * CFI (call frame information). Beginning in Mac OS X 10.6, the unwind information is 602 * also encoded in the __TEXT/__unwind_info section using a two-level lookup table of 603 * compact unwind encodings. 604 * The unwinddump tool displays the content of the __TEXT/__unwind_info section." 605 * 606 * A better fix would be to save the registers next to the frame pointer. 607 */ 608 argv.push("-Xlinker"); 609 argv.push("-no_compact_unwind"); 610 } 611 if (driverParams.map || global.params.mapfile.length) 612 { 613 argv.push("-Xlinker"); 614 version (OSX) 615 { 616 argv.push("-map"); 617 } 618 else 619 { 620 argv.push("-Map"); 621 } 622 if (!global.params.mapfile.length) 623 { 624 const(char)[] fn = FileName.forceExt(global.params.exefile, map_ext); 625 const(char)[] path = FileName.path(global.params.exefile); 626 global.params.mapfile = path.length ? fn : FileName.combine(global.params.objdir, fn); 627 } 628 argv.push("-Xlinker"); 629 argv.push(global.params.mapfile.xarraydup.ptr); 630 } 631 if (0 && global.params.exefile) 632 { 633 /* This switch enables what is known as 'smart linking' 634 * in the Windows world, where unreferenced sections 635 * are removed from the executable. It eliminates unreferenced 636 * functions, essentially making a 'library' out of a module. 637 * Although it is documented to work with ld version 2.13, 638 * in practice it does not, but just seems to be ignored. 639 * Thomas Kuehne has verified that it works with ld 2.16.1. 640 * BUG: disabled because it causes exception handling to fail 641 * because EH sections are "unreferenced" and elided 642 */ 643 argv.push("-Xlinker"); 644 argv.push("--gc-sections"); 645 } 646 647 // return true if flagp should be ordered in with the library flags 648 static bool flagIsLibraryRelated(const char* p) 649 { 650 const flag = p.toDString(); 651 652 return startsWith(p, "-l") || startsWith(p, "-L") 653 || flag == "-(" || flag == "-)" 654 || flag == "--start-group" || flag == "--end-group" 655 || FileName.equalsExt(p, "a") 656 ; 657 } 658 659 /* Add libraries. The order of libraries passed is: 660 * 1. link switches without a -L prefix, 661 e.g. --whole-archive "lib.a" --no-whole-archive (global.params.linkswitches) 662 * 2. static libraries ending with *.a (global.params.libfiles) 663 * 3. link switches with a -L prefix (global.params.linkswitches) 664 * 4. libraries specified by pragma(lib), which were appended 665 * to global.params.libfiles. These are prefixed with "-l" 666 * 5. dynamic libraries passed to the command line (global.params.dllfiles) 667 * 6. standard libraries. 668 */ 669 670 // STEP 1 671 foreach (pi, p; global.params.linkswitches) 672 { 673 if (p && p[0] && !flagIsLibraryRelated(p)) 674 { 675 if (!global.params.linkswitchIsForCC[pi]) 676 argv.push("-Xlinker"); 677 argv.push(p); 678 } 679 } 680 681 // STEP 2 682 foreach (p; global.params.libfiles) 683 { 684 if (FileName.equalsExt(p, "a")) 685 argv.push(p); 686 } 687 688 // STEP 3 689 foreach (pi, p; global.params.linkswitches) 690 { 691 if (p && p[0] && flagIsLibraryRelated(p)) 692 { 693 if (!startsWith(p, "-l") && !startsWith(p, "-L") && !global.params.linkswitchIsForCC[pi]) 694 { 695 // Don't need -Xlinker if switch starts with -l or -L. 696 // Eliding -Xlinker is significant for -L since it allows our paths 697 // to take precedence over gcc defaults. 698 // All other link switches were already added in step 1. 699 argv.push("-Xlinker"); 700 } 701 argv.push(p); 702 } 703 } 704 705 // STEP 4 706 foreach (p; global.params.libfiles) 707 { 708 if (!FileName.equalsExt(p, "a")) 709 { 710 const plen = strlen(p); 711 char* s = cast(char*)mem.xmalloc(plen + 3); 712 s[0] = '-'; 713 s[1] = 'l'; 714 memcpy(s + 2, p, plen + 1); 715 argv.push(s); 716 } 717 } 718 719 // STEP 5 720 foreach (p; global.params.dllfiles) 721 { 722 argv.push(p); 723 } 724 725 // STEP 6 726 /* D runtime libraries must go after user specified libraries 727 * passed with -l. 728 */ 729 const libname = phobosLibname; 730 if (libname.length) 731 { 732 const bufsize = 2 + libname.length + 1; 733 auto buf = (cast(char*) malloc(bufsize))[0 .. bufsize]; 734 Mem.check(buf.ptr); 735 buf[0 .. 2] = "-l"; 736 737 char* getbuf(const(char)[] suffix) 738 { 739 buf[2 .. 2 + suffix.length] = suffix[]; 740 buf[2 + suffix.length] = 0; 741 return buf.ptr; 742 } 743 744 if (libname.length > 3 + 2 && libname[0 .. 3] == "lib") 745 { 746 if (libname[$-2 .. $] == ".a") 747 { 748 argv.push("-Xlinker"); 749 argv.push("-Bstatic"); 750 argv.push(getbuf(libname[3 .. $-2])); 751 argv.push("-Xlinker"); 752 argv.push("-Bdynamic"); 753 } 754 else if (libname[$-3 .. $] == ".so") 755 argv.push(getbuf(libname[3 .. $-3])); 756 else 757 argv.push(getbuf(libname)); 758 } 759 else 760 { 761 argv.push(getbuf(libname)); 762 } 763 } 764 //argv.push("-ldruntime"); 765 argv.push("-lpthread"); 766 argv.push("-lm"); 767 version (linux) 768 { 769 // Changes in ld for Ubuntu 11.10 require this to appear after phobos2 770 argv.push("-lrt"); 771 // Link against libdl for phobos usage of dlopen 772 argv.push("-ldl"); 773 } 774 else version (OpenBSD) 775 { 776 // Link against -lc++abi for Unwind symbols 777 argv.push("-lc++abi"); 778 // Link against -lexecinfo for backtrace symbols 779 argv.push("-lexecinfo"); 780 } 781 if (global.params.v.verbose) 782 { 783 // Print it 784 OutBuffer buf; 785 for (size_t i = 0; i < argv.length; i++) 786 { 787 buf.writestring(argv[i]); 788 buf.writeByte(' '); 789 } 790 message(buf.peekChars()); 791 } 792 argv.push(null); 793 // set up pipes 794 int[2] fds; 795 if (pipe(fds.ptr) == -1) 796 { 797 perror("unable to create pipe to linker"); 798 return -1; 799 } 800 // vfork instead of fork to avoid https://issues.dlang.org/show_bug.cgi?id=21089 801 childpid = vfork(); 802 if (childpid == 0) 803 { 804 // pipe linker stderr to fds[0] 805 dup2(fds[1], STDERR_FILENO); 806 close(fds[0]); 807 execvp(argv[0], argv.tdata()); 808 perror(argv[0]); // failed to execute 809 _exit(-1); 810 } 811 else if (childpid == -1) 812 { 813 perror("unable to fork"); 814 return -1; 815 } 816 close(fds[1]); 817 const(int) nme = findNoMainError(fds[0]); 818 waitpid(childpid, &status, 0); 819 if (WIFEXITED(status)) 820 { 821 status = WEXITSTATUS(status); 822 if (status) 823 { 824 if (nme == -1) 825 { 826 perror("error with the linker pipe"); 827 return -1; 828 } 829 else 830 { 831 error(Loc.initial, "linker exited with status %d", status); 832 if (nme == 1) 833 error(Loc.initial, "no main function specified"); 834 } 835 } 836 } 837 else if (WIFSIGNALED(status)) 838 { 839 error(Loc.initial, "linker killed by signal %d", WTERMSIG(status)); 840 status = 1; 841 } 842 return status; 843 } 844 else 845 { 846 error(Loc.initial, "linking is not yet supported for this version of DMD."); 847 return -1; 848 } 849 } 850 851 852 /****************************** 853 * Execute a rule. Return the status. 854 * cmd program to run 855 * args arguments to cmd, as a string 856 */ 857 version (Windows) 858 { 859 private int executecmd(const(char)* cmd, const(char)* args) 860 { 861 int status; 862 size_t len; 863 if (global.params.v.verbose) 864 message("%s %s", cmd, args); 865 if (target.objectFormat() == Target.ObjectFormat.omf) 866 { 867 if ((len = strlen(args)) > 255) 868 { 869 status = putenvRestorable("_CMDLINE", args[0 .. len]); 870 if (status == 0) 871 args = "@_CMDLINE"; 872 else 873 error(Loc.initial, "command line length of %llu is too long", cast(ulong) len); 874 } 875 } 876 // Normalize executable path separators 877 // https://issues.dlang.org/show_bug.cgi?id=9330 878 cmd = toWinPath(cmd); 879 version (CRuntime_Microsoft) 880 { 881 // Open scope so dmd doesn't complain about alloca + exception handling 882 { 883 // Use process spawning through the WinAPI to avoid issues with executearg0 and spawnlp 884 OutBuffer cmdbuf; 885 cmdbuf.writestring("\""); 886 cmdbuf.writestring(cmd); 887 cmdbuf.writestring("\" "); 888 cmdbuf.writestring(args); 889 890 STARTUPINFOA startInf; 891 startInf.dwFlags = STARTF_USESTDHANDLES; 892 startInf.hStdInput = GetStdHandle(STD_INPUT_HANDLE); 893 startInf.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 894 startInf.hStdError = GetStdHandle(STD_ERROR_HANDLE); 895 PROCESS_INFORMATION procInf; 896 897 BOOL b = CreateProcessA(null, cmdbuf.peekChars(), null, null, 1, NORMAL_PRIORITY_CLASS, null, null, &startInf, &procInf); 898 if (b) 899 { 900 WaitForSingleObject(procInf.hProcess, INFINITE); 901 DWORD returnCode; 902 GetExitCodeProcess(procInf.hProcess, &returnCode); 903 status = returnCode; 904 CloseHandle(procInf.hProcess); 905 } 906 else 907 { 908 status = -1; 909 } 910 } 911 } 912 else 913 { 914 status = executearg0(cmd, args); 915 if (status == -1) 916 { 917 status = spawnlp(0, cmd, cmd, args, null); 918 } 919 } 920 if (status) 921 { 922 if (status == -1) 923 error(Loc.initial, "can't run '%s', check PATH", cmd); 924 else 925 error(Loc.initial, "linker exited with status %d", status); 926 } 927 return status; 928 } 929 } 930 931 /************************************** 932 * Attempt to find command to execute by first looking in the directory 933 * where DMD was run from. 934 * Returns: 935 * -1 did not find command there 936 * !=-1 exit status from command 937 */ 938 version (Windows) 939 { 940 private int executearg0(const(char)* cmd, const(char)* args) 941 { 942 const argv0 = global.params.argv0; 943 //printf("argv0='%s', cmd='%s', args='%s'\n",argv0,cmd,args); 944 // If cmd is fully qualified, we don't do this 945 if (FileName.absolute(cmd.toDString())) 946 return -1; 947 const file = FileName.replaceName(argv0, cmd.toDString); 948 //printf("spawning '%s'\n",file); 949 // spawnlp returns intptr_t in some systems, not int 950 return spawnl(0, file.ptr, file.ptr, args, null); 951 } 952 } 953 954 /*************************************** 955 * Run the compiled program. 956 * Return exit status. 957 */ 958 public int runProgram() 959 { 960 //printf("runProgram()\n"); 961 if (global.params.v.verbose) 962 { 963 OutBuffer buf; 964 buf.writestring(global.params.exefile); 965 for (size_t i = 0; i < global.params.runargs.length; ++i) 966 { 967 buf.writeByte(' '); 968 buf.writestring(global.params.runargs[i]); 969 } 970 message(buf.peekChars()); 971 } 972 // Build argv[] 973 Strings argv; 974 argv.push(global.params.exefile.xarraydup.ptr); 975 for (size_t i = 0; i < global.params.runargs.length; ++i) 976 { 977 const(char)* a = global.params.runargs[i]; 978 version (Windows) 979 { 980 // BUG: what about " appearing in the string? 981 if (strchr(a, ' ')) 982 { 983 const blen = 3 + strlen(a); 984 char* b = cast(char*)mem.xmalloc(blen); 985 snprintf(b, blen, "\"%s\"", a); 986 a = b; 987 } 988 } 989 argv.push(a); 990 } 991 argv.push(null); 992 restoreEnvVars(); 993 version (Windows) 994 { 995 const(char)[] ex = FileName.name(global.params.exefile); 996 if (ex == global.params.exefile) 997 ex = FileName.combine(".", ex); 998 else 999 ex = global.params.exefile; 1000 // spawnlp returns intptr_t in some systems, not int 1001 return spawnv(0, ex.xarraydup.ptr, argv.tdata()); 1002 } 1003 else version (Posix) 1004 { 1005 pid_t childpid; 1006 int status; 1007 childpid = fork(); 1008 if (childpid == 0) 1009 { 1010 const(char)[] fn = argv[0].toDString(); 1011 // Make it "./fn" if needed 1012 if (!FileName.absolute(fn)) 1013 fn = FileName.combine(".", fn); 1014 fn.toCStringThen!((fnp) { 1015 execv(fnp.ptr, argv.tdata()); 1016 // If execv returns, it failed to execute 1017 perror(fnp.ptr); 1018 }); 1019 return -1; 1020 } 1021 waitpid(childpid, &status, 0); 1022 if (WIFEXITED(status)) 1023 { 1024 status = WEXITSTATUS(status); 1025 //printf("--- errorlevel %d\n", status); 1026 } 1027 else if (WIFSIGNALED(status)) 1028 { 1029 error(Loc.initial, "program killed by signal %d", WTERMSIG(status)); 1030 status = 1; 1031 } 1032 return status; 1033 } 1034 else 1035 { 1036 assert(0); 1037 } 1038 } 1039 1040 /*************************************** 1041 * Run the C preprocessor. 1042 * Params: 1043 * cpp = name of C preprocessor program 1044 * filename = C source file name 1045 * importc_h = filename of importc.h 1046 * cppswitches = array of switches to pass to C preprocessor 1047 * output = preprocessed output file name 1048 * defines = buffer to append any `#define` and `#undef` lines encountered to 1049 * Returns: 1050 * exit status. 1051 */ 1052 public int runPreprocessor(const(char)[] cpp, const(char)[] filename, const(char)* importc_h, ref Array!(const(char)*) cppswitches, 1053 const(char)[] output, OutBuffer* defines) 1054 { 1055 //printf("runPreprocessor() cpp: %.*s filename: %.*s\n", cast(int)cpp.length, cpp.ptr, cast(int)filename.length, filename.ptr); 1056 version (Windows) 1057 { 1058 // Build argv[] 1059 Strings argv; 1060 if (target.objectFormat() == Target.ObjectFormat.coff) 1061 { 1062 static if (1) 1063 { 1064 /* Run command, intercept stdout, remove first line that CL insists on emitting 1065 */ 1066 OutBuffer buf; 1067 buf.writestring(cpp); 1068 buf.printf(" /P /Zc:preprocessor /PD /nologo %.*s /FI%s /Fi%.*s", 1069 cast(int)filename.length, filename.ptr, importc_h, cast(int)output.length, output.ptr); 1070 1071 /* Append preprocessor switches to command line 1072 */ 1073 foreach (a; cppswitches) 1074 { 1075 if (a && a[0]) 1076 { 1077 buf.writeByte(' '); 1078 buf.writestring(a); 1079 } 1080 } 1081 1082 if (global.params.v.verbose) 1083 message(buf.peekChars()); 1084 1085 ubyte[2048] buffer = void; 1086 1087 OutBuffer linebuf; // each line from stdout 1088 bool print = false; // print line collected from stdout 1089 1090 /* Collect text captured from stdout to linebuf[]. 1091 * Then decide to print or discard the contents. 1092 * Discarding lines that consist only of a filename is necessary to pass 1093 * the D test suite which diffs the output. CL's emission of filenames cannot 1094 * be turned off. 1095 */ 1096 void sink(ubyte[] data) 1097 { 1098 foreach (c; data) 1099 { 1100 switch (c) 1101 { 1102 case '\r': 1103 break; 1104 1105 case '\n': 1106 if (print) 1107 printf("%s\n", linebuf.peekChars()); 1108 1109 // set up for next line 1110 linebuf.setsize(0); 1111 print = false; 1112 break; 1113 1114 case '\t': 1115 case ';': 1116 case '(': 1117 case '\'': 1118 case '"': // various non-filename characters 1119 print = true; // mean it's not a filename 1120 goto default; 1121 1122 default: 1123 linebuf.writeByte(c); 1124 break; 1125 } 1126 } 1127 } 1128 1129 // Convert command to wchar 1130 wchar[1024] scratch = void; 1131 auto smbuf = SmallBuffer!wchar(scratch.length, scratch[]); 1132 auto szCommand = toWStringz(buf.peekChars()[0 .. buf.length], smbuf); 1133 1134 int exitCode = runProcessCollectStdout(szCommand.ptr, buffer[], &sink); 1135 1136 if (linebuf.length && print) // anything leftover from stdout collection 1137 printf("%s\n", defines.peekChars()); 1138 1139 return exitCode; 1140 } 1141 else 1142 { 1143 argv.push("cl".ptr); // null terminated copy 1144 argv.push("/P".ptr); // preprocess only 1145 argv.push("/nologo".ptr); // don't print logo 1146 argv.push(filename.xarraydup.ptr); // and the input 1147 1148 OutBuffer buf; 1149 buf.writestring("/Fi"); // https://docs.microsoft.com/en-us/cpp/build/reference/fi-preprocess-output-file-name?view=msvc-170 1150 buf.writeStringz(output); 1151 argv.push(buf.extractData()); // output file 1152 1153 argv.push(null); // argv[] always ends with a null 1154 // spawnlp returns intptr_t in some systems, not int 1155 return spawnvp(_P_WAIT, "cl".ptr, argv.tdata()); 1156 } 1157 } 1158 else if (target.objectFormat() == Target.ObjectFormat.omf) 1159 { 1160 /* Digital Mars Win32 target 1161 * sppn filename -oooutput 1162 * https://www.digitalmars.com/ctg/sc.html 1163 */ 1164 1165 static if (1) 1166 { 1167 /* Run command 1168 */ 1169 OutBuffer buf; 1170 buf.writestring(cpp); 1171 buf.printf(" %.*s -HI%s -ED -o%.*s", 1172 cast(int)filename.length, filename.ptr, importc_h, cast(int)output.length, output.ptr); 1173 1174 /* Append preprocessor switches to command line 1175 */ 1176 foreach (a; cppswitches) 1177 { 1178 if (a && a[0]) 1179 { 1180 buf.writeByte(' '); 1181 buf.writestring(a); 1182 } 1183 } 1184 1185 if (global.params.v.verbose) 1186 message(buf.peekChars()); 1187 1188 ubyte[2048] buffer = void; 1189 1190 /* Write lines captured from stdout to either defines[] or stdout 1191 */ 1192 enum S 1193 { 1194 start, // start of line 1195 hash, // write to defines[] 1196 other, // write to stdout 1197 } 1198 1199 S state = S.start; 1200 1201 void sinkomf(ubyte[] data) 1202 { 1203 foreach (c; data) 1204 { 1205 final switch (state) 1206 { 1207 case S.start: 1208 if (c == '#') 1209 { 1210 defines.writeByte(c); 1211 state = S.hash; 1212 } 1213 else 1214 { 1215 fputc(c, stdout); 1216 state = S.other; 1217 } 1218 break; 1219 1220 case S.hash: 1221 defines.writeByte(c); 1222 if (c == '\n') 1223 state = S.start; 1224 break; 1225 1226 case S.other: 1227 fputc(c, stdout); 1228 if (c == '\n') 1229 state = S.start; 1230 break; 1231 } 1232 } 1233 //printf("%.*s", cast(int)data.length, data.ptr); 1234 } 1235 1236 // Convert command to wchar 1237 wchar[1024] scratch = void; 1238 auto smbuf = SmallBuffer!wchar(scratch.length, scratch[]); 1239 auto szCommand = toWStringz(buf.peekChars()[0 .. buf.length], smbuf); 1240 1241 //printf("szCommand: %ls\n", szCommand.ptr); 1242 int exitCode = runProcessCollectStdout(szCommand.ptr, buffer[], &sinkomf); 1243 printf("\n"); 1244 return exitCode; 1245 } 1246 else 1247 { 1248 auto cmd = cpp.xarraydup.ptr; 1249 argv.push(cmd); // Digita; Mars C preprocessor 1250 argv.push(filename.xarraydup.ptr); // and the input file 1251 1252 OutBuffer buf; 1253 buf.writestring("-o"); // https://www.digitalmars.com/ctg/sc.html#dashofilename 1254 buf.writeString(output); 1255 argv.push(buf.extractData()); // output file 1256 1257 argv.push(null); // argv[] always ends with a null 1258 // spawnlp returns intptr_t in some systems, not int 1259 return spawnvp(_P_WAIT, cmd, argv.tdata()); 1260 } 1261 } 1262 else 1263 { 1264 assert(0); 1265 } 1266 } 1267 else version (Posix) 1268 { 1269 // Build argv[] 1270 Strings argv; 1271 argv.push(cpp.xarraydup.ptr); // null terminated copy 1272 1273 foreach (p; cppswitches) 1274 { 1275 if (p && p[0]) 1276 argv.push(p); 1277 } 1278 1279 // Set memory model 1280 argv.push(target.isX86_64 ? "-m64" : "-m32"); 1281 1282 // merge #define's with output 1283 argv.push("-dD"); // https://gcc.gnu.org/onlinedocs/cpp/Invocation.html#index-dD 1284 1285 // need to redefine some macros in importc.h 1286 argv.push("-Wno-builtin-macro-redefined"); 1287 1288 if (target.os == Target.OS.OSX) 1289 { 1290 argv.push("-fno-blocks"); // disable clang blocks extension 1291 argv.push("-E"); // run preprocessor only for clang 1292 argv.push("-include"); // OSX cpp has switch order dependencies 1293 argv.push(importc_h); 1294 argv.push(filename.xarraydup.ptr); // and the input 1295 argv.push("-o"); // specify output file 1296 } 1297 else 1298 { 1299 argv.push(filename.xarraydup.ptr); // and the input 1300 argv.push("-include"); 1301 argv.push(importc_h); 1302 } 1303 if (target.os == Target.OS.FreeBSD || target.os == Target.OS.OpenBSD) 1304 argv.push("-o"); // specify output file 1305 argv.push(output.xarraydup.ptr); // and the output 1306 argv.push(null); // argv[] always ends with a null 1307 1308 if (global.params.v.verbose) 1309 { 1310 OutBuffer buf; 1311 1312 foreach (i, a; argv[]) 1313 { 1314 if (a) 1315 { 1316 if (i) 1317 buf.writeByte(' '); 1318 buf.writestring(a); 1319 } 1320 } 1321 message(buf.peekChars()); 1322 } 1323 1324 pid_t childpid = fork(); 1325 if (childpid == 0) 1326 { 1327 const(char)[] fn = argv[0].toDString(); 1328 fn.toCStringThen!((fnp) { 1329 execvp(fnp.ptr, argv.tdata()); 1330 // If execv returns, it failed to execute 1331 perror(fnp.ptr); 1332 }); 1333 return -1; 1334 } 1335 int status; 1336 waitpid(childpid, &status, 0); 1337 if (WIFEXITED(status)) 1338 { 1339 status = WEXITSTATUS(status); 1340 //printf("--- errorlevel %d\n", status); 1341 } 1342 else if (WIFSIGNALED(status)) 1343 { 1344 error(Loc.initial, "program killed by signal %d", WTERMSIG(status)); 1345 status = 1; 1346 } 1347 return status; 1348 } 1349 else 1350 { 1351 assert(0); 1352 } 1353 } 1354 1355 /********************************* 1356 * Run a command and intercept its stdout, which is redirected 1357 * to sink(). 1358 * Params: 1359 * szCommand = command to run 1360 * buffer = buffer to collect stdout data to 1361 * sink = stdout data is sent to sink() 1362 * Returns: 1363 * 0 on success 1364 * Reference: 1365 * Based on 1366 * https://github.com/dlang/visuald/blob/master/tools/pipedmd.d#L252 1367 */ 1368 version (Windows) 1369 int runProcessCollectStdout(const(wchar)* szCommand, ubyte[] buffer, void delegate(ubyte[]) sink) 1370 { 1371 import core.sys.windows.windows; 1372 import core.sys.windows.wtypes; 1373 import core.sys.windows.psapi; 1374 1375 //printf("runProcess() command: %ls\n", szCommand); 1376 // Set the bInheritHandle flag so pipe handles are inherited. 1377 SECURITY_ATTRIBUTES saAttr; 1378 saAttr.nLength = SECURITY_ATTRIBUTES.sizeof; 1379 saAttr.bInheritHandle = TRUE; 1380 saAttr.lpSecurityDescriptor = null; 1381 1382 // Create a pipe for the child process's STDOUT. 1383 HANDLE hStdOutRead; 1384 HANDLE hStdOutWrite; 1385 if ( !CreatePipe(&hStdOutRead, &hStdOutWrite, &saAttr, 0) ) 1386 assert(0); 1387 // Ensure the read handle to the pipe for STDOUT is not inherited. 1388 if ( !SetHandleInformation(hStdOutRead, HANDLE_FLAG_INHERIT, 0) ) 1389 assert(0); 1390 1391 // Another pipe 1392 HANDLE hStdInRead; 1393 HANDLE hStdInWrite; 1394 if ( !CreatePipe(&hStdInRead, &hStdInWrite, &saAttr, 0) ) 1395 assert(0); 1396 if ( !SetHandleInformation(hStdInWrite, HANDLE_FLAG_INHERIT, 0) ) 1397 assert(0); 1398 1399 // Set up members of the PROCESS_INFORMATION structure. 1400 PROCESS_INFORMATION piProcInfo; 1401 memset( &piProcInfo, 0, PROCESS_INFORMATION.sizeof ); 1402 1403 // Set up members of the STARTUPINFO structure. 1404 // This structure specifies the STDIN and STDOUT handles for redirection. 1405 STARTUPINFOW siStartInfo; 1406 memset( &siStartInfo, 0, STARTUPINFOW.sizeof ); 1407 siStartInfo.cb = STARTUPINFOW.sizeof; 1408 siStartInfo.hStdError = hStdOutWrite; 1409 siStartInfo.hStdOutput = hStdOutWrite; 1410 siStartInfo.hStdInput = hStdInRead; 1411 siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 1412 1413 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw 1414 BOOL bSuccess = CreateProcessW(null, 1415 cast(wchar*)szCommand, // command line 1416 null, // process security attributes 1417 null, // primary thread security attributes 1418 TRUE, // handles are inherited 1419 CREATE_SUSPENDED, // creation flags 1420 null, // use parent's environment 1421 null, // use parent's current directory 1422 &siStartInfo, // STARTUPINFO pointer 1423 &piProcInfo); // receives PROCESS_INFORMATION 1424 1425 if (!bSuccess) 1426 { 1427 printf("failed launching %ls\n", cast(wchar*)szCommand); // https://issues.dlang.org/show_bug.cgi?id=21958 1428 return 1; 1429 } 1430 1431 ResumeThread(piProcInfo.hThread); 1432 1433 DWORD bytesFilled = 0; 1434 DWORD bytesAvailable = 0; 1435 DWORD bytesRead = 0; 1436 DWORD exitCode = 0; 1437 1438 while (true) 1439 { 1440 DWORD dwlen = cast(DWORD)buffer.length; 1441 bSuccess = PeekNamedPipe(hStdOutRead, buffer.ptr + bytesFilled, dwlen - bytesFilled, &bytesRead, &bytesAvailable, null); 1442 if (bSuccess && bytesRead > 0) 1443 bSuccess = ReadFile(hStdOutRead, buffer.ptr + bytesFilled, dwlen - bytesFilled, &bytesRead, null); 1444 if (bSuccess && bytesRead > 0) 1445 { 1446 sink(buffer[0 .. bytesRead]); 1447 } 1448 1449 bSuccess = GetExitCodeProcess(piProcInfo.hProcess, &exitCode); 1450 if (!bSuccess || exitCode != 259) //259 == STILL_ACTIVE 1451 { 1452 break; 1453 } 1454 Sleep(1); 1455 } 1456 1457 // close the handles to the process 1458 CloseHandle(hStdInWrite); 1459 CloseHandle(hStdOutRead); 1460 CloseHandle(piProcInfo.hProcess); 1461 CloseHandle(piProcInfo.hThread); 1462 1463 return exitCode; 1464 }