/* 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. */ /* * Central SIGPROF dispatcher to various module event handlers * (REALPROF profile, HWC check, overview sample, manual sample) */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "gp-defs.h" #include "gp-experiment.h" #include "collector.h" #include "collector_module.h" #include "tsd.h" #include "hwcdrv.h" #include "memmgr.h" static void collector_sigprof_dispatcher (int, siginfo_t*, void*); static int init_interposition_intf (); static int collector_timer_create (timer_t * ptimerid); static int collector_timer_settime (int period, timer_t timerid); static int collector_timer_gettime (timer_t timerid); static volatile int collector_sigprof_entries = 0; /* counter for SIGPROF signals in DISPATCH_TST mode */ static timer_t collector_master_thread_timerid = NULL; static collector_mutex_t collector_clone_libc_lock = COLLECTOR_MUTEX_INITIALIZER; static unsigned dispatcher_key = COLLECTOR_TSD_INVALID_KEY; static int (*__real_clone) (int (*fn)(void *), void *child_stack, int flags, void *arg, ...) = NULL; static int (*__real_timer_create) (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) = NULL; static int (*__real_timer_settime) (timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) = NULL; static int (*__real_timer_delete) (timer_t timerid) = NULL; static int (*__real_timer_gettime) (timer_t timerid, struct itimerspec *curr_value) = NULL; static int (*__real_pthread_create_2_34) (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL; static int (*__real_pthread_create_2_17) (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL; static int (*__real_pthread_create_2_2_5) (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL; static int (*__real_pthread_create_2_1) (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL; static int (*__real_pthread_create_2_0) (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL; static int (*__real_timer_create_2_34) (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) = NULL; static int (*__real_timer_create_2_17) (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) = NULL; static int (*__real_timer_create_2_3_3) (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) = NULL; static int (*__real_timer_create_2_2_5) (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) = NULL; static int (*__real_timer_create_2_2) (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) = NULL; int (*__real_pthread_sigmask_2_32) (int, const sigset_t *, sigset_t *) = NULL; int (*__real_pthread_sigmask_2_17) (int, const sigset_t *, sigset_t *) = NULL; int (*__real_pthread_sigmask_2_2_5) (int, const sigset_t *, sigset_t *) = NULL; int (*__real_pthread_sigmask_2_0) (int, const sigset_t *, sigset_t *) = NULL; /* Original SIGPROF handler which will be replaced with the dispatcher. Used * to properly interact with libaio, which uses SIGPROF as its SIGAIOCANCEL. */ static struct sigaction original_sigprof_handler; enum { DISPATCH_NYI = -1, /* dispatcher not yet installed */ DISPATCH_OFF = 0, /* dispatcher installed, but disabled */ DISPATCH_ON = 1, /* dispatcher installed, and enabled */ DISPATCH_TST = 2 /* dispatcher installed, and enabled in testing mode */ }; static struct sigaction sigaction_0; static int dispatch_mode = DISPATCH_NYI; /* controls SIGPROF dispatching */ static int itimer_period_requested = 0; /* dispatcher itimer period */ static int itimer_period_actual = 0; /* actual dispatcher itimer period */ static int (*__real_sigaction) (int signum, const struct sigaction *act, struct sigaction *oldact) = NULL; static int (*__real_setitimer) (int which, const struct itimerval *new_value, struct itimerval *old_value) = NULL; static int (*__real_libc_setitimer) (int which, const struct itimerval *new_value, struct itimerval *old_value) = NULL; static int (*__real_sigprocmask) (int how, const sigset_t *set, sigset_t *oldset) = NULL; static int (*__real_thr_sigsetmask) (int how, const sigset_t *iset, sigset_t *oset) = NULL; static int (*__real_pthread_sigmask) (int how, const sigset_t *set, sigset_t *oldset) = NULL; static int (*__real_pthread_create) (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) = NULL; /* * void collector_sigprof_dispatcher() * * Common SIGPROF event handler which dispatches events to appropriate * module handlers, if they are active for this collection and due. * Dispatch sequence, logic and handlers currently hardcoded in dispatcher. */ static void collector_sigprof_dispatcher (int sig, siginfo_t *info, void *context) { if (info == NULL || (info->si_code <= 0 && info->si_code != SI_TIMER)) { TprintfT (DBG_LT2, "collector_sigprof_dispatcher signal for %p\n", original_sigprof_handler.sa_handler); /* pass signal to previous handler */ /* watch for recursion, SIG_IGN, and SIG_DFL */ if (original_sigprof_handler.sa_handler == SIG_DFL) __collector_SIGDFL_handler (SIGPROF); else if (original_sigprof_handler.sa_handler != SIG_IGN && original_sigprof_handler.sa_sigaction != &collector_sigprof_dispatcher) { (original_sigprof_handler.sa_sigaction)(sig, info, context); TprintfT (DBG_LT2, "collector_sigprof_dispatcher handled\n"); } } else if (dispatch_mode == DISPATCH_ON) { #if ARCH(SPARC) ucontext_t uctxmem; ucontext_t *uctx = &uctxmem; uctx->uc_link = NULL; /* 23340823 signal handler third argument should point to a ucontext_t */ /* Convert sigcontext to ucontext_t on sparc-Linux */ struct sigcontext *sctx = (struct sigcontext*) context; #if WSIZE(32) uctx->uc_mcontext.gregs[REG_PC] = sctx->si_regs.pc; __collector_memcpy (&uctx->uc_mcontext.gregs[3], sctx->si_regs.u_regs, sizeof (sctx->si_regs.u_regs)); #else uctx->uc_mcontext.mc_gregs[MC_PC] = sctx->sigc_regs.tpc; __collector_memcpy (&uctx->uc_mcontext.mc_gregs[3], sctx->sigc_regs.u_regs, sizeof (sctx->sigc_regs.u_regs)); #endif /* WSIZE() */ #else /* not sparc-Linux */ ucontext_t *uctx = (ucontext_t*) context; #endif /* ARCH() */ TprintfT (DBG_LT3, "collector_sigprof_dispatcher dispatching signal\n"); /* XXXX the order of these checks/activities may need adjustment */ /* XXXX should also check (first) for a "cached" manual sample */ /* HWC check for each LWP: required even if collection is paused */ /* This should be first, otherwise it's likely to find the counters * stopped due to an event/overflow during some of the other activities. */ /* XXXX HWC check performed every time (skipping if HWC profiling inactive) * to avoid complexity of maintaining separate check times for each LWP */ __collector_ext_hwc_check (info, uctx); /* XXXX if sigemtpending, should perhaps skip __collector_ext_usage_sample * (and get it next time through) */ /* check for experiment past delay start */ if (__collector_delay_start != 0) { hrtime_t now = __collector_gethrtime (); if (__collector_delay_start < now) { TprintfT (0, "__collector_ext_usage_sample: now (%lld) > delay_start (%lld)\n", (now - __collector_start_time), (__collector_delay_start - __collector_start_time)); /* resume the data collection */ __collector_delay_start = 0; __collector_resume (); /* don't take a periodic sample, just let the resume sample cover it */ if (__collector_sample_period != 0) { /* this update should only be done for periodic samples */ while (__collector_next_sample < now) __collector_next_sample += ((hrtime_t) NANOSEC) * __collector_sample_period; } /* return; */ } } /* check for periodic sampling */ if (__collector_gethrtime () > __collector_next_sample) __collector_ext_usage_sample (PERIOD_SMPL, "periodic"); /* check for experiment past termination time */ if (__collector_exp_active && __collector_terminate_time != 0) { hrtime_t now = __collector_gethrtime (); if (__collector_terminate_time < now) { TprintfT (0, "__collector_ext_usage_sample: now (%lld) > terminate_time (%lld); closing experiment\n", (now - __collector_start_time), (__collector_terminate_time - __collector_start_time)); /* close the experiment */ __collector_close_experiment (); } } /* call the code to process the profile data, and generate the packet */ /* (must always be called, otherwise profile data must be aggregated, * but can be left till last, as already have the required data) */ __collector_ext_profile_handler (info, uctx); } else if (dispatch_mode == DISPATCH_TST) { collector_sigprof_entries++; return; } } /* * __collector_sigprof_install */ int __collector_sigprof_install () { TprintfT (DBG_LT2, "__collector_sigprof_install\n"); struct sigaction oact; if (__collector_sigaction (SIGPROF, NULL, &oact) != 0) return COL_ERROR_DISPINIT; if (oact.sa_sigaction == collector_sigprof_dispatcher) /* signal handler is already in place; we are probably in a fork-child */ TprintfT (DBG_LT1, "dispatcher: __collector_ext_dispatcher_install() collector_sigprof_dispatcher already installed\n"); else { struct sigaction c_act = sigaction_0; sigemptyset (&c_act.sa_mask); sigaddset (&c_act.sa_mask, HWCFUNCS_SIGNAL); /* block SIGEMT delivery in handler */ c_act.sa_sigaction = collector_sigprof_dispatcher; c_act.sa_flags = SA_RESTART | SA_SIGINFO; if (__collector_sigaction (SIGPROF, &c_act, &original_sigprof_handler)) return COL_ERROR_DISPINIT; } dispatch_mode = DISPATCH_OFF; /* don't dispatch yet */ TprintfT (DBG_LT2, "__collector_sigprof_install done\n"); return COL_ERROR_NONE; } /* * void __collector_ext_dispatcher_tsd_create_key() * * create tsd key for dispatcher */ void __collector_ext_dispatcher_tsd_create_key () { dispatcher_key = __collector_tsd_create_key (sizeof (timer_t), NULL, NULL); } /* * int __collector_ext_dispatcher_install() * * installs a common handler/dispatcher (and itimer) for SIGPROF events */ int __collector_ext_dispatcher_install () { int timer_period; TprintfT (DBG_LT2, "__collector_ext_dispatcher_install\n"); /* check period set for interval timer, which will be used as the basis * for all timed activities: if not set, no role for SIGPROF dispatcher */ if (itimer_period_requested <= 0) { TprintfT (DBG_LT1, "No interval timer set: skipping dispatcher install!\n"); return COL_ERROR_NONE; /* no itimer/dispatcher required */ } /* check for an existing interval timer */ if (collector_master_thread_timerid == NULL) if (collector_timer_create (&collector_master_thread_timerid) < 0) return COL_ERROR_ITMRINIT; timer_t *timeridptr = __collector_tsd_get_by_key (dispatcher_key); if (timeridptr != NULL) *timeridptr = collector_master_thread_timerid; // store for per thread timer stop/start TprintfT (DBG_LT3, "__collector_ext_dispatcher_install: collector_master_thread_timerid=%p\n", collector_master_thread_timerid); timer_period = collector_timer_gettime (collector_master_thread_timerid); if (timer_period > 0) { TprintfT (DBG_LT1, "Overriding app-set interval timer with period %d\n", timer_period); (void) __collector_log_write ("%d->%d\n", SP_JCMD_CWARN, COL_WARN_ITMRPOVR, timer_period, itimer_period_requested); } /* install the interval timer used for all timed activities */ if (collector_timer_settime (itimer_period_requested, collector_master_thread_timerid) < 0) return COL_ERROR_ITMRINIT; TprintfT (DBG_LT2, "__collector_ext_dispatcher_install done\n"); dispatch_mode = DISPATCH_ON; /* activate SIGPROF dispatch to event handlers */ return COL_ERROR_NONE; } int __collector_sigaction (int sig, const struct sigaction *nact, struct sigaction *oact) { TprintfT (DBG_LT1, "__collector_sigaction: %d, %p\n", sig, nact ? nact->sa_sigaction : NULL); if (NULL_PTR (sigaction)) init_interposition_intf (); /* Whether we change the signal handler in the kernel * or not make sure the real sigaction is aware about * our new handler (6227565) */ return CALL_REAL (sigaction)(sig, nact, oact); } /* * We have special dispatchers for SIGPROF and HWCFUNCS_SIGNAL to * decide whether the signal was intended for us or for the user. * One special case is SIGDFL, in which case we don't have a * user-function address to call. If the user did indeed set * default disposition for one of these signals and sent that * signal, we honor that action, even though it will lead to * termination. */ void __collector_SIGDFL_handler (int sig) { /* remove our dispatcher, replacing it with the default disposition */ struct sigaction act = sigaction_0; act.sa_handler = SIG_DFL; if (__collector_sigaction (sig, &act, NULL)) { /* XXXXXX what are we supposed to do here? we're committing suicide anyhow */ } /* resend the signal we intercepted earlier */ // XXXX Bug 18177509 - additional sigprof signal kills target program kill (getpid (), sig); } /* * suspend/resume timer per thread */ void __collector_ext_dispatcher_thread_timer_suspend () { timer_t * timeridptr = __collector_tsd_get_by_key (dispatcher_key); if (timeridptr != NULL && *timeridptr != NULL) (void) collector_timer_settime (0, *timeridptr); return; } int __collector_ext_dispatcher_thread_timer_resume () { timer_t * timeridptr = __collector_tsd_get_by_key (dispatcher_key); if (timeridptr == NULL) return -1; if (*timeridptr == NULL) { // timer id not initialized yet TprintfT (DBG_LT2, "__collector_ext_dispatcher_thread_timer_resume: timer not initialized yet, create it\n"); if (collector_timer_create (timeridptr) == -1) { TprintfT (0, "__collector_ext_dispatcher_thread_timer_resume(): WARNING: No timer created\n"); return -1; } } return collector_timer_settime (itimer_period_requested, *timeridptr); } void __collector_ext_dispatcher_suspend () { TprintfT (DBG_LT2, "__collector_ext_dispatcher_suspend\n"); if (dispatch_mode == DISPATCH_NYI) { TprintfT (0, "__collector_ext_dispatcher_suspend(): WARNING: No dispatcher installed\n"); return; } /* disable SIGPROF dispatching */ dispatch_mode = DISPATCH_OFF; /* disable the interval timer; ignore any failures */ __collector_ext_dispatcher_thread_timer_suspend (); return; } void __collector_ext_dispatcher_restart () { TprintfT (DBG_LT2, "__collector_ext_dispatcher_restart(ip=%d)\n", itimer_period_requested); if (dispatch_mode == DISPATCH_NYI) { TprintfT (0, "__collector_ext_dispatcher_restart(): WARNING: No dispatcher installed\n"); return; } /* restart the interval timer used for all timed activities */ if (__collector_ext_dispatcher_thread_timer_resume () == 0) dispatch_mode = DISPATCH_ON; /* re-activate SIGPROF dispatch to handlers */ return; } /* * void __collector_ext_dispatcher_deinstall() * * If installed, disables SIGPROF dispatch and interval timer. * Includes checks for last SIGPROF dispatch time, interval timer period, * and currently installed SIGPROF handler, with appropriate warnings logged. * The dispatcher remains installed to handle pending collector SIGPROFs and * forward non-collector SIGPROFs to the application's handler(s). * If the decision is ever made actually to deinstall the dispatcher, * consider bug 4183714 and what to do about any possible pending * SIGPROFs. */ void __collector_ext_dispatcher_deinstall () { TprintfT (DBG_LT1, "__collector_ext_dispatcher_deinstall()\n"); if (dispatch_mode == DISPATCH_NYI) { TprintfT (0, "__collector_ext_dispatcher_deinstall(): WARNING: No dispatcher installed\n"); return; } dispatch_mode = DISPATCH_OFF; /* disable SIGPROF dispatching */ /* verify that interval timer is still installed with expected period */ int timer_period = collector_timer_gettime (collector_master_thread_timerid); if (timer_period != itimer_period_actual) { TprintfT (DBG_LT2, "dispatcher: Collector interval timer period changed %d -> %d\n", itimer_period_actual, timer_period); if ((itimer_period_actual >= (timer_period + timer_period / 10)) || (itimer_period_actual <= (timer_period - timer_period / 10))) __collector_log_write ("%d -> %d\n", SP_JCMD_CWARN, COL_WARN_ITMRREP, itimer_period_actual, timer_period); else __collector_log_write ("%d -> %d\n", SP_JCMD_COMMENT, COL_WARN_PROFRND, itimer_period_actual, timer_period); } /* Verify that SIGPROF dispatcher is still installed. * (still required with sigaction interposition and management, * since interposition is not done for attach experiments) */ struct sigaction curr; if (__collector_sigaction (SIGPROF, NULL, &curr) == -1) TprintfT (0, "ERROR: dispatcher sigaction check failed: errno=%d\n", errno); else if (curr.sa_sigaction != collector_sigprof_dispatcher) { TprintfT (0, "ERROR: collector dispatcher replaced by %p!\n", curr.sa_handler); (void) __collector_log_write ("%p\n", SP_JCMD_CWARN, COL_WARN_SIGPROF, curr.sa_handler); } else TprintfT (DBG_LT2, "collector dispatcher integrity verified!\n"); /* disable the interval timer; ignore any failures */ if (collector_master_thread_timerid != NULL) { (void) CALL_REAL (timer_delete)(collector_master_thread_timerid); collector_master_thread_timerid = NULL; } dispatcher_key = COLLECTOR_TSD_INVALID_KEY; itimer_period_requested = 0; itimer_period_actual = 0; } /* * void __collector_ext_dispatcher_fork_child_cleanup() * * delete timer, clear timer interval */ void __collector_ext_dispatcher_fork_child_cleanup () { if (collector_master_thread_timerid != NULL) { (void) CALL_REAL (timer_delete)(collector_master_thread_timerid); collector_master_thread_timerid = NULL; } __collector_mutex_init (&collector_clone_libc_lock); dispatcher_key = COLLECTOR_TSD_INVALID_KEY; itimer_period_requested = 0; itimer_period_actual = 0; } /* * int __collector_ext_itimer_set (int rperiod) * * set itimer period, if not yet set to a positive number of microseconds, * (after rounding to sys_resolution if necessary) and return its value */ int __collector_ext_itimer_set (int rperiod) { int period; /* if rperiod is negative, force setting */ if (rperiod < 0) { itimer_period_actual = 0; period = -rperiod; } else period = rperiod; // ignore SIGPROF while testing itimer interval setting int saved = dispatch_mode; dispatch_mode = DISPATCH_OFF; if (collector_timer_create (&collector_master_thread_timerid) == -1) { TprintfT (0, "__collector_ext_itimer_set(): WARNING: No timer created\n"); return itimer_period_actual; } if (collector_timer_settime (period, collector_master_thread_timerid) == 0) { itimer_period_actual = collector_timer_gettime (collector_master_thread_timerid); (void) collector_timer_settime (0, collector_master_thread_timerid); /* XXXX unset for now */ itimer_period_requested = period; if (itimer_period_requested != itimer_period_actual) { TprintfT (DBG_LT2, " itimer period %d adjusted to %d\n", itimer_period_requested, itimer_period_actual); // (void) __collector_log_write("%d -> %d\n", // SP_JCMD_CWARN, COL_WARN_PROFRND, itimer_period_requested, itimer_period_actual); } else TprintfT (DBG_LT2, " itimer period %d accepted\n", period); } // restore dispatching SIGPROF handler dispatch_mode = saved; TprintfT (0, "__collector_ext_itimer_set(%d), requested=%d, actual=%d)\n", rperiod, itimer_period_requested, itimer_period_actual); return (itimer_period_actual); } static int collector_timer_gettime (timer_t timerid) { int timer_period; struct itimerspec itimer; if (timerid == NULL) return (0); // timer was not initialized if (CALL_REAL (timer_gettime)(timerid, &itimer) == -1) { /* this should never reasonably fail, so not worth logging */ TprintfT (DBG_LT1, "WARNING: timer_gettime failed: errno=%d\n", errno); return (-1); } timer_period = ((itimer.it_interval.tv_sec * NANOSEC) + itimer.it_interval.tv_nsec) / 1000; TprintfT (DBG_LT2, "collector_timer_gettime (period=%d)\n", timer_period); return (timer_period); } static int collector_timer_create (timer_t * ptimerid) { struct sigevent sigev; if (NULL_PTR (timer_create)) init_interposition_intf (); TprintfT (DBG_LT2, "collector_timer_settime(): timer_create is %p\n", __real_timer_create); sigev.sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL; sigev.sigev_signo = SIGPROF; sigev.sigev_value.sival_ptr = ptimerid; #if !defined(__MUSL_LIBC) sigev._sigev_un._tid = __collector_gettid (); #endif if (CALL_REAL (timer_create)(CLOCK_THREAD_CPUTIME_ID, &sigev, ptimerid) == -1) { TprintfT (DBG_LT2, "collector_timer_settime() failed! errno=%d\n", errno); return -1; } return 0; } static int collector_timer_settime (int period, timer_t timerid) { struct itimerspec itimer; if (NULL_PTR (timer_settime)) init_interposition_intf (); TprintfT (DBG_LT2, "collector_timer_settime(period=%d)\n", period); time_t NPM = 1000; itimer.it_interval.tv_sec = NPM * period / NANOSEC; itimer.it_interval.tv_nsec = (NPM * period) % NANOSEC; itimer.it_value = itimer.it_interval; if (CALL_REAL (timer_settime)(timerid, 0, &itimer, NULL) == -1) { TprintfT (DBG_LT2, "collector_timer_settime(%d) failed! errno=%d\n", period, errno); return -1; } return 0; } static void protect_profiling_signals (sigset_t* lset) { static unsigned int protected_sigprof = 0; static unsigned int protected_sigemt = 0; // T1 relies on thread signal masking, so best not to mess with it: // T1 users have already been warned about the dangers of its use if (__collector_libthread_T1) return; if (sigismember (lset, SIGPROF) && (dispatch_mode == DISPATCH_ON)) { TprintfT (0, "WARNING: ignoring %s block while profiling\n", "SIGPROF"); if (protected_sigprof == 0) __collector_log_write ("%s\n", SP_JCMD_CWARN, COL_WARN_SIGMASK, "SIGPROF"); sigdelset (lset, SIGPROF); protected_sigprof++; } if (sigismember (lset, HWCFUNCS_SIGNAL) && __collector_ext_hwc_active ()) { TprintfT (0, "WARNING: ignoring %s block while profiling\n", "SIGEMT"); if (protected_sigemt == 0) __collector_log_write ("%s\n", SP_JCMD_CWARN, COL_WARN_SIGMASK, HWCFUNCS_SIGNAL_STRING); sigdelset (lset, HWCFUNCS_SIGNAL); protected_sigemt++; } } static int init_interposition_intf () { if (__collector_dlsym_guard) return 1; void *dlflag; /* Linux requires RTLD_LAZY, Solaris can do just RTLD_NOLOAD */ void *handle = dlopen (SYS_LIBC_NAME, RTLD_LAZY | RTLD_NOLOAD); __real_setitimer = dlsym (RTLD_NEXT, "setitimer"); if (__real_setitimer == NULL) { __real_setitimer = dlsym (RTLD_DEFAULT, "setitimer"); if (__real_setitimer == NULL) { TprintfT (DBG_LT2, "init_interposition_intf() setitimer not found\n"); return 1; } dlflag = RTLD_DEFAULT; } else dlflag = RTLD_NEXT; TprintfT (DBG_LT2, "init_interposition_intf() using RTLD_%s\n", (dlflag == RTLD_DEFAULT) ? "DEFAULT" : "NEXT"); __real_sigaction = dlsym (dlflag, "sigaction"); /* also explicitly get libc.so/setitimer (as a backup) */ __real_libc_setitimer = dlsym (handle, "setitimer"); __real_sigprocmask = dlsym (dlflag, "sigprocmask"); __real_thr_sigsetmask = dlsym (dlflag, "thr_sigsetmask"); __real_pthread_sigmask_2_32 = dlvsym (dlflag, "pthread_sigmask", "GLIBC_2.32"); __real_pthread_sigmask_2_17 = dlvsym (dlflag, "pthread_sigmask", "GLIBC_2.17"); __real_pthread_sigmask_2_2_5 = dlvsym (dlflag, "pthread_sigmask", "GLIBC_2.2.5"); __real_pthread_sigmask_2_0 = dlvsym (dlflag, "pthread_sigmask", "GLIBC_2.0"); if (__real_pthread_sigmask_2_32) __real_pthread_sigmask = __real_pthread_sigmask_2_32; else if (__real_pthread_sigmask_2_17) __real_pthread_sigmask = __real_pthread_sigmask_2_17; else if (__real_pthread_sigmask_2_2_5) __real_pthread_sigmask = __real_pthread_sigmask_2_2_5; else if (__real_pthread_sigmask_2_0) __real_pthread_sigmask = __real_pthread_sigmask_2_0; else __real_pthread_sigmask = dlsym (dlflag, "pthread_sigmask"); __real_pthread_create_2_34 = dlvsym (dlflag, "pthread_create", "GLIBC_2.34"); __real_pthread_create_2_17 = dlvsym (dlflag, "pthread_create", "GLIBC_2.17"); __real_pthread_create_2_2_5 = dlvsym (dlflag, "pthread_create", "GLIBC_2.2.5"); __real_pthread_create_2_1 = dlvsym (dlflag, "pthread_create", "GLIBC_2.1"); __real_pthread_create_2_0 = dlvsym (dlflag, "pthread_create", "GLIBC_2.0"); if (__real_pthread_create_2_34) __real_pthread_create = __real_pthread_create_2_34; else if (__real_pthread_create_2_17) __real_pthread_create = __real_pthread_create_2_17; else if (__real_pthread_create_2_2_5) __real_pthread_create = __real_pthread_create_2_2_5; else if (__real_pthread_create_2_1) __real_pthread_create = __real_pthread_create_2_1; else if (__real_pthread_create_2_0) __real_pthread_create = __real_pthread_create_2_0; else __real_pthread_create = dlsym (dlflag, "pthread_create"); __real_timer_create_2_34 = dlvsym (dlflag, "timer_create", "GLIBC_2.34"); __real_timer_create_2_17 = dlvsym (dlflag, "timer_create", "GLIBC_2.17"); __real_timer_create_2_3_3 = dlvsym (dlflag, "timer_create", "GLIBC_2.3.3"); __real_timer_create_2_2_5 = dlvsym (dlflag, "timer_create", "GLIBC_2.2.5"); __real_timer_create_2_2 = dlvsym (dlflag, "timer_create", "GLIBC_2.2"); if (__real_timer_create_2_34) __real_timer_create = __real_timer_create_2_34; else if (__real_timer_create_2_17) __real_timer_create = __real_timer_create_2_17; else if (__real_timer_create_2_3_3) __real_timer_create = __real_timer_create_2_3_3; else if (__real_timer_create_2_2_5) __real_timer_create = __real_timer_create_2_2_5; else if (__real_timer_create_2_2) __real_timer_create = __real_timer_create_2_2; else __real_timer_create = dlsym (dlflag, "timer_create"); void *t; if ((t = dlvsym (dlflag, "timer_settime", "GLIBC_2.34")) != NULL) __real_timer_settime = t; else if ((t = dlvsym (dlflag, "timer_settime", "GLIBC_2.17")) != NULL) __real_timer_settime = t; else if ((t = dlvsym (dlflag, "timer_settime", "GLIBC_2.3.3")) != NULL) __real_timer_settime = t; else if ((t = dlvsym (dlflag, "timer_settime", "GLIBC_2.2.5")) != NULL) __real_timer_settime = t; else if ((t = dlvsym (dlflag, "timer_settime", "GLIBC_2.0")) != NULL) __real_timer_settime = t; else __real_timer_settime = dlsym (dlflag, "timer_settime"); if ((t = dlvsym (dlflag, "timer_delete", "GLIBC_2.34")) != NULL) __real_timer_delete = t; else if ((t = dlvsym (dlflag, "timer_delete", "GLIBC_2.17")) != NULL) __real_timer_delete = t; else if ((t = dlvsym (dlflag, "timer_delete", "GLIBC_2.3.3")) != NULL) __real_timer_delete = t; else if ((t = dlvsym (dlflag, "timer_delete", "GLIBC_2.2.5")) != NULL) __real_timer_delete = t; else if ((t = dlvsym (dlflag, "timer_delete", "GLIBC_2.2")) != NULL) __real_timer_delete = t; else __real_timer_delete = dlsym (dlflag, "timer_delete"); if ((t = dlvsym (dlflag, "timer_gettime", "GLIBC_2.34")) != NULL) __real_timer_gettime = t; else if ((t = dlvsym (dlflag, "timer_gettime", "GLIBC_2.17")) != NULL) __real_timer_gettime = t; else if ((t = dlvsym (dlflag, "timer_gettime", "GLIBC_2.3.3")) != NULL) __real_timer_gettime = t; else if ((t = dlvsym (dlflag, "timer_gettime", "GLIBC_2.2.5")) != NULL) __real_timer_gettime = t; else if ((t = dlvsym (dlflag, "timer_gettime", "GLIBC_2.0")) != NULL) __real_timer_gettime = t; else __real_timer_gettime = dlsym (dlflag, "timer_gettime"); __real_clone = dlsym (dlflag, "clone"); #define PR_FUNC(f) TprintfT (DBG_LT2, " dispetcher.c: " #f ": @%p\n", f) PR_FUNC (__real_clone); PR_FUNC (__real_libc_setitimer); PR_FUNC (__real_pthread_create); PR_FUNC (__real_pthread_create_2_0); PR_FUNC (__real_pthread_create_2_1); PR_FUNC (__real_pthread_create_2_17); PR_FUNC (__real_pthread_create_2_2_5); PR_FUNC (__real_pthread_create_2_34); PR_FUNC (__real_pthread_sigmask); PR_FUNC (__real_pthread_sigmask_2_0); PR_FUNC (__real_pthread_sigmask_2_2_5); PR_FUNC (__real_pthread_sigmask_2_17); PR_FUNC (__real_pthread_sigmask_2_32); PR_FUNC (__real_setitimer); PR_FUNC (__real_sigaction); PR_FUNC (__real_sigprocmask); PR_FUNC (__real_thr_sigsetmask); PR_FUNC (__real_timer_create); PR_FUNC (__real_timer_create_2_17); PR_FUNC (__real_timer_create_2_2); PR_FUNC (__real_timer_create_2_2_5); PR_FUNC (__real_timer_create_2_3_3); PR_FUNC (__real_timer_create_2_34); PR_FUNC (__real_timer_delete); PR_FUNC (__real_timer_gettime); PR_FUNC (__real_timer_settime); return 0; } /*------------------------------------------------------------- sigaction */ /* NB: need a global interposing function called "sigaction" */ int sigaction (int sig, const struct sigaction *nact, struct sigaction *oact) { int ret = 0; int err = 0; if (NULL_PTR (sigaction)) err = init_interposition_intf (); if (err) return -1; TprintfT (DBG_LT3, "sigaction(sig=%02d, nact=%p) interposing\n", sig, nact); if (sig == SIGPROF && dispatch_mode != DISPATCH_NYI) { if (oact != NULL) { oact->sa_handler = original_sigprof_handler.sa_handler; oact->sa_mask = original_sigprof_handler.sa_mask; oact->sa_flags = original_sigprof_handler.sa_flags; } if (nact != NULL) { original_sigprof_handler.sa_handler = nact->sa_handler; original_sigprof_handler.sa_mask = nact->sa_mask; original_sigprof_handler.sa_flags = nact->sa_flags; TprintfT (DBG_LT1, "dispatcher: new sigaction(sig=%02d) set\n", sig); } } else if (sig == HWCFUNCS_SIGNAL) ret = collector_sigemt_sigaction (nact, oact); else { if (sig != SIGCHLD || collector_sigchld_sigaction (nact, oact)) ret = CALL_REAL (sigaction)(sig, nact, oact); TprintfT (DBG_LT3, "Real sigaction(sig=%02d) returned %d (oact=%p)\n", sig, ret, oact); /* but check for other important signals */ /* check for sample and pause/resume signals; give warning once, if need be */ if ((sig == __collector_sample_sig) && (__collector_sample_sig_warn == 0)) { /* give user a warning */ (void) __collector_log_write ("%d\n", SP_JCMD_CWARN, COL_WARN_SAMPSIGUSED, __collector_sample_sig); __collector_sample_sig_warn = 1; } if ((sig == __collector_pause_sig) && (__collector_pause_sig_warn == 0)) { /* give user a warning */ (void) __collector_log_write ("%d\n", SP_JCMD_CWARN, COL_WARN_PAUSESIGUSED, __collector_pause_sig); __collector_pause_sig_warn = 1; } } TprintfT (DBG_LT3, "sigaction() returning %d (oact=%p)\n", ret, oact); return ret; } /* * In addition to interposing on sigaction(), should we also interpose * on other important signal functions like signal() or sigset()? * - On Solaris, those other functions apparently call sigaction(). * So, we only have to interpose on it. * - On Linux, we should perhaps interpose on these other functions, * but they are less portable than sigaction() and deprecated or even obsolete. * So, we interpose, but don't overly worry about doing a good job. */ sighandler_t signal (int sig, sighandler_t handler) { struct sigaction nact; struct sigaction oact; TprintfT (DBG_LT3, "signal(sig=%02d, handler=%p) interposing\n", sig, handler); sigemptyset (&nact.sa_mask); nact.sa_handler = handler; nact.sa_flags = SA_RESTART; if (sigaction (sig, &nact, &oact)) return SIG_ERR; TprintfT (DBG_LT3, "signal() returning %p\n", oact.sa_handler); return oact.sa_handler; } sighandler_t sigset (int sig, sighandler_t handler) { TprintfT (DBG_LT3, "sigset(sig=%02d, handler=%p) interposing\n", sig, handler); return signal (sig, handler); } /*------------------------------------------------------------- timer_create */ // map interposed symbol versions static int gprofng_timer_create (int (real_func) (clockid_t, struct sigevent *, timer_t *), clockid_t clockid, struct sigevent *sevp, timer_t *timerid) { // collector reserves SIGPROF if (sevp == NULL || sevp->sigev_notify != SIGEV_SIGNAL || sevp->sigev_signo != SIGPROF) { int ret = real_func (clockid, sevp, timerid); TprintfT (DBG_LT2, "timer_create @%p (%d) ret=%d\n", real_func, (int) clockid, ret); return ret; } /* log that application's timer_create request is overridden */ (void) __collector_log_write ("%d\n", SP_JCMD_CWARN, COL_WARN_ITMROVR, -1); errno = EBUSY; TprintfT (DBG_LT2, "timer_create @%p (%d) ret=%d\n", real_func, (int) clockid, -1); \ return -1; } #define DCL_TIMER_CREATE(dcl_f) \ int dcl_f (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) \ { \ if (__real_timer_create == NULL) \ init_interposition_intf (); \ return gprofng_timer_create (__real_timer_create, clockid, sevp, timerid); \ } DCL_FUNC_VER (DCL_TIMER_CREATE, timer_create_2_34, timer_create@GLIBC_2.34) DCL_FUNC_VER (DCL_TIMER_CREATE, timer_create_2_17, timer_create@GLIBC_2.17) DCL_FUNC_VER (DCL_TIMER_CREATE, timer_create_2_3_3, timer_create@GLIBC_2.3.3) DCL_FUNC_VER (DCL_TIMER_CREATE, timer_create_2_2_5, timer_create@GLIBC_2.2.5) DCL_FUNC_VER (DCL_TIMER_CREATE, timer_create_2_2, timer_create@GLIBC_2.2) DCL_TIMER_CREATE (timer_create) /*------------------------------------------------------------- setitimer */ int _setitimer (int which, const struct itimerval *nval, struct itimerval *oval) { int ret; int period; if (NULL_PTR (setitimer)) init_interposition_intf (); if (nval == NULL) period = -1; else period = (nval->it_interval.tv_sec * MICROSEC) + nval->it_interval.tv_usec; TprintfT (DBG_LT1, "setitimer(which=%d,nval=%dus) interposing\n", which, period); /* collector reserves ITIMER_REALPROF for its own use, and ITIMER_PROF * uses the same signal (SIGPROF) so it must also be reserved */ if (((which != ITIMER_REALPROF) && (which != ITIMER_PROF)) || (nval == NULL)) { ret = CALL_REAL (setitimer)(which, nval, oval); if (oval == NULL) period = -1; else period = (oval->it_interval.tv_sec * MICROSEC) + oval->it_interval.tv_usec; TprintfT (DBG_LT2, "Real setitimer(%d) returned %d (oval=%dus)\n", which, ret, period); return ret; } /* log that application's setitimer request is overridden */ (void) __collector_log_write ("%d\n", SP_JCMD_CWARN, COL_WARN_ITMROVR, period); if (oval == NULL) period = -1; else { getitimer (which, oval); /* return current itimer setting */ period = (oval->it_interval.tv_sec * MICROSEC) + oval->it_interval.tv_usec; } ret = -1; errno = EBUSY; TprintfT (DBG_LT2, "setitimer() returning %d (oval=%dus)\n", ret, period); return ret; } /*--------------------------------------------------------------- sigprocmask */ int __collector_sigprocmask (int how, const sigset_t* iset, sigset_t* oset) { int err = 0; if (NULL_PTR (sigprocmask)) err = init_interposition_intf (); if (err) return -1; TprintfT (DBG_LT2, "__collector_sigprocmask(%d) interposing\n", how); sigset_t lsigset; sigset_t* lset = NULL; if (iset) { lsigset = *iset; lset = &lsigset; if ((how == SIG_BLOCK) || (how == SIG_SETMASK)) protect_profiling_signals (lset); } int ret = CALL_REAL (sigprocmask)(how, lset, oset); TprintfT (DBG_LT2, "__collector_sigprocmask(%d) returning %d\n", how, ret); return ret; } /*------------------------------------------------------------ thr_sigsetmask */ int __collector_thr_sigsetmask (int how, const sigset_t* iset, sigset_t* oset) { if (NULL_PTR (thr_sigsetmask)) init_interposition_intf (); TprintfT (DBG_LT1, "__collector_thr_sigsetmask(%d) interposing\n", how); sigset_t lsigset; sigset_t* lset = NULL; if (iset) { lsigset = *iset; lset = &lsigset; if ((how == SIG_BLOCK) || (how == SIG_SETMASK)) protect_profiling_signals (lset); } int ret = CALL_REAL (thr_sigsetmask)(how, lset, oset); TprintfT (DBG_LT1, "__collector_thr_sigsetmask(%d) returning %d\n", how, ret); return ret; } /*----------------------------------------------------------- pthread_sigmask */ // map interposed symbol versions static int gprofng_pthread_sigmask (int (real_func) (int, const sigset_t *, sigset_t*), int how, const sigset_t *iset, sigset_t* oset) { sigset_t lsigset; sigset_t* lset = NULL; if (iset) { lsigset = *iset; lset = &lsigset; if (how == SIG_BLOCK || how == SIG_SETMASK) protect_profiling_signals (lset); } int ret = (real_func) (how, lset, oset); TprintfT (DBG_LT1, "real_pthread_sigmask @%p (%d) ret=%d\n", real_func, how, ret); return ret; } #define DCL_PTHREAD_SIGMASK(dcl_f) \ int dcl_f (int how, const sigset_t *iset, sigset_t* oset) \ { \ if (__real_pthread_sigmask == NULL) \ init_interposition_intf (); \ return gprofng_pthread_sigmask (__real_pthread_sigmask, how, iset, oset); \ } DCL_FUNC_VER (DCL_PTHREAD_SIGMASK, pthread_sigmask_2_32, pthread_sigmask@GLIBC_2.32) DCL_FUNC_VER (DCL_PTHREAD_SIGMASK, pthread_sigmask_2_17, pthread_sigmask@GLIBC_2.17) DCL_FUNC_VER (DCL_PTHREAD_SIGMASK, pthread_sigmask_2_2_5, pthread_sigmask@GLIBC_2.2.5) DCL_FUNC_VER (DCL_PTHREAD_SIGMASK, pthread_sigmask_2_0, pthread_sigmask@GLIBC_2.0) DCL_PTHREAD_SIGMASK (pthread_sigmask) /*----------------------------------------------------------- pthread_create */ typedef struct _CollectorArgs { void *(*func)(void*); void *arg; void *stack; int isPthread; } CollectorArgs; static void * collector_root (void *cargs) { /* save the real arguments and free cargs */ void *(*func)(void*) = ((CollectorArgs*) cargs)->func; void *arg = ((CollectorArgs*) cargs)->arg; void *stack = ((CollectorArgs*) cargs)->stack; int isPthread = ((CollectorArgs*) cargs)->isPthread; __collector_freeCSize (__collector_heap, cargs, sizeof (CollectorArgs)); /* initialize tsd for this thread */ if (__collector_tsd_allocate () == 0) /* init tsd for unwind, called right after __collector_tsd_allocate()*/ __collector_ext_unwind_key_init (isPthread, stack); if (!isPthread) __collector_mutex_lock (&collector_clone_libc_lock); /* set the profile timer */ timer_t *timeridptr = __collector_tsd_get_by_key (dispatcher_key); timer_t timerid = NULL; if (timeridptr != NULL) { collector_timer_create (timeridptr); if (*timeridptr != NULL) collector_timer_settime (itimer_period_requested, *timeridptr); timerid = *timeridptr; } int hwc_rc = __collector_ext_hwc_lwp_init (); if (!isPthread) __collector_mutex_unlock (&collector_clone_libc_lock); /* call the real function */ void *ret = func (arg); if (!isPthread) __collector_mutex_lock (&collector_clone_libc_lock); if (timerid != NULL) CALL_REAL (timer_delete)(timerid); if (!hwc_rc) /* pthread_kill not handled here */ __collector_ext_hwc_lwp_fini (); if (!isPthread) __collector_mutex_unlock (&collector_clone_libc_lock); /* if we have this chance, release tsd */ __collector_tsd_release (); return ret; } // map interposed symbol versions static int gprofng_pthread_create (int (real_func) (pthread_t *, const pthread_attr_t *, void *(*)(void *), void *), pthread_t *thread, const pthread_attr_t *attr, void *(*func)(void*), void *arg) { TprintfT (DBG_LTT, "gprofng_pthread_create @%p\n", real_func); if (dispatch_mode != DISPATCH_ON) return (real_func) (thread, attr, func, arg); CollectorArgs *cargs = __collector_allocCSize (__collector_heap, sizeof (CollectorArgs), 1); if (cargs == NULL) return (real_func) (thread, attr, func, arg); cargs->func = func; cargs->arg = arg; cargs->stack = NULL; cargs->isPthread = 1; int ret = (real_func) (thread, attr, &collector_root, cargs); if (ret) __collector_freeCSize (__collector_heap, cargs, sizeof (CollectorArgs)); TprintfT (DBG_LT1, "gprofng_pthread_create @%p returns %d\n", real_func, ret); return ret; } #define DCL_PTHREAD_CREATE(dcl_f) \ int dcl_f (pthread_t *thread, const pthread_attr_t *attr, \ void *(*func)(void*), void *arg) \ { \ if (__real_pthread_create == NULL) \ init_interposition_intf (); \ return gprofng_pthread_create (__real_pthread_create, thread, attr, func, arg); \ } DCL_FUNC_VER (DCL_PTHREAD_CREATE, pthread_create_2_34, pthread_create@GLIBC_2.34) DCL_FUNC_VER (DCL_PTHREAD_CREATE, pthread_create_2_17, pthread_create@GLIBC_2.17) DCL_FUNC_VER (DCL_PTHREAD_CREATE, pthread_create_2_2_5, pthread_create@GLIBC_2.2.5) DCL_FUNC_VER (DCL_PTHREAD_CREATE, pthread_create_2_1, pthread_create@GLIBC_2.1) DCL_FUNC_VER (DCL_PTHREAD_CREATE, pthread_create_2_0, pthread_create@GLIBC_2.0) DCL_PTHREAD_CREATE (pthread_create) int __collector_ext_clone_pthread (int (*fn)(void *), void *child_stack, int flags, void *arg, va_list va /* pid_t *ptid, struct user_desc *tls, pid_t *" ctid" */) { if (NULL_PTR (clone)) init_interposition_intf (); TprintfT (0, "clone thread interposing\n"); pid_t * ptid = NULL; struct user_desc * tls = NULL; pid_t * ctid = NULL; int num_args = 0; 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; } int ret = 0; if (dispatch_mode != DISPATCH_ON) { 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; } return ret; } CollectorArgs *cargs = __collector_allocCSize (__collector_heap, sizeof (CollectorArgs), 1); if (cargs == 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; } return ret; } cargs->func = (void *(*)(void*))fn; cargs->arg = arg; cargs->stack = child_stack; cargs->isPthread = 0; switch (num_args) { case 3: ret = CALL_REAL (clone)((int(*)(void*))collector_root, child_stack, flags, cargs, ptid, tls, ctid); break; case 2: ret = CALL_REAL (clone)((int(*)(void*))collector_root, child_stack, flags, cargs, ptid, tls); break; case 1: ret = CALL_REAL (clone)((int(*)(void*))collector_root, child_stack, flags, cargs, ptid); break; default: ret = CALL_REAL (clone)((int(*)(void*))collector_root, child_stack, flags, cargs); break; } if (ret < 0) __collector_freeCSize (__collector_heap, cargs, sizeof (CollectorArgs)); TprintfT (DBG_LT1, "clone thread returning %d\n", ret); return ret; } // weak symbols: int sigprocmask (int, const sigset_t*, sigset_t*) __attribute__ ((weak, alias ("__collector_sigprocmask"))); int thr_sigsetmask (int, const sigset_t*, sigset_t*) __attribute__ ((weak, alias ("__collector_thr_sigsetmask"))); __typeof(setitimer) setitimer __attribute__ ((weak, alias ("_setitimer")));