| /* 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 <unistd.h> | |
| #include <sys/utsname.h> | |
| #include <sys/param.h> | |
| #include "gp-defs.h" | |
| #include "Elf.h" | |
| #include "collctrl.h" | |
| #include "i18n.h" | |
| #include "util.h" | |
| #include "collect.h" | |
| void | |
| collect::check_target (int argc, char **argv) | |
| { | |
| char *next; | |
| char *last = 0; | |
| char *a; | |
| char *ccret; | |
| char **lasts = &last; | |
| int tindex = targ_index; | |
| int ret; | |
| char *basename; | |
| is_64 = false; | |
| /* now check the executable */ | |
| nargs = argc - targ_index; | |
| Exec_status rv = check_executable (argv[targ_index]); | |
| switch (rv) | |
| { | |
| case EXEC_OK: | |
| njargs = cc->get_java_arg_cnt (); | |
| arglist = (char **) calloc (nargs + 5 + njargs, sizeof (char *)); | |
| jargs = cc->get_java_args (); | |
| // store the first argument -- target name | |
| ret = 0; | |
| arglist[ret++] = argv[tindex++]; | |
| if (cc->get_java_mode () == 1) | |
| { | |
| // add any user-specified -J (Java) arguments | |
| int length = (int) strlen (argv[targ_index]); | |
| int is_java = 0; | |
| if ((length >= 6) && strcmp (&argv[targ_index][length - 5], NTXT ("/java")) == 0) | |
| is_java = 1; | |
| else if ((length == 4) && strcmp (&argv[targ_index][0], NTXT ("java")) == 0) | |
| is_java = 1; | |
| if (njargs != 0 && is_java) | |
| { | |
| next = strtok_r (jargs, NTXT (" \t"), lasts); | |
| arglist[ret++] = next; | |
| for (;;) | |
| { | |
| next = strtok_r (NULL, NTXT (" \t"), lasts); | |
| if (next == NULL) | |
| break; | |
| arglist[ret++] = next; | |
| } | |
| } | |
| } | |
| // copy the rest of the arguments | |
| for (int i = 1; i < nargs; i++) | |
| arglist[ret++] = argv[tindex++]; | |
| nargs = ret; | |
| break; | |
| case EXEC_IS_JAR: | |
| // Preface the user-supplied argument list with | |
| // the path to the java, the collector invocation, | |
| // any -J java arguments provided, and "-jar". | |
| ccret = cc->set_java_mode (NTXT ("on")); | |
| if (ccret != NULL) | |
| { | |
| writeStr (2, ccret); | |
| exit (1); | |
| } | |
| njargs = cc->get_java_arg_cnt (); | |
| arglist = (char **) calloc (nargs + 5 + njargs, sizeof (char *)); | |
| jargs = cc->get_java_args (); | |
| a = find_java (); | |
| if (a == NULL) | |
| exit (1); // message was written | |
| ret = 0; | |
| arglist[ret++] = a; | |
| // add any user-specified Java arguments | |
| if (njargs != 0) | |
| { | |
| next = strtok_r (jargs, NTXT (" \t"), lasts); | |
| arglist[ret++] = next; | |
| for (;;) | |
| { | |
| next = strtok_r (NULL, NTXT (" \t"), lasts); | |
| if (next == NULL) | |
| break; | |
| arglist[ret++] = next; | |
| } | |
| } | |
| arglist[ret++] = NTXT ("-jar"); | |
| for (int i = 0; i < nargs; i++) | |
| arglist[ret++] = argv[tindex++]; | |
| nargs = ret; | |
| break; | |
| case EXEC_IS_CLASSCLASS: | |
| // remove the .class from the name | |
| ret = (int) strlen (argv[targ_index]); | |
| argv[targ_index][ret - 6] = 0; | |
| // now fall through to the EXEC_IS_CLASS case | |
| case EXEC_IS_CLASS: | |
| // Preface the user-supplied argument list with | |
| // the path to the java, the collector invocation, | |
| // and any -J java arguments provided. | |
| ccret = cc->set_java_mode (NTXT ("on")); | |
| if (ccret != NULL) | |
| { | |
| writeStr (2, ccret); | |
| exit (1); | |
| } | |
| jargs = cc->get_java_args (); | |
| njargs = cc->get_java_arg_cnt (); | |
| arglist = (char **) calloc (nargs + 4 + njargs, sizeof (char *)); | |
| a = find_java (); | |
| if (a == NULL) | |
| exit (1); // message was written | |
| ret = 0; | |
| arglist[ret++] = a; | |
| // add any user-specified Java arguments | |
| if (njargs != 0) | |
| { | |
| next = strtok_r (jargs, NTXT (" \t"), lasts); | |
| arglist[ret++] = next; | |
| for (;;) | |
| { | |
| next = strtok_r (NULL, NTXT (" \t"), lasts); | |
| if (next == NULL) | |
| break; | |
| arglist[ret++] = next; | |
| } | |
| } | |
| // copy the remaining arguments to the new list | |
| for (int i = 0; i < nargs; i++) | |
| arglist[ret++] = argv[tindex++]; | |
| nargs = ret; | |
| break; | |
| case EXEC_ELF_NOSHARE: | |
| case EXEC_OPEN_FAIL: | |
| case EXEC_ELF_LIB: | |
| case EXEC_ELF_HEADER: | |
| case EXEC_ELF_ARCH: | |
| case EXEC_ISDIR: | |
| case EXEC_NOT_EXEC: | |
| case EXEC_NOT_FOUND: | |
| default: | |
| /* something wrong; write a message */ | |
| char *errstr = status_str (rv, argv[targ_index]); | |
| if (errstr) | |
| { | |
| dbe_write (2, "%s", errstr); | |
| free (errstr); | |
| } | |
| exit (1); | |
| } | |
| cc->set_target (arglist[0]); | |
| /* check the experiment */ | |
| char *ccwarn; | |
| ccret = cc->check_expt (&ccwarn); | |
| if (ccwarn != NULL) | |
| { | |
| writeStr (2, ccwarn); | |
| free (ccwarn); | |
| } | |
| if (ccret != NULL) | |
| { | |
| writeStr (2, ccret); | |
| exit (1); | |
| } | |
| /* check if java, to see if -j flag was given */ | |
| if ((basename = strrchr (arglist[0], '/')) == NULL) | |
| basename = arglist[0]; | |
| else | |
| basename++; | |
| if (strcmp (basename, NTXT ("java")) == 0) | |
| { | |
| /* the target's name is java; was java flag set? */ | |
| if ((jseen_global == 0) && (cc->get_java_mode () == 0)) | |
| { | |
| char *cret = cc->set_java_mode (NTXT ("on")); | |
| if (cret != NULL) | |
| { | |
| writeStr (2, cret); | |
| exit (1); | |
| } | |
| } | |
| } | |
| } | |
| collect::Exec_status | |
| collect::check_executable (char *target_name) | |
| { | |
| char target_path[MAXPATHLEN]; | |
| dbe_stat_t statbuf; | |
| if (target_name == NULL) // not set, but assume caller knows what it's doing | |
| return EXEC_OK; | |
| if (getenv ("GPROFNG_SKIP_VALIDATION")) // don't check target | |
| return EXEC_OK; | |
| // see if target exists and is not a directory | |
| if ((dbe_stat (target_name, &statbuf) == 0) && ((statbuf.st_mode & S_IFMT) != S_IFDIR)) | |
| { | |
| // target is found, check for access as executable | |
| if (access (target_name, X_OK) != 0) | |
| { | |
| // not an executable, check for jar or class file | |
| int i = (int) strlen (target_name); | |
| if ((i >= 5) && strcmp (&target_name[i - 4], NTXT (".jar")) == 0) | |
| { | |
| // could be a jar file | |
| // XXXX -- need better check for real jar file | |
| cc->set_java_mode ("on"); | |
| return EXEC_IS_JAR; | |
| } | |
| if ((i >= 7) && strcmp (&target_name[i - 6], NTXT (".class")) == 0) | |
| { | |
| // could be a class file | |
| // XXXX -- need better check for real class file | |
| cc->set_java_mode (NTXT ("on")); | |
| return EXEC_IS_CLASSCLASS; | |
| } | |
| // not a jar or class file, return not an executable | |
| return EXEC_NOT_EXEC; | |
| } | |
| else // found, and it is executable. set the path to it | |
| snprintf (target_path, sizeof (target_path), NTXT ("%s"), target_name); | |
| } | |
| else | |
| { | |
| // not found, look on path | |
| char *exe_name = get_realpath (target_name); | |
| if (access (exe_name, X_OK) != 0) | |
| { | |
| // target can't be located | |
| // one last attempt: append .class to name, and see if we can find it | |
| snprintf (target_path, sizeof (target_path), NTXT ("%s.class"), target_name); | |
| if (dbe_stat (target_path, &statbuf) == 0) | |
| { | |
| // the file exists | |
| if ((statbuf.st_mode & S_IFMT) == S_IFDIR) | |
| { | |
| // this is a directory; that won't do. | |
| return EXEC_ISDIR; | |
| } | |
| // say it's a class file | |
| cc->set_java_mode (NTXT ("on")); | |
| return EXEC_IS_CLASS; | |
| } | |
| return EXEC_NOT_FOUND; | |
| } | |
| snprintf (target_path, sizeof (target_path), NTXT ("%s"), exe_name); | |
| delete exe_name; | |
| } | |
| // target_path is now the purported executable | |
| // check for ELF library out of date | |
| if (Elf::elf_version (EV_CURRENT) == EV_NONE) | |
| return EXEC_ELF_LIB; | |
| Elf *elf = Elf::elf_begin (target_path); | |
| if (elf == NULL) | |
| return EXEC_OK; | |
| // do not by pass checking architectural match | |
| collect::Exec_status exec_stat = check_executable_arch (elf); | |
| delete elf; | |
| return exec_stat; | |
| } | |
| collect::Exec_status | |
| collect::check_executable_arch (Elf *elf) | |
| { | |
| Elf_Internal_Ehdr *ehdrp = elf->elf_getehdr (); | |
| if (ehdrp == NULL) | |
| return EXEC_ELF_HEADER; | |
| unsigned short machine = ehdrp->e_machine; | |
| switch (machine) | |
| { | |
| #if ARCH(SPARC) | |
| case EM_SPARC: | |
| case EM_SPARC32PLUS: | |
| break; | |
| case EM_SPARCV9: | |
| is_64 = true; | |
| break; | |
| #elif ARCH(Intel) | |
| case EM_X86_64: | |
| { | |
| is_64 = true; | |
| // now figure out if the platform can run it | |
| struct utsname unbuf; | |
| int r = uname (&unbuf); | |
| if (r == 0 && strstr (unbuf.machine, "_64") == NULL) | |
| // machine can not run 64 bits, but this code is 64-bit | |
| return EXEC_ELF_ARCH; | |
| } | |
| break; | |
| case EM_386: | |
| break; | |
| #elif ARCH(Aarch64) | |
| case EM_AARCH64: | |
| is_64 = true; | |
| break; | |
| #endif | |
| default: | |
| return EXEC_ELF_ARCH; | |
| } | |
| // now check if target was built with shared libraries | |
| int dynamic = 0; | |
| for (unsigned cnt = 0; cnt < ehdrp->e_phnum; cnt++) | |
| { | |
| Elf_Internal_Phdr *phdrp = elf->get_phdr (cnt); | |
| if (phdrp && phdrp->p_type == PT_DYNAMIC) | |
| { | |
| dynamic = 1; | |
| break; | |
| } | |
| } | |
| if (dynamic == 0) | |
| { | |
| // target is not a dynamic executable or shared object; | |
| // can't record data | |
| return EXEC_ELF_NOSHARE; | |
| } | |
| return EXEC_OK; | |
| } | |
| char * | |
| collect::status_str (Exec_status rv, char *target_name) | |
| { | |
| switch (rv) | |
| { | |
| case EXEC_OK: | |
| case EXEC_IS_JAR: | |
| case EXEC_IS_CLASS: | |
| case EXEC_IS_CLASSCLASS: | |
| // supported flavors -- no error message | |
| return NULL; | |
| case EXEC_ELF_NOSHARE: | |
| return dbe_sprintf (GTXT ("Target executable `%s' must be built with shared libraries\n"), target_name); | |
| case EXEC_OPEN_FAIL: | |
| return dbe_sprintf (GTXT ("Can't open target executable `%s'\n"), target_name); | |
| case EXEC_ELF_LIB: | |
| return strdup (GTXT ("Internal error: Not a working version of ELF library\n")); | |
| case EXEC_ELF_HEADER: | |
| return dbe_sprintf (GTXT ("Target `%s' is not a valid ELF executable\n"), target_name); | |
| case EXEC_ELF_ARCH: | |
| return dbe_sprintf (GTXT ("Target architecture of executable `%s' is not supported on this machine\n"), target_name); | |
| case EXEC_ISDIR: | |
| return dbe_sprintf (GTXT ("Target `%s' is a directory, not an executable\n"), target_name); | |
| case EXEC_NOT_EXEC: | |
| return dbe_sprintf (GTXT ("Target `%s' is not executable\n"), target_name); | |
| case EXEC_NOT_FOUND: | |
| return dbe_sprintf (GTXT ("Target `%s' not found\n"), target_name); | |
| } | |
| return NULL; | |
| } | |
| char * | |
| collect::find_java (void) | |
| { | |
| char buf[MAXPATHLEN]; | |
| char *var = NULL; | |
| Exec_status rv = EXEC_OK; | |
| // first see if the user entered a -j argument | |
| var = cc->get_java_path (); | |
| if (var != NULL) | |
| { | |
| snprintf (buf, sizeof (buf), NTXT ("%s/bin/java"), var); | |
| java_how = NTXT ("-j"); | |
| rv = check_executable (buf); | |
| } | |
| // then try JDK_HOME | |
| if (java_how == NULL) | |
| { | |
| var = getenv (NTXT ("JDK_HOME")); | |
| if ((var != NULL) && (strlen (var) > 0)) | |
| { | |
| snprintf (buf, sizeof (buf), NTXT ("%s/bin/java"), var); | |
| java_how = NTXT ("JDK_HOME"); | |
| rv = check_executable (buf); | |
| } | |
| } | |
| // then try JAVA_PATH | |
| if (java_how == NULL) | |
| { | |
| var = getenv (NTXT ("JAVA_PATH")); | |
| if ((var != NULL) && (strlen (var) > 0)) | |
| { | |
| snprintf (buf, sizeof (buf), NTXT ("%s/bin/java"), var); | |
| java_how = NTXT ("JAVA_PATH"); | |
| rv = check_executable (buf); | |
| } | |
| } | |
| // try the user's path | |
| if (java_how == NULL) | |
| { | |
| snprintf (buf, sizeof (buf), NTXT ("java")); | |
| rv = check_executable (buf); | |
| if (rv == EXEC_OK) | |
| java_how = NTXT ("PATH"); | |
| } | |
| // finally, just try /usr/java -- system default | |
| if (java_how == NULL) | |
| { | |
| snprintf (buf, sizeof (buf), NTXT ("/usr/java/bin/java")); | |
| rv = check_executable (buf); | |
| java_how = NTXT ("/usr/java/bin/java"); | |
| } | |
| // we now have a nominal path to java, and how we chose it | |
| // and we have rv set to the check_executable return | |
| switch (rv) | |
| { | |
| case EXEC_OK: | |
| java_path = strdup (buf); | |
| if (verbose == 1) | |
| dbe_write (2, GTXT ("Path to `%s' (set from %s) used for Java profiling\n"), | |
| java_path, java_how); | |
| return ( strdup (buf)); | |
| default: | |
| dbe_write (2, GTXT ("Path to `%s' (set from %s) does not point to a JVM executable\n"), | |
| buf, java_how); | |
| break; | |
| } | |
| return NULL; | |
| } | |
| void | |
| collect::validate_config (int how) | |
| { | |
| if (getenv (NTXT ("GPROFNG_SKIP_VALIDATION")) != NULL) | |
| return; | |
| char *cmd = dbe_sprintf (NTXT ("%s/perftools_validate"), run_dir); | |
| if (access (cmd, X_OK) != 0) | |
| { | |
| if (how) | |
| dbe_write (2, GTXT ("WARNING: Unable to validate system: `%s' could not be executed\n"), cmd); | |
| return; | |
| } | |
| char *quiet = how == 0 ? NTXT ("") : NTXT ("-q"); // check collection, verbosely | |
| char *buf; | |
| if (cc->get_java_default () == 0 && java_path) | |
| buf = dbe_sprintf (NTXT ("%s -c -j %s -H \"%s\" %s"), cmd, java_path, java_how, quiet); | |
| else // not java mode -- don't check the java version | |
| buf = dbe_sprintf (NTXT ("%s -c %s"), cmd, quiet); | |
| free (cmd); | |
| /* now run the command */ | |
| int ret = system (buf); | |
| int status = WEXITSTATUS (ret); | |
| if ((status & 0x1) != 0) | |
| dbe_write (2, GTXT ("WARNING: Data collection may fail: system is not properly configured or is unsupported.\n")); | |
| if ((status & 0x2) != 0) | |
| dbe_write (2, GTXT ("WARNING: Java data collection may fail: J2SE[tm] version is unsupported.\n")); | |
| free (buf); | |
| } | |
| void | |
| collect::validate_java (const char *jvm, const char *jhow, int q) | |
| { | |
| char *cmd = dbe_sprintf (NTXT ("%s/perftools_ckjava"), run_dir); | |
| if (access (cmd, X_OK) != 0) | |
| { | |
| dbe_write (2, GTXT ("WARNING: Unable to validate Java: `%s' could not be executed\n"), cmd); | |
| return; | |
| } | |
| char *buf = dbe_sprintf (NTXT ("%s -j %s -H \"%s\" %s"), cmd, jvm, jhow, | |
| (q == 1 ? "-q" : "")); | |
| free (cmd); | |
| /* now run the command */ | |
| int ret = system (buf); | |
| int status = WEXITSTATUS (ret); | |
| if (status != 0) | |
| dbe_write (2, GTXT ("WARNING: Java data collection may fail: J2SE[tm] version is unsupported.\n")); | |
| free (buf); | |
| } |