From bb368aad297fe3ad40cf397e6fc85aa471429a28 Mon Sep 17 00:00:00 2001 From: Vladimir Mezentsev Date: Fri, 11 Mar 2022 08:58:31 +0000 Subject: gprofng: a new GNU profiler top-level * Makefile.def: Add gprofng module. * configure.ac: Add --enable-gprofng option. * src-release.sh: Add gprofng. * Makefile.in: Regenerate. * configure: Regenerate. * gprofng: New directory. binutils * MAINTAINERS: Add gprofng maintainer. * README-how-to-make-a-release: Add gprofng. include. * collectorAPI.h: New file. * libcollector.h: New file. * libfcollector.h: New file. --- gprofng/libcollector/dispatcher.c | 1263 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1263 insertions(+) create mode 100644 gprofng/libcollector/dispatcher.c (limited to 'gprofng/libcollector/dispatcher.c') diff --git a/gprofng/libcollector/dispatcher.c b/gprofng/libcollector/dispatcher.c new file mode 100644 index 0000000..f9a7de1 --- /dev/null +++ b/gprofng/libcollector/dispatcher.c @@ -0,0 +1,1263 @@ +/* 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. */ + +/* + * 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 +#include + +#include "gp-defs.h" +#include "gp-experiment.h" +#include "collector.h" +#include "collector_module.h" +#include "tsd.h" +#include "hwcdrv.h" + + +/* TprintfT(,...) 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 + +static void collector_sigprof_dispatcher (int, siginfo_t*, void*); +static int init_interposition_intf (); +#include "memmgr.h" +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 void *__real_clone = NULL; +static void *__real_timer_create = NULL; +static void *__real_timer_settime = NULL; +static void *__real_timer_delete = NULL; +static void *__real_timer_gettime = NULL; +#if ARCH(Intel) && WSIZE(32) +static void *__real_pthread_create_2_1 = NULL; +static void *__real_pthread_create_2_0 = NULL; +#elif ARCH(Intel) && WSIZE(64) +static void *__real_timer_create_2_3_3 = NULL; +static void *__real_timer_create_2_2_5 = NULL; +#elif ARCH(SPARC) && WSIZE(64) +static void *__real_timer_create_2_3_3 = NULL; +static void *__real_timer_create_2_2 = NULL; +#endif + +/* 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 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 */ + +#define CALL_REAL(x) (*(int(*)())__real_##x) +#define NULL_PTR(x) ( __real_##x == NULL ) + +static void *__real_sigaction = NULL; +static void *__real_setitimer = NULL; +static void *__real_libc_setitimer = NULL; +static void *__real_sigprocmask = NULL; +static void *__real_thr_sigsetmask = NULL; +static void *__real_pthread_sigmask = NULL; +static void *__real_pthread_create = 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; + CALL_UTIL (memset)(&c_act, 0, sizeof c_act); + 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; + CALL_UTIL (memset)(&act, 0, sizeof (act)); + 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; + sigev._sigev_un._tid = __collector_gettid (); + 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++; + } +} + +#define SYS_SETITIMER_NAME "setitimer" +#define SYS_SIGACTION_NAME "sigaction" +#define SYS_SIGPROCMASK_NAME "sigprocmask" +#define SYS_PTHREAD_SIGMASK "pthread_sigmask" +#define SYS_THR_SIGSETMASK "thr_sigsetmask" + +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); + +#if ARCH(SPARC) && WSIZE(64) + /* dlopen a bogus path to avoid CR 23608692 */ + dlopen ("/bogus_path_for_23608692_workaround/", RTLD_LAZY | RTLD_NOLOAD); +#endif + __real_setitimer = dlsym (RTLD_NEXT, SYS_SETITIMER_NAME); + + if (__real_setitimer == NULL) + { + __real_setitimer = dlsym (RTLD_DEFAULT, SYS_SETITIMER_NAME); + 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"); + TprintfT (DBG_LT2, "@%p __real_setitimer\n", __real_setitimer); + + __real_sigaction = dlsym (dlflag, SYS_SIGACTION_NAME); + TprintfT (DBG_LT2, "@%p __real_sigaction\n", __real_sigaction); + + /* also explicitly get libc.so/setitimer (as a backup) */ + __real_libc_setitimer = dlsym (handle, SYS_SETITIMER_NAME); + TprintfT (DBG_LT2, "@%p __real_libc_setitimer\n", __real_libc_setitimer); + + __real_sigprocmask = dlsym (dlflag, SYS_SIGPROCMASK_NAME); + TprintfT (DBG_LT2, "@%p __real_sigprocmask\n", __real_sigprocmask); + + __real_thr_sigsetmask = dlsym (dlflag, SYS_THR_SIGSETMASK); + TprintfT (DBG_LT2, "@%p __real_thr_sigsetmask\n", __real_thr_sigsetmask); + + __real_pthread_sigmask = dlsym (dlflag, SYS_PTHREAD_SIGMASK); + TprintfT (DBG_LT2, "@%p __real_pthread_sigmask\n", __real_pthread_sigmask); + +#if ARCH(Aarch64) + __real_pthread_create = dlvsym (dlflag, "pthread_create", SYS_PTHREAD_CREATE_VERSION); + __real_timer_create = dlsym (dlflag, "timer_create"); + __real_timer_settime = dlsym (dlflag, "timer_settime"); + __real_timer_delete = dlsym (dlflag, "timer_delete"); + __real_timer_gettime = dlsym (dlflag, "timer_gettime"); +#else + __real_pthread_create = dlvsym (dlflag, "pthread_create", SYS_PTHREAD_CREATE_VERSION); + TprintfT (DBG_LT2, "[%s] @%p __real_pthread_create\n", SYS_PTHREAD_CREATE_VERSION, __real_pthread_create); + __real_timer_create = dlvsym (dlflag, "timer_create", SYS_TIMER_X_VERSION); + TprintfT (DBG_LT2, "init_lineage_intf() [%s] @0x%p __real_timer_create\n", SYS_TIMER_X_VERSION, __real_timer_create); + __real_timer_settime = dlvsym (dlflag, "timer_settime", SYS_TIMER_X_VERSION); + TprintfT (DBG_LT2, "init_lineage_intf() [%s] @0x%p __real_timer_settime\n", SYS_TIMER_X_VERSION, __real_timer_settime); + __real_timer_delete = dlvsym (dlflag, "timer_delete", SYS_TIMER_X_VERSION); + TprintfT (DBG_LT2, "init_lineage_intf() [%s] @0x%p __real_timer_delete\n", SYS_TIMER_X_VERSION, __real_timer_delete); + __real_timer_gettime = dlvsym (dlflag, "timer_gettime", SYS_TIMER_X_VERSION); + TprintfT (DBG_LT2, "init_lineage_intf() [%s] @0x%p __real_timer_gettime\n", SYS_TIMER_X_VERSION, __real_timer_gettime); + __real_clone = dlsym (dlflag, "clone"); + TprintfT (DBG_LT2, "init_lineage_intf() @0x%p __real_clone\n", __real_clone); +#if ARCH(Intel) && WSIZE(32) + __real_pthread_create_2_1 = __real_pthread_create; + __real_pthread_create_2_0 = dlvsym (dlflag, "pthread_create", "GLIBC_2.0"); +#elif ARCH(Intel) && WSIZE(64) + __real_timer_create_2_3_3 = __real_timer_create; + __real_timer_create_2_2_5 = dlvsym (dlflag, "timer_create", "GLIBC_2.2.5"); +#elif ARCH(SPARC) && WSIZE(64) + __real_timer_create_2_3_3 = __real_timer_create; + __real_timer_create_2_2 = dlvsym (dlflag, "timer_create", "GLIBC_2.2"); +#endif /* ARCH() && SIZE() */ +#endif + 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 +#if WSIZE(64) +#if ARCH(SPARC) || ARCH(Intel) +static int +__collector_timer_create_symver (int(real_timer_create) (), clockid_t clockid, struct sigevent *sevp, + timer_t *timerid); + +int +__collector_timer_create_2_3_3 (clockid_t clockid, struct sigevent *sevp, + timer_t *timerid) +{ + if (NULL_PTR (timer_create)) + init_interposition_intf (); + TprintfT (DBG_LTT, "dispatcher: GLIBC: __collector_timer_create_2_3_3@%p\n", CALL_REAL (timer_create_2_3_3)); + return __collector_timer_create_symver (CALL_REAL (timer_create_2_3_3), clockid, sevp, timerid); +} +__asm__(".symver __collector_timer_create_2_3_3,timer_create@@GLIBC_2.3.3"); +#endif /* ARCH(SPARC) || ARCH(Intel)*/ + +#if ARCH(SPARC) + +int +__collector_timer_create_2_2 (clockid_t clockid, struct sigevent *sevp, + timer_t *timerid) +{ + if (NULL_PTR (timer_create)) + init_interposition_intf (); + TprintfT (DBG_LTT, "dispatcher: GLIBC: __collector_timer_create_2_2@%p\n", CALL_REAL (timer_create_2_2)); + return __collector_timer_create_symver (CALL_REAL (timer_create_2_2), clockid, sevp, timerid); +} + +__asm__(".symver __collector_timer_create_2_2,timer_create@GLIBC_2.2"); + +#elif ARCH(Intel) + +int +__collector_timer_create_2_2_5 (clockid_t clockid, struct sigevent *sevp, + timer_t *timerid) +{ + if (NULL_PTR (timer_create)) + init_interposition_intf (); + TprintfT (DBG_LTT, "dispatcher: GLIBC: __collector_timer_create_2_2_5@%p\n", CALL_REAL (timer_create_2_2_5)); + return __collector_timer_create_symver (CALL_REAL (timer_create_2_2_5), clockid, sevp, timerid); +} +__asm__(".symver __collector_timer_create_2_2_5,timer_create@GLIBC_2.2.5"); +#endif /* ARCH() */ +#endif /* WSIZE(64) */ + +#if ARCH(Aarch64) || (ARCH(Intel) && WSIZE(32)) +int timer_create (clockid_t clockid, struct sigevent *sevp, timer_t *timerid) +#else +static int +__collector_timer_create_symver (int(real_timer_create) (), clockid_t clockid, + struct sigevent *sevp, timer_t *timerid) +#endif +{ + int ret; + + if (NULL_PTR (timer_create)) + init_interposition_intf (); + + /* collector reserves SIGPROF + */ + if (sevp == NULL || sevp->sigev_notify != SIGEV_SIGNAL + || sevp->sigev_signo != SIGPROF) + { +#if ARCH(Aarch64) || (ARCH(Intel) && WSIZE(32)) + ret = CALL_REAL (timer_create)(clockid, sevp, timerid); +#else + ret = (real_timer_create) (clockid, sevp, timerid); +#endif + TprintfT (DBG_LT2, "Real timer_create(%d) returned %d\n", + 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); + ret = -1; + errno = EBUSY; + TprintfT (DBG_LT2, "timer_create() returning %d\n", ret); + return ret; +} +/*------------------------------------------------------------- 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 */ + +int +pthread_sigmask (int how, const sigset_t* iset, sigset_t* oset) +{ + if (NULL_PTR (pthread_sigmask)) + init_interposition_intf (); + TprintfT (DBG_LT1, "__collector_pthread_sigmask(%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 (pthread_sigmask)(how, lset, oset); + TprintfT (DBG_LT1, "__collector_pthread_sigmask(%d) returning %d\n", how, ret); + return ret; +} +/*----------------------------------------------------------- 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 +#if ARCH(Intel) && WSIZE(32) +static int +__collector_pthread_create_symver (int(real_pthread_create) (), + pthread_t *thread, + const pthread_attr_t *attr, + void *(*func)(void*), + void *arg); + +int +__collector_pthread_create_2_1 (pthread_t *thread, + const pthread_attr_t *attr, + void *(*func)(void*), + void *arg) +{ + if (NULL_PTR (pthread_create)) + init_interposition_intf (); + TprintfT (DBG_LTT, "dispatcher: GLIBC: __collector_pthread_create_2_1@%p\n", CALL_REAL (pthread_create_2_1)); + return __collector_pthread_create_symver (CALL_REAL (pthread_create_2_1), thread, attr, func, arg); +} + +int +__collector_pthread_create_2_0 (pthread_t *thread, + const pthread_attr_t *attr, + void *(*func)(void*), + void *arg) +{ + if (NULL_PTR (pthread_create)) + init_interposition_intf (); + TprintfT (DBG_LTT, "dispatcher: GLIBC: __collector_pthread_create_2_0@%p\n", CALL_REAL (pthread_create_2_0)); + return __collector_pthread_create_symver (CALL_REAL (pthread_create_2_0), thread, attr, func, arg); +} + +__asm__(".symver __collector_pthread_create_2_1,pthread_create@@GLIBC_2.1"); +__asm__(".symver __collector_pthread_create_2_0,pthread_create@GLIBC_2.0"); + +#endif + +#if ARCH(Intel) && WSIZE(32) +static int +__collector_pthread_create_symver (int(real_pthread_create) (), + pthread_t *thread, + const pthread_attr_t *attr, + void *(*func)(void*), + void *arg) +#else +int +pthread_create (pthread_t *thread, const pthread_attr_t *attr, + void *(*func)(void*), void *arg) +#endif +{ + if (NULL_PTR (pthread_create)) + init_interposition_intf (); + + TprintfT (DBG_LT1, "pthread_create interposition called\n"); + + if (dispatch_mode != DISPATCH_ON) + { +#if ARCH(Intel) && WSIZE(32) + return (real_pthread_create) (thread, attr, func, arg); +#else + return CALL_REAL (pthread_create)(thread, attr, func, arg); +#endif + } + CollectorArgs *cargs = __collector_allocCSize (__collector_heap, sizeof (CollectorArgs), 1); + + if (cargs == NULL) + { +#if ARCH(Intel) && WSIZE(32) + return (real_pthread_create) (thread, attr, func, arg); +#else + return CALL_REAL (pthread_create)(thread, attr, func, arg); +#endif + } + cargs->func = func; + cargs->arg = arg; + cargs->stack = NULL; + cargs->isPthread = 1; + int ret = -1; +#if ARCH(Intel) && WSIZE(32) + ret = (real_pthread_create) (thread, attr, &collector_root, cargs); +#else + ret = CALL_REAL (pthread_create)(thread, attr, &collector_root, cargs); +#endif + if (ret) + __collector_freeCSize (__collector_heap, cargs, sizeof (CollectorArgs)); + TprintfT (DBG_LT1, "pthread_create returning %d\n", ret); + return ret; +} + +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 () __attribute__ ((weak, alias ("__collector_sigprocmask"))); +int thr_sigsetmask () __attribute__ ((weak, alias ("__collector_thr_sigsetmask"))); +int setitimer () __attribute__ ((weak, alias ("_setitimer"))); -- cgit v1.1