/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "set.h" #include "cmd.h" #include "spec.h" #include "expr.h" #include "source.h" #include "list.h" #include "prbk.h" /* * Defines - Project private interfaces */ #define DEBUG_ENTRY "tnf_probe_debug" #ifdef TESTING #define EMPTY_ENTRY "tnf_probe_empty" #endif #define USER_OUTSIZE (4*1024*1024) #define KERNEL_OUTSIZE (384*1024) #if defined(__sparc) #define PREX32DIR "/sparcv7/" #elif defined(__i386) || defined(__amd64) #define PREX32DIR "/i86/" #endif #define PREX32EXEC "/usr/bin" PREX32DIR "prex" /* * Globals */ char **g_argv; /* copy of argv pointer */ tnfctl_handle_t *g_hndl; /* handle on target or kernel */ static int g_verbose; /* debugging to stderr */ static char *g_cmdname; /* target command name */ static char **g_cmdargs; /* target command args */ static pid_t g_targetpid; /* target process id */ static volatile boolean_t g_getcmds; /* accept input flag */ static boolean_t g_testflag; /* asserted in test mode */ static char *g_preload; /* objects to preload */ static char *g_outname; /* tracefile name */ static char *tracefile; /* tracefile name used by list cmd */ int g_outsize; /* tracefile size */ boolean_t g_kernelmode; /* -k flag: kernel mode */ static int prex_dmodel; /* prex data model */ /* * Local Declarations */ static void usage(char **argv, const char *msg); static void scanargs(int argc, char **argv); static int set_signal(void); static int get_data_model(pid_t pid); static int get_elf_class(char *filename); static int get_executable(char *); static void prex_isaexec(char **argv, char **envp); static void check_pid_model(char **argv, char **envp); static void check_exec_model(char **argv, char **envp); /* #### - FIXME - need to put this in a private header file */ extern void err_fatal(char *s, ...); extern int yyparse(void); static tnfctl_errcode_t check_trace_error(tnfctl_handle_t *hndl); static void set_default_cmd(void); static void get_commands(void); static tnfctl_errcode_t set_tracefile(tnfctl_handle_t *hndl); static tnfctl_errcode_t set_probe_discovery_callback(tnfctl_handle_t *hndl); static void * perprobe(tnfctl_handle_t *hndl, tnfctl_probe_t *probe_p); static tnfctl_errcode_t perprobe2(tnfctl_handle_t *hndl, tnfctl_probe_t *probe_p, void *ignored); static tnfctl_errcode_t percmd(expr_t *expr_p, cmd_kind_t kind, fcn_t *fcn_p, boolean_t isnew, void *calldata_p); void quit(boolean_t killtarget, boolean_t runtarget); void cmd_listtracefile(); /* * usage() - gives a description of the arguments, and exits */ static void usage(char *argv[], const char *msg) { if (msg) (void) fprintf(stderr, gettext("%s: %s\n"), argv[0], msg); (void) fprintf(stderr, gettext( "usage: %s [options] [cmd-args...]\n"), argv[0]); (void) fprintf(stderr, gettext( "usage: %s [options] -p \n"), argv[0]); (void) fprintf(stderr, gettext( "usage: %s -s -k\n"), argv[0]); (void) fprintf(stderr, gettext( "options:\n")); (void) fprintf(stderr, gettext( " -o set trace output file name\n")); (void) fprintf(stderr, gettext( " -s set trace file size\n")); (void) fprintf(stderr, gettext( " -l shared objects to " "be preloaded (cmd only)\n")); exit(1); } /* * main() - */ int main(int argc, char **argv, char **envp) { tnfctl_errcode_t err = TNFCTL_ERR_NONE; int sys_err; tnfctl_trace_attrs_t trace_attrs; tnfctl_event_t event = TNFCTL_EVENT_EINTR; pid_t prex_pid; /* internationalization stuff */ (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); g_argv = argv; prex_pid = getpid(); #if defined(DEBUG) fprintf(stderr, "### prex_pid = %d ###\n", prex_pid); #endif prex_dmodel = get_data_model(prex_pid); #if defined(DEBUG) fprintf(stderr, "### prex_dmodel = %d ###\n", prex_dmodel); #endif scanargs(argc, argv); if (g_kernelmode) { /* prexing the kernel */ err = tnfctl_kernel_open(&g_hndl); if (err) { err_fatal(gettext( "%s: trouble attaching to the kernel: %s\n"), argv[0], tnfctl_strerror(err)); } } else { /* prexing a user process */ if (g_targetpid != 0) { /* check data model */ check_pid_model(argv, envp); /* attach case */ err = tnfctl_pid_open(g_targetpid, &g_hndl); if (err == TNFCTL_ERR_NOLIBTNFPROBE) { err_fatal(gettext( "%s: missing symbols, is " "libtnfprobe.so loaded in target?\n"), argv[0], tnfctl_strerror(err)); } else if (err) { err_fatal(gettext( "%s: trouble attaching to target " "process: %s\n"), argv[0], tnfctl_strerror(err)); } } else { /* check elf class model */ check_exec_model(argv, envp); /* exec case */ err = tnfctl_exec_open(g_cmdname, g_cmdargs, NULL, g_preload, NULL, &g_hndl); if (err == TNFCTL_ERR_NONE) err = tnfctl_trace_attrs_get(g_hndl, &trace_attrs); if (err) { err_fatal(gettext( "%s: trouble creating target process: " "%s\n"), argv[0], tnfctl_strerror(err)); } g_targetpid = trace_attrs.targ_pid; } sys_err = set_signal(); if (sys_err) err_fatal(gettext( "%s: trouble setting up signal handler: %s\n"), argv[0], strerror(err)); } /* initialize the source stack for the parser */ source_init(); if (!g_kernelmode) { /* set the tracefile name and size */ err = set_tracefile(g_hndl); if (err) { (void) fprintf(stderr, gettext( "%s: trouble initializing tracefile: %s\n"), argv[0], tnfctl_strerror(err)); goto Cleanup; } err = check_trace_error(g_hndl); if (err) { (void) fprintf(stderr, gettext( "%s: cannot read tracing status : %s\n"), argv[0], tnfctl_strerror(err)); goto Cleanup; } } /* accept commands from stdin the first time through */ g_getcmds = B_TRUE; /* set up default aliases */ set_default_cmd(); /* set up creator/destructor function to call for new probes */ err = set_probe_discovery_callback(g_hndl); if (err) { (void) fprintf(stderr, gettext( "%s: error in probe discovery : %s\n"), argv[0], tnfctl_strerror(err)); goto Cleanup; } if (g_kernelmode) { prbk_warn_pfilter_empty(); } while (err == TNFCTL_ERR_NONE) { if (g_kernelmode || g_getcmds) { g_getcmds = B_FALSE; get_commands(); } if (!g_kernelmode && (g_getcmds == B_FALSE)) { err = tnfctl_continue(g_hndl, &event, NULL); if (err) { (void) fprintf(stderr, gettext( "%s: cannot continue target : %s\n"), argv[0], tnfctl_strerror(err)); goto Cleanup; } } err = check_trace_error(g_hndl); if (err) { (void) fprintf(stderr, gettext( "%s: cannot read tracing status : %s\n"), argv[0], tnfctl_strerror(err)); goto Cleanup; } if (!g_kernelmode) { if (event == TNFCTL_EVENT_EXEC) { (void) printf(gettext( "Target process exec'd\n")); quit(B_FALSE, B_TRUE); /* quit resume */ } else if (event == TNFCTL_EVENT_EXIT) { /* target exited */ (void) fprintf(stderr, gettext( "%s: target process exited\n"), g_argv[0]); goto Cleanup; } else if (event == TNFCTL_EVENT_TARGGONE) { /* target terminated */ (void) fprintf(stderr, gettext("%s: target process disappeared (without calling exit)\n"), g_argv[0]); goto Cleanup; } } } Cleanup: err = tnfctl_close(g_hndl, TNFCTL_TARG_DEFAULT); if (err) (void) fprintf(stderr, gettext( "%s: error on closing : %s\n"), argv[0], tnfctl_strerror(err)); exit(0); return (0); } /* * check_trace_error() - checks whether there was an error in tracing */ static tnfctl_errcode_t check_trace_error(tnfctl_handle_t *hndl) { tnfctl_trace_attrs_t trace_attrs; tnfctl_errcode_t err; err = tnfctl_trace_attrs_get(hndl, &trace_attrs); if (err) return (err); if (trace_attrs.trace_buf_state == TNFCTL_BUF_BROKEN) { (void) printf(gettext("Tracing shut down in target program " "due to an internal error - Please restart prex " "and target\n")); } return (TNFCTL_ERR_NONE); } /* * set_default_cmd() - set the default debug entry and $all */ static void set_default_cmd(void) { if (!g_kernelmode) fcn(strdup("debug"), DEBUG_ENTRY); #ifdef TESTING fcn(strdup("empty"), EMPTY_ENTRY); #endif (void) set(strdup("all"), expr(spec(strdup("keys"), SPEC_EXACT), spec(strdup(".*"), SPEC_REGEXP))); } /* * process() - enable and disable selected probes */ typedef struct { tnfctl_probe_t *probe_p; tnfctl_handle_t *hndl; } process_args_t; static tnfctl_errcode_t percmd(expr_t *expr_p, cmd_kind_t kind, fcn_t *fcn_p, boolean_t isnew, void *calldata_p) { process_args_t *args_p = (process_args_t *)calldata_p; tnfctl_handle_t *hndl = args_p->hndl; tnfctl_probe_t *probe_p = args_p->probe_p; tnfctl_errcode_t err = TNFCTL_ERR_NONE; char *attrs; attrs = list_getattrs(probe_p); if (expr_match(expr_p, attrs)) { #if defined(DEBUG) || defined(lint) if (g_verbose) { char *cmdstr[] = { "enable", "disable", "connect", "clear", "trace", "untrace"}; (void) fprintf(stderr, ": %s command: %s ", (isnew) ? "new" : "old", cmdstr[kind]); expr_print(stderr, expr_p); } #endif switch (kind) { case CMD_ENABLE: err = tnfctl_probe_enable(hndl, probe_p, NULL); break; case CMD_DISABLE: err = tnfctl_probe_disable(hndl, probe_p, NULL); break; case CMD_TRACE: err = tnfctl_probe_trace(hndl, probe_p, NULL); break; case CMD_UNTRACE: err = tnfctl_probe_untrace(hndl, probe_p, NULL); break; case CMD_CONNECT: err = tnfctl_probe_connect(hndl, probe_p, NULL, fcn_p->entry_name_p); break; case CMD_CLEAR: err = tnfctl_probe_disconnect_all(hndl, probe_p, NULL); break; } #if defined(DEBUG) || defined(lint) if (g_verbose) (void) fprintf(stderr, "\n"); #endif } if (attrs) free(attrs); return (err); } /*ARGSUSED*/ static void * perprobe(tnfctl_handle_t *hndl, tnfctl_probe_t *probe_p) { process_args_t args; tnfctl_errcode_t err; args.probe_p = probe_p; args.hndl = hndl; err = cmd_traverse(percmd, &args); if (err) { (void) fprintf(stderr, gettext( "%s: error on new (dlopened) probe : %s\n"), g_argv[0], tnfctl_strerror(err)); } return (NULL); } static tnfctl_errcode_t set_probe_discovery_callback(tnfctl_handle_t *hndl) { tnfctl_errcode_t err; err = tnfctl_register_funcs(hndl, perprobe, NULL); if (err) return (err); return (TNFCTL_ERR_NONE); } static tnfctl_errcode_t perprobe2(tnfctl_handle_t *hndl, tnfctl_probe_t *probe_p, void *cd) { cmd_t *cmd = cd; process_args_t args; tnfctl_errcode_t err; args.probe_p = probe_p; args.hndl = hndl; err = cmd_callback(cmd, percmd, &args); if (err) { (void) fprintf(stderr, gettext( "%s: error on probe operation: %s\n"), g_argv[0], tnfctl_strerror(err)); } return (err); } void process_cmd(tnfctl_handle_t *hndl, cmd_t *cmd) { #if defined(DEBUG) || defined(lint) if (g_verbose) (void) fprintf(stderr, "processing commands\n"); #endif (void) tnfctl_probe_apply(hndl, perprobe2, cmd); } /* * get_commands() - process commands from stdin */ static void get_commands(void) { /* Read commands from STDIN */ if (g_kernelmode) { (void) printf(gettext("Type \"help\" for help ...\n")); } else { if (g_testflag) (void) printf("prex(%ld), target(%ld): ", getpid(), g_targetpid); (void) printf(gettext("Target process stopped\n")); (void) printf(gettext( "Type \"continue\" to resume the target, " "\"help\" for help ...\n")); } while (yyparse()); } /* * quit() - called to quit the controlling process. The boolean argument * specifies whether to terminate the target as well. */ void quit(boolean_t killtarget, boolean_t runtarget) { tnfctl_errcode_t err; if (killtarget && runtarget) err = tnfctl_close(g_hndl, TNFCTL_TARG_DEFAULT); else if (killtarget && !runtarget) err = tnfctl_close(g_hndl, TNFCTL_TARG_KILL); else if (!killtarget && runtarget) err = tnfctl_close(g_hndl, TNFCTL_TARG_RESUME); else if (!killtarget && !runtarget) err = tnfctl_close(g_hndl, TNFCTL_TARG_SUSPEND); if (err) { (void) fprintf(stderr, gettext( "%s: trouble quitting : %s\n"), g_argv[0], tnfctl_strerror(err)); exit(1); } exit(0); } /* * scanargs() - processes the command line arguments */ #define strneq(s1, s2, n) (strncmp(s1, s2, n) == 0) static void scanargs(int argc, char **argv) { int c; #if defined(DEBUG) || defined(lint) char *optstr = "l:o:p:s:tkv:"; /* debugging options */ #else char *optstr = "l:o:p:s:tk"; /* production options */ #endif /* set up some defaults */ g_targetpid = 0; g_cmdname = NULL; g_cmdargs = NULL; g_preload = NULL; g_outname = NULL; g_outsize = -1; while ((c = getopt(argc, argv, optstr)) != EOF) { switch (c) { case 'l': /* preload objects */ g_preload = optarg; break; case 'o': /* tracefile name */ g_outname = optarg; break; case 'p': /* target pid (attach case) */ g_targetpid = atoi(optarg); break; case 's': /* tracefile size */ g_outsize = atoi(optarg) * 1024; break; case 't': /* test flag */ g_testflag = B_TRUE; (void) setvbuf(stdout, NULL, _IOLBF, 0); break; case 'k': /* kernel mode */ g_kernelmode = B_TRUE; break; #if defined(DEBUG) || defined(lint) case 'v': /* verbose flag */ g_verbose = atoi(optarg); break; #endif case '?': /* error case */ usage(argv, gettext("unrecognized argument")); } } if (optind < argc) { g_cmdname = strdup(argv[optind]); g_cmdargs = &argv[optind]; } /* sanity clause */ if (!g_kernelmode && (g_cmdname == NULL && g_targetpid == 0)) usage(argv, gettext("need to specify cmd or pid")); if (g_cmdname != NULL && g_targetpid != 0) usage(argv, gettext("can't specify both cmd and pid")); if (g_targetpid && g_preload) usage(argv, gettext("can't use preload option with attach")); if (g_kernelmode) { if (g_outname) usage(argv, "can't specify a filename in kernel mode"); if (g_cmdname) usage(argv, "can't specify a command in kernel mode"); if (g_targetpid) usage(argv, "can't specify pid in kernel mode"); if (g_preload) usage(argv, "can't use preload option in kernel mode"); } /* default output size */ if (g_outsize == -1) g_outsize = g_kernelmode ? KERNEL_OUTSIZE : USER_OUTSIZE; #ifdef OLD int i; for (i = 1; i < argc; i++) { if (strneq(argv[i], "-v", 2)) { int vlevel; vlevel = (strlen(argv[i]) > 2)? atoi(&argv[i][2]) : 1; g_verbose = B_TRUE; prb_verbose_set(vlevel); } else if (strneq(argv[i], "-pid", 2)) { if (++i >= argc) usage(argv, gettext("missing pid argument")); g_targetpid = atoi(argv[i]); } else if (strneq(argv[i], "-t", 2)) { g_testflag = B_TRUE; (void) setvbuf(stdout, NULL, _IOLBF, 0); } else if (argv[i][0] != '-') { g_cmdname = strdup(argv[i]); if (!g_cmdname) { err_fatal(gettext( "%s: out of memory"), argv[0]); } if (g_verbose >= 2) { (void) fprintf(stderr, "cmdname=%s\n", g_cmdname); } /* * rest of arguments are the args to the executable - * by convention argv[0] should be name of * executable, so we don't increment i */ g_cmdargs = &argv[i]; break; } else { usage(argv, gettext("unrecognized argument")); } } #endif } /* end scanargs */ /* * sig_handler() - cleans up if a signal is received */ /*ARGSUSED*/ static void sig_handler(int signo) { g_getcmds = B_TRUE; } /* end sig_handler */ /* * set_signal() - sets up function to call for clean up */ static int set_signal(void) { struct sigaction newact; newact.sa_handler = sig_handler; (void) sigemptyset(&newact.sa_mask); newact.sa_flags = 0; if (sigaction(SIGINT, &newact, NULL) < 0) { return (errno); } return (0); } /* * set_tracefile() - initializes tracefile, sets the tracefile name and size */ static tnfctl_errcode_t set_tracefile(tnfctl_handle_t *hndl) { tnfctl_errcode_t err; tnfctl_trace_attrs_t attrs; size_t minoutsize; char path[MAXPATHLEN]; char *outfile_name; char *tmpdir; /* Init tracefile name used by list cmd */ tracefile = NULL; err = tnfctl_trace_attrs_get(hndl, &attrs); if (err) return (err); if (attrs.trace_buf_state == TNFCTL_BUF_BROKEN) return (TNFCTL_ERR_BUFBROKEN); if (attrs.trace_buf_state == TNFCTL_BUF_OK) { /* trace file set already - can't change it */ return (TNFCTL_ERR_NONE); } minoutsize = attrs.trace_min_size; if (g_outsize < minoutsize) { (void) fprintf(stderr, gettext("specified tracefile size smaller then " "minimum; setting to %d kbytes\n"), minoutsize / 1024); g_outsize = minoutsize; } /* where is $TMPDIR? */ tmpdir = getenv("TMPDIR"); if (!tmpdir || *tmpdir == '\0') { tmpdir = "/tmp"; } /* do we have an absolute, relative or no pathname specified? */ if (g_outname == NULL) { /* default, no tracefile specified */ if ((strlen(tmpdir) + 1 + 20) > (size_t)MAXPATHLEN) { (void) fprintf(stderr, gettext( "%s: $TMPDIR too long\n"), g_argv[0]); exit(1); } (void) sprintf(path, "%s/trace-%ld", tmpdir, g_targetpid); outfile_name = path; } else { /* filename specified */ outfile_name = g_outname; } tracefile = strdup(outfile_name); if (tracefile == NULL) { if ((errno == ENOMEM) || (errno == EAGAIN)) { return (TNFCTL_ERR_ALLOCFAIL); } else { return (TNFCTL_ERR_INTERNAL); } } #if defined(DEBUG) || defined(lint) if (g_verbose) (void) fprintf(stderr, "setting tracefile name=\"%s\", size=%d\n", path, g_outsize); #endif err = tnfctl_buffer_alloc(hndl, outfile_name, g_outsize); return (err); } /* * get_data_model() - get the process data model from psinfo * structure. */ #define PROCFORMAT "/proc/%d" static int get_data_model(pid_t pid) { char path[MAXPATHLEN]; int fd, dmodel = -1; prpsinfo_t psinfo; (void) sprintf(path, PROCFORMAT, (int)pid); fd = open(path, O_RDONLY); if (fd == -1) return (dmodel); if ((dmodel = ioctl(fd, PIOCPSINFO, &psinfo)) == -1) return (dmodel); return ((int)psinfo.pr_dmodel); } /* * get_executable - return file descriptor for PATH-resolved * target file. * */ static int get_executable(char *name) { int fd = -1; if (name != NULL) { char path[PATH_MAX + 1]; char line[MAX_INPUT + 1]; char *p = line; char *fname = name; int N = sizeof (line); struct stat file_att; while (*fname == ' ') fname++; if (fname[0] == '-' || strchr(fname, '/')) { fd = open(fname, O_RDONLY); } else { int len = strlen(fname); char *dirlist = getenv("PATH"); char *dir = NULL; if (dirlist != NULL) { dirlist = strdup(dirlist); dir = strtok(dirlist, ":"); } while (fd < 0 && dir != NULL) { if ((strlen(dir) + len + 1) < sizeof (path)) { strcat(strcat(strcpy(path, dir), "/"), fname); fd = open(path, O_RDONLY); } dir = strtok(NULL, ":"); } if (dirlist != NULL) free(dirlist); } if (fstat(fd, &file_att) || !S_ISREG(file_att.st_mode)) { if (fd >= 0) close(fd); return (-1); } if (read(fd, p, 2) && p[0] == '#' && p[1] == '!') { while (N-- > 1 && read(fd, p, 1) && *p != '\n') p++; *p = '\0'; close(fd); return (get_executable(line)); } if (fd >= 0) lseek(fd, 0, SEEK_SET); } /* %$#@! cstyle complaint */ return (fd); } /* * get_elf_class - get the target executable elf class * i.e. ELFCLASS64 or ELFCLASS32. */ static int get_elf_class(char *filename) { int elfclass = -1; int elffd = get_executable(filename); Elf *elf; size_t size; char *ident; GElf_Ehdr ehdr; if (elffd < 0) return (elfclass); if (elf_version(EV_CURRENT) == EV_NONE) { (void) close(elffd); return (elfclass); } elf = elf_begin(elffd, ELF_C_READ, (Elf *) 0); /* * verify information in file header */ if (gelf_getehdr(elf, &ehdr) == (GElf_Ehdr *) 0) { close(elffd); return (elfclass); } ident = elf_getident(elf, &size); if (ident[EI_CLASS] == ELFCLASS32) elfclass = ELFCLASS32; if (ident[EI_CLASS] == ELFCLASS64) elfclass = ELFCLASS64; close(elffd); return (elfclass); } /* * check_exec_model() - check the consistency between prex data model * and target elf class and act accordingly */ static void check_exec_model(char **argv, char **envp) { int elfclass; elfclass = get_elf_class(g_cmdname); if (((elfclass == ELFCLASS32) && (prex_dmodel == PR_MODEL_ILP32)) || ((elfclass == ELFCLASS64) && (prex_dmodel == PR_MODEL_LP64))) return; if ((prex_dmodel == PR_MODEL_ILP32) && (elfclass == ELFCLASS64)) { (void) fprintf(stderr, gettext( "Error: 32 bit prex can not exec 64 bit target\n")); exit(1); } if ((prex_dmodel == PR_MODEL_LP64) && (elfclass == ELFCLASS32)) prex_isaexec(argv, envp); } /* * check_pid_model() - check the consistency between prex data model * and target data model and act accordingly */ static void check_pid_model(char **argv, char **envp) { int dmodel; dmodel = get_data_model(g_targetpid); if (prex_dmodel == dmodel) return; if ((prex_dmodel == PR_MODEL_ILP32) && (dmodel == PR_MODEL_LP64)) { (void) fprintf(stderr, gettext( "Error: 32 bit prex can not exec 64 bit target\n")); exit(1); } if ((prex_dmodel == PR_MODEL_LP64) && (dmodel == PR_MODEL_ILP32)) prex_isaexec(argv, envp); } /* * prex_isaexec() - there is only one case this function get called * 64 bit prex, 32 bit target, need to exec 32 bit * prex here. */ static void prex_isaexec(char **argv, char **envp) { char path[PATH_MAX + sizeof (PREX32DIR)]; strcat(strcat(strcpy(path, dirname(dirname(argv[0]))), PREX32DIR), basename(argv[0])); if (get_elf_class(path) != ELFCLASS32) strcpy(path, PREX32EXEC); argv[0] = path; (void) execve(path, argv, envp); (void) fprintf(stderr, gettext("%s: execve(\"%s\") failed\n"), argv[0], path); } void cmd_listtracefile() { if (g_kernelmode) { (void) fprintf(stderr, gettext("There is no trace file in kernel mode!\n")); } else { (void) printf(gettext("Current trace file is: %s\n"), tracefile); } }