mirror of
https://github.com/ioacademy-jikim/debugging
synced 2025-06-08 00:16:11 +00:00
428 lines
13 KiB
C
428 lines
13 KiB
C
|
|
/*--------------------------------------------------------------------*/
|
|
/*--- Launching valgrind launcher-darwin.c ---*/
|
|
/*--------------------------------------------------------------------*/
|
|
|
|
/*
|
|
This file is part of Valgrind, a dynamic binary instrumentation
|
|
framework.
|
|
|
|
Copyright (C) 2000-2015 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);
|
|
}
|