| /* Copyright (C) 2021-2024 Free Software Foundation, Inc. |
| Contributed by Oracle. |
| |
| This file is part of GNU Binutils. |
| |
| 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 3, 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, 51 Franklin Street - Fifth Floor, Boston, |
| MA 02110-1301, USA. */ |
| |
| #include "config.h" |
| #include <ctype.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <strings.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/wait.h> |
| #include <sys/utsname.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <time.h> |
| #include <errno.h> |
| #include <sys/ptrace.h> |
| |
| #include "gp-defs.h" |
| #include "cpu_frequency.h" |
| #include "util.h" |
| #include "collctrl.h" |
| #include "hwcdrv.h" |
| #include "gp-experiment.h" |
| #include "collect.h" |
| #include "StringBuilder.h" |
| |
| #define SP_COLLECTOR_FOUNDER "SP_COLLECTOR_FOUNDER" |
| |
| extern char **environ; |
| |
| static volatile int interrupt = 0; |
| static int saved_stdout = -1; |
| static int saved_stderr = -1; |
| static int no_short_usage = 0; |
| static int usage_fd = 2; |
| static collect *collect_obj = NULL; |
| extern "C" void sigint_handler (int sig, siginfo_t *info, void *context); |
| static char *outredirect = NULL; |
| static int precheck; |
| static int nprocesses; |
| static Process **processes; |
| |
| int |
| main (int argc, char *argv[]) |
| { |
| // disable any alarm that might be pending |
| int r = alarm (0); |
| if (r != 0) |
| dbe_write (2, GTXT ("collect has alarm(%d) pending\n"), r); |
| collect_obj = new collect (argc, argv, environ); |
| collect_obj->start (argc, argv); |
| delete collect_obj; |
| return 0; |
| } |
| |
| extern "C" void |
| sigint_handler (int, siginfo_t *, void *) |
| { |
| interrupt = 1; |
| if (collect_obj->cc != NULL) |
| collect_obj->cc->interrupt (); |
| return; |
| } |
| |
| extern "C" void |
| sigalrm_handler (int, siginfo_t *, void *) |
| { |
| dbe_write (2, GTXT ("collect: unexpected alarm clock signal received\n")); |
| return; |
| } |
| |
| extern "C" void |
| sigterm_handler (int, siginfo_t *, void *) |
| { |
| for (int i = 0; i < nprocesses; i++) |
| { |
| Process *proc = processes[i]; |
| if (proc != NULL) |
| kill (proc->pid, SIGTERM); |
| } |
| } |
| |
| collect::collect (int argc, char *argv[], char **envp) |
| : Application (argc, argv) |
| { |
| verbose = 0; |
| disabled = 0; |
| cc = NULL; |
| collect_warnings = NULL; |
| collect_warnings_idx = 0; |
| int ii; |
| for (ii = 0; ii < MAX_LD_PRELOAD_TYPES; ii++) |
| sp_preload_list[ii] = NULL; |
| for (ii = 0; ii < MAX_LD_PRELOAD_TYPES; ii++) |
| sp_libpath_list[ii] = NULL; |
| java_path = NULL; |
| java_how = NULL; |
| jseen_global = 0; |
| nlabels = 0; |
| origargc = argc; |
| origargv = argv; |
| origenvp = envp; |
| mem_so_me = false; |
| } |
| |
| collect::~collect () |
| { |
| delete cc; |
| } |
| |
| struct sigaction old_sigint_handler; |
| struct sigaction old_sigalrm_handler; |
| |
| void |
| collect::start (int argc, char *argv[]) |
| { |
| char *ccret; |
| char *extype; |
| /* create a collector control structure, disabling aggressive warning */ |
| cc = new Coll_Ctrl (0, false, false); |
| if (prog_name) |
| { |
| char *s = strrchr (prog_name, '/'); |
| if (s && (s - prog_name) > 5) // Remove /bin/ |
| { |
| s = dbe_sprintf (NTXT ("%.*s"), (int) (s - prog_name - 4), prog_name); |
| cc->set_project_home (s); |
| free (s); |
| } |
| } |
| char * errenable = cc->enable_expt (); |
| if (errenable) |
| { |
| writeStr (2, errenable); |
| free (errenable); |
| } |
| |
| /* install a handler for SIGALRM */ |
| struct sigaction act; |
| memset (&act, 0, sizeof (struct sigaction)); |
| sigemptyset (&act.sa_mask); |
| act.sa_handler = (SignalHandler) sigalrm_handler; |
| act.sa_flags = SA_RESTART | SA_SIGINFO; |
| if (sigaction (SIGALRM, &act, &old_sigalrm_handler) == -1) |
| { |
| writeStr (2, GTXT ("Unable to install SIGALRM handler\n")); |
| exit (-1); |
| } |
| |
| /* install a handler for SIGINT */ |
| sigemptyset (&act.sa_mask); |
| act.sa_handler = (SignalHandler) sigint_handler; |
| act.sa_flags = SA_RESTART | SA_SIGINFO; |
| if (sigaction (SIGINT, &act, &old_sigint_handler) == -1) |
| { |
| writeStr (2, GTXT ("Unable to install SIGINT handler\n")); |
| exit (-1); |
| } |
| |
| /* install a handler for SIGTERM */ |
| sigemptyset (&act.sa_mask); |
| act.sa_sigaction = sigterm_handler; |
| act.sa_flags = SA_RESTART | SA_SIGINFO; |
| if (sigaction (SIGTERM, &act, NULL) == -1) |
| { |
| writeStr (2, GTXT ("Unable to install SIGTERM handler\n")); |
| exit (-1); |
| } |
| if (argc > 1 && strncmp (argv[1], NTXT ("--whoami="), 9) == 0) |
| { |
| whoami = argv[1] + 9; |
| argc--; |
| argv++; |
| } |
| |
| /* check for no arguments -- usage message */ |
| if (argc == 1) |
| { |
| verbose = 1; |
| usage_fd = 1; |
| validate_config (0); |
| usage (); |
| exit (0); |
| } |
| else if (argc == 2 && strcmp (argv[1], NTXT ("-h")) == 0) |
| { |
| /* only one argument, -h */ |
| verbose = 1; |
| validate_config (0); |
| /* now print the HWC usage message */ |
| show_hwc_usage (); |
| exit (0); |
| } |
| else if (argc == 2 && (strcmp (argv[1], NTXT ("-help")) == 0 || |
| strcmp (argv[1], NTXT ("--help")) == 0)) |
| { |
| /* only one argument, -help or --help */ |
| verbose = 1; |
| usage_fd = 1; |
| validate_config (0); |
| usage (); |
| exit (0); |
| } |
| // Ruud |
| else if ((argc == 2) && |
| (strcmp (argv[1], NTXT ("--version")) == 0)) |
| { |
| /* only one argument, --version */ |
| |
| /* print the version info */ |
| Application::print_version_info (); |
| exit (0); |
| } |
| |
| /* precheck the arguments -- scan for -O, -M flagS */ |
| precheck = 1; |
| targ_index = check_args (argc, argv); |
| if (targ_index < 0) |
| { |
| /* message has already been written */ |
| usage_fd = 2; |
| short_usage (); |
| exit (1); |
| } |
| /* crack the arguments */ |
| precheck = 0; |
| targ_index = check_args (argc, argv); |
| if (targ_index <= 0) |
| { |
| /* message has already been written */ |
| usage_fd = 2; |
| short_usage (); |
| exit (1); |
| } |
| if (targ_index != 0) |
| check_target (argc, argv); |
| if (disabled != 0 && cc->get_count () == 0) |
| { |
| // show collection parameters; count data |
| ccret = cc->show (0); |
| writeStr (1, ccret); |
| } |
| |
| // see if Java version should be checked |
| if (cc->get_java_default () == 0 && java_path != NULL) |
| validate_java (java_path, java_how, verbose); |
| |
| /* if count data is requested, exec bit to do the real work */ |
| /* even for a dryrun */ |
| if (cc->get_count () != 0) |
| get_count_data (); |
| |
| /* if a dry run, just exit */ |
| if (disabled != 0) |
| { |
| writeStr (1, cc->show_expt ()); |
| StringBuilder sb; |
| sb.append (GTXT ("Exec argv[] = ")); |
| for (int i = 0; i < nargs; i++) |
| sb.appendf (NTXT ("%s "), arglist[i]); |
| sb.append (NTXT ("\n")); |
| char *s = sb.toString (); |
| writeStr (1, s); |
| free (s); |
| exit (0); |
| } |
| |
| // If the mem_so_me flag is set, preload mem.so |
| // and launch the process |
| if (mem_so_me) |
| { |
| /* set env vars for mem.so */ |
| if (putenv_memso () != 0) |
| exit (1); /* message has already been written */ |
| /* ensure original outputs restored for target */ |
| reset_output (); |
| |
| /* now exec the target ... */ |
| if (cc->get_debug_mode () == 1) |
| { |
| traceme (arglist[0], arglist); |
| extype = NTXT ("traceme"); |
| } |
| else |
| { |
| execvp (arglist[0], arglist); |
| extype = NTXT ("exevcp"); |
| } |
| /* oops, exec of the target failed */ |
| char *em = strerror (errno); |
| set_output (); /* restore output for collector */ |
| if (em == NULL) |
| dbe_write (2, GTXT ("memso %s of %s failed: errno = %d\n"), extype, argv[targ_index], errno); |
| else |
| dbe_write (2, GTXT ("memso %s of %s failed: %s\n"), extype, argv[targ_index], em); |
| exit (1); |
| } |
| |
| /* normal path, setting up an experiment and launching the target */ |
| /* set up the experiment */ |
| ccret = cc->setup_experiment (); |
| if (ccret != NULL) |
| { |
| dbe_write (2, NTXT ("%s\n"), ccret); |
| free (ccret); |
| exit (1); |
| } |
| /* Beyond this point, the experiment is created */ |
| if (collect_warnings != NULL) |
| { |
| warn_open (); |
| for (int i = 0; i < collect_warnings_idx; i++) |
| warn_comment (SP_JCMD_CWARN, COL_WARN_APP_NOT_READY, collect_warnings[i], (int) strlen (collect_warnings[i])); |
| warn_close (); |
| } |
| /* check cpu frequency variation for intel*/ |
| unsigned char mode = COL_CPUFREQ_NONE; |
| int max_freq = get_cpu_frequency (&mode); |
| char freq_scaling[256]; |
| char turbo_mode[256]; |
| *freq_scaling = 0; |
| *turbo_mode = 0; |
| if (mode & COL_CPUFREQ_SCALING) |
| snprintf (freq_scaling, sizeof (freq_scaling), NTXT (" frequency_scaling=\"enabled\"")); |
| if (mode & COL_CPUFREQ_TURBO) |
| snprintf (turbo_mode, sizeof (turbo_mode), NTXT (" turbo_mode=\"enabled\"")); |
| if (mode != COL_CPUFREQ_NONE) |
| { |
| warn_open (); |
| if (warn_file != NULL) |
| { |
| warn_write ("<powerm>\n<frequency clk=\"%d\"%s%s/>\n</powerm>\n", |
| max_freq, freq_scaling, turbo_mode); |
| warn_close (); |
| } |
| } |
| |
| /* check for labels to write to notes file */ |
| if (nlabels != 0) |
| { |
| char *nbuf; |
| char nbuf2[MAXPATHLEN]; |
| // fetch the experiment name and CWD |
| char *exp = cc->get_experiment (); |
| char *ev = getcwd (nbuf2, sizeof (nbuf2)); |
| |
| // format the environment variable for the experiment directory name |
| if (ev != NULL && exp[0] != '/') |
| // cwd succeeded, and experiment is a relative path |
| nbuf = dbe_sprintf (NTXT ("%s/%s/%s"), nbuf2, exp, SP_NOTES_FILE); |
| else |
| // getcwd failed or experiment is a fullpath |
| nbuf = dbe_sprintf (NTXT ("%s/%s"), exp, SP_NOTES_FILE); |
| |
| FILE *f = fopen (nbuf, NTXT ("w")); |
| free (nbuf); |
| if (f != NULL) |
| { |
| for (int i = 0; i < nlabels; i++) |
| fprintf (f, NTXT ("%s\n"), label[i]); |
| fclose (f); |
| } |
| } |
| /* check for user interrupt */ |
| if (interrupt == 1) |
| { |
| cc->delete_expt (); |
| writeStr (2, GTXT ("User interrupt\n")); |
| exit (0); |
| } |
| |
| /* print data-collection parameters */ |
| if (verbose) |
| { |
| ccret = cc->show (0); |
| if (ccret != NULL) |
| writeStr (2, ccret); |
| } |
| ccret = cc->show_expt (); |
| if (ccret != NULL) |
| writeStr (1, ccret); /* write this to stdout */ |
| |
| pid_t pid = (pid_t) cc->get_attach_pid (); |
| if (pid == (pid_t) 0) |
| { |
| /* No attach */ |
| /* Set the environment for libcollector */ |
| if (putenv_libcollector () != 0) |
| { |
| /* message has already been written */ |
| cc->delete_expt (); |
| exit (1); |
| } |
| /* ensure original output fds restored for target */ |
| reset_output (); |
| |
| /* now exec the target ... */ |
| if (cc->get_debug_mode () == 1) |
| { |
| traceme (arglist[0], arglist); |
| extype = NTXT ("traceme"); |
| } |
| else |
| { |
| execvp (arglist[0], arglist); |
| extype = NTXT ("execvp"); |
| } |
| |
| /* we reach this point only if the target launch failed */ |
| char *em = strerror (errno); |
| |
| /* restore output for collector */ |
| set_output (); |
| |
| /* exec failed; delete experiment */ |
| cc->delete_expt (); |
| |
| /* print a message and exit */ |
| if (em == NULL) |
| dbe_write (2, GTXT ("%s of %s failed: errno = %d\n"), extype, argv[targ_index], errno); |
| else |
| dbe_write (2, GTXT ("%s of %s failed: %s\n"), extype, argv[targ_index], em); |
| exit (1); |
| } |
| else |
| abort (); |
| } |
| |
| /** |
| * Prepare a warning message and pass it to warn_write() |
| * @Parameters: |
| * kind Type of comment |
| * num ID |
| * s Comment sting |
| * len Length of the string |
| * @Return: none. |
| */ |
| void |
| collect::warn_comment (const char *kind, int num, char *s, int len) |
| { |
| if (len != 0) |
| warn_write (NTXT ("<event kind=\"%s\" id=\"%d\">%.*s</event>\n"), |
| kind, num, len, s); |
| else if (s == NULL) |
| warn_write (NTXT ("<event kind=\"%s\" id=\"%d\"/>\n"), kind, num); |
| else |
| warn_write (NTXT ("<event kind=\"%s\" id=\"%d\">%s</event>\n"), kind, num, s); |
| } |
| |
| /** |
| * Open the warnings file in Append mode ("aw") |
| */ |
| void |
| collect::warn_open () |
| { |
| // open the warnings file |
| warnfilename = dbe_sprintf (NTXT ("%s/%s"), cc->get_experiment (), SP_WARN_FILE); |
| int fd = open (warnfilename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| warn_file = fdopen (fd, NTXT ("aw")); |
| } |
| |
| /** |
| * Close the warnings file |
| */ |
| void |
| collect::warn_close () |
| { |
| (void) fclose (warn_file); |
| } |
| |
| /** |
| * Format the warning message and write it to the warnings file |
| */ |
| void |
| collect::warn_write (const char *format, ...) |
| { |
| char buf[4096]; |
| // format the input arguments into a string |
| va_list va; |
| va_start (va, format); |
| vsnprintf (buf, sizeof (buf), format, va); |
| va_end (va); |
| // write it to the warnings file (warnings.xml) |
| fwrite (buf, 1, strlen (buf), warn_file); |
| fflush (warn_file); |
| } |
| |
| /* process the args, setting expt. params, |
| * and finding offset for a.out name |
| */ |
| int |
| collect::check_args (int argc, char *argv[]) |
| { |
| int hseen = 0; |
| int hoffseen = 0; |
| int lseen = 0; |
| int tseen = 0; |
| int pseen = 0; |
| int sseen = 0; |
| int yseen = 0; |
| int Fseen = 0; |
| int Aseen = 0; |
| int Sseen = 0; |
| int Hseen = 0; |
| int iseen = 0; |
| int Jseen = 0; |
| int ofseen = 0; |
| char *expName = NULL; |
| bool overwriteExp = false; |
| char *ccret; |
| char *ccwarn; |
| for (targ_index = 1; targ_index < argc; targ_index++) |
| { |
| if (argv[targ_index] == NULL) |
| break; |
| if (dbe_strcmp (argv[targ_index], "--") == 0) |
| { |
| targ_index++; |
| break; |
| } |
| if (argv[targ_index][0] != '-') |
| break; |
| int param; |
| switch (argv[targ_index][1]) |
| { |
| case 'y': |
| { |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| char *ptr; |
| int resume = 1; |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (yseen != 0) |
| { |
| dupflagseen ('y'); |
| return -1; |
| } |
| yseen++; |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| { |
| writeStr (2, GTXT ("-y requires a signal argument\n")); |
| return -1; |
| } |
| if ((ptr = strrchr (argv[targ_index], ',')) != NULL) |
| { |
| if ((*(ptr + 1) != 'r') || (*(ptr + 2) != 0)) |
| { |
| /* not the right trailer */ |
| dbe_write (2, GTXT ("Invalid delay signal %s\n"), argv[targ_index]); |
| return -1; |
| } |
| resume = 0; |
| *ptr = 0; |
| } |
| param = cc->find_sig (argv[targ_index]); |
| if (param < 0) |
| { |
| /* invalid signal */ |
| dbe_write (2, GTXT ("Invalid delay signal %s\n"), argv[targ_index]); |
| return -1; |
| } |
| ccret = cc->set_pauseresume_signal (param, resume); |
| if (ccret != NULL) |
| { |
| /* invalid signal; write message */ |
| writeStr (2, ccret); |
| return -1; |
| } |
| break; |
| } |
| case 'l': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (lseen != 0) |
| { |
| dupflagseen ('l'); |
| return -1; |
| } |
| lseen++; |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| { |
| writeStr (2, GTXT ("-l requires a signal argument\n")); |
| return -1; |
| } |
| param = cc->find_sig (argv[targ_index]); |
| if (param < 0) |
| { |
| /* invalid signal */ |
| dbe_write (2, GTXT ("Invalid sample signal %s\n"), argv[targ_index]); |
| return -1; |
| } |
| ccret = cc->set_sample_signal (param); |
| if (ccret != NULL) |
| { |
| /* invalid signal; write message */ |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| break; |
| |
| #ifdef GPROFNG_DOES_NOT_SUPPORT |
| case 'P': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| if (Pseen != 0) |
| { |
| dupflagseen ('P'); |
| return -1; |
| } |
| Pseen++; |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| { |
| writeStr (2, GTXT ("-P requires a process pid argument\n")); |
| return -1; |
| } |
| ccret = cc->set_attach_pid (argv[targ_index]); |
| if (ccret != NULL) |
| { |
| /* error; write message */ |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| break; |
| #endif |
| case 't': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (tseen != 0) |
| { |
| dupflagseen ('t'); |
| return -1; |
| } |
| tseen++; |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| { |
| writeStr (2, GTXT ("-t requires a run-duration argument\n")); |
| return -1; |
| } |
| ccret = cc->set_time_run (argv[targ_index]); |
| if (ccret != NULL) |
| { |
| /* error; write message */ |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| break; |
| case 'p': |
| { |
| char *warnmsg; |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (pseen != 0) |
| { |
| dupflagseen ('p'); |
| return -1; |
| } |
| pseen++; |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| { |
| writeStr (2, GTXT ("-p requires a clock-profiling argument\n")); |
| return -1; |
| } |
| ccret = cc->set_clkprof (argv[targ_index], &warnmsg); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| if (warnmsg != NULL) |
| { |
| writeStr (2, warnmsg); |
| free (warnmsg); |
| } |
| break; |
| } |
| case 's': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (sseen != 0) |
| { |
| dupflagseen ('s'); |
| return -1; |
| } |
| sseen++; |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| { |
| writeStr (2, GTXT ("-s requires a synchronization-tracing argument\n")); |
| return -1; |
| } |
| ccret = cc->set_synctrace (argv[targ_index]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| break; |
| case 'h': |
| { |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| targ_index++; |
| if ((argv[targ_index] == NULL) || (strlen (argv[targ_index]) == 0)) |
| { |
| writeStr (2, GTXT ("-h requires a HW-counter-profiling argument\n")); |
| return -1; |
| } |
| // Check for some special cases |
| char * string = argv[targ_index]; |
| if (strcmp (argv[targ_index], NTXT ("off")) == 0) |
| { |
| if (hseen != 0) |
| { |
| no_short_usage = 1; |
| writeStr (2, GTXT ("-h off cannot be used with any other -h arguments\n")); |
| return -1; |
| } |
| hoffseen = 1; |
| hseen = 1; |
| cc->disable_hwc (); |
| break; |
| } |
| // Check to see if we can use HWC |
| unsigned hwc_maxregs = hwc_get_max_concurrent (false); |
| if (hwc_maxregs == 0) |
| { |
| char buf[1024]; |
| char *pch = hwcfuncs_errmsg_get (buf, sizeof (buf), 0); |
| if (*pch) |
| dbe_write (2, GTXT ("HW counter profiling is not supported on this system: %s%s"), |
| pch, pch[strlen (pch) - 1] == '\n' ? "" : "\n"); |
| else |
| dbe_write (2, GTXT ("HW counter profiling is not supported on this system\n")); |
| no_short_usage = 1; |
| return -1; |
| } |
| // Make sure there's no other -h after -h off |
| if (hoffseen != 0) |
| { |
| no_short_usage = 1; |
| writeStr (2, GTXT ("No -h arguments can be used after -h off\n")); |
| return -1; |
| } |
| // set up to process HW counters (to know about default counters) |
| cc->setup_hwc (); |
| hseen++; |
| char *warnmsg; |
| if (strcmp (argv[targ_index], NTXT ("on")) == 0) |
| ccret = cc->add_default_hwcstring ("on", &warnmsg, true); |
| else if (strcmp (argv[targ_index], NTXT ("hi")) == 0 || |
| strcmp (argv[targ_index], NTXT ("high")) == 0) |
| ccret = cc->add_default_hwcstring ("hi", &warnmsg, true); |
| else if (strcmp (argv[targ_index], NTXT ("lo")) == 0 || |
| strcmp (argv[targ_index], NTXT ("low")) == 0) |
| ccret = cc->add_default_hwcstring ("lo", &warnmsg, true); |
| else if (strcmp (argv[targ_index], NTXT ("auto")) == 0) |
| ccret = cc->add_default_hwcstring ("auto", &warnmsg, true); |
| else |
| ccret = cc->add_hwcstring (string, &warnmsg); |
| if (ccret != NULL) |
| { |
| /* set global flag to suppress the short_usage message for any subsequent HWC errors */ |
| no_short_usage = 1; |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| if (warnmsg != NULL) |
| { |
| writeStr (2, warnmsg); |
| free (warnmsg); |
| } |
| break; |
| } |
| case 'O': |
| overwriteExp = true; |
| ATTRIBUTE_FALLTHROUGH |
| case 'o': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s must be followed by a file name\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (expName != NULL) |
| { |
| dbe_write (2, GTXT ("Only one -o or -O argument may be used\n")); |
| dupflagseen ('o'); |
| return -1; |
| } |
| expName = argv[targ_index + 1]; |
| targ_index++; |
| break; |
| case 'S': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s must be followed by a sample interval name\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (Sseen != 0) |
| { |
| dupflagseen ('S'); |
| return -1; |
| } |
| Sseen++; |
| ccret = cc->set_sample_period (argv[targ_index + 1]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| targ_index++; |
| break; |
| case 'H': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s requires a heap-tracing argument\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (Hseen != 0) |
| { |
| dupflagseen ('H'); |
| return -1; |
| } |
| Hseen++; |
| ccret = cc->set_heaptrace (argv[targ_index + 1]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| if (cc->get_java_default () == 1) |
| cc->set_java_mode (NTXT ("off")); |
| targ_index++; |
| break; |
| case 'i': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| fprintf (stderr, GTXT ("Argument %s requires an I/O-tracing argument\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (iseen != 0) |
| { |
| dupflagseen ('i'); |
| return -1; |
| } |
| iseen++; |
| ccret = cc->set_iotrace (argv[targ_index + 1]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| targ_index++; |
| break; |
| case 'j': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s requires a java-profiling argument\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (jseen_global != 0) |
| { |
| dupflagseen ('j'); |
| return -1; |
| } |
| jseen_global++; |
| ccret = cc->set_java_mode (argv[targ_index + 1]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| targ_index++; |
| break; |
| case 'J': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s requires a java argument\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (Jseen != 0) |
| { |
| dupflagseen ('J'); |
| return -1; |
| } |
| Jseen++; |
| ccret = cc->set_java_args (argv[targ_index + 1]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| targ_index++; |
| break; |
| case 'F': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s requires a descendant-following argument\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (Fseen != 0) |
| { |
| dupflagseen ('F'); |
| return -1; |
| } |
| Fseen++; |
| ccret = cc->set_follow_mode (argv[targ_index + 1]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| targ_index++; |
| break; |
| case 'a': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s requires a load-object archiving argument\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (Aseen != 0) |
| { |
| dupflagseen ('a'); |
| return -1; |
| } |
| Aseen++; |
| ccret = cc->set_archive_mode (argv[targ_index + 1]); |
| if (ccret != NULL) |
| { |
| writeStr (2, ccret); |
| free (ccret); |
| return -1; |
| } |
| targ_index++; |
| break; |
| case 'C': |
| if (precheck == 1) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| if (checkflagterm (argv[targ_index]) == -1) |
| return -1; |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s must be followed by a comment\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (nlabels == MAXLABELS) |
| { |
| dbe_write (2, GTXT ("No more than %d comments may be specified\n"), |
| MAXLABELS); |
| return -1; |
| } |
| label[nlabels] = argv[targ_index + 1]; |
| nlabels++; |
| targ_index++; |
| break; |
| case 'n': |
| case 'v': |
| case 'V': |
| if (precheck == 1) |
| break; |
| do_flag (&argv[targ_index][1]); |
| break; |
| case 'Z': |
| // special undocumented argument for debug builds only to allow analyzer to |
| // LD_PRELOAD mem.so for the target it spawns |
| mem_so_me = true; |
| break; |
| case '-': |
| if (strcmp (argv[targ_index], NTXT ("--verbose")) == 0) |
| do_flag ("v"); |
| else if (strcmp (argv[targ_index], "--outfile") == 0) |
| { |
| if (precheck == 0) |
| { |
| targ_index++; |
| if (argv[targ_index] == NULL) |
| return 0; |
| break; |
| } |
| // process this argument now |
| if (argv[targ_index + 1] == NULL) |
| { |
| dbe_write (2, GTXT ("Argument %s requires a file argument\n"), |
| argv[targ_index]); |
| return -1; |
| } |
| if (ofseen != 0) |
| { |
| dupflagseen (argv[targ_index]); |
| return -1; |
| } |
| ofseen++; |
| if (outredirect == NULL) |
| { |
| outredirect = argv[targ_index + 1]; |
| set_output (); |
| } // else already redirected; ignore with no message |
| targ_index++; |
| } |
| else |
| { |
| dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), argv[targ_index]); |
| return -1; |
| } |
| break; |
| default: |
| dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), argv[targ_index]); |
| return -1; |
| } |
| } |
| if (targ_index >= argc) |
| return -1; |
| if (argv[targ_index] == NULL) |
| { |
| if (precheck == 1) |
| return 0; |
| if (cc->get_attach_pid () != 0) /* no target is OK, if we're attaching */ |
| return 0; |
| writeStr (2, GTXT ("Name of target must be specified\n")); |
| return -1; |
| } |
| if (expName) |
| { |
| ccwarn = NULL; |
| ccret = cc->set_expt (expName, &ccwarn, overwriteExp); |
| if (ccwarn) |
| { |
| writeStr (2, ccwarn); |
| free (ccwarn); |
| } |
| if (ccret) |
| { |
| writeStr (2, ccret); |
| return -1; |
| } |
| } |
| if (cc->get_attach_pid () != 0) |
| { |
| writeStr (2, GTXT ("Name of target must not be specified when -P is used\n")); |
| return -1; |
| } |
| return targ_index; |
| } |
| |
| int |
| collect::checkflagterm (const char *c) |
| { |
| if (c[2] != 0) |
| { |
| dbe_write (2, GTXT ("collect: unrecognized argument `%s'\n"), c); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int |
| collect::do_flag (const char *flags) |
| { |
| char *s; |
| for (int i = 0;; i++) |
| { |
| switch (flags[i]) |
| { |
| case 0: // end of string |
| return 0; |
| case 'n': |
| disabled = 1; |
| if (verbose != 1) |
| { |
| // Ruud |
| Application::print_version_info (); |
| /* |
| dbe_write (2, NTXT ("GNU %s version %s\n"), |
| get_basename (prog_name), VERSION); |
| */ |
| verbose = 1; |
| } |
| break; |
| case 'x': |
| s = cc->set_debug_mode (1); |
| if (s) |
| { |
| writeStr (2, s); |
| free (s); |
| } |
| break; |
| case 'v': |
| if (verbose != 1) |
| { |
| // Ruud |
| Application::print_version_info (); |
| /* |
| dbe_write (2, NTXT ("GNU %s version %s\n"), |
| get_basename (prog_name), VERSION); |
| */ |
| verbose = 1; |
| } |
| break; |
| case 'V': |
| // Ruud |
| Application::print_version_info (); |
| /* |
| dbe_write (2, NTXT ("GNU %s version %s\n"), |
| get_basename (prog_name), VERSION); |
| */ |
| /* no further processing.... */ |
| exit (0); |
| } |
| } |
| } |
| |
| /* |
| * traceme - cause the caller to stop at the end of the next exec() |
| * so that a debugger can attach to the new program |
| * |
| * Takes same arguments as execvp() |
| */ |
| int |
| collect::traceme (const char *execvp_file, char *const execvp_argv[]) |
| { |
| int ret = -1; |
| pid_t pid = fork (); |
| if (pid == 0) |
| { // child |
| // child will set up itself to be PTRACE'd, and then exec the target executable |
| /* reset the SP_COLLECTOR_FOUNDER value to the new pid */ |
| pid_t mypid = getpid (); |
| char *ev = dbe_sprintf (NTXT ("%s=%d"), SP_COLLECTOR_FOUNDER, mypid); |
| if (putenv (ev) != 0) |
| { |
| dbe_write (2, GTXT ("fork-child: Can't putenv of \"%s\": run aborted\n"), ev); |
| return 1; |
| } |
| ptrace (PTRACE_TRACEME, 0, NULL, NULL); // initiate trace |
| ret = execvp (execvp_file, execvp_argv); // execvp user command |
| return ret; // execvp failed |
| } |
| else if (pid > 0) |
| { // parent |
| int status; |
| if (waitpid (pid, &status, 0) != pid) |
| { // wait for execvp to cause signal |
| writeStr (2, GTXT ("parent waitpid() failed\n")); |
| return -2; |
| } |
| if (!WIFSTOPPED (status)) |
| writeStr (2, GTXT ("WIFSTOPPED(status) failed\n")); |
| |
| // originally, PTRACE_DETACH would send SIGTSTP, but now we do it here: |
| if (kill (pid, SIGTSTP) != 0) |
| writeStr (2, GTXT ("kill(pid, SIGTSTP) failed\n")); |
| if (ptrace (PTRACE_DETACH, pid, NULL, 0) != 0) |
| { // detach trace |
| writeStr (2, GTXT ("ptrace(PTRACE_DETACH) failed\n")); |
| return -4; |
| } |
| dbe_write (2, GTXT ("Waiting for attach from debugger: pid=%d\n"), (int) pid); |
| |
| // wait for an external debugger to attach |
| if (waitpid (pid, &status, 0) != pid) |
| { // keep parent alive until child quits |
| writeStr (2, GTXT ("parent final waitpid() failed\n")); |
| return -5; |
| } |
| } |
| else |
| return -1; // fork failed |
| exit (0); |
| } |
| |
| void |
| collect::dupflagseen (char c) |
| { |
| dbe_write (2, GTXT ("Only one -%c argument may be used\n"), c); |
| } |
| |
| void |
| collect::dupflagseen (const char *s) |
| { |
| dbe_write (2, GTXT ("Only one %s argument may be used\n"), s); |
| } |
| |
| int |
| collect::set_output () |
| { |
| static int initial = 1; |
| if (outredirect) |
| { |
| int fd = open (outredirect, O_WRONLY | O_CREAT | O_APPEND, |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| if (fd == -1) |
| { |
| dbe_write (2, GTXT ("Warning: Can't open collector output `%s': %s\n"), |
| outredirect, strerror (errno)); |
| } |
| else |
| { |
| if ((saved_stdout = dup (1)) == -1 || dup2 (fd, 1) == -1) |
| dbe_write (2, GTXT ("Warning: Can't divert collector %s: %s\n"), |
| NTXT ("stdout"), strerror (errno)); |
| if ((saved_stderr = dup (2)) == -1 || dup2 (fd, 2) == -1) |
| dbe_write (2, GTXT ("Warning: Can't divert collector %s: %s\n"), |
| NTXT ("stderr"), strerror (errno)); |
| close (fd); |
| if ((saved_stdout != -1) && (saved_stderr != -1)) |
| { |
| if (initial) |
| { |
| struct timeval tp; |
| gettimeofday (&tp, NULL); |
| writeStr (2, ctime (&tp.tv_sec)); |
| initial = 0; |
| } |
| return 1; // diversion in place |
| } |
| } |
| } |
| return 0; // no diversion |
| } |
| |
| void |
| collect::reset_output () |
| { |
| if (saved_stdout != -1 && |
| (dup2 (saved_stdout, 1) == -1 || close (saved_stdout))) |
| dbe_write (2, GTXT ("Warning: Can't restore collector stdout: %s\n"), |
| strerror (errno)); |
| if (saved_stderr != -1 && |
| (dup2 (saved_stderr, 2) == -1 || close (saved_stderr))) |
| dbe_write (2, GTXT ("Warning: Can't restore collector stderr: %s\n"), |
| strerror (errno)); |
| } |
| |
| void |
| collect::usage () |
| { |
| |
| /* |
| Ruud - Isolate this line because it has an argument. Otherwise it would be at the |
| end of this long list. |
| */ |
| printf ( GTXT ( |
| "Usage: gprofng collect app [OPTION(S)] TARGET [TARGET_ARGUMENTS]\n")); |
| |
| /* |
| ------------------------------------------------------------------------------- |
| For a reason I don't understand, the continuation line(s) need to start at |
| column 26 in order for help2man to do the righ thing. Ruud |
| ------------------------------------------------------------------------------- |
| */ |
| printf ( GTXT ( |
| "\n" |
| "Collect performance data on the target program. In addition to Program\n" |
| "Counter PC) sampling, hardware event counters and various tracing options\n" |
| "are supported.\n" |
| "\n" |
| "Options:\n" |
| "\n" |
| " --version print the version number and exit.\n" |
| " --help print usage information and exit.\n" |
| " --verbose {on|off} enable (on) or disable (off) verbose mode; the default is \"off\".\n" |
| "\n" |
| " -p {off|on|lo|hi|<value>} disable (off) or enable (on) clock-profiling using a default\n" |
| " sampling granularity, or enable clock-profiling implicitly by\n" |
| " setting the sampling granularity (lo, hi, or a specific value\n" |
| " in ms); by default clock profiling is enabled.\n" |
| "\n" |
| " -h {<ctr_def>...,<ctr_n_def>} enable hardware event counter profiling and select\n" |
| " the counter(s); to see the supported counters on this system use\n" |
| " the -h option without other arguments.\n" |
| "\n" |
| " -o <exp_name> specify the name for (and path to) the experiment directory; the\n" |
| " the default path is the current directory.\n" |
| "\n" |
| " -O <exp_name> the same as -o, but unlike the -o option, silently overwrite an\n" |
| " existing experiment directory with the same name.\n" |
| "\n" |
| " -C <label> add up to 10 comment labels to the experiment; comments appear in\n" |
| " the notes section of the header.\n" |
| "\n" |
| " -j {on|off|<path>} enable (on), or disable (off) Java profiling when the target\n" |
| " program is a JVM; optionally set the <path> to a non-default JVM;\n" |
| " the default is \"-j on\".\n" |
| "\n" |
| " -J <java-args> specify arguments to the JVM.\n" |
| "\n" |
| " -t <duration>[m|s] specify the duration over which to record data; the default unit\n" |
| " is seconds (s), but can be set to minutes (m).\n" |
| "\n" |
| " -n dry run; display several run-time settings, but do not run the\n" |
| " target, or collect performance data.\n" |
| "\n" |
| " -y <signal>[,r] specify delayed initialization and a pause/resume signal; by default\n" |
| " the target starts in paused mode; if the optional r keyword is\n" |
| " provided, start in resumed mode.\n" |
| "\n" |
| " -F {off|on|=<regex>} control to follow descendant processes; disable (off), enable (on),\n" |
| " or collect data on all descendant processes whose name matches the\n" |
| " specified regular expression; the default is \"-F on\".\n" |
| "\n" |
| " -a {off|on|ldobjects|src|usedldobjects|usedsrc} specify archiving of binaries and other files;\n" |
| " in addition to disable this feature (off), or enable archiving off all\n" |
| " loadobjects and sources (on), the other options support a more\n" |
| " refined selection. All of these options enable archiving, but the\n" |
| " keyword controls what exactly is selected: all load objects (ldobjects),\n" |
| " all source files (src), the loadobjects asscoiated with a program counter\n" |
| " (usedldobjects), or the source files associated with a program counter\n" |
| " (usedsrc); the default is \"-a ldobjects\".\n" |
| "\n" |
| " -S {off|on|<seconds>} disable (off) or enable (on) periodic sampling of process-wide resource\n" |
| " utilization; by default sampling occurs every second; use the <seconds>\n" |
| " option to change this; the default is \"-S on\".\n" |
| "\n" |
| " -l <signal> specify a signal that will trigger a sample of process-wide resource utilization.\n" |
| "\n" |
| " -s <option>[,<API>] enable synchronization wait tracing; <option> is used to define the specifics\n" |
| " of the tracing (on, off, <threshold>, or all); <API> is used to select the API:\n" |
| " \"n\" selects native/Pthreads, \"j\" selects Java, and \"nj\" selects both;\n" |
| " the default is \"-s off\".\n" |
| "\n" |
| " -H {off|on|N1[-N2]} disable (off), or enable (on) heap tracing, or\n" |
| " specify the heap data collection range. The default is \"-H off\".\n" |
| "\n" |
| " -i {off|on} disable (off), or enable (on) I/O tracing; the default is \"-i off\".\n" |
| "\n" |
| "Documentation:\n" |
| "\n" |
| "A getting started guide for gprofng is maintained as a Texinfo manual. If the info and\n" |
| "gprofng programs are properly installed at your site, the command \"info gprofng\"\n" |
| "should give you access to this document.\n" |
| "\n" |
| "See also:\n" |
| "\n" |
| "gprofng(1), gp-archive(1), gp-display-html(1), gp-display-src(1), gp-display-text(1)\n")); |
| } |
| |
| void |
| collect::short_usage () |
| { |
| if (no_short_usage == 0) |
| dbe_write (usage_fd, GTXT ("Run \"%s --help\" for a usage message.\n"), whoami); |
| } |
| |
| void |
| collect::show_hwc_usage () |
| { |
| usage_fd = 1; |
| short_usage (); |
| cc->setup_hwc (); |
| hwc_usage (false, whoami, NULL); |
| } |
| |
| void |
| collect::writeStr (int f, const char *buf) |
| { |
| if (buf != NULL) |
| write (f, buf, strlen (buf)); |
| } |