| /* 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; |
| #elif ARCH(RISCV) |
| case EM_RISCV: |
| 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); |
| } |