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