/*--------------------------------------------------------------------*/
/*--- Launching valgrind launcher-darwin.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Valgrind, a dynamic binary instrumentation
framework.
Copyright (C) 2000-2012 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" },
{ CPU_TYPE_POWERPC, "ppc", "ppc32" },
{ CPU_TYPE_POWERPC64, "ppc64", "ppc64" },
};
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);
bzero(&valid_archs[i], 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);
}