/* Copyright (C) 2021 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} disable (off), or enable (on) heap tracing; 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"));
/*
char *s = dbe_sprintf (GTXT ("Usage: %s <args> target <target-args>\n"),
whoami);
writeStr (usage_fd, s);
free (s);
writeStr (usage_fd, GTXT (" -p {lo|on|hi|off|<value>}\tspecify clock-profiling\n"));
writeStr (usage_fd, GTXT ("\t`lo' per-thread rate of ~10 samples/second\n"));
writeStr (usage_fd, GTXT ("\t`on' per-thread rate of ~100 samples/second (default)\n"));
writeStr (usage_fd, GTXT ("\t`hi' per-thread rate of ~1000 samples/second\n"));
writeStr (usage_fd, GTXT ("\t`off' disable clock profiling\n"));
writeStr (usage_fd, GTXT ("\t<value> specify profile timer period in millisec.\n"));
s = dbe_sprintf (GTXT ("\t\t\tRange on this system is from %.3f to %.3f millisec.\n\t\t\tResolution is %.3f millisec.\n"),
(double) cc->get_clk_min () / 1000.,
(double) cc->get_clk_max () / 1000.,
(double) cc->get_clk_res () / 1000.);
writeStr (usage_fd, s);
free (s);
writeStr (usage_fd, GTXT (" -h <ctr_def>...[,<ctr_n_def>]\tspecify HW counter profiling\n"));
s = dbe_sprintf (GTXT ("\tto see the supported HW counters on this machine, run \"%s -h\" with no other arguments\n"),
whoami);
writeStr (usage_fd, s);
free (s);
writeStr (usage_fd, GTXT (" -s <threshold>[,<scope>]\tspecify synchronization wait tracing\n"));
writeStr (usage_fd, GTXT ("\t<scope> is \"j\" for tracing Java-APIs, \"n\" for tracing native-APIs, or \"nj\" for tracing both\n"));
writeStr (usage_fd, GTXT (" -H {on|off}\tspecify heap tracing\n"));
writeStr (usage_fd, GTXT (" -i {on|off}\tspecify I/O tracing\n"));
writeStr (usage_fd, GTXT (" -N <lib>\tspecify library to exclude count from instrumentation (requires -c also)\n"));
writeStr (usage_fd, GTXT (" \tmultiple -N arguments can be provided\n"));
writeStr (usage_fd, GTXT (" -j {on|off|path}\tspecify Java profiling\n"));
writeStr (usage_fd, GTXT (" -J <java-args>\tspecify arguments to Java for Java profiling\n"));
writeStr (usage_fd, GTXT (" -t <duration>\tspecify time over which to record data\n"));
writeStr (usage_fd, GTXT (" -n\tdry run -- don't run target or collect performance data\n"));
writeStr (usage_fd, GTXT (" -y <signal>[,r]\tspecify delayed initialization and pause/resume signal\n"));
writeStr (usage_fd, GTXT ("\tWhen set, the target starts in paused mode;\n\t if the optional r is provided, it starts in resumed mode\n"));
writeStr (usage_fd, GTXT (" -F {on|off|=<regex>}\tspecify following descendant processes\n"));
writeStr (usage_fd, GTXT (" -a {on|ldobjects|src|usedldobjects|usedsrc|off}\tspecify archiving of binaries and other files;\n"));
writeStr (usage_fd, GTXT (" -S {on|off|<seconds>}\t Set the interval for periodic sampling of process-wide resource utilization\n"));
writeStr (usage_fd, GTXT (" -l <signal>\tspecify signal that will trigger a sample of process-wide resource utilization\n"));
writeStr (usage_fd, GTXT (" -o <expt>\tspecify experiment name\n"));
writeStr (usage_fd, GTXT (" --verbose\tprint expanded log of processing\n"));
writeStr (usage_fd, GTXT (" -C <label>\tspecify comment label (up to 10 may appear)\n"));
writeStr (usage_fd, GTXT (" -V|--version\tprint version number and exit\n"));
*/
/* don't document this feature */
// writeStr (usage_fd, GTXT(" -Z\tPreload mem.so, and launch target [no experiment]\n") );
/*
writeStr (usage_fd, GTXT ("\n See the gp-collect(1) man page for more information\n"));
*/
#if 0
/* print an extended usage message */
/* find a Java for Java profiling, set Java on to check Java */
find_java ();
cc->set_java_mode (NTXT ("on"));
/* check for variable-clock rate */
unsigned char mode = COL_CPUFREQ_NONE;
get_cpu_frequency (&mode);
if (mode != COL_CPUFREQ_NONE)
writeStr (usage_fd, GTXT ("NOTE: system has variable clock frequency, which may cause variable program run times.\n"));
/* show the experiment that would be run */
writeStr (usage_fd, GTXT ("\n Default experiment:\n"));
char *ccret = cc->setup_experiment ();
if (ccret != NULL)
{
writeStr (usage_fd, ccret);
free (ccret);
exit (1);
}
cc->delete_expt ();
ccret = cc->show (1);
if (ccret != NULL)
{
writeStr (usage_fd, ccret);
free (ccret);
}
#endif
}
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));
}