| /* Copyright (C) 2021 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. */ |
| |
| /* |
| * Lineage events for process fork, exec, etc. |
| */ |
| |
| #include "config.h" |
| #include <string.h> |
| #include <elf.h> |
| #include <regex.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| #include <limits.h> |
| |
| |
| #include "descendants.h" |
| |
| /* TprintfT(<level>,...) definitions. Adjust per module as needed */ |
| #define DBG_LT0 0 // for high-level configuration, unexpected errors/warnings |
| #define DBG_LTT 0 // for interposition on GLIBC functions |
| #define DBG_LT1 1 // for configuration details, warnings |
| #define DBG_LT2 2 |
| #define DBG_LT3 3 |
| |
| #define LT_MAXNAMELEN 1024 |
| #define LT_MAXPATHLEN 1024 |
| |
| int __collector_linetrace_shutdown_hwcs_6830763_XXXX = 0; |
| int dbg_current_mode = FOLLOW_NONE; /* for debug only */ |
| unsigned line_key = COLLECTOR_TSD_INVALID_KEY; |
| line_mode_t line_mode = LM_DORMANT; |
| int user_follow_mode = FOLLOW_ON; |
| int java_mode = 0; |
| |
| static char *user_follow_spec; |
| static char new_lineage[LT_MAXNAMELEN]; |
| static char curr_lineage[LT_MAXNAMELEN]; |
| static char linetrace_exp_dir_name[LT_MAXPATHLEN + 1]; // experiment directory |
| |
| /* lineage tracking for descendants of this process */ |
| |
| static int fork_linenum = 0; |
| static int line_initted = 0; |
| static collector_mutex_t fork_lineage_lock = COLLECTOR_MUTEX_INITIALIZER; |
| static collector_mutex_t clone_lineage_lock = COLLECTOR_MUTEX_INITIALIZER; |
| |
| /* interposition */ |
| #define CALL_REAL(x) (*(int(*)())__real_##x) |
| #define CALL_REALC(x) (*(char*(*)())__real_##x) |
| #define CALL_REALF(x) (*(FILE*(*)())__real_##x) |
| #define NULL_PTR(x) ( __real_##x == NULL ) |
| |
| // For a given Linux, which lib functions have more than one GLIBC version? Do this: |
| // objdump -T `find /lib /lib64 -name "*.so*"` | grep GLIBC | grep text | grep \( |
| static void *__real_fork = NULL; |
| static void *__real_vfork = NULL; |
| static void *__real_execve = NULL; |
| static void *__real_execvp = NULL; |
| static void *__real_execv = NULL; |
| static void *__real_execle = NULL; |
| static void *__real_execlp = NULL; |
| static void *__real_execl = NULL; |
| static void *__real_clone = NULL; |
| static void *__real_grantpt = NULL; |
| static void *__real_ptsname = NULL; |
| static void *__real_popen = NULL; |
| static int clone_linenum = 0; |
| #if ARCH(Intel) |
| #if WSIZE(32) |
| static void *__real_popen_2_1 = NULL; |
| static void *__real_popen_2_0 = NULL; |
| static void *__real_posix_spawn_2_15 = NULL; |
| static void *__real_posix_spawnp_2_15 = NULL; |
| static void *__real_posix_spawn_2_2 = NULL; |
| static void *__real_posix_spawnp_2_2 = NULL; |
| #elif WSIZE(64) |
| static void *__real_posix_spawn_2_15 = NULL; |
| static void *__real_posix_spawnp_2_15 = NULL; |
| static void *__real_posix_spawn_2_2_5 = NULL; |
| static void *__real_posix_spawnp_2_2_5 = NULL; |
| #endif /* WSIZE() */ |
| #endif /* ARCH(Intel) */ |
| static void *__real_system = NULL; |
| static void *__real_posix_spawn = NULL; |
| static void *__real_posix_spawnp = NULL; |
| static void *__real_setuid = NULL; |
| static void *__real_seteuid = NULL; |
| static void *__real_setreuid = NULL; |
| static void *__real_setgid = NULL; |
| static void *__real_setegid = NULL; |
| static void *__real_setregid = NULL; |
| static void linetrace_dormant (); |
| static int check_follow_fork (); |
| static int check_follow_exec (const char *execfile); |
| static int check_follow_combo (const char *execfile); |
| static int path_collectable (const char *execfile); |
| static char * build_experiment_path (char *instring, size_t instring_sz, const char *lineage_str); |
| static int init_lineage_intf (); |
| |
| /* ------- "Previously dbx-visible" function prototypes ----------------- */ |
| static int linetrace_follow_experiment (const char *follow_spec, const char *lineage_str, const char *execfile); |
| static char *lineage_from_expname (char *lineage_str, size_t lstr_sz, const char *expname); |
| static void linetrace_ext_fork_prologue (const char *variant, char * new_lineage, int *following_fork); |
| static void linetrace_ext_fork_epilogue (const char *variant, const pid_t ret, char * new_lineage, int *following_fork); |
| static char **linetrace_ext_exec_prologue (const char *variant, |
| const char* path, char *const argv[], char *const envp[], int *following_exec); |
| static void linetrace_ext_exec_epilogue (const char *variant, char *const envp[], const int ret, int *following_exec); |
| static void linetrace_ext_combo_prologue (const char *variant, const char *cmd, int *following_combo); |
| static void linetrace_ext_combo_epilogue (const char *variant, const int ret, int *following_combo); |
| |
| #ifdef DEBUG |
| static int |
| get_combo_flag () |
| { |
| int * guard = NULL; |
| int combo_flag = ((line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0); |
| return combo_flag; |
| } |
| #endif /* DEBUG */ |
| |
| /* must be called for potentially live experiment */ |
| int |
| __collector_ext_line_init (int *precord_this_experiment, |
| const char * progspec, const char * progname) |
| { |
| *precord_this_experiment = 1; |
| TprintfT (DBG_LT0, "__collector_ext_line_init(%s)\n", progspec); |
| if (NULL_PTR (fork)) |
| if (init_lineage_intf ()) |
| { |
| TprintfT (DBG_LT0, "__collector_ext_line_init() ERROR: initialization failed.\n"); |
| return COL_ERROR_LINEINIT; |
| } |
| /* check the follow spec */ |
| user_follow_spec = CALL_UTIL (getenv)(SP_COLLECTOR_FOLLOW_SPEC); |
| if (user_follow_spec != NULL) |
| { |
| TprintfT (DBG_LT0, "collector: %s=%s\n", SP_COLLECTOR_FOLLOW_SPEC, user_follow_spec); |
| if (!linetrace_follow_experiment (user_follow_spec, curr_lineage, progname)) |
| { |
| *precord_this_experiment = 0; |
| TprintfT (DBG_LT0, "collector: -F =<regex> does not match, will NOT be followed\n"); |
| } |
| else |
| TprintfT (DBG_LT0, "collector: -F =<regex> matches, will be followed\n"); |
| user_follow_mode = FOLLOW_ALL; |
| } |
| __collector_env_save_preloads (); |
| TprintfT (DBG_LT0, "__collector_ext_line_init(), progname=%s, followspec=%s, followthis=%d\n", |
| progname, user_follow_spec ? user_follow_spec : "NULL", |
| *precord_this_experiment); |
| line_mode = LM_TRACK_LINEAGE; /* even if we don't follow, we report the interposition */ |
| line_initted = 1; |
| return COL_ERROR_NONE; |
| } |
| |
| /* |
| * int __collector_ext_line_install(args) |
| * Check args to determine which line events to follow. |
| * Create tsd key for combo flag. |
| */ |
| int |
| __collector_ext_line_install (char *args, const char * expname) |
| { |
| if (!line_initted) |
| { |
| TprintfT (DBG_LT0, "__collector_ext_line_install(%s) ERROR: init hasn't be called yet\n", args); |
| return COL_ERROR_EXPOPEN; |
| } |
| TprintfT (DBG_LT0, "__collector_ext_line_install(%s, %s)\n", args, expname); |
| line_key = __collector_tsd_create_key (sizeof (int), NULL, NULL); |
| |
| /* determine experiment name */ |
| __collector_strlcpy (linetrace_exp_dir_name, expname, sizeof (linetrace_exp_dir_name)); |
| lineage_from_expname (curr_lineage, sizeof (curr_lineage), linetrace_exp_dir_name); |
| user_follow_mode = CALL_UTIL (atoi)(args); |
| TprintfT (DBG_LT0, "__collector_ext_line_install() user_follow_mode=0x%X, linetrace_exp_dir_name=%s\n", |
| user_follow_mode, linetrace_exp_dir_name); |
| |
| // determine java mode |
| char * java_follow_env = CALL_UTIL (getenv)(JAVA_TOOL_OPTIONS); |
| if (java_follow_env != NULL && CALL_UTIL (strstr)(java_follow_env, COLLECTOR_JVMTI_OPTION)) |
| java_mode = 1; |
| |
| // backup collector specific env |
| if (sp_env_backup == NULL) |
| { |
| sp_env_backup = __collector_env_backup (); |
| TprintfT (DBG_LT0, "__collector_ext_line_install creating sp_env_backup -- 0x%p\n", sp_env_backup); |
| } |
| else |
| TprintfT (DBG_LT0, "__collector_ext_line_install existing sp_env_backup -- 0x%p\n", sp_env_backup); |
| if (user_follow_mode == FOLLOW_NONE) |
| __collector_env_unset (NULL); |
| |
| char logmsg[256]; |
| logmsg[0] = '\0'; |
| if (user_follow_mode != FOLLOW_NONE) |
| CALL_UTIL (strlcat)(logmsg, "fork|exec|combo", sizeof (logmsg)); |
| size_t slen = __collector_strlen (logmsg); |
| if (slen > 0) |
| logmsg[slen] = '\0'; |
| else |
| CALL_UTIL (strlcat)(logmsg, "none", sizeof (logmsg)); |
| |
| /* report which line events are followed */ |
| (void) __collector_log_write ("<setting %s=\"%s\"/>\n", SP_JCMD_LINETRACE, logmsg); |
| TprintfT (DBG_LT0, "__collector_ext_line_install(%s): %s \n", expname, logmsg); |
| return COL_ERROR_NONE; |
| } |
| |
| char * |
| lineage_from_expname (char *lineage_str, size_t lstr_sz, const char *expname) |
| { |
| TprintfT (DBG_LT0, "lineage_from_expname(%s, %s)\n", lineage_str, expname); |
| char *p = NULL; |
| if (lstr_sz < 1 || !lineage_str || !expname) |
| { |
| TprintfT (DBG_LT0, "lineage_from_expname(): ERROR, null string\n"); |
| return NULL; |
| } |
| /* determine lineage from experiment name */ |
| p = __collector_strrchr (expname, '/'); |
| if ((p == NULL) || (*++p != '_')) |
| { |
| lineage_str[0] = 0; |
| TprintfT (DBG_LT2, "lineage_from_expname(): expt=%s lineage=\".\" (founder)\n", expname); |
| } |
| else |
| { |
| size_t tmp = __collector_strlcpy (lineage_str, p, lstr_sz); |
| if (tmp >= lstr_sz) |
| TprintfT (DBG_LT0, "lineage_from_expname(): ERROR: expt=%s lineage=\"%s\" truncated %ld characters\n", |
| expname, lineage_str, (long) (tmp - lstr_sz)); |
| lineage_str[lstr_sz - 1] = 0; |
| p = __collector_strchr (lineage_str, '.'); |
| if (p != NULL) |
| *p = '\0'; |
| TprintfT (DBG_LT2, "lineage_from_expname(): expt=%s lineage=\"%s\"\n", expname, lineage_str); |
| } |
| return lineage_str; |
| } |
| |
| /* |
| * void __collector_line_cleanup (void) |
| * Disable logging. Clear backup ENV. |
| */ |
| void |
| __collector_line_cleanup (void) |
| { |
| if (line_mode == LM_CLOSED) |
| { |
| TprintfT (DBG_LT0, "__collector_line_cleanup(): WARNING, line is already closed\n"); |
| return; |
| } |
| else if (line_mode == LM_DORMANT) |
| TprintfT (DBG_LT0, "__collector_line_cleanup(): ERROR, line is DORMANT\n"); |
| else |
| TprintfT (DBG_LT0, "__collector_line_cleanup()\n"); |
| line_mode = LM_CLOSED; |
| user_follow_mode = FOLLOW_NONE; |
| dbg_current_mode = FOLLOW_NONE; /* for debug only */ |
| line_key = COLLECTOR_TSD_INVALID_KEY; |
| java_mode = 0; |
| if (sp_env_backup != NULL) |
| { |
| __collector_env_backup_free (); |
| sp_env_backup = NULL; |
| } |
| return; |
| } |
| |
| /* |
| * void __collector_ext_line_close (void) |
| * Disable logging. Cleans ENV vars. Clear backup ENV. |
| */ |
| void |
| __collector_ext_line_close (void) |
| { |
| TprintfT (DBG_LT0, "__collector_ext_line_close()\n"); |
| __collector_line_cleanup (); |
| __collector_env_unset (NULL); |
| return; |
| } |
| |
| /* |
| * void linetrace_dormant(void) |
| * Disable logging. Preserve ENV vars. |
| */ |
| static void |
| linetrace_dormant (void) |
| { |
| if (line_mode == LM_DORMANT) |
| { |
| TprintfT (DBG_LT0, "linetrace_dormant() -- already dormant\n"); |
| return; |
| } |
| else if (line_mode == LM_CLOSED) |
| { |
| TprintfT (DBG_LT0, "linetrace_dormant(): ERROR, line is already CLOSED\n"); |
| return; |
| } |
| else |
| TprintfT (DBG_LT0, "linetrace_dormant()\n"); |
| line_mode = LM_DORMANT; |
| return; |
| } |
| |
| static int |
| check_follow_fork () |
| { |
| int follow = (user_follow_mode != FOLLOW_NONE); |
| TprintfT (DBG_LT0, "check_follow_fork()=%d\n", follow); |
| return follow; |
| } |
| |
| static int |
| check_follow_exec (const char *execfile) |
| { |
| int follow = (user_follow_mode != FOLLOW_NONE); |
| if (follow) |
| { |
| /* revise based on collectability of execfile */ |
| follow = path_collectable (execfile); |
| } |
| TprintfT (DBG_LT0, "check_follow_exec(%s)=%d\n", execfile, follow); |
| return follow; |
| } |
| |
| static int |
| check_follow_combo (const char *execfile) |
| { |
| int follow = (user_follow_mode != FOLLOW_NONE); |
| TprintfT (DBG_LT0, "check_follow_combo(%s)=%d\n", execfile, follow); |
| return follow; |
| } |
| |
| static int |
| check_fd_dynamic (int fd) |
| { |
| TprintfT (DBG_LT0, "check_fd_dynamic(%d)\n", fd); |
| off_t off = CALL_UTIL (lseek)(fd, (off_t) 0, SEEK_END); |
| size_t sz = (size_t) 8192; /* one page should suffice */ |
| if (sz > off) |
| sz = off; |
| char *p = CALL_UTIL (mmap64_)((char *) 0, sz, PROT_READ, MAP_PRIVATE, fd, (off64_t) 0); |
| if (p == MAP_FAILED) |
| { |
| TprintfT (DBG_LT0, "check_fd_dynamic(): ERROR/WARNING: mmap failed for `%d'\n", fd); |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_NOFOLLOW, "mmap-failed"); |
| return 0; |
| } |
| char elfclass = p[EI_CLASS]; |
| if ((p[EI_MAG0] != ELFMAG0) || |
| (p[EI_MAG1] != ELFMAG1) || |
| (p[EI_MAG2] != ELFMAG2) || |
| (p[EI_MAG3] != ELFMAG3) || |
| (elfclass != ELFCLASS32 && elfclass != ELFCLASS64) |
| ) |
| { |
| TprintfT (DBG_LT0, "check_fd_dynamic(): WARNING: Command `%d' is not executable ELF!\n", fd); |
| CALL_UTIL (munmap)(p, sz); |
| return 1; |
| } |
| Elf32_Ehdr *ehdr32 = (Elf32_Ehdr*) p; |
| Elf64_Ehdr *ehdr64 = (Elf64_Ehdr*) p; |
| Elf64_Off e_phoff; |
| Elf64_Half e_phnum; |
| Elf64_Half e_phentsize; |
| if (elfclass == ELFCLASS32) |
| { |
| e_phoff = ehdr32->e_phoff; |
| e_phnum = ehdr32->e_phnum; |
| e_phentsize = ehdr32->e_phentsize; |
| } |
| else |
| { |
| e_phoff = ehdr64->e_phoff; |
| e_phnum = ehdr64->e_phnum; |
| e_phentsize = ehdr64->e_phentsize; |
| } |
| if ((sizeof (Elf32_Ehdr) > sz) || |
| (sizeof (Elf64_Ehdr) > sz) || |
| (e_phoff + e_phentsize * (e_phnum - 1) > sz)) |
| { |
| TprintfT (DBG_LT0, "check_fd_dynamic(): WARNING: Command `%d' ELF file did not fit in page!\n", fd); |
| #if 0 |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_RISKYFOLLOW, "ELF header size"); |
| #endif |
| CALL_UTIL (munmap)(p, sz); |
| return 1; |
| } |
| TprintfT (DBG_LT2, "check_fd_dynamic(): elfclass=%d, e_phoff=%lu e_phnum=%lu e_phentsize=%lu\n", |
| (int) elfclass, (unsigned long) e_phoff, (unsigned long) e_phnum, |
| (unsigned long) e_phentsize); |
| int dynamic = 0; |
| Elf64_Half i; |
| for (i = 0; i < e_phnum; i++) |
| { |
| if (elfclass == ELFCLASS32) |
| { |
| if (PT_DYNAMIC == |
| ((Elf32_Phdr*) (p + e_phoff + e_phentsize * i))->p_type) |
| { |
| dynamic = 1; |
| break; |
| } |
| } |
| else |
| { |
| if (PT_DYNAMIC == |
| ((Elf64_Phdr*) (p + e_phoff + e_phentsize * i))->p_type) |
| { |
| dynamic = 1; |
| break; |
| } |
| } |
| } |
| if (!dynamic) |
| { |
| TprintfT (DBG_LT0, "check_fd_dynamic(): ERROR/WARNING: Command `%d' is not a dynamic executable!\n", fd); |
| #if 0 |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_NOFOLLOW, "!dynamic"); |
| #endif |
| } |
| else |
| TprintfT (DBG_LT2, "check_fd_dynamic(): Command `%d' is a dynamic executable!\n", fd); |
| CALL_UTIL (munmap)(p, sz); |
| return dynamic; |
| } |
| |
| static int |
| check_dynamic (const char *execfile) |
| { |
| TprintfT (DBG_LT2, "check_dynamic(%s)\n", execfile); |
| int fd = CALL_UTIL (open)(execfile, O_RDONLY); |
| if (fd == -1) |
| { |
| TprintfT (DBG_LT0, "check_dynamic(): ERROR/WARNING: Command `%s' could not be opened!\n", execfile); |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_RISKYFOLLOW, "open"); |
| return 1; /* follow, though exec will presumably fail */ |
| } |
| int ret = check_fd_dynamic (fd); |
| CALL_UTIL (close)(fd); |
| return ret; |
| } |
| |
| static int |
| path_collectable (const char *execfile) |
| { |
| TprintfT (DBG_LT0, "path_collectable(%s)\n", execfile); |
| /* Check that execfile exists and is a collectable executable */ |
| /* logging warning when collection is likely to be unsuccessful */ |
| /* (if check isn't accurate, generally best not to include it) */ |
| |
| if (execfile && !__collector_strchr (execfile, '/')) |
| { /* got an unqualified name */ |
| /* XXXX locate execfile on PATH to be able to check it */ |
| TprintfT (DBG_LT0, "path_collectable(): WARNING: Can't check unqualified executable `%s'\n", execfile); |
| #if 0 |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_RISKYFOLLOW, "path"); |
| #endif |
| return 1; /* follow unqualified execfile unchecked */ |
| } |
| struct stat sbuf; |
| if (stat (execfile, &sbuf)) |
| { /* can't stat it */ |
| TprintfT (DBG_LT0, "path_collectable(): WARNING: Can't stat `%s'\n", execfile); |
| #if 0 |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_RISKYFOLLOW, "stat"); |
| #endif |
| return 1; /* follow, though exec will probably fail */ |
| } |
| TprintfT (DBG_LT2, "path_collectable(%s) mode=0%o uid=%d gid=%d\n", |
| execfile, sbuf.st_mode, sbuf.st_uid, sbuf.st_gid); |
| if (((sbuf.st_mode & S_IXUSR) == 0) || ((sbuf.st_mode & S_IFMT) == S_IFDIR)) |
| { |
| TprintfT (DBG_LT0, "path_collectable(): WARNING: Command `%s' is NOT an executable file!\n", execfile); |
| #if 0 |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_RISKYFOLLOW, "mode"); |
| #endif |
| return 1; /* follow, though exec will presumably fail */ |
| } |
| /* XXXX setxid(root) is OK iff libcollector is registered as secure */ |
| /* XXXX setxid(non-root) is OK iff umask is accomodating */ |
| if (((sbuf.st_mode & S_ISUID) != 0) || ((sbuf.st_mode & S_ISGID) != 0)) |
| { |
| TprintfT (DBG_LT0, "path_collectable(): WARNING: Command `%s' is SetXID!\n", execfile); |
| #if 0 |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_RISKYFOLLOW, "setxid"); |
| #endif |
| return 1; /* follow, though collection may be unreliable */ |
| } |
| if (!check_dynamic (execfile)) |
| { |
| TprintfT (DBG_LT0, "path_collectable(%s) WARNING/ERROR: not dynamic, not collectng!\n", execfile); |
| return 0; /* don't follow, collection will fail unpredictably */ |
| } |
| TprintfT (DBG_LT2, "path_collectable(%s) OK!\n", execfile); |
| return 1; /* OK to follow */ |
| } |
| |
| static char * |
| build_experiment_path (char * instring, size_t instring_sz, const char *lineage_str) |
| { |
| TprintfT (DBG_LT0, "build_experiment_path(,%ld, %s)\n", |
| (long) instring_sz, lineage_str); |
| const char *p = CALL_UTIL (strstr)(linetrace_exp_dir_name, DESCENDANT_EXPT_KEY); |
| int basedir_sz; |
| if (p) |
| basedir_sz = p - linetrace_exp_dir_name + 4; /* +3 because of DESCENDANT_EXPT_KEY */ |
| else |
| basedir_sz = __collector_strlen (linetrace_exp_dir_name) + 1; |
| int additional_sz = __collector_strlen (lineage_str) + 4; |
| if (basedir_sz + additional_sz > instring_sz) |
| { |
| TprintfT (DBG_LT0, "build_experiment_path(%s,%s): ERROR: path too long: %d > %ld\n", |
| linetrace_exp_dir_name, lineage_str, |
| basedir_sz + additional_sz, (long) instring_sz); |
| *instring = 0; |
| return NULL; |
| } |
| __collector_strlcpy (instring, linetrace_exp_dir_name, basedir_sz); |
| size_t slen = __collector_strlen (instring); |
| CALL_UTIL (snprintf)(instring + slen, instring_sz - slen, "/%s.er", lineage_str); |
| assert (__collector_strlen (instring) + 1 == basedir_sz + additional_sz); |
| return instring; |
| } |
| |
| static void |
| check_reuid_change (uid_t ruid, uid_t euid) |
| { |
| uid_t curr_ruid = getuid (); |
| uid_t curr_euid = geteuid (); |
| mode_t curr_umask = umask (0); |
| umask (curr_umask); /* restore original umask */ |
| int W_oth = !(curr_umask & S_IWOTH); |
| TprintfT (DBG_LT0, "check_reuid_change(%d,%d): umask=%03o\n", ruid, euid, curr_umask); |
| TprintfT (DBG_LT0, "check_reuid_change(): umask W usr=%d grp=%d oth=%d\n", |
| (int) (!(curr_umask & S_IWUSR)), (int) (!(curr_umask & S_IWGRP)), W_oth); |
| if (ruid != -1) |
| { |
| TprintfT (DBG_LT0, "check_reuid_change(%d->%d)\n", curr_ruid, ruid); |
| if ((curr_euid == 0) && (ruid != 0) && !W_oth) |
| { |
| /* changing to non-root ID, with umask blocking writes by other */ |
| TprintfT (DBG_LT0, "check_reuid_change(): ERROR/WARNING: umask blocks write other after ruid change (%d->%d)\n", |
| curr_ruid, ruid); |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">umask %03o ruid %d->%d</event>\n", |
| SP_JCMD_CWARN, COL_WARN_IDCHNG, curr_umask, curr_ruid, ruid); |
| } |
| } |
| if (euid != -1) |
| { |
| TprintfT (DBG_LT0, "check_reuid_change(%d->%d)\n", curr_euid, euid); |
| if ((curr_euid == 0) && (euid != 0) && !W_oth) |
| { |
| /* changing to non-root ID, with umask blocking writes by other */ |
| TprintfT (DBG_LT0, "check_reuid_change(): ERROR/WARNING: umask blocks write other after euid change (%d->%d)\n", |
| curr_euid, euid); |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">umask %03o euid %d->%d</event>\n", |
| SP_JCMD_CWARN, COL_WARN_IDCHNG, curr_umask, curr_euid, euid); |
| } |
| } |
| } |
| |
| static void |
| check_regid_change (gid_t rgid, gid_t egid) |
| { |
| gid_t curr_rgid = getgid (); |
| gid_t curr_egid = getegid (); |
| uid_t curr_euid = geteuid (); |
| mode_t curr_umask = umask (0); |
| umask (curr_umask); /* restore original umask */ |
| int W_oth = !(curr_umask & S_IWOTH); |
| TprintfT (DBG_LT0, "check_regid_change(%d,%d): umask=%03o euid=%d\n", |
| rgid, egid, curr_umask, curr_euid); |
| TprintfT (DBG_LT0, "umask W usr=%d grp=%d oth=%d\n", |
| (int) (!(curr_umask & S_IWUSR)), (int) (!(curr_umask & S_IWGRP)), W_oth); |
| if (rgid != -1) |
| { |
| TprintfT (DBG_LT0, "check_regid_change(%d->%d)\n", curr_rgid, rgid); |
| if ((curr_euid == 0) && (rgid != 0) && !W_oth) |
| { |
| /* changing to non-root ID, with umask blocking writes by other */ |
| TprintfT (DBG_LT0, "check_regid_change(): WARNING/ERROR: umask blocks write other after rgid change (%d->%d)\n", |
| curr_rgid, rgid); |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">umask %03o rgid %d->%d</event>\n", |
| SP_JCMD_CWARN, COL_WARN_IDCHNG, curr_umask, curr_rgid, rgid); |
| } |
| } |
| if (egid != -1) |
| { |
| TprintfT (DBG_LT0, "check_regid_change(): check_egid_change(%d->%d)\n", curr_egid, egid); |
| if ((curr_euid == 0) && (egid != 0) && !W_oth) |
| { |
| /* changing to non-root ID, with umask blocking writes by other */ |
| TprintfT (DBG_LT0, "check_regid_change(): WARNING/ERROR: umask blocks write other after egid change (%d->%d)\n", |
| curr_egid, egid); |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">umask %03o egid %d->%d</event>\n", |
| SP_JCMD_CWARN, COL_WARN_IDCHNG, curr_umask, curr_egid, egid); |
| } |
| } |
| } |
| |
| static int |
| init_lineage_intf () |
| { |
| void *dlflag; |
| TprintfT (DBG_LT2, "init_lineage_intf()\n"); |
| |
| static int nesting_check = 0; |
| if (nesting_check >= 2) |
| { |
| /* segv before stack blows up */ |
| nesting_check /= (nesting_check - 2); |
| } |
| nesting_check++; |
| |
| __real_fork = dlsym (RTLD_NEXT, "fork"); |
| if (__real_fork == NULL) |
| { |
| __real_fork = dlsym (RTLD_DEFAULT, "fork"); |
| if (__real_fork == NULL) |
| return 1; |
| dlflag = RTLD_DEFAULT; |
| } |
| else |
| dlflag = RTLD_NEXT; |
| TprintfT (DBG_LT2, "init_lineage_intf() using RTLD_%s\n", |
| dlflag == RTLD_DEFAULT ? "DEFAULT" : "NEXT"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_fork\n", __real_fork); |
| __real_vfork = dlsym (dlflag, "vfork"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_vfork\n", __real_vfork); |
| __real_execve = dlsym (dlflag, "execve"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_execve\n", __real_execve); |
| __real_execvp = dlsym (dlflag, "execvp"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_execvp\n", __real_execvp); |
| __real_execv = dlsym (dlflag, "execv"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_execv\n", __real_execv); |
| __real_execle = dlsym (dlflag, "execle"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_execle\n", __real_execle); |
| __real_execlp = dlsym (dlflag, "execlp"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_execlp\n", __real_execlp); |
| __real_execl = dlsym (dlflag, "execl"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_execl\n", __real_execl); |
| __real_clone = dlsym (dlflag, "clone"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_clone\n", __real_clone); |
| __real_posix_spawn = dlsym (dlflag, "posix_spawn"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_posix_spawn\n", |
| __real_posix_spawn); |
| __real_posix_spawnp = dlsym (dlflag, "posix_spawnp"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_posix_spawnp\n", |
| __real_posix_spawnp); |
| __real_popen = dlvsym (dlflag, "popen", SYS_POPEN_VERSION); |
| TprintfT (DBG_LT2, "init_lineage_intf()[%s] @0x%p __real_popen\n", |
| SYS_POPEN_VERSION, __real_popen); |
| #if ARCH(Intel) |
| __real_posix_spawn_2_15 = dlvsym (dlflag, "posix_spawn", SYS_POSIX_SPAWN_VERSION); |
| __real_posix_spawnp_2_15 = dlvsym (dlflag, "posix_spawnp", SYS_POSIX_SPAWN_VERSION); |
| #if WSIZE(32) |
| __real_popen_2_1 = __real_popen; |
| __real_popen_2_0 = dlvsym (dlflag, "popen", "GLIBC_2.0"); |
| __real_posix_spawn_2_2 = dlvsym (dlflag, "posix_spawn", "GLIBC_2.2"); |
| __real_posix_spawnp_2_2 = dlvsym (dlflag, "posix_spawnp", "GLIBC_2.2"); |
| #elif WSIZE(64) |
| __real_posix_spawn_2_2_5 = dlvsym (dlflag, "posix_spawn", "GLIBC_2.2.5"); |
| __real_posix_spawnp_2_2_5 = dlvsym (dlflag, "posix_spawnp", "GLIBC_2.2.5"); |
| #endif /* WSIZE() */ |
| #endif /* ARCH(Intel) */ |
| __real_grantpt = dlsym (dlflag, "grantpt"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_grantpt\n", __real_grantpt); |
| __real_ptsname = dlsym (dlflag, "ptsname"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_ptsname\n", __real_ptsname); |
| __real_system = dlsym (dlflag, "system"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_system\n", __real_system); |
| __real_setuid = dlsym (dlflag, "setuid"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_setuid\n", __real_setuid); |
| __real_seteuid = dlsym (dlflag, "seteuid"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_seteuid\n", __real_seteuid); |
| __real_setreuid = dlsym (dlflag, "setreuid"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_setreuid\n", __real_setreuid); |
| __real_setgid = dlsym (dlflag, "setgid"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_setgid\n", __real_setgid); |
| __real_setegid = dlsym (dlflag, "setegid"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_setegid\n", __real_setegid); |
| __real_setregid = dlsym (dlflag, "setregid"); |
| TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_setregid\n", __real_setregid); |
| return 0; |
| } |
| |
| /*------------------------------------------------------------------------ */ |
| /* Note: The following _prologue and _epilogue functions used to be dbx-visible. |
| |
| They are used to appropriately manage lineage-changing events, by |
| quiescing and re-enabling/re-setting experiment collection before and after, |
| and logging the lineage-change in the process/experiment undertaking it. |
| As shown by the interposition functions for fork, exec, etc., which follow, |
| the _prologue should be called immediately prior (such as a breakpoint |
| action defined at function entry) and the _epilogue called immediately |
| after (such as a breakpoint action defined at function return). |
| */ |
| |
| /* |
| Notes on MT from Solaris 10 man pthread_atfork: |
| |
| All multithreaded applications that call fork() in a POSIX |
| threads program and do more than simply call exec(2) in the |
| child of the fork need to ensure that the child is protected |
| from deadlock. |
| |
| Since the "fork-one" model results in duplicating only the |
| thread that called fork(), it is possible that at the time |
| of the call another thread in the parent owns a lock. This |
| thread is not duplicated in the child, so no thread will |
| unlock this lock in the child. Deadlock occurs if the sin- |
| gle thread in the child needs this lock. |
| |
| The problem is more serious with locks in libraries. Since |
| a library writer does not know if the application using the |
| library calls fork(), the library must protect itself from |
| such a deadlock scenario. If the application that links |
| with this library calls fork() and does not call exec() in |
| the child, and if it needs a library lock that may be held |
| by some other thread in the parent that is inside the |
| library at the time of the fork, the application deadlocks |
| inside the library. |
| */ |
| |
| static void |
| linetrace_ext_fork_prologue (const char *variant, char * n_lineage, int *following_fork) |
| { |
| TprintfT (DBG_LT0, "linetrace_ext_fork_prologue; variant=%s; new_lineage=%s; follow=%d\n", |
| variant, n_lineage, *following_fork); |
| __collector_env_print ("fork_prologue start"); |
| if (dbg_current_mode != FOLLOW_NONE) |
| TprintfT (DBG_LT0, "linetrace_ext_fork_prologue(%s) ERROR: dbg_current_mode=%d, changing to FOLLOW_FORK!\n", |
| variant, dbg_current_mode); |
| dbg_current_mode = FOLLOW_ON; |
| if (__collector_strncmp ((char *) variant, "clone", sizeof ("clone") - 1) == 0) |
| { |
| __collector_mutex_lock (&clone_lineage_lock); |
| CALL_UTIL (snprintf)(n_lineage, LT_MAXNAMELEN, "%s_C%d", curr_lineage, ++clone_linenum); |
| __collector_mutex_unlock (&clone_lineage_lock); |
| } |
| else |
| { |
| __collector_mutex_lock (&fork_lineage_lock); |
| CALL_UTIL (snprintf)(n_lineage, LT_MAXNAMELEN, "%s_f%d", curr_lineage, ++fork_linenum); |
| __collector_mutex_unlock (&fork_lineage_lock); |
| } |
| *following_fork = check_follow_fork (); |
| |
| /* write message before suspending, or it won't be written */ |
| hrtime_t ts = GETRELTIME (); |
| TprintfT (DBG_LT0, "linetrace_ext_fork_prologue; variant=%s; new_lineage=%s; follow=%d\n", |
| variant, n_lineage, *following_fork); |
| __collector_log_write ("<event kind=\"%s\" tstamp=\"%u.%09u\" variant=\"%s\" lineage=\"%s\" follow=\"%d\"/>\n", |
| SP_JCMD_DESC_START, |
| (unsigned) (ts / NANOSEC), (unsigned) (ts % NANOSEC), |
| variant, n_lineage, *following_fork); |
| __collector_ext_dispatcher_thread_timer_suspend (); |
| __collector_ext_hwc_lwp_suspend (); |
| __collector_env_print ("fork_prologue end"); |
| } |
| |
| static void |
| linetrace_ext_fork_epilogue (const char *variant, const pid_t ret, char * n_lineage, int *following_fork) |
| { |
| if (dbg_current_mode == FOLLOW_NONE) |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue(%s) ERROR: dbg_current_mode=%d!\n", |
| variant, dbg_current_mode); |
| /* compute descendant experiment name */ |
| char new_exp_name[LT_MAXPATHLEN]; |
| /* save exp_name to global var */ |
| if (!build_experiment_path (new_exp_name, sizeof (new_exp_name), n_lineage)) |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue(%s): ERROR SP_COLLECTOR_EXPNAME not set\n", n_lineage); |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue(%s):%d() returned %d %s; child experiment name = %s\n", |
| variant, *following_fork, ret, (ret ? "parent" : "child"), new_exp_name); |
| if (ret == 0) |
| { |
| /* *************************************child */ |
| __collector_env_print ("fork_epilogue child at start"); |
| /* start a new line */ |
| fork_linenum = 0; |
| __collector_mutex_init (&fork_lineage_lock); |
| clone_linenum = 0; |
| __collector_mutex_init (&clone_lineage_lock); |
| __collector_env_update (NULL); |
| __collector_env_print ("fork_epilogue child after env_update"); |
| __collector_clean_state (); |
| __collector_env_print ("fork_epilogue child after clean_slate"); |
| __collector_line_cleanup (); |
| __collector_env_print ("fork_epilogue child after line_cleanup"); |
| if (*following_fork) |
| { |
| /* stop recording this experiment, but preserve env vars */ |
| linetrace_dormant (); |
| __collector_env_print ("fork_epilogue child after linetrace_dormant"); |
| |
| //static char exp_name_env[LT_MAXPATHLEN]; |
| char * exp_name_env = CALL_UTIL (calloc)(LT_MAXPATHLEN, 1); |
| CALL_UTIL (snprintf)(exp_name_env, LT_MAXPATHLEN, "%s=%s", SP_COLLECTOR_EXPNAME, new_exp_name); |
| CALL_UTIL (putenv)(exp_name_env); |
| |
| const char *params = CALL_UTIL (getenv)(SP_COLLECTOR_PARAMS); |
| int ret; |
| if (new_exp_name == NULL) |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue: ERROR: getenv(%s) undefined -- new expt aborted!\n", |
| SP_COLLECTOR_EXPNAME); |
| else if (params == NULL) |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue: ERROR: getenv(%s) undefined -- new expt aborted!\n", |
| SP_COLLECTOR_PARAMS); |
| else if ((ret = __collector_open_experiment (new_exp_name, params, SP_ORIGIN_FORK))) |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue: ERROR: '%s' open failed, ret=%d\n", |
| new_exp_name, ret); |
| else |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue: opened(%s)\n", new_exp_name); |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue(%s) returning to *child*\n", variant); |
| } |
| else |
| { |
| /* disable current and further linetrace experiment resumption */ |
| TprintfT (DBG_LT0, "linetrace_ext_fork_epilogue(%s) child calling line_close\n", variant); |
| __collector_ext_line_close (); |
| } |
| __collector_env_print ("fork_epilogue child at end"); |
| /* *************************************end child */ |
| } |
| else |
| { |
| /* *************************************parent */ |
| __collector_env_print ("fork_epilogue parent at start"); |
| __collector_ext_dispatcher_thread_timer_resume (); |
| __collector_ext_hwc_lwp_resume (); |
| hrtime_t ts = GETRELTIME (); |
| char msg[256 + LT_MAXPATHLEN]; |
| if (ret >= 0) |
| CALL_UTIL (snprintf)(msg, sizeof (msg), "pid=%d", ret); |
| else |
| { |
| /* delete stillborn experiment? */ |
| char errmsg[256]; |
| strerror_r (errno, errmsg, sizeof (errmsg)); |
| CALL_UTIL (snprintf)(msg, sizeof (msg), "err %s", errmsg); |
| } |
| __collector_log_write ("<event kind=\"%s\" tstamp=\"%u.%09u\" variant=\"%s\" lineage=\"%s\" follow=\"%d\" msg=\"%s\"/>\n", |
| SP_JCMD_DESC_STARTED, |
| (unsigned) (ts / NANOSEC), (unsigned) (ts % NANOSEC), |
| variant, n_lineage, *following_fork, msg); |
| /* environment remains set for collection */ |
| __collector_env_print ("fork_epilogue parent at end"); |
| /* *************************************end parent */ |
| } |
| dbg_current_mode = FOLLOW_NONE; |
| *following_fork = 0; |
| } |
| |
| static char** |
| linetrace_ext_exec_prologue_end (const char *variant, const char* cmd_string, |
| char *const envp[], int following_exec) |
| { |
| char **coll_env; |
| TprintfT (DBG_LT0, "linetrace_ext_exec_prologue_end; variant=%s; cmd_string=%s; follow=%d\n", |
| variant, cmd_string, following_exec); |
| /* write message before suspending, or it won't be written */ |
| hrtime_t ts = GETRELTIME (); |
| __collector_log_write ("<event kind=\"%s\" tstamp=\"%u.%09u\" variant=\"%s\" lineage=\"%s\" follow=\"%d\" msg=\"%s\"/>\n", |
| SP_JCMD_EXEC_START, |
| (unsigned) (ts / NANOSEC), (unsigned) (ts % NANOSEC), |
| variant, new_lineage, following_exec, cmd_string); |
| if (following_exec) |
| { |
| coll_env = __collector_env_allocate (envp, 0); |
| __collector_env_update (coll_env); |
| extern char **environ; /* the process' actual environment */ |
| if (environ == envp) /* user selected process environment */ |
| environ = coll_env; |
| } |
| else |
| coll_env = (char**) envp; |
| __collector_env_printall ("linetrace_ext_exec_prologue_end", coll_env); |
| if (!CALL_UTIL (strstr)(variant, "posix_spawn")) |
| { |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 1; |
| __collector_suspend_experiment ("suspend_for_exec"); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 0; |
| } |
| if (CALL_UTIL (strstr)(variant, "posix_spawn")) |
| { |
| __collector_ext_dispatcher_thread_timer_suspend (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 1; |
| __collector_ext_hwc_lwp_suspend (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 0; |
| } |
| return (coll_env); |
| } |
| |
| static char** |
| linetrace_ext_exec_prologue (const char *variant, |
| const char* path, char *const argv[], |
| char *const envp[], int *following_exec) |
| { |
| char cmd_string[_POSIX_ARG_MAX] = {'\0'}; |
| |
| if (dbg_current_mode != FOLLOW_NONE) |
| TprintfT (DBG_LT0, "linetrace_ext_exec_prologue() ERROR: dbg_current_mode=%d, changing to FOLLOW_EXEC!\n", dbg_current_mode); |
| dbg_current_mode = FOLLOW_ON; |
| *following_exec = check_follow_exec (path); |
| if (path != NULL) |
| { |
| /* escape any newline, " or \ characters in the exec command */ |
| TprintfT (DBG_LT3, "linetrace_ext_exec_prologue(): arg0=%s\n", path); |
| /* leave space in log message for terminator (and header) */ |
| __collector_strlcpy (cmd_string, path, sizeof (cmd_string)); |
| size_t len; |
| unsigned argn = 0; |
| if (argv[0]) |
| { |
| char *p; |
| while (((p = argv[++argn]) != 0) && |
| (len = __collector_strlen (cmd_string)) < sizeof (cmd_string) - 2) |
| { |
| cmd_string[len++] = ' '; |
| __collector_strlcpy (cmd_string + len, p, sizeof (cmd_string) - len); |
| } |
| } |
| } |
| TprintfT (DBG_LT0, "linetrace_ext_exec_prologue(%s), lineage=%s, follow=%d, prog=%s, path=%s \n", |
| variant, new_lineage, *following_exec, cmd_string, path); |
| return linetrace_ext_exec_prologue_end (variant, cmd_string, envp, *following_exec); |
| } |
| |
| static void |
| linetrace_ext_exec_epilogue (const char *variant, char *const envp[], const int ret, int *following_exec) |
| { |
| /* For exec, this routine is only entered if the exec failed */ |
| /* However, posix_spawn() is expected to return */ |
| if (dbg_current_mode == FOLLOW_NONE) |
| TprintfT (DBG_LT0, "linetrace_ext_exec_epilogue() ERROR: dbg_current_mode=%d!\n", dbg_current_mode); |
| TprintfT (DBG_LT0, "linetrace_ext_exec_epilogue(%s):%d returned: %d, errno=%d\n", |
| variant, *following_exec, ret, errno); |
| if (!CALL_UTIL (strstr)(variant, "posix_spawn")) |
| { |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 1; |
| __collector_resume_experiment (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 0; |
| } |
| if (CALL_UTIL (strstr)(variant, "posix_spawn")) |
| { |
| __collector_ext_dispatcher_thread_timer_resume (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 1; |
| __collector_ext_hwc_lwp_resume (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 0; |
| } |
| hrtime_t ts = GETRELTIME (); |
| char msg[256]; |
| if (ret) |
| { |
| char errmsg[256]; |
| strerror_r (errno, errmsg, sizeof (errmsg)); |
| CALL_UTIL (snprintf)(msg, sizeof (msg), "err %s", errmsg); |
| } |
| else |
| CALL_UTIL (snprintf)(msg, sizeof (msg), "rc=%d", ret); |
| if (!CALL_UTIL (strstr)(variant, "posix_spawn")) |
| __collector_log_write ("<event kind=\"%s\" tstamp=\"%u.%09u\" variant=\"%s\" lineage=\"%s\" follow=\"%d\" msg=\"%s\"/>\n", |
| SP_JCMD_EXEC_ERROR, |
| (unsigned) (ts / NANOSEC), (unsigned) (ts % NANOSEC), |
| variant, new_lineage, *following_exec, msg); |
| if (envp == NULL) |
| TprintfT (DBG_LT0, "linetrace_ext_exec_epilogue() ERROR: envp NULL after %s!\n", variant); |
| dbg_current_mode = FOLLOW_NONE; |
| *following_exec = 0; |
| } |
| |
| static void |
| linetrace_ext_combo_prologue (const char *variant, const char *cmd, int *following_combo) |
| { |
| char cmd_string[_POSIX_ARG_MAX] = {'\0'}; |
| char execfile[_POSIX_ARG_MAX] = {'\0'}; |
| |
| if (dbg_current_mode != FOLLOW_NONE) |
| TprintfT (DBG_LT0, "linetrace_ext_combo_prologue() ERROR: dbg_current_mode=%d! changing to FOLLOW_ON\n", |
| dbg_current_mode); |
| dbg_current_mode = FOLLOW_ON; |
| if (cmd != NULL) |
| { |
| /* extract executable name from combo command */ |
| unsigned len = strcspn (cmd, " "); |
| __collector_strlcpy (execfile, cmd, len + 1); |
| |
| /* escape any newline, " or \ characters in the combo command */ |
| /* leave space in log message for terminator (and header) */ |
| __collector_strlcpy (cmd_string, cmd, sizeof (cmd_string)); |
| } |
| |
| *following_combo = check_follow_combo (execfile); |
| TprintfT (DBG_LT0, "linetrace_ext_combo_prologue(%s) follow=%d, prog=%s\n\n", |
| variant, *following_combo, cmd_string); |
| |
| /* Construct the lineage string for the new image */ |
| new_lineage[0] = 0; |
| __collector_strcat (new_lineage, "XXX"); |
| |
| /* write message before suspending, or it won't be written */ |
| hrtime_t ts = GETRELTIME (); |
| __collector_log_write ("<event kind=\"%s\" tstamp=\"%u.%09u\" variant=\"%s\" lineage=\"%s\" follow=\"%d\" msg=\"%s\"/>\n", |
| SP_JCMD_DESC_START, |
| (unsigned) (ts / NANOSEC), (unsigned) (ts % NANOSEC), |
| variant, new_lineage, *following_combo, cmd_string); |
| if (*following_combo) |
| { |
| __collector_env_update (NULL); |
| TprintfT (DBG_LT0, "linetrace_ext_combo_prologue(): Following %s(\"%s\")\n", variant, execfile); |
| } |
| __collector_ext_dispatcher_thread_timer_suspend (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 1; |
| __collector_ext_hwc_lwp_suspend (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 0; |
| } |
| |
| static void |
| linetrace_ext_combo_epilogue (const char *variant, const int ret, int *following_combo) |
| { |
| if (dbg_current_mode == FOLLOW_NONE) |
| TprintfT (DBG_LT0, "linetrace_ext_combo_epilogue() ERROR: dbg_current_mode=FOLLOW_NONE\n"); |
| TprintfT (DBG_LT0, "linetrace_ext_combo_epilogue(%s):%d() returned %d\n", |
| variant, *following_combo, ret); |
| __collector_ext_dispatcher_thread_timer_resume (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 1; |
| __collector_ext_hwc_lwp_resume (); |
| __collector_linetrace_shutdown_hwcs_6830763_XXXX = 0; |
| hrtime_t ts = GETRELTIME (); |
| __collector_log_write ("<event kind=\"%s\" tstamp=\"%u.%09u\" variant=\"%s\" follow=\"%d\" msg=\"rc=%d\"/>\n", |
| SP_JCMD_DESC_STARTED, |
| (unsigned) (ts / NANOSEC), (unsigned) (ts % NANOSEC), |
| variant, *following_combo, ret); |
| |
| dbg_current_mode = FOLLOW_NONE; |
| *following_combo = 0; |
| } |
| |
| /*------------------------------------------------------------- fork */ |
| pid_t fork () __attribute__ ((weak, alias ("__collector_fork"))); |
| pid_t _fork () __attribute__ ((weak, alias ("__collector_fork"))); |
| |
| pid_t |
| __collector_fork (void) |
| { |
| pid_t ret; |
| if (NULL_PTR (fork)) |
| { |
| TprintfT (DBG_LT0, "__collector_fork() calling init_lineage_intf()\n"); |
| init_lineage_intf (); |
| } |
| __collector_env_print ("__collector_fork start"); |
| int * guard = NULL; |
| int combo_flag = (line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0; |
| TprintfT (DBG_LT0, "__collector_fork() interposition: line_mode=%d combo=%d\n", |
| line_mode, combo_flag); |
| if ((line_mode != LM_TRACK_LINEAGE) || combo_flag) |
| { |
| TprintfT (DBG_LT0, "__collector_fork() not following, returning CALL_REAL(fork)()\n"); |
| return CALL_REAL (fork)(); |
| } |
| int following_fork = 0; |
| linetrace_ext_fork_prologue ("fork", new_lineage, &following_fork); |
| |
| /* since libpthread/fork ends up calling fork1, it's a combo */ |
| PUSH_REENTRANCE (guard); |
| ret = CALL_REAL (fork)(); |
| POP_REENTRANCE (guard); |
| linetrace_ext_fork_epilogue ("fork", ret, new_lineage, &following_fork); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------- vfork */ |
| /* vfork interposition in the usual sense is not possible, since vfork(2) |
| relies on specifics of the stack frames in the parent and child which |
| only work when the child's exec (or _exit) are in the same stack frame |
| as the vfork: this isn't the case when there's interposition on exec. |
| As a workaround, the interposing vfork calls fork1 instead of the real |
| vfork. Note that fork1 is somewhat less efficient than vfork, and requires |
| additional memory, which may result in a change of application behaviour |
| when libcollector is loaded (even when collection is not active), |
| affecting not only direct use of vfork by the subject application, |
| but also indirect use through system, popen, and the other combos. |
| */ |
| pid_t vfork () __attribute__ ((weak, alias ("__collector_vfork"))); |
| pid_t _vfork () __attribute__ ((weak, alias ("__collector_vfork"))); |
| |
| pid_t |
| __collector_vfork (void) |
| { |
| if (NULL_PTR (vfork)) |
| init_lineage_intf (); |
| |
| int * guard = NULL; |
| int combo_flag = (line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0; |
| |
| TprintfT (DBG_LT0, "__collector_vfork() interposing: line_mode=%d combo=%d\n", |
| line_mode, combo_flag); |
| if ((line_mode != LM_TRACK_LINEAGE) || combo_flag) |
| return CALL_REAL (fork)(); |
| |
| /* this warning is also appropriate for combos which use vfork, |
| however, let's assume this is achieved elsewhere */ |
| (void) __collector_log_write ("<event kind=\"%s\" id=\"%d\">%s</event>\n", SP_JCMD_CWARN, |
| COL_WARN_VFORK, "fork"); |
| |
| char new_lineage[LT_MAXNAMELEN]; |
| new_lineage[0] = 0; |
| int following_fork = 0; |
| linetrace_ext_fork_prologue ("vfork", new_lineage, &following_fork); |
| |
| pid_t ret = CALL_REAL (fork)(); |
| linetrace_ext_fork_epilogue ("vfork", ret, new_lineage, &following_fork); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------- execve */ |
| int execve () __attribute__ ((weak, alias ("__collector_execve"))); |
| |
| int |
| __collector_execve (const char* path, char *const argv[], char *const envp[]) |
| { |
| static char **coll_env = NULL; /* environment for collection */ |
| if (NULL_PTR (execve)) |
| init_lineage_intf (); |
| int * guard = NULL; |
| int combo_flag = (line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0; |
| TprintfT (DBG_LT0, |
| "__collector_execve(path=%s, argv[0]=%s, env[0]=%s) interposing: line_mode=%d combo=%d\n", |
| path ? path : "NULL", |
| argv ? (argv[0] ? argv[0] : "NULL") : "NULL", |
| envp ? (envp[0] ? envp[0] : "NULL") : "NULL", |
| line_mode, combo_flag); |
| if (line_mode == LM_CLOSED) /* ensure collection environment is sanitised */ |
| __collector_env_unset ((char**) envp); |
| if (line_mode != LM_TRACK_LINEAGE || combo_flag) |
| return CALL_REAL (execve)(path, argv, envp); |
| |
| int following_exec = 0; |
| coll_env = linetrace_ext_exec_prologue ("execve", path, argv, envp, &following_exec); |
| TprintfT (DBG_LT2, "__collector_execve(): coll_env=0x%p\n", coll_env); |
| __collector_env_printall ("__collector_execve", coll_env); |
| int ret = CALL_REAL (execve)(path, argv, coll_env); |
| linetrace_ext_exec_epilogue ("execve", envp, ret, &following_exec); |
| return ret; |
| } |
| |
| int execvp () __attribute__ ((weak, alias ("__collector_execvp"))); |
| |
| int |
| __collector_execvp (const char* file, char *const argv[]) |
| { |
| extern char **environ; /* the process' actual environment */ |
| char ** envp = environ; |
| if (NULL_PTR (execvp)) |
| init_lineage_intf (); |
| int * guard = NULL; |
| int combo_flag = (line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0; |
| TprintfT (DBG_LT0, |
| "__collector_execvp(file=%s, argv[0]=%s) interposing: line_mode=%d combo=%d\n", |
| file ? file : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", |
| line_mode, combo_flag); |
| if (line_mode == LM_CLOSED) /* ensure collection environment is sanitised */ |
| __collector_env_unset ((char**) envp); |
| if ((line_mode != LM_TRACK_LINEAGE) || combo_flag) |
| return CALL_REAL (execvp)(file, argv); |
| |
| int following_exec = 0; |
| #ifdef DEBUG |
| char **coll_env = /* environment for collection */ |
| #endif /* DEBUG */ |
| linetrace_ext_exec_prologue ("execvp", file, argv, envp, &following_exec); |
| TprintfT (DBG_LT0, "__collector_execvp(): coll_env=0x%p\n", coll_env); |
| |
| int ret = CALL_REAL (execvp)(file, argv); |
| linetrace_ext_exec_epilogue ("execvp", envp, ret, &following_exec); |
| return ret; |
| } |
| |
| int execv () __attribute__ ((weak, alias ("__collector_execv"))); |
| |
| int |
| __collector_execv (const char* path, char *const argv[]) |
| { |
| int ret; |
| extern char **environ; /* the process' actual environment */ |
| char ** envp = environ; |
| TprintfT (DBG_LT0, "__collector_execv(path=%s, argv[0]=%s) interposing: line_mode=%d combo=%d\n", |
| path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", |
| line_mode, get_combo_flag ()); |
| |
| ret = __collector_execve (path, argv, envp); |
| return ret; |
| } |
| |
| int execle (const char* path, const char *arg0, ...) __attribute__ ((weak, alias ("__collector_execle"))); |
| |
| int |
| __collector_execle (const char* path, const char *arg0, ...) |
| { |
| TprintfT (DBG_LT0, |
| "__collector_execle(path=%s, arg0=%s) interposing: line_mode=%d combo=%d\n", |
| path ? path : "NULL", arg0 ? arg0 : "NULL", |
| line_mode, get_combo_flag ()); |
| |
| char **argp; |
| va_list args; |
| char **argvec; |
| register char **environmentp; |
| int nargs = 0; |
| char *nextarg; |
| |
| va_start (args, arg0); |
| while (va_arg (args, char *) != (char *) 0) |
| nargs++; |
| |
| /* |
| * save the environment pointer, which is at the end of the |
| * variable argument list |
| */ |
| environmentp = va_arg (args, char **); |
| va_end (args); |
| |
| /* |
| * load the arguments in the variable argument list |
| * into the argument vector, and add the terminating null pointer |
| */ |
| va_start (args, arg0); |
| /* workaround for bugid 1242839 */ |
| argvec = (char **) alloca ((size_t) ((nargs + 2) * sizeof (char *))); |
| argp = argvec; |
| *argp++ = (char *) arg0; |
| while ((nextarg = va_arg (args, char *)) != (char *) 0) |
| *argp++ = nextarg; |
| va_end (args); |
| *argp = (char *) 0; |
| return __collector_execve (path, argvec, environmentp); |
| } |
| |
| int execlp (const char* file, const char *arg0, ...) __attribute__ ((weak, alias ("__collector_execlp"))); |
| |
| int |
| __collector_execlp (const char* file, const char *arg0, ...) |
| { |
| TprintfT (DBG_LT0, |
| "__collector_execlp(file=%s, arg0=%s) interposing: line_mode=%d combo=%d\n", |
| file ? file : "NULL", arg0 ? arg0 : "NULL", |
| line_mode, get_combo_flag ()); |
| char **argp; |
| va_list args; |
| char **argvec; |
| int nargs = 0; |
| char *nextarg; |
| |
| va_start (args, arg0); |
| while (va_arg (args, char *) != (char *) 0) |
| nargs++; |
| va_end (args); |
| |
| /* |
| * load the arguments in the variable argument list |
| * into the argument vector and add the terminating null pointer |
| */ |
| va_start (args, arg0); |
| |
| /* workaround for bugid 1242839 */ |
| argvec = (char **) alloca ((size_t) ((nargs + 2) * sizeof (char *))); |
| argp = argvec; |
| *argp++ = (char *) arg0; |
| while ((nextarg = va_arg (args, char *)) != (char *) 0) |
| *argp++ = nextarg; |
| va_end (args); |
| *argp = (char *) 0; |
| return __collector_execvp (file, argvec); |
| } |
| |
| int execl (const char* path, const char *arg0, ...) __attribute__ ((weak, alias ("__collector_execl"))); |
| |
| int |
| __collector_execl (const char* path, const char *arg0, ...) |
| { |
| TprintfT (DBG_LT0, |
| "__collector_execl(path=%s, arg0=%s) interposing: line_mode=%d combo=%d\n", |
| path ? path : "NULL", arg0 ? arg0 : "NULL", |
| line_mode, get_combo_flag ()); |
| char **argp; |
| va_list args; |
| char **argvec; |
| extern char **environ; |
| int nargs = 0; |
| char *nextarg; |
| va_start (args, arg0); |
| while (va_arg (args, char *) != (char *) 0) |
| nargs++; |
| va_end (args); |
| |
| /* |
| * load the arguments in the variable argument list |
| * into the argument vector and add the terminating null pointer |
| */ |
| va_start (args, arg0); |
| |
| /* workaround for bugid 1242839 */ |
| argvec = (char **) alloca ((size_t) ((nargs + 2) * sizeof (char *))); |
| argp = argvec; |
| *argp++ = (char *) arg0; |
| while ((nextarg = va_arg (args, char *)) != (char *) 0) |
| *argp++ = nextarg; |
| va_end (args); |
| *argp = (char *) 0; |
| return __collector_execve (path, argvec, environ); |
| } |
| |
| #include <spawn.h> |
| |
| /*-------------------------------------------------------- posix_spawn */ |
| #if ARCH(Intel) |
| // map interposed symbol versions |
| static int |
| __collector_posix_spawn_symver (int(real_posix_spawn) (), |
| pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]); |
| |
| SYMVER_ATTRIBUTE (__collector_posix_spawn_2_15, posix_spawn@GLIBC_2.15) |
| int |
| __collector_posix_spawn_2_15 (pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]) |
| { |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_posix_spawn_2_15@%p(path=%s, argv[0]=%s, env[0]=%s)\n", |
| CALL_REAL (posix_spawn_2_15), path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", envp ? (envp[0] ? envp[0] : "NULL") : "NULL"); |
| if (NULL_PTR (posix_spawn)) |
| init_lineage_intf (); |
| return __collector_posix_spawn_symver (CALL_REAL (posix_spawn_2_15), pidp, |
| path, file_actions, attrp, argv, envp); |
| } |
| |
| #if WSIZE(32) |
| SYMVER_ATTRIBUTE (__collector_posix_spawn_2_2, posix_spawn@GLIBC_2.2) |
| int |
| __collector_posix_spawn_2_2 (pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]) |
| { |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_posix_spawn_2_2@%p(path=%s, argv[0]=%s, env[0]=%s)\n", |
| CALL_REAL (posix_spawn_2_2), path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", envp ? (envp[0] ? envp[0] : "NULL") : "NULL"); |
| if (NULL_PTR (posix_spawn)) |
| init_lineage_intf (); |
| return __collector_posix_spawn_symver (CALL_REAL (posix_spawn_2_2), pidp, |
| path, file_actions, attrp, argv, envp); |
| } |
| |
| #else /* ^WSIZE(32) */ |
| SYMVER_ATTRIBUTE (__collector_posix_spawn_2_2_5, posix_spawn@GLIBC_2.2.5) |
| int |
| __collector_posix_spawn_2_2_5 (pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]) |
| { |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_posix_spawn_2_2_5@%p(path=%s, argv[0]=%s, env[0]=%s)\n", |
| CALL_REAL (posix_spawn_2_2_5), path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", envp ? (envp[0] ? envp[0] : "NULL") : "NULL"); |
| if (NULL_PTR (posix_spawn)) |
| init_lineage_intf (); |
| return __collector_posix_spawn_symver (CALL_REAL (posix_spawn_2_2_5), pidp, |
| path, file_actions, attrp, argv, envp); |
| } |
| #endif /* ^WSIZE(32) */ |
| |
| static int |
| __collector_posix_spawn_symver (int(real_posix_spawn) (), |
| #else /* ^ARCH(Intel) */ |
| int |
| __collector_posix_spawn ( |
| #endif /* ARCH() */ |
| pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]) |
| { |
| int ret; |
| static char **coll_env = NULL; /* environment for collection */ |
| if (NULL_PTR (posix_spawn)) |
| init_lineage_intf (); |
| if (NULL_PTR (posix_spawn)) |
| { |
| TprintfT (DBG_LT0, "__collector_posix_spawn(path=%s) interposing: ERROR, posix_spawn() not found by dlsym\n", |
| path ? path : "NULL"); |
| return -1; /* probably should set errno */ |
| } |
| int * guard = NULL; |
| int combo_flag = (line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0; |
| TprintfT (DBG_LT0, "__collector_posix_spawn(path=%s, argv[0]=%s, env[0]=%s) interposing: line_mode=%d combo=%d\n", |
| path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", envp ? (envp[0] ? envp[0] : "NULL") : "NULL", line_mode, combo_flag); |
| if (line_mode == LM_CLOSED) /* ensure collection environment is sanitised */ |
| __collector_env_unset ((char**) envp); |
| |
| if ((line_mode != LM_TRACK_LINEAGE) || combo_flag) |
| { |
| #if ARCH(Intel) |
| return (real_posix_spawn) (pidp, path, file_actions, attrp, argv, envp); |
| #else |
| return CALL_REAL (posix_spawn)(pidp, path, file_actions, attrp, argv, envp); |
| #endif |
| } |
| int following_exec = 0; |
| coll_env = linetrace_ext_exec_prologue ("posix_spawn", path, argv, envp, &following_exec); |
| TprintfT (DBG_LT0, "__collector_posix_spawn(): coll_env=0x%p\n", coll_env); |
| __collector_env_printall ("__collector_posix_spawn", coll_env); |
| PUSH_REENTRANCE (guard); |
| #if ARCH(Intel) |
| ret = (real_posix_spawn) (pidp, path, file_actions, attrp, argv, coll_env); |
| #else |
| ret = CALL_REAL (posix_spawn)(pidp, path, file_actions, attrp, argv, coll_env); |
| #endif |
| POP_REENTRANCE (guard); |
| linetrace_ext_exec_epilogue ("posix_spawn", envp, ret, &following_exec); |
| return ret; |
| } |
| |
| /*-------------------------------------------------------- posix_spawnp */ |
| #if ARCH(Intel) |
| // map interposed symbol versions |
| |
| static int |
| __collector_posix_spawnp_symver (int(real_posix_spawnp) (), pid_t *pidp, |
| const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]); |
| |
| SYMVER_ATTRIBUTE (__collector_posix_spawnp_2_15, posix_spawnp@GLIBC_2.15) |
| int // Common interposition |
| __collector_posix_spawnp_2_15 (pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]) |
| { |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_posix_spawnp_2_15@%p(path=%s, argv[0]=%s, env[0]=%s)\n", |
| CALL_REAL (posix_spawnp_2_15), path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", envp ? (envp[0] ? envp[0] : "NULL") : "NULL"); |
| if (NULL_PTR (posix_spawnp)) |
| init_lineage_intf (); |
| return __collector_posix_spawnp_symver (CALL_REAL (posix_spawnp_2_15), pidp, |
| path, file_actions, attrp, argv, envp); |
| } |
| |
| #if WSIZE(32) |
| |
| SYMVER_ATTRIBUTE (__collector_posix_spawnp_2_2, posix_spawnp@GLIBC_2.2) |
| int |
| __collector_posix_spawnp_2_2 (pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]) |
| { |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_posix_spawnp_2_2@%p(path=%s, argv[0]=%s, env[0]=%s)\n", |
| CALL_REAL (posix_spawnp_2_2), path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", envp ? (envp[0] ? envp[0] : "NULL") : "NULL"); |
| if (NULL_PTR (posix_spawnp)) |
| init_lineage_intf (); |
| return __collector_posix_spawnp_symver (CALL_REAL (posix_spawnp_2_2), pidp, |
| path, file_actions, attrp, argv, envp); |
| } |
| |
| #else /* ^WSIZE(32) */ |
| SYMVER_ATTRIBUTE (__collector_posix_spawnp_2_2_5, posix_spawnp@GLIBC_2.2.5) |
| int |
| __collector_posix_spawnp_2_2_5 (pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]) |
| { |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_posix_spawnp_2_2_5@%p(path=%s, argv[0]=%s, env[0]=%s)\n", |
| CALL_REAL (posix_spawnp_2_2_5), path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", envp ? (envp[0] ? envp[0] : "NULL") : "NULL"); |
| if (NULL_PTR (posix_spawnp)) |
| init_lineage_intf (); |
| return __collector_posix_spawnp_symver (CALL_REAL (posix_spawnp_2_2_5), pidp, |
| path, file_actions, attrp, argv, envp); |
| } |
| |
| #endif /* ^WSIZE(32) */ |
| |
| static int |
| __collector_posix_spawnp_symver (int(real_posix_spawnp) (), |
| #else /* ^ARCH(Intel) */ |
| int |
| __collector_posix_spawnp ( |
| #endif /* ARCH() */ |
| pid_t *pidp, const char *path, |
| const posix_spawn_file_actions_t *file_actions, |
| const posix_spawnattr_t *attrp, |
| char *const argv[], char *const envp[]){ |
| int ret; |
| static char **coll_env = NULL; /* environment for collection */ |
| if (NULL_PTR (posix_spawnp)) |
| init_lineage_intf (); |
| if (NULL_PTR (posix_spawnp)) |
| { |
| TprintfT (DBG_LT0, "__collector_posix_spawnp(path=%s) interposing: ERROR, posix_spawnp() not found by dlsym\n", |
| path ? path : "NULL"); |
| return -1; /* probably should set errno */ |
| } |
| int * guard = NULL; |
| int combo_flag = (line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0; |
| TprintfT (DBG_LT0, "__collector_posix_spawnp(path=%s, argv[0]=%s, env[0]=%s) interposing: line_mode=%d combo=%d\n", |
| path ? path : "NULL", argv ? (argv[0] ? argv[0] : "NULL") : "NULL", |
| envp ? (envp[0] ? envp[0] : "NULL") : "NULL", line_mode, combo_flag); |
| |
| if (line_mode == LM_CLOSED) /* ensure collection environment is sanitised */ |
| __collector_env_unset ((char**) envp); |
| if (line_mode != LM_TRACK_LINEAGE || combo_flag) |
| { |
| #if ARCH(Intel) |
| return (real_posix_spawnp) (pidp, path, file_actions, attrp, argv, envp); |
| #else |
| return CALL_REAL (posix_spawnp)(pidp, path, file_actions, attrp, argv, envp); |
| #endif |
| } |
| int following_exec = 0; |
| coll_env = linetrace_ext_exec_prologue ("posix_spawnp", path, argv, envp, &following_exec); |
| TprintfT (DBG_LT0, "__collector_posix_spawnp(): coll_env=0x%p\n", coll_env); |
| __collector_env_printall ("__collector_posix_spawnp", coll_env); |
| PUSH_REENTRANCE (guard); |
| #if ARCH(Intel) |
| ret = (real_posix_spawnp) (pidp, path, file_actions, attrp, argv, coll_env); |
| #else |
| ret = CALL_REAL (posix_spawnp)(pidp, path, file_actions, attrp, argv, coll_env); |
| #endif |
| POP_REENTRANCE (guard); |
| linetrace_ext_exec_epilogue ("posix_spawnp", envp, ret, &following_exec); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------- system */ |
| int system () __attribute__ ((weak, alias ("__collector_system"))); |
| |
| int |
| __collector_system (const char *cmd) |
| { |
| if (NULL_PTR (system)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, |
| "__collector_system(cmd=%s) interposing: line_mode=%d combo=%d\n", |
| cmd ? cmd : "NULL", line_mode, get_combo_flag ()); |
| int *guard = NULL; |
| if (line_mode == LM_TRACK_LINEAGE) |
| INIT_REENTRANCE (guard); |
| if (guard == NULL) |
| return CALL_REAL (system)(cmd); |
| int following_combo = 0; |
| linetrace_ext_combo_prologue ("system", cmd, &following_combo); |
| PUSH_REENTRANCE (guard); |
| int ret = CALL_REAL (system)(cmd); |
| POP_REENTRANCE (guard); |
| linetrace_ext_combo_epilogue ("system", ret, &following_combo); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------- popen */ |
| // map interposed symbol versions |
| #if ARCH(Intel) && WSIZE(32) |
| static FILE * |
| __collector_popen_symver (FILE*(real_popen) (), const char *cmd, const char *mode); |
| |
| SYMVER_ATTRIBUTE (__collector_popen_2_1, popen@GLIBC_2.1) |
| FILE * |
| __collector_popen_2_1 (const char *cmd, const char *mode) |
| { |
| if (NULL_PTR (popen)) |
| init_lineage_intf (); |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_popen_2_1@%p\n", CALL_REAL (popen_2_1)); |
| return __collector_popen_symver (CALL_REALF (popen_2_1), cmd, mode); |
| } |
| |
| SYMVER_ATTRIBUTE (__collector_popen_2_0, popen@GLIBC_2.0) |
| FILE * |
| __collector_popen_2_0 (const char *cmd, const char *mode) |
| { |
| if (NULL_PTR (popen)) |
| init_lineage_intf (); |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector_popen_2_0@%p\n", CALL_REAL (popen_2_0)); |
| return __collector_popen_symver (CALL_REALF (popen_2_0), cmd, mode); |
| } |
| |
| SYMVER_ATTRIBUTE (__collector__popen_2_1, _popen@GLIBC_2.1) |
| FILE * |
| __collector__popen_2_1 (const char *cmd, const char *mode) |
| { |
| if (NULL_PTR (popen)) |
| init_lineage_intf (); |
| TprintfT (DBG_LTT, "linetrace: GLIBC: __collector__popen_2_1@%p\n", CALL_REAL (popen_2_1)); |
| return __collector_popen_symver (CALL_REALF (popen_2_1), cmd, mode); |
| } |
| |
| SYMVER_ATTRIBUTE (__collector__popen_2_0, _popen@GLIBC_2.0) |
| FILE * |
| __collector__popen_2_0 (const char *cmd, const char *mode) |
| { |
| if (NULL_PTR (popen)) |
| init_lineage_intf (); |
| return __collector_popen_symver (CALL_REALF (popen_2_0), cmd, mode); |
| } |
| #else // WSIZE(64) |
| FILE * popen () __attribute__ ((weak, alias ("__collector_popen"))); |
| #endif |
| |
| #if ARCH(Intel) && WSIZE(32) |
| static FILE * |
| __collector_popen_symver (FILE*(real_popen) (), const char *cmd, const char *mode) |
| #else |
| |
| FILE * |
| __collector_popen (const char *cmd, const char *mode) |
| #endif |
| { |
| FILE *ret; |
| if (NULL_PTR (popen)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, |
| "__collector_popen(cmd=%s) interposing: line_mode=%d combo=%d\n", |
| cmd ? cmd : "NULL", line_mode, get_combo_flag ()); |
| int *guard = NULL; |
| if (line_mode == LM_TRACK_LINEAGE) |
| INIT_REENTRANCE (guard); |
| if (guard == NULL) |
| { |
| #if ARCH(Intel) && WSIZE(32) |
| return (real_popen) (cmd, mode); |
| #else |
| return CALL_REALF (popen)(cmd, mode); |
| #endif |
| } |
| int following_combo = 0; |
| linetrace_ext_combo_prologue ("popen", cmd, &following_combo); |
| PUSH_REENTRANCE (guard); |
| #if ARCH(Intel) && WSIZE(32) |
| ret = (real_popen) (cmd, mode); |
| #else |
| ret = CALL_REALF (popen)(cmd, mode); |
| #endif |
| POP_REENTRANCE (guard); |
| linetrace_ext_combo_epilogue ("popen", (ret == NULL) ? (-1) : 0, &following_combo); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------- grantpt */ |
| int grantpt () __attribute__ ((weak, alias ("__collector_grantpt"))); |
| |
| int |
| __collector_grantpt (const int fildes) |
| { |
| if (NULL_PTR (grantpt)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, |
| "__collector_grantpt(%d) interposing: line_mode=%d combo=%d\n", |
| fildes, line_mode, get_combo_flag ()); |
| int *guard = NULL; |
| if (line_mode == LM_TRACK_LINEAGE) |
| INIT_REENTRANCE (guard); |
| if (guard == NULL) |
| return CALL_REAL (grantpt)(fildes); |
| int following_combo = 0; |
| linetrace_ext_combo_prologue ("grantpt", "/usr/lib/pt_chmod", &following_combo); |
| PUSH_REENTRANCE (guard); |
| int ret = CALL_REAL (grantpt)(fildes); |
| POP_REENTRANCE (guard); |
| linetrace_ext_combo_epilogue ("grantpt", ret, &following_combo); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------- ptsname */ |
| char *ptsname () __attribute__ ((weak, alias ("__collector_ptsname"))); |
| |
| char * |
| __collector_ptsname (const int fildes) |
| { |
| if (NULL_PTR (ptsname)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, |
| "__collector_ptsname(%d) interposing: line_mode=%d combo=%d\n", |
| fildes, line_mode, get_combo_flag ()); |
| int *guard = NULL; |
| if (line_mode == LM_TRACK_LINEAGE) |
| INIT_REENTRANCE (guard); |
| if (guard == NULL) |
| return CALL_REALC (ptsname)(fildes); |
| int following_combo = 0; |
| linetrace_ext_combo_prologue ("ptsname", "/usr/lib/pt_chmod", &following_combo); |
| PUSH_REENTRANCE (guard); |
| char *ret = CALL_REALC (ptsname)(fildes); |
| POP_REENTRANCE (guard); |
| linetrace_ext_combo_epilogue ("ptsname", (ret == NULL) ? (-1) : 1, &following_combo); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------- clone */ |
| /* clone can be fork-like or pthread_create-like, depending on whether |
| * the flag CLONE_VM is set. If CLONE_VM is not set, then we interpose |
| * clone in the way similar to interposing fork; if CLONE_VM is set, |
| * then we interpose clone in the way similar to interposing pthread_create. |
| * One special case is not handled: when CLONE_VM is set but CLONE_THREAD |
| * is not, if the parent process exits earlier than the child process, |
| * experiment will close, losing data from child process. |
| */ |
| typedef struct __collector_clone_arg |
| { |
| int (*fn)(void *); |
| void * arg; |
| char * new_lineage; |
| int following_fork; |
| } __collector_clone_arg_t; |
| |
| static int |
| __collector_clone_fn (void *fn_arg) |
| { |
| int (*fn)(void *) = ((__collector_clone_arg_t*) fn_arg)->fn; |
| void * arg = ((__collector_clone_arg_t*) fn_arg)->arg; |
| char * new_lineage = ((__collector_clone_arg_t*) fn_arg)->new_lineage; |
| int following_fork = ((__collector_clone_arg_t*) fn_arg)->following_fork; |
| __collector_freeCSize (__collector_heap, fn_arg, sizeof (__collector_clone_arg_t)); |
| linetrace_ext_fork_epilogue ("clone", 0, new_lineage, &following_fork); |
| return fn (arg); |
| } |
| |
| int clone (int (*fn)(void *), void *, int, void *, ...) __attribute__ ((weak, alias ("__collector_clone"))); |
| |
| int |
| __collector_clone (int (*fn)(void *), void *child_stack, int flags, void *arg, |
| ... /* pid_t *ptid, struct user_desc *tls, pid_t *" ctid" */) |
| { |
| int ret; |
| va_list va; |
| if (flags & CLONE_VM) |
| { |
| va_start (va, arg); |
| ret = __collector_ext_clone_pthread (fn, child_stack, flags, arg, va); |
| va_end (va); |
| } |
| else |
| { |
| if (NULL_PTR (clone)) |
| init_lineage_intf (); |
| int *guard = NULL; |
| int combo_flag = (line_mode == LM_TRACK_LINEAGE) ? ((CHCK_REENTRANCE (guard)) ? 1 : 0) : 0; |
| TprintfT (DBG_LT0, "__collector_clone() interposition: line_mode=%d combo=%d\n", |
| line_mode, combo_flag); |
| char new_lineage[LT_MAXNAMELEN]; |
| int following_fork = 0; |
| __collector_clone_arg_t *funcinfo = __collector_allocCSize (__collector_heap, sizeof (__collector_clone_arg_t), 1); |
| (*funcinfo).fn = fn; |
| (*funcinfo).arg = arg; |
| (*funcinfo).new_lineage = new_lineage; |
| (*funcinfo).following_fork = 0; |
| pid_t * ptid = NULL; |
| struct user_desc * tls = NULL; |
| pid_t * ctid = NULL; |
| int num_args = 0; |
| va_start (va, arg); |
| if (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) |
| { |
| ptid = va_arg (va, pid_t *); |
| tls = va_arg (va, struct user_desc*); |
| ctid = va_arg (va, pid_t *); |
| num_args = 3; |
| } |
| else if (flags & CLONE_SETTLS) |
| { |
| ptid = va_arg (va, pid_t *); |
| tls = va_arg (va, struct user_desc*); |
| num_args = 2; |
| } |
| else if (flags & CLONE_PARENT_SETTID) |
| { |
| ptid = va_arg (va, pid_t *); |
| num_args = 1; |
| } |
| if ((line_mode != LM_TRACK_LINEAGE) || combo_flag || funcinfo == NULL) |
| { |
| switch (num_args) |
| { |
| case 3: |
| ret = CALL_REAL (clone)(fn, child_stack, flags, arg, ptid, tls, ctid); |
| break; |
| case 2: |
| ret = CALL_REAL (clone)(fn, child_stack, flags, arg, ptid, tls); |
| break; |
| case 1: |
| ret = CALL_REAL (clone)(fn, child_stack, flags, arg, ptid); |
| break; |
| default: |
| ret = CALL_REAL (clone)(fn, child_stack, flags, arg); |
| break; |
| } |
| |
| va_end (va); |
| return ret; |
| } |
| linetrace_ext_fork_prologue ("clone", new_lineage, &following_fork); |
| (*funcinfo).following_fork = following_fork; |
| switch (num_args) |
| { |
| case 3: |
| ret = CALL_REAL (clone)(__collector_clone_fn, child_stack, flags, funcinfo, ptid, tls, ctid); |
| break; |
| case 2: |
| ret = CALL_REAL (clone)(__collector_clone_fn, child_stack, flags, funcinfo, ptid, tls); |
| break; |
| case 1: |
| ret = CALL_REAL (clone)(__collector_clone_fn, child_stack, flags, funcinfo, ptid); |
| break; |
| default: |
| ret = CALL_REAL (clone)(__collector_clone_fn, child_stack, flags, funcinfo); |
| break; |
| } |
| va_end (va); |
| if (ret < 0) |
| __collector_freeCSize (__collector_heap, funcinfo, sizeof (__collector_clone_arg_t)); |
| TprintfT (DBG_LT0, "__collector_clone() interposing: pid=%d\n", ret); |
| linetrace_ext_fork_epilogue ("clone", ret, new_lineage, &following_fork); |
| } |
| return ret; |
| } |
| |
| /*-------------------------------------------------------------------- setuid */ |
| int setuid () __attribute__ ((weak, alias ("__collector_setuid"))); |
| int _setuid () __attribute__ ((weak, alias ("__collector_setuid"))); |
| |
| int |
| __collector_setuid (uid_t ruid) |
| { |
| if (NULL_PTR (setuid)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, "__collector_setuid(0x%x) interposing\n", ruid); |
| check_reuid_change (ruid, -1); |
| int ret = CALL_REAL (setuid)(ruid); |
| TprintfT (DBG_LT0, "__collector_setuid(0x%x) returning %d\n", ruid, ret); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------------- seteuid */ |
| int seteuid () __attribute__ ((weak, alias ("__collector_seteuid"))); |
| int _seteuid () __attribute__ ((weak, alias ("__collector_seteuid"))); |
| |
| int |
| __collector_seteuid (uid_t euid) |
| { |
| if (NULL_PTR (seteuid)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, "__collector_seteuid(0x%x) interposing\n", euid); |
| check_reuid_change (-1, euid); |
| int ret = CALL_REAL (seteuid)(euid); |
| TprintfT (DBG_LT0, "__collector_seteuid(0x%x) returning %d\n", euid, ret); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------------ setreuid */ |
| int setreuid () __attribute__ ((weak, alias ("__collector_setreuid"))); |
| int _setreuid () __attribute__ ((weak, alias ("__collector_setreuid"))); |
| |
| int |
| __collector_setreuid (uid_t ruid, uid_t euid) |
| { |
| if (NULL_PTR (setreuid)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, "__collector_setreuid(0x%x,0x%x) interposing\n", ruid, euid); |
| check_reuid_change (ruid, euid); |
| int ret = CALL_REAL (setreuid)(ruid, euid); |
| TprintfT (DBG_LT0, "__collector_setreuid(0x%x,0x%x) returning %d\n", ruid, euid, ret); |
| return ret; |
| } |
| |
| /*-------------------------------------------------------------------- setgid */ |
| int setgid () __attribute__ ((weak, alias ("__collector_setgid"))); |
| int _setgid () __attribute__ ((weak, alias ("__collector_setgid"))); |
| |
| int |
| __collector_setgid (gid_t rgid) |
| { |
| if (NULL_PTR (setgid)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, "__collector_setgid(0x%x) interposing\n", rgid); |
| check_regid_change (rgid, -1); |
| int ret = CALL_REAL (setgid)(rgid); |
| TprintfT (DBG_LT0, "__collector_setgid(0x%x) returning %d\n", rgid, ret); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------------- setegid */ |
| int setegid () __attribute__ ((weak, alias ("__collector_setegid"))); |
| int _setegid () __attribute__ ((weak, alias ("__collector_setegid"))); |
| |
| int |
| __collector_setegid (gid_t egid) |
| { |
| if (NULL_PTR (setegid)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, "__collector_setegid(0x%x) interposing\n", egid); |
| check_regid_change (-1, egid); |
| int ret = CALL_REAL (setegid)(egid); |
| TprintfT (DBG_LT0, "__collector_setegid(0x%x) returning %d\n", egid, ret); |
| return ret; |
| } |
| |
| /*------------------------------------------------------------------ setregid */ |
| int setregid () __attribute__ ((weak, alias ("__collector_setregid"))); |
| int _setregid () __attribute__ ((weak, alias ("__collector_setregid"))); |
| |
| int |
| __collector_setregid (gid_t rgid, gid_t egid) |
| { |
| if (NULL_PTR (setregid)) |
| init_lineage_intf (); |
| TprintfT (DBG_LT0, "__collector_setregid(0x%x,0x%x) interposing\n", rgid, egid); |
| check_regid_change (rgid, egid); |
| int ret = CALL_REAL (setregid)(rgid, egid); |
| TprintfT (DBG_LT0, "__collector_setregid(0x%x,0x%x) returning %d\n", rgid, egid, ret); |
| return ret; |
| } |
| |
| /*------------------------------------------------------- selective following */ |
| |
| static int |
| linetrace_follow_experiment (const char *follow_spec, const char *lineage_str, const char *progname) |
| { |
| regex_t regex_desc; |
| if (!follow_spec) |
| { |
| TprintfT (DBG_LT0, "linetrace_follow_experiment(): MATCHES NULL follow_spec\n"); |
| return 1; |
| } |
| int ercode = regcomp (®ex_desc, follow_spec, REG_EXTENDED | REG_NOSUB | REG_NEWLINE); |
| if (ercode) |
| { |
| // syntax error in parsing string |
| #ifdef DEBUG |
| char errbuf[256]; |
| regerror (ercode, ®ex_desc, errbuf, sizeof (errbuf)); |
| TprintfT (DBG_LT0, "linetrace_follow_experiment: regerror()=%s\n", errbuf); |
| #endif |
| return 1; |
| } |
| TprintfT (DBG_LT0, "linetrace_follow_experiment(): compiled spec='%s'\n", follow_spec); |
| if (lineage_str) |
| { |
| if (!regexec (®ex_desc, lineage_str, 0, NULL, 0)) |
| { |
| TprintfT (DBG_LT0, "linetrace_follow_experiment(): MATCHES lineage (follow_spec=%s,lineage=%s)\n", |
| follow_spec, lineage_str); |
| return 1; |
| } |
| } |
| if (progname) |
| { |
| if (!regexec (®ex_desc, progname, 0, NULL, 0)) |
| { |
| TprintfT (DBG_LT0, "linetrace_follow_experiment(): MATCHES progname (follow_spec=%s,progname=%s)\n", |
| follow_spec, progname); |
| return 1; |
| } |
| } |
| TprintfT (DBG_LT0, "linetrace_follow_experiment(): DOES NOT MATCH (follow_spec=%s,lineage=%s,progname=%s)\n", |
| follow_spec, lineage_str ? lineage_str : "NULL", |
| progname ? progname : "NULL"); |
| return 0; |
| } |