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 }