/* 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 #include #include #include #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 () != NULL) 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 (xstrdup ("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 StringBuilder sb; sb.append ("SP_COLLECTOR_LIBRARY_PATH="); int len = sb.length (); int cnt = 0; char *fname; char *ev = getenv (GPROFNG_PRELOAD_LIBDIRS); char *libpath_list = NULL; if (ev) { /* GPROFNG_PRELOAD_LIBDIRS is used only in the gprofng testing. * Use these directories first. */ ev = xstrdup (ev); for (char *s = ev; s;) { char *s1 = strchr (s, ':'); if (s1) *(s1++) = 0; fname = dbe_sprintf ("%s/%s", s, LIBGP_COLLECTOR); if (access (fname, R_OK | F_OK) == 0) { if (++cnt != 1) sb.append (':'); sb.append (s); } free (fname); s = s1; } free (ev); ev = NULL; } if (settings->preload_libdirs == NULL) { settings->read_rc (false); ev = settings->preload_libdirs; } ev = dbe_strdup (ev); 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] = xstrdup (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; }