| /* Copyright (C) 2021-2023 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 <assert.h> | 
 | #include <ctype.h> | 
 | #include <sys/param.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include "gp-defs.h" | 
 | #include "util.h" | 
 | #include "collctrl.h" | 
 | #include "collect.h" | 
 | #include "StringBuilder.h" | 
 | #include "Settings.h" | 
 |  | 
 | #define	STDEBUFSIZE	24000 | 
 |  | 
 | #define LIBGP_COLLECTOR             "libgp-collector.so" | 
 | #define GPROFNG_PRELOAD_LIBDIRS     "GPROFNG_PRELOAD_LIBDIRS" | 
 | #define SP_COLLECTOR_EXPNAME        "SP_COLLECTOR_EXPNAME" | 
 | #define SP_COLLECTOR_FOLLOW_SPEC    "SP_COLLECTOR_FOLLOW_SPEC" | 
 | #define SP_COLLECTOR_PARAMS         "SP_COLLECTOR_PARAMS" | 
 | #define SP_COLLECTOR_FOUNDER        "SP_COLLECTOR_FOUNDER" | 
 | #define SP_COLLECTOR_ORIGIN_COLLECT "SP_COLLECTOR_ORIGIN_COLLECT" | 
 |  | 
 | static const char *LD_AUDIT[] = { | 
 |   //    "LD_AUDIT",	Do not set LD_AUDIT on Linux | 
 |   NULL | 
 | }; | 
 |  | 
 | static const char *LD_PRELOAD[] = { | 
 |   "LD_PRELOAD", | 
 |   NULL | 
 | }; | 
 |  | 
 | static const char *SP_PRELOAD[] = { | 
 |   "SP_COLLECTOR_PRELOAD", | 
 |   NULL | 
 | }; | 
 |  | 
 | static const char *LD_LIBRARY_PATH[] = { | 
 |   "LD_LIBRARY_PATH", | 
 |   NULL, | 
 | }; | 
 |  | 
 | static int | 
 | add_env (char *ev) | 
 | { | 
 |   int r = putenv (ev); | 
 |   if (r != 0) | 
 |     { | 
 |       dbe_write (2, GTXT ("Can't putenv of %s: run aborted\n"), ev); | 
 |       free (ev); | 
 |     } | 
 |   return r; | 
 | } | 
 |  | 
 | int | 
 | collect::putenv_libcollector_ld_audits () | 
 | { | 
 |   StringBuilder sb; | 
 |   for (unsigned int ii = 0; ii < ARR_SIZE (LD_AUDIT) && LD_AUDIT[ii]; ++ii) | 
 |     { | 
 |       sb.sprintf ("%s=%s", LD_AUDIT[ii], SP_LIBAUDIT_NAME); | 
 |       // Append the current value. Check if already set | 
 |       char *old_val = getenv (LD_AUDIT[ii]); | 
 |       if (old_val != NULL) | 
 | 	{ | 
 | 	  while (isspace (*old_val)) | 
 | 	    ++old_val; | 
 | 	  if (*old_val != (char) 0) | 
 | 	    { | 
 | 	      int fromIdx = sb.length (); | 
 | 	      sb.append (" "); | 
 | 	      sb.append (old_val); | 
 | 	      if (sb.indexOf (SP_LIBAUDIT_NAME, fromIdx) >= 0) | 
 | 		continue;       // Already set. Do nothing. | 
 | 	    } | 
 | 	} | 
 |       if (add_env (sb.toString ())) | 
 | 	return 1; | 
 |     } | 
 |   return 0; | 
 | } | 
 |  | 
 | int | 
 | collect::putenv_libcollector_ld_preloads () | 
 | { | 
 |   // for those data types that get extra libs LD_PRELOAD'd, add them | 
 |   if (cc->get_synctrace_mode () != 0) | 
 |     add_ld_preload ("libgp-sync.so"); | 
 |   if (cc->get_heaptrace_mode () != 0) | 
 |     add_ld_preload ("libgp-heap.so"); | 
 |   if (cc->get_iotrace_mode () != 0) | 
 |     add_ld_preload ("libgp-iotrace.so"); | 
 |   add_ld_preload (SP_LIBCOLLECTOR_NAME); | 
 |  | 
 |   // --- putenv SP_COLLECTOR_PRELOAD* | 
 |   int ii; | 
 |   for (ii = 0; SP_PRELOAD[ii]; ii++) | 
 |     { | 
 |       // construct the SP_PRELOAD_* environment variables | 
 |       // and put them into the environment | 
 |       if (add_env (dbe_sprintf ("%s=%s", SP_PRELOAD[ii], sp_preload_list[ii]))) | 
 | 	return 1; | 
 |     } | 
 |   // --- putenv LD_PRELOADS | 
 |   /* purge LD_PRELOAD* of values containing contents of SP_LIBCOLLECTOR_NAME */ | 
 |   if (putenv_purged_ld_preloads (SP_LIBCOLLECTOR_NAME)) | 
 |     dbe_write (2, GTXT ("Warning: %s is already defined in one or more LD_PRELOAD environment variables\n"), | 
 | 	       SP_LIBCOLLECTOR_NAME); | 
 |   if (putenv_ld_preloads ()) | 
 |     return 1; | 
 |   return 0; | 
 | } | 
 |  | 
 | int | 
 | collect::putenv_libcollector_ld_misc () | 
 | { | 
 | #if 0 // XXX 1 turns on LD_DEBUG | 
 |   putenv (strdup ("LD_DEBUG=audit,bindings,detail")); | 
 | #endif | 
 |   // workaround to have the dynamic linker use absolute names | 
 |   if (add_env (dbe_strdup ("LD_ORIGIN=yes"))) | 
 |     return 1; | 
 |  | 
 |   // On Linux we have to provide SP_COLLECTOR_LIBRARY_PATH and LD_LIBRARY_PATH | 
 |   // so that -agentlib:gp-collector works | 
 |   // and so that collect -F works with 32/64-bit mix of processes | 
 |  | 
 |   // Set GPROFNG_PRELOAD_LIBDIRS | 
 |   char *ev = getenv (GPROFNG_PRELOAD_LIBDIRS); | 
 |   char *libpath_list = NULL; | 
 |   if (ev == NULL && settings->preload_libdirs == NULL) | 
 |     { | 
 |       settings->read_rc (false); | 
 |       ev = settings->preload_libdirs; | 
 |     } | 
 |   ev = dbe_strdup (ev); | 
 |   StringBuilder sb; | 
 |   sb.appendf ("%s=", "SP_COLLECTOR_LIBRARY_PATH"); | 
 |   int len = sb.length (); | 
 |   int cnt = 0; | 
 |   char *fname = dbe_sprintf ("%s/%s/%s", LIBDIR, PACKAGE, LIBGP_COLLECTOR); | 
 |   if (access (fname, R_OK | F_OK) == 0) | 
 |     { | 
 |       ++cnt; | 
 |       sb.appendf ("%s/%s", LIBDIR, PACKAGE); | 
 |     } | 
 |   free (fname); | 
 |   for (char *s = ev; s;) | 
 |     { | 
 |       char *s1 = strchr (s, ':'); | 
 |       if (s1) | 
 | 	*(s1++) = 0; | 
 |       if (*s == '/') | 
 | 	{ | 
 | 	  fname = dbe_sprintf ("%s/%s/%s", s, PACKAGE, LIBGP_COLLECTOR); | 
 | 	  if (access (fname, R_OK | F_OK) == 0) | 
 | 	    { | 
 | 	      if (++cnt != 1) | 
 | 		sb.append (':'); | 
 | 	      sb.appendf ("%s", s); | 
 | 	    } | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  fname = dbe_sprintf ("%s/%s/%s/%s", run_dir, s, PACKAGE, LIBGP_COLLECTOR); | 
 | 	  if (access (fname, R_OK | F_OK) == 0) | 
 | 	    { | 
 | 	      if (++cnt != 1) | 
 | 		sb.append (':'); | 
 | 	      sb.appendf ("%s/%s/%s", run_dir, s, PACKAGE); | 
 | 	    } | 
 | 	} | 
 |       free (fname); | 
 |       s = s1; | 
 |     } | 
 |   free (ev); | 
 |   if (cnt == 0) | 
 |     { | 
 |       dbe_write (2, GTXT ("configuration error: can not find %s. run aborted\n"), | 
 | 		 LIBGP_COLLECTOR); | 
 |       return 1; | 
 |     } | 
 |   libpath_list = sb.toString (); | 
 |   if (add_env (libpath_list)) | 
 |     return 1; | 
 |   libpath_list += len; | 
 |  | 
 |   // --- set LD_LIBRARY_PATH using libpath_list | 
 |   char *old = getenv (LD_LIBRARY_PATH[0]); | 
 |   if (old) | 
 |     ev = dbe_sprintf ("%s=%s:%s", LD_LIBRARY_PATH[0], libpath_list, old); | 
 |   else | 
 |     ev = dbe_sprintf ("%s=%s", LD_LIBRARY_PATH[0], libpath_list); | 
 |   if (add_env (ev)) | 
 |     return 1; | 
 |   return 0; | 
 | } | 
 |  | 
 | void | 
 | collect::add_ld_preload (const char *lib) | 
 | { | 
 |   for (int ii = 0; SP_PRELOAD[ii]; ii++) | 
 |     { | 
 |       char *old_sp = sp_preload_list[ii]; | 
 |       if (old_sp == NULL) | 
 | 	sp_preload_list[ii] = strdup (lib); | 
 |       else | 
 | 	{ | 
 | 	  sp_preload_list[ii] = dbe_sprintf ("%s %s", old_sp, lib); | 
 | 	  free (old_sp); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | int | 
 | collect::putenv_memso () | 
 | { | 
 |   // Set environment variable "MEM_USE_LOG" to 1, to keep it out of stderr | 
 |   if (add_env (dbe_strdup ("MEM_USE_LOG=1"))) | 
 |     return 1; | 
 |   // Set environment variable "MEM_ABORT_ON_ERROR", to force a core dump | 
 |   if (add_env (dbe_strdup ("MEM_ABORT_ON_ERROR=1"))) | 
 |     return 1; | 
 |   add_ld_preload ("mem.so"); | 
 |   return putenv_ld_preloads (); | 
 | } | 
 |  | 
 | // set LD_PRELOAD and friends to prepend the given library or libraries | 
 |  | 
 | int | 
 | collect::putenv_ld_preloads () | 
 | { | 
 |   for (int ii = 0; LD_PRELOAD[ii]; ii++) | 
 |     { | 
 |       char *old_val = getenv (LD_PRELOAD[ii]); | 
 |       int sp_num = ii; | 
 |       assert (SP_PRELOAD[sp_num]); | 
 |       char *preload_def; | 
 |       if (old_val) | 
 | 	preload_def = dbe_sprintf ("%s=%s %s", LD_PRELOAD[ii], sp_preload_list[sp_num], old_val); | 
 |       else | 
 | 	preload_def = dbe_sprintf ("%s=%s", LD_PRELOAD[ii], sp_preload_list[sp_num]); | 
 |       if (add_env (preload_def)) | 
 | 	return 1; | 
 |     } | 
 |   return 0; | 
 | } | 
 |  | 
 | /* copied from linetrace.c */ | 
 | /* | 
 |    function: env_strip() | 
 |      Finds str in env; Removes | 
 |      all characters from previous ':' or ' ' | 
 |      up to and including any trailing ':' or ' '. | 
 |    params: | 
 |      env: environment variable | 
 |      str: substring to find | 
 |      return: count of instances removed from env | 
 |  */ | 
 | int | 
 | collect::env_strip (char *env, const char *str) | 
 | { | 
 |   int removed = 0; | 
 |   char *p, *q; | 
 |   if (env == NULL || str == NULL || *str == 0) | 
 |     return 0; | 
 |   size_t maxlen = strlen (env); | 
 |   size_t len = strlen (str); | 
 |   q = env; | 
 |   while ((p = strstr (q, str)) != NULL) | 
 |     { | 
 |       q = p; | 
 |       p += len; | 
 |       if (*p) | 
 | 	{ | 
 | 	  while ((*p) && (*p != ':') && (*p != ' ')) | 
 | 	    p++;      /* skip the rest of the name*/ | 
 | 	  while ((*p == ':') || (*p == ' ')) | 
 | 	    p++; /* strip trailing separator */ | 
 | 	} | 
 |       while (*q != ':' && *q != ' ' && *q != '=' && q != env) | 
 | 	q--; /* strip path */ | 
 |       if (*p) | 
 | 	{ /* copy the rest of the string */ | 
 | 	  if (q != env) | 
 | 	    q++; /* restore leading separator (if any) */ | 
 | 	  size_t n = (maxlen - (q - env)); | 
 | 	  strncpy (q, p, n); | 
 | 	} | 
 |       else | 
 | 	*q = 0; | 
 |       removed++; | 
 |     } | 
 |   return removed; | 
 | } | 
 | /* | 
 |    function: putenv_purged_ld_preloads() | 
 |      Remove selected preload strings from all LD_PRELOAD* env vars. | 
 |    params: | 
 |      var: executable name (leading characters don't have to match) | 
 |      return: number of instances removed from all PRELOAD vars. | 
 |  */ | 
 | int | 
 | collect::putenv_purged_ld_preloads (const char *var) | 
 | { | 
 |   int total_removed = 0; | 
 |   if (!var || *var == 0) | 
 |     return 0; | 
 |   for (int ii = 0; LD_PRELOAD[ii]; ii++) | 
 |     { | 
 |       char *ev = getenv (LD_PRELOAD[ii]); | 
 |       int removed = 0; | 
 |       if (!ev) | 
 | 	continue; | 
 |       removed = env_strip (ev, var); | 
 |       if (!removed) | 
 | 	continue; | 
 |       if (putenv (ev) != 0) | 
 | 	dbe_write (2, GTXT ("Can't putenv of %s\n"), ev); | 
 |       total_removed += removed; | 
 |     } | 
 |   return total_removed; | 
 | } | 
 | /* | 
 |    function: putenv_append() | 
 |      append string to current enviroment variable setting and then do a putenv() | 
 |    params: | 
 |      var: environment variable name | 
 |      val: string to append | 
 |  */ | 
 | int | 
 | collect::putenv_append (const char *var, const char *val) | 
 | { | 
 |   char *ev; | 
 |   if (!var || !val) | 
 |     return 1; | 
 |   const char *old_val = getenv (var); | 
 |   if (old_val == NULL || *old_val == 0) | 
 |     ev = dbe_sprintf ("%s=%s", var, val); | 
 |   else | 
 |     ev = dbe_sprintf ("%s=%s %s", var, old_val, val); | 
 |  | 
 |   // now put the new variable into the environment | 
 |   if (add_env (ev)) | 
 |     return 1; | 
 |   return 0; | 
 | } | 
 |  | 
 | int | 
 | collect::putenv_libcollector (void) | 
 | { | 
 |   char buf[MAXPATHLEN + 1]; | 
 |   // --- set SP_COLLECTOR_EXPNAME | 
 |   // fetch the experiment name and CWD | 
 |   char *exp = cc->get_experiment (); | 
 |   char *cwd = getcwd (buf, MAXPATHLEN); | 
 |   char *ev; | 
 |  | 
 |   // format the environment variable for the experiment directory name | 
 |   if (cwd != NULL && exp[0] != '/')  // experiment is a relative path | 
 |     ev = dbe_sprintf ("%s=%s/%s", SP_COLLECTOR_EXPNAME, cwd, exp); | 
 |   else      // getcwd failed or experiment is a fullpath | 
 |     ev = dbe_sprintf ("%s=%s", SP_COLLECTOR_EXPNAME, exp); | 
 |  | 
 |   // set the experiment directory name | 
 |   if (add_env (ev)) | 
 |     return 1; | 
 |  | 
 |   // --- set SP_COLLECTOR_PARAMS | 
 |   // set the data descriptor | 
 |   exp = cc->get_data_desc (); | 
 |   if (add_env (dbe_sprintf ("%s=%s", SP_COLLECTOR_PARAMS, exp))) | 
 |     return 1; | 
 |  | 
 |   // --- set SP_COLLECTOR_FOLLOW_SPEC | 
 |   const char *follow_spec = cc->get_follow_cmp_spec (); | 
 |   if (follow_spec) | 
 |     // selective following has been enabled | 
 |     if (add_env (dbe_sprintf ("%s=%s", SP_COLLECTOR_FOLLOW_SPEC, follow_spec))) | 
 |       return 1; | 
 |  | 
 |   if (add_env (dbe_sprintf ("%s=%d", SP_COLLECTOR_FOUNDER, getpid ()))) | 
 |     return 1; | 
 |   if (add_env (dbe_sprintf ("%s=1", SP_COLLECTOR_ORIGIN_COLLECT))) | 
 |     return 1; | 
 |  | 
 |   // --- set LD_* | 
 |   if (putenv_libcollector_ld_misc ()) | 
 |     return 1; | 
 |  | 
 |   // --- set LD_PRELOAD* | 
 |   if (putenv_libcollector_ld_preloads () != 0) | 
 |     return 1; | 
 |  | 
 |   // --- set JAVA_TOOL_OPTIONS | 
 |   if (cc->get_java_mode () == 1) | 
 |     if (putenv_append ("JAVA_TOOL_OPTIONS", "-agentlib:gp-collector")) | 
 | 	exit (1); | 
 | #if 0 | 
 |   // --- set LD_AUDIT* | 
 |   if (putenv_libcollector_ld_audits () != 0) | 
 |     return 1; | 
 | #endif | 
 |   return 0; | 
 | } |