| /* 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 <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 | |
| 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 = strdup (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] = 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; | |
| } |