| |
| /*--------------------------------------------------------------------*/ |
| /*--- Launching valgrind launcher-darwin.c ---*/ |
| /*--------------------------------------------------------------------*/ |
| |
| /* |
| This file is part of Valgrind, a dynamic binary instrumentation |
| framework. |
| |
| Copyright (C) 2000-2013 Julian Seward |
| jseward@acm.org |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License as |
| published by the Free Software Foundation; either version 2 of the |
| License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307, USA. |
| |
| The GNU General Public License is contained in the file COPYING. |
| */ |
| |
| /* Note: this is a "normal" program and not part of Valgrind proper, |
| and so it doesn't have to conform to Valgrind's arcane rules on |
| no-glibc-usage etc. */ |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <libgen.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| #include <sys/user.h> |
| #include <unistd.h> |
| #include <mach-o/fat.h> |
| #include <mach-o/loader.h> |
| |
| #include "pub_core_debuglog.h" |
| #include "pub_core_vki.h" // Avoids warnings from pub_core_libcfile.h |
| #include "pub_core_libcproc.h" // For VALGRIND_LIB, VALGRIND_LAUNCHER |
| #include "pub_core_ume.h" |
| |
| static struct { |
| cpu_type_t cputype; |
| const char *apple_name; // e.g. x86_64 |
| const char *valgrind_name; // e.g. amd64 |
| } valid_archs[] = { |
| { CPU_TYPE_X86, "i386", "x86" }, |
| { CPU_TYPE_X86_64, "x86_64", "amd64" }, |
| { CPU_TYPE_ARM, "arm", "arm" }, |
| /* Not that it's actually relevant, since we don't support PPC on |
| MacOS X, but .. the Apple PPC descriptors refer to the BE |
| variant, since the LE variant is something that appeared long |
| after Apple dropped PPC. */ |
| { CPU_TYPE_POWERPC, "ppc", "ppc32" }, |
| { CPU_TYPE_POWERPC64, "ppc64", "ppc64be" } |
| }; |
| static int valid_archs_count = sizeof(valid_archs)/sizeof(valid_archs[0]); |
| |
| static const char *name_for_cputype(cpu_type_t cputype) |
| { |
| int i; |
| for (i = 0; i < valid_archs_count; i++) { |
| if (valid_archs[i].cputype == cputype) { |
| return valid_archs[i].valgrind_name; |
| } |
| } |
| return NULL; |
| } |
| |
| /* Report fatal errors */ |
| __attribute__((noreturn)) |
| static void barf ( const char *format, ... ) |
| { |
| va_list vargs; |
| |
| va_start(vargs, format); |
| fprintf(stderr, "valgrind: "); |
| vfprintf(stderr, format, vargs); |
| fprintf(stderr, "\n"); |
| va_end(vargs); |
| |
| exit(1); |
| /*NOTREACHED*/ |
| assert(0); |
| } |
| |
| /* Search the path for the client program */ |
| static const char *find_client(const char *clientname) |
| { |
| static char fullname[PATH_MAX]; |
| const char *path = getenv("PATH"); |
| const char *colon; |
| |
| while (path) |
| { |
| if ((colon = strchr(path, ':')) == NULL) |
| { |
| strcpy(fullname, path); |
| path = NULL; |
| } |
| else |
| { |
| memcpy(fullname, path, colon - path); |
| fullname[colon - path] = '\0'; |
| path = colon + 1; |
| } |
| |
| strcat(fullname, "/"); |
| strcat(fullname, clientname); |
| |
| if (access(fullname, R_OK|X_OK) == 0) |
| return fullname; |
| } |
| |
| return clientname; |
| } |
| |
| static int fat_has_cputype(struct fat_header *fh, cpu_type_t cputype) |
| { |
| struct fat_arch *fa = (struct fat_arch *)(fh+1); |
| uint32_t nfat_arch = ntohl(fh->nfat_arch); |
| uint32_t i; |
| for (i = 0; i < nfat_arch; i++) { |
| if (ntohl(fa[i].cputype) == cputype) return 1; |
| } |
| return 0; |
| } |
| |
| /* Examine the client and work out which arch it is for */ |
| static const char *select_arch( |
| const char *clientname, cpu_type_t default_cputype, |
| const char *default_arch) |
| { |
| uint8_t buf[4096]; |
| ssize_t bytes; |
| int fd = open(find_client(clientname), O_RDONLY); |
| if (fd < 0) { |
| barf("%s: %s", clientname, strerror(errno)); |
| } |
| |
| bytes = read(fd, buf, sizeof(buf)); |
| close(fd); |
| if (bytes != sizeof(buf)) { |
| return NULL; |
| } |
| |
| // If it's thin, return that arch. |
| { |
| struct mach_header *mh = (struct mach_header *)buf; |
| if (mh->magic == MH_MAGIC || mh->magic == MH_MAGIC_64) { |
| return name_for_cputype(mh->cputype); |
| } else if (mh->magic == MH_CIGAM || mh->magic == MH_CIGAM_64) { |
| return name_for_cputype(OSSwapInt32(mh->cputype)); |
| } |
| } |
| |
| // If it's fat, look for a good arch. |
| { |
| struct fat_header *fh = (struct fat_header *)buf; |
| if (ntohl(fh->magic) == FAT_MAGIC) { |
| uint32_t nfat_arch = ntohl(fh->nfat_arch); |
| int i; |
| // If only one fat arch, use it. |
| if (nfat_arch == 1) { |
| struct fat_arch *fa = (struct fat_arch *)(fh+1); |
| return name_for_cputype(ntohl(fa->cputype)); |
| } |
| // Scan fat headers for default arch. |
| if (fat_has_cputype(fh, default_cputype)) { |
| return default_arch; |
| } |
| |
| // Scan fat headers for any supported arch. |
| for (i = 0; i < valid_archs_count; i++) { |
| if (fat_has_cputype(fh, valid_archs[i].cputype)) { |
| return valid_archs[i].valgrind_name; |
| } |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* Where we expect to find all our aux files */ |
| static const char *valgrind_lib; |
| |
| int main(int argc, char** argv, char** envp) |
| { |
| int i, j, loglevel; |
| const char *toolname = NULL; |
| const char *clientname = NULL; |
| int clientname_arg = 0; |
| const char *archname = NULL; |
| const char *arch; |
| const char *default_arch; |
| cpu_type_t default_cputype; |
| char *toolfile; |
| char launcher_name[PATH_MAX+1]; |
| char* new_line; |
| char* set_cwd; |
| char* cwd; |
| char** new_env; |
| char **new_argv; |
| int new_argc; |
| |
| /* Start the debugging-log system ASAP. First find out how many |
| "-d"s were specified. This is a pre-scan of the command line. |
| At the same time, look for the tool name. */ |
| loglevel = 0; |
| for (i = 1; i < argc; i++) { |
| if (argv[i][0] != '-') { |
| clientname = argv[i]; |
| clientname_arg = i; |
| break; |
| } |
| if (0 == strcmp(argv[i], "--")) { |
| if (i+1 < argc) { |
| clientname = argv[i+1]; |
| clientname_arg = i; |
| } |
| break; |
| } |
| if (0 == strcmp(argv[i], "-d")) |
| loglevel++; |
| if (0 == strncmp(argv[i], "--tool=", 7)) |
| toolname = argv[i] + 7; |
| if (0 == strncmp(argv[i], "--arch=", 7)) |
| archname = argv[i] + 7; |
| } |
| |
| /* ... and start the debug logger. Now we can safely emit logging |
| messages all through startup. */ |
| VG_(debugLog_startup)(loglevel, "Stage 1"); |
| |
| /* Make sure we know which tool we're using */ |
| if (toolname) { |
| VG_(debugLog)(1, "launcher", "tool '%s' requested\n", toolname); |
| } else { |
| VG_(debugLog)(1, "launcher", |
| "no tool requested, defaulting to 'memcheck'\n"); |
| toolname = "memcheck"; |
| } |
| |
| /* Find the real executable if clientname is an app bundle. */ |
| if (clientname) { |
| struct stat st; |
| if (0 == stat(clientname, &st) && (st.st_mode & S_IFDIR)) { |
| char *copy = strdup(clientname); |
| char *appname = basename(copy); |
| char *dot = strrchr(appname, '.'); |
| if (dot) { |
| char *newclient; |
| *dot = '\0'; |
| asprintf(&newclient, "%s/Contents/MacOS/%s", clientname, appname); |
| VG_(debugLog)(1, "launcher", "Using executable in app bundle: %s\n", newclient); |
| clientname = newclient; |
| argv[clientname_arg] = newclient; |
| } |
| free(copy); |
| } |
| } |
| |
| /* Establish the correct VALGRIND_LIB. */ |
| { const char *cp; |
| cp = getenv(VALGRIND_LIB); |
| valgrind_lib = ( cp == NULL ? VG_LIBDIR : cp ); |
| VG_(debugLog)(1, "launcher", "valgrind_lib = %s\n", valgrind_lib); |
| } |
| |
| /* Find installed architectures. Use vgpreload_core-<platform>.so as the |
| * indicator of whether the platform is installed. */ |
| for (i = 0; i < valid_archs_count; i++) { |
| char *vgpreload_core; |
| asprintf(&vgpreload_core, "%s/vgpreload_core-%s-darwin.so", valgrind_lib, valid_archs[i].valgrind_name); |
| if (access(vgpreload_core, R_OK|X_OK) != 0) { |
| VG_(debugLog)(1, "launcher", "arch '%s' IS NOT installed\n", valid_archs[i].valgrind_name); |
| memset(&valid_archs[i], 0, sizeof(valid_archs[i])); |
| } else { |
| VG_(debugLog)(1, "launcher", "arch '%s' IS installed\n", valid_archs[i].valgrind_name); |
| } |
| free(vgpreload_core); |
| } |
| |
| /* Find the "default" arch (VGCONF_ARCH_PRI from configure). |
| This is the preferred arch from fat files and the fallback. */ |
| default_arch = NULL; |
| default_cputype = 0; |
| for (i = 0; i < valid_archs_count; i++) { |
| if (!valid_archs[i].cputype) continue; |
| if (0 == strncmp(VG_PLATFORM, valid_archs[i].valgrind_name, |
| strlen(valid_archs[i].valgrind_name))) |
| { |
| default_arch = valid_archs[i].valgrind_name; |
| default_cputype = valid_archs[i].cputype; |
| break; |
| } |
| } |
| if (i == valid_archs_count) barf("Unknown/uninstalled VG_PLATFORM '%s'", VG_PLATFORM); |
| assert(NULL != default_arch); |
| assert(0 != default_cputype); |
| |
| /* Work out what arch to use, or use the default arch if not possible. */ |
| if (archname != NULL) { |
| // --arch from command line |
| arch = NULL; |
| for (i = 0; i < valid_archs_count; i++) { |
| if (0 == strcmp(archname, valid_archs[i].apple_name) || |
| 0 == strcmp(archname, valid_archs[i].valgrind_name)) |
| { |
| arch = valid_archs[i].valgrind_name; |
| break; |
| } |
| } |
| if (i == valid_archs_count) barf("Unknown --arch '%s'", archname); |
| assert(NULL != arch); |
| VG_(debugLog)(1, "launcher", "using arch '%s' from --arch=%s\n", |
| arch, archname); |
| } |
| else if (clientname == NULL) { |
| // no client executable; use default as fallback |
| VG_(debugLog)(1, "launcher", |
| "no client specified, defaulting arch to '%s'\n", |
| default_arch); |
| arch = default_arch; |
| } |
| else if ((arch = select_arch(clientname, default_cputype,default_arch))) { |
| // arch from client executable |
| VG_(debugLog)(1, "launcher", "selected arch '%s'\n", arch); |
| } |
| else { |
| // nothing found in client executable; use default as fallback |
| VG_(debugLog)(1, "launcher", |
| "no arch detected, defaulting arch to '%s'\n", |
| default_arch); |
| arch = default_arch; |
| } |
| |
| cwd = getcwd(NULL, 0); |
| if (!cwd) barf("Current directory no longer exists."); |
| |
| /* Figure out the name of this executable (viz, the launcher), so |
| we can tell stage2. stage2 will use the name for recursive |
| invokations of valgrind on child processes. */ |
| memset(launcher_name, 0, PATH_MAX+1); |
| for (i = 0; envp[i]; i++) |
| ; /* executable path is after last envp item */ |
| /* envp[i] == NULL ; envp[i+1] == executable_path */ |
| if (envp[i+1][0] != '/') { |
| strcpy(launcher_name, cwd); |
| strcat(launcher_name, "/"); |
| } |
| if (strlen(launcher_name) + strlen(envp[i+1]) > PATH_MAX) |
| barf("launcher path is too long"); |
| strcat(launcher_name, envp[i+1]); |
| VG_(debugLog)(1, "launcher", "launcher_name = %s\n", launcher_name); |
| |
| /* tediously augment the env: VALGRIND_LAUNCHER=launcher_name */ |
| asprintf(&new_line, VALGRIND_LAUNCHER "=%s", launcher_name); |
| |
| /* tediously augment the env: VALGRIND_STARTUP_PWD_%PID_XYZZY=current_working_dir */ |
| asprintf(&set_cwd, "VALGRIND_STARTUP_PWD_%u_XYZZY=%s", getppid(), cwd); |
| |
| // Note that Apple binaries get a secret fourth arg, "char* apple", which |
| // contains the executable path. Don't forget about it. |
| for (j = 0; envp[j]; j++) |
| ; |
| new_env = malloc((j+4) * sizeof(char*)); |
| if (new_env == NULL) |
| barf("malloc of new_env failed."); |
| for (i = 0; i < j; i++) |
| new_env[i] = envp[i]; |
| new_env[i++] = new_line; |
| new_env[i++] = set_cwd; |
| new_env[i++] = NULL; |
| new_env[i ] = envp[i-2]; // the 'apple' arg == the executable_path |
| assert(i == j+3); |
| |
| /* tediously edit env: hide dyld options from valgrind's captive dyld */ |
| for (i = 0; envp[i]; i++) { |
| if (0 == strncmp(envp[i], "DYLD_", 5)) { |
| envp[i][0] = 'V'; /* VYLD_; changed back by initimg-darwin */ |
| } |
| } |
| |
| /* tediously edit argv: remove --arch= */ |
| new_argv = malloc((1+argc) * sizeof(char *)); |
| for (i = 0, new_argc = 0; i < argc; i++) { |
| if (0 == strncmp(argv[i], "--arch=", 7)) { |
| // skip |
| } else { |
| new_argv[new_argc++] = argv[i]; |
| } |
| } |
| new_argv[new_argc++] = NULL; |
| |
| /* Build the stage2 invokation, and execve it. Bye! */ |
| asprintf(&toolfile, "%s/%s-%s-darwin", valgrind_lib, toolname, arch); |
| if (access(toolfile, R_OK|X_OK) != 0) { |
| barf("tool '%s' not installed (%s) (%s)", toolname, toolfile, strerror(errno)); |
| } |
| |
| VG_(debugLog)(1, "launcher", "launching %s\n", toolfile); |
| |
| execve(toolfile, new_argv, new_env); |
| |
| fprintf(stderr, "valgrind: failed to start tool '%s' for platform '%s-darwin': %s\n", |
| toolname, arch, strerror(errno)); |
| |
| exit(1); |
| } |