• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*--------------------------------------------------------------------*/
3 /*--- Launching valgrind                         launcher-darwin.c ---*/
4 /*--------------------------------------------------------------------*/
5 
6 /*
7    This file is part of Valgrind, a dynamic binary instrumentation
8    framework.
9 
10    Copyright (C) 2000-2011 Julian Seward
11       jseward@acm.org
12 
13    This program is free software; you can redistribute it and/or
14    modify it under the terms of the GNU General Public License as
15    published by the Free Software Foundation; either version 2 of the
16    License, or (at your option) any later version.
17 
18    This program is distributed in the hope that it will be useful, but
19    WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21    General Public License for more details.
22 
23    You should have received a copy of the GNU General Public License
24    along with this program; if not, write to the Free Software
25    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
26    02111-1307, USA.
27 
28    The GNU General Public License is contained in the file COPYING.
29 */
30 
31 /* Note: this is a "normal" program and not part of Valgrind proper,
32    and so it doesn't have to conform to Valgrind's arcane rules on
33    no-glibc-usage etc. */
34 
35 #include <assert.h>
36 #include <ctype.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <libgen.h>
40 #include <stdarg.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <sys/mman.h>
45 #include <sys/param.h>
46 #include <sys/stat.h>
47 #include <sys/user.h>
48 #include <unistd.h>
49 #include <mach-o/fat.h>
50 #include <mach-o/loader.h>
51 
52 #include "pub_core_debuglog.h"
53 #include "pub_core_vki.h"       // Avoids warnings from pub_core_libcfile.h
54 #include "pub_core_libcproc.h"  // For VALGRIND_LIB, VALGRIND_LAUNCHER
55 #include "pub_core_ume.h"
56 
57 static struct {
58    cpu_type_t cputype;
59    const char *apple_name;     // e.g. x86_64
60    const char *valgrind_name;  // e.g. amd64
61 } valid_archs[] = {
62    { CPU_TYPE_X86,       "i386",   "x86" },
63    { CPU_TYPE_X86_64,    "x86_64", "amd64" },
64    { CPU_TYPE_ARM,       "arm",    "arm" },
65    { CPU_TYPE_POWERPC,   "ppc",    "ppc32" },
66    { CPU_TYPE_POWERPC64, "ppc64",  "ppc64" },
67 };
68 static int valid_archs_count = sizeof(valid_archs)/sizeof(valid_archs[0]);
69 
name_for_cputype(cpu_type_t cputype)70 static const char *name_for_cputype(cpu_type_t cputype)
71 {
72    int i;
73    for (i = 0; i < valid_archs_count; i++) {
74       if (valid_archs[i].cputype == cputype) {
75          return valid_archs[i].valgrind_name;
76       }
77    }
78    return NULL;
79 }
80 
81 /* Report fatal errors */
82 __attribute__((noreturn))
barf(const char * format,...)83 static void barf ( const char *format, ... )
84 {
85    va_list vargs;
86 
87    va_start(vargs, format);
88    fprintf(stderr, "valgrind: ");
89    vfprintf(stderr, format, vargs);
90    fprintf(stderr, "\n");
91    va_end(vargs);
92 
93    exit(1);
94    /*NOTREACHED*/
95    assert(0);
96 }
97 
98 /* Search the path for the client program */
find_client(const char * clientname)99 static const char *find_client(const char *clientname)
100 {
101    static char fullname[PATH_MAX];
102    const char *path = getenv("PATH");
103    const char *colon;
104 
105    while (path)
106    {
107       if ((colon = strchr(path, ':')) == NULL)
108       {
109          strcpy(fullname, path);
110          path = NULL;
111       }
112       else
113       {
114          memcpy(fullname, path, colon - path);
115          fullname[colon - path] = '\0';
116          path = colon + 1;
117       }
118 
119       strcat(fullname, "/");
120       strcat(fullname, clientname);
121 
122       if (access(fullname, R_OK|X_OK) == 0)
123          return fullname;
124    }
125 
126    return clientname;
127 }
128 
fat_has_cputype(struct fat_header * fh,cpu_type_t cputype)129 static int fat_has_cputype(struct fat_header *fh, cpu_type_t cputype)
130 {
131    struct fat_arch *fa = (struct fat_arch *)(fh+1);
132    uint32_t nfat_arch = ntohl(fh->nfat_arch);
133    uint32_t i;
134    for (i = 0; i < nfat_arch; i++) {
135       if (ntohl(fa[i].cputype) == cputype) return 1;
136    }
137    return 0;
138 }
139 
140 /* Examine the client and work out which arch it is for */
select_arch(const char * clientname,cpu_type_t default_cputype,const char * default_arch)141 static const char *select_arch(
142       const char *clientname, cpu_type_t default_cputype,
143       const char *default_arch)
144 {
145    uint8_t buf[4096];
146    ssize_t bytes;
147    int fd = open(find_client(clientname), O_RDONLY);
148    if (fd < 0) {
149       barf("%s: %s", clientname, strerror(errno));
150    }
151 
152    bytes = read(fd, buf, sizeof(buf));
153    close(fd);
154    if (bytes != sizeof(buf)) {
155       return NULL;
156    }
157 
158    // If it's thin, return that arch.
159    {
160       struct mach_header *mh = (struct mach_header *)buf;
161       if (mh->magic == MH_MAGIC  ||  mh->magic == MH_MAGIC_64) {
162          return name_for_cputype(mh->cputype);
163       } else if (mh->magic == MH_CIGAM  ||  mh->magic == MH_CIGAM_64) {
164          return name_for_cputype(OSSwapInt32(mh->cputype));
165       }
166    }
167 
168    // If it's fat, look for a good arch.
169    {
170       struct fat_header *fh = (struct fat_header *)buf;
171       if (ntohl(fh->magic) == FAT_MAGIC) {
172          uint32_t nfat_arch = ntohl(fh->nfat_arch);
173          int i;
174          // If only one fat arch, use it.
175          if (nfat_arch == 1) {
176             struct fat_arch *fa = (struct fat_arch *)(fh+1);
177             return name_for_cputype(ntohl(fa->cputype));
178          }
179          // Scan fat headers for default arch.
180          if (fat_has_cputype(fh, default_cputype)) {
181             return default_arch;
182          }
183 
184          // Scan fat headers for any supported arch.
185          for (i = 0; i < valid_archs_count; i++) {
186             if (fat_has_cputype(fh, valid_archs[i].cputype)) {
187                return valid_archs[i].valgrind_name;
188             }
189          }
190       }
191    }
192 
193    return NULL;
194 }
195 
196 
197 /* Where we expect to find all our aux files */
198 static const char *valgrind_lib;
199 
main(int argc,char ** argv,char ** envp)200 int main(int argc, char** argv, char** envp)
201 {
202    int i, j, loglevel;
203    const char *toolname = NULL;
204    const char *clientname = NULL;
205    int clientname_arg = 0;
206    const char *archname = NULL;
207    const char *arch;
208    const char *default_arch;
209    cpu_type_t default_cputype;
210    char *toolfile;
211    char launcher_name[PATH_MAX+1];
212    char* new_line;
213    char* set_cwd;
214    char* cwd;
215    char** new_env;
216    char **new_argv;
217    int new_argc;
218 
219    /* Start the debugging-log system ASAP.  First find out how many
220       "-d"s were specified.  This is a pre-scan of the command line.
221       At the same time, look for the tool name. */
222    loglevel = 0;
223    for (i = 1; i < argc; i++) {
224       if (argv[i][0] != '-') {
225          clientname = argv[i];
226          clientname_arg = i;
227          break;
228       }
229       if (0 == strcmp(argv[i], "--")) {
230          if (i+1 < argc) {
231             clientname = argv[i+1];
232             clientname_arg = i;
233          }
234          break;
235       }
236       if (0 == strcmp(argv[i], "-d"))
237          loglevel++;
238       if (0 == strncmp(argv[i], "--tool=", 7))
239          toolname = argv[i] + 7;
240       if (0 == strncmp(argv[i], "--arch=", 7))
241          archname = argv[i] + 7;
242    }
243 
244    /* ... and start the debug logger.  Now we can safely emit logging
245       messages all through startup. */
246    VG_(debugLog_startup)(loglevel, "Stage 1");
247 
248    /* Make sure we know which tool we're using */
249    if (toolname) {
250       VG_(debugLog)(1, "launcher", "tool '%s' requested\n", toolname);
251    } else {
252       VG_(debugLog)(1, "launcher",
253                        "no tool requested, defaulting to 'memcheck'\n");
254       toolname = "memcheck";
255    }
256 
257    /* Find the real executable if clientname is an app bundle. */
258    if (clientname) {
259       struct stat st;
260       if (0 == stat(clientname, &st)  &&  (st.st_mode & S_IFDIR)) {
261          char *copy = strdup(clientname);
262          char *appname = basename(copy);
263          char *dot = strrchr(appname, '.');
264          if (dot) {
265             char *newclient;
266             *dot = '\0';
267             asprintf(&newclient, "%s/Contents/MacOS/%s", clientname, appname);
268             VG_(debugLog)(1, "launcher", "Using executable in app bundle: %s\n", newclient);
269             clientname = newclient;
270             argv[clientname_arg] = newclient;
271          }
272          free(copy);
273       }
274    }
275 
276    /* Establish the correct VALGRIND_LIB. */
277    {  const char *cp;
278       cp = getenv(VALGRIND_LIB);
279       valgrind_lib = ( cp == NULL ? VG_LIBDIR : cp );
280       VG_(debugLog)(1, "launcher", "valgrind_lib = %s\n", valgrind_lib);
281    }
282 
283    /* Find installed architectures. Use vgpreload_core-<platform>.so as the
284     * indicator of whether the platform is installed. */
285    for (i = 0; i < valid_archs_count; i++) {
286       char *vgpreload_core;
287       asprintf(&vgpreload_core, "%s/vgpreload_core-%s-darwin.so", valgrind_lib, valid_archs[i].valgrind_name);
288       if (access(vgpreload_core, R_OK|X_OK) != 0) {
289          VG_(debugLog)(1, "launcher", "arch '%s' IS NOT installed\n", valid_archs[i].valgrind_name);
290          bzero(&valid_archs[i], sizeof(valid_archs[i]));
291       } else {
292          VG_(debugLog)(1, "launcher", "arch '%s' IS installed\n", valid_archs[i].valgrind_name);
293       }
294       free(vgpreload_core);
295    }
296 
297    /* Find the "default" arch (VGCONF_ARCH_PRI from configure).
298       This is the preferred arch from fat files and the fallback. */
299    default_arch = NULL;
300    default_cputype = 0;
301    for (i = 0; i < valid_archs_count; i++) {
302       if (!valid_archs[i].cputype) continue;
303       if (0 == strncmp(VG_PLATFORM, valid_archs[i].valgrind_name,
304                        strlen(valid_archs[i].valgrind_name)))
305       {
306          default_arch = valid_archs[i].valgrind_name;
307          default_cputype = valid_archs[i].cputype;
308          break;
309       }
310    }
311    if (i == valid_archs_count) barf("Unknown/uninstalled VG_PLATFORM '%s'", VG_PLATFORM);
312    assert(NULL != default_arch);
313    assert(0 != default_cputype);
314 
315    /* Work out what arch to use, or use the default arch if not possible. */
316    if (archname != NULL) {
317       // --arch from command line
318       arch = NULL;
319       for (i = 0; i < valid_archs_count; i++) {
320          if (0 == strcmp(archname, valid_archs[i].apple_name)  ||
321              0 == strcmp(archname, valid_archs[i].valgrind_name))
322          {
323             arch = valid_archs[i].valgrind_name;
324             break;
325          }
326       }
327       if (i == valid_archs_count) barf("Unknown --arch '%s'", archname);
328       assert(NULL != arch);
329       VG_(debugLog)(1, "launcher", "using arch '%s' from --arch=%s\n",
330                     arch, archname);
331    }
332    else if (clientname == NULL) {
333       // no client executable; use default as fallback
334       VG_(debugLog)(1, "launcher",
335                        "no client specified, defaulting arch to '%s'\n",
336                         default_arch);
337       arch = default_arch;
338    }
339    else if ((arch = select_arch(clientname, default_cputype,default_arch))) {
340       // arch from client executable
341       VG_(debugLog)(1, "launcher", "selected arch '%s'\n", arch);
342    }
343    else {
344       // nothing found in client executable; use default as fallback
345       VG_(debugLog)(1, "launcher",
346                        "no arch detected, defaulting arch to '%s'\n",
347                        default_arch);
348       arch = default_arch;
349    }
350 
351    cwd = getcwd(NULL, 0);
352    if (!cwd) barf("Current directory no longer exists.");
353 
354    /* Figure out the name of this executable (viz, the launcher), so
355       we can tell stage2.  stage2 will use the name for recursive
356       invokations of valgrind on child processes. */
357    memset(launcher_name, 0, PATH_MAX+1);
358    for (i = 0; envp[i]; i++)
359        ; /* executable path is after last envp item */
360    /* envp[i] == NULL ; envp[i+1] == executable_path */
361    if (envp[i+1][0] != '/') {
362       strcpy(launcher_name, cwd);
363       strcat(launcher_name, "/");
364    }
365    if (strlen(launcher_name) + strlen(envp[i+1]) > PATH_MAX)
366       barf("launcher path is too long");
367    strcat(launcher_name, envp[i+1]);
368    VG_(debugLog)(1, "launcher", "launcher_name = %s\n", launcher_name);
369 
370    /* tediously augment the env: VALGRIND_LAUNCHER=launcher_name */
371    asprintf(&new_line, VALGRIND_LAUNCHER "=%s", launcher_name);
372 
373    /* tediously augment the env: VALGRIND_STARTUP_PWD_%PID_XYZZY=current_working_dir */
374    asprintf(&set_cwd, "VALGRIND_STARTUP_PWD_%u_XYZZY=%s", getppid(), cwd);
375 
376    // Note that Apple binaries get a secret fourth arg, "char* apple", which
377    // contains the executable path.  Don't forget about it.
378    for (j = 0; envp[j]; j++)
379       ;
380    new_env = malloc((j+4) * sizeof(char*));
381    if (new_env == NULL)
382       barf("malloc of new_env failed.");
383    for (i = 0; i < j; i++)
384       new_env[i] = envp[i];
385    new_env[i++] = new_line;
386    new_env[i++] = set_cwd;
387    new_env[i++] = NULL;
388    new_env[i  ] = envp[i-2]; // the 'apple' arg == the executable_path
389    assert(i == j+3);
390 
391    /* tediously edit env: hide dyld options from valgrind's captive dyld */
392    for (i = 0; envp[i]; i++) {
393       if (0 == strncmp(envp[i], "DYLD_", 5)) {
394          envp[i][0] = 'V';  /* VYLD_; changed back by initimg-darwin */
395       }
396    }
397 
398    /* tediously edit argv: remove --arch= */
399    new_argv = malloc((1+argc) * sizeof(char *));
400    for (i = 0, new_argc = 0; i < argc; i++) {
401       if (0 == strncmp(argv[i], "--arch=", 7)) {
402          // skip
403       } else {
404          new_argv[new_argc++] = argv[i];
405       }
406    }
407    new_argv[new_argc++] = NULL;
408 
409    /* Build the stage2 invokation, and execve it.  Bye! */
410    asprintf(&toolfile, "%s/%s-%s-darwin", valgrind_lib, toolname, arch);
411    if (access(toolfile, R_OK|X_OK) != 0) {
412       barf("tool '%s' not installed (%s) (%s)", toolname, toolfile, strerror(errno));
413    }
414 
415    VG_(debugLog)(1, "launcher", "launching %s\n", toolfile);
416 
417    execve(toolfile, new_argv, new_env);
418 
419    fprintf(stderr, "valgrind: failed to start tool '%s' for platform '%s-darwin': %s\n",
420                    toolname, arch, strerror(errno));
421 
422    exit(1);
423 }
424