/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Includes
*/
#ifndef DEBUG
#endif
#include <thread.h>
#include <pthread.h>
#include <synch.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#ifdef sparc
#include <setjmp.h>
#endif /* sparc */
#include "tnf_trace.h"
/*
* Typedefs
*/
void *arg,
long flags,
const pthread_attr_t *attr,
void * arg);
typedef struct args {
void *real_arg;
} args_t;
/*
* Local Declarations
*/
static void * tnf_threaded_test(void *dummy,
static void * tnf_non_threaded_test(void *dummy,
static tnf_ops_t *tnf_probe_getfunc(void);
static void *probestart(void *arg);
static void probe_setup(void *data);
static tnf_ops_t *tnf_get_ops();
/*
* Static Globals
*/
extern tnf_ops_t tnf_trace_initial_tpd;
#ifdef sparc
#endif /* sparc */
/*
* Project Private interfaces:
* These are interfaces between prex and libtnfw, or
* between libtnfw and libtthread.
*/
/* variable indicates if libtnfw has sync'ed up with libthread or not */
long __tnf_probe_thr_sync = 0;
/* head of the list that is used to chain all probes */
int __tnf_probe_list_valid = 0;
/* notify function that libthread calls after primordial thread is created */
void __tnf_probe_notify(void);
/*
* Externs
*/
extern tnf_context_t thr_probe_getfunc_addr;
extern void thr_probe_setup(void *);
/* ---------------------------------------------------------------- */
/* ----------------------- Public Functions ----------------------- */
/* ---------------------------------------------------------------- */
/*
* probe_setup() - the thread probe setup function for the non-threaded
* case.
*/
static void
{
#ifdef DEBUG
/* #### - TEMPORARY */
#endif
} /* end probe_setup */
/*
* __tnf_probe_notify() - libthread calls this function to notify us
* that the primordial thread has been created.
*/
void
__tnf_probe_notify(void)
{
/* paranoia: thr_probe_setup should be defined */
assert(thr_probe_setup != 0);
/*
* no race with prex if we set flag first
* - this is an idempotent operation
*/
__tnf_probe_thr_sync = 1;
#ifdef DEBUG
{
}
#endif
/*
* Use dlsym to test for the present of "thr_probe_getfunc_addr" .
*/
/*
* I think in this case that we do not need to check the
* __tnf_probe_list_valid flag since __tnf_probe_notify is
* called very early.
*/
/* replace all existing test functions with libthread's test func */
return;
} /* end __tnf_probe_notify */
/*
* _tnf_fork_thread_setup - function called by buffering layer
* whenever it finds a thread in the newly forked process that
* hasn't been re-initialized in this process.
*/
void
_tnf_fork_thread_setup(void)
{
#ifdef DEBUGFUNCS
{
}
#endif
/* get the tpd */
ops = tnf_get_ops();
if (!ops)
return;
/* null out tag_index, so that a new one is initialized and written */
return;
}
/* ---------------------------------------------------------------- */
/* ---------------------- Interposed Functions -------------------- */
/* ---------------------------------------------------------------- */
/*
* thr_create() - this function is interposed in front of the
* actual thread create function in libthread.
*/
int
void * (*real_func)(void *),
void *real_arg,
long flags,
{
#ifdef VERYVERBOSE
#endif
/* use dlsym to find the address of the "real" thr_create function */
if (real_thr_create == NULL) {
}
/* set up the interposed argument block */
flags, new_thread));
} /* end thr_create */
int
const pthread_attr_t *attr,
void * (*real_func)(void *),
void *real_arg)
{
#ifdef VERYVERBOSE
#endif
/* use dlsym to find the address of the "real" pthread_create func */
if (real_pthread_create == NULL) {
}
/* set up the interposed argument block */
(void *) arg_p));
} /* end pthread_create */
void
{
/* use dlsym to find the address of the "real" pthread_create func */
if (real_thr_exit == NULL) {
}
/*
* Calling tnf_thread_disable() whenever a thread exits...
* This has the side-effect of unlocking our currently
* locked block in the trace buffer. This keeps a dying
* thread from taking a block with it when it dies, but
* it means that we won't be able to trace events from
* the thread-specific data destructors. We will lose
* out on any events a thread spits out AFTER is calls thr_exit().
* This code was added to fix a bug where tracing breaks when trying
* to trace a program with large numbers of thread-ids.
*
* Addendum:
* Now you can't get events for thr_exit using an interposition library.
* Since thr_exit is a really helpful event, this is a problem.
* Also, breaking this interposition will probably break
* BAT, the DevPro TNF perf tool.
*
* Addendum:
* Correction: You can get interposition events if the interposition
* library comes BEFORE libtnfprobe.so. But not, if the interp.
* library comes AFTER libtnfprobe.so. This is a more difficult
* constraint that it might sound like because of the following:
* The tnfctl functional interface and the prex command line
* interface provide convenience features where you can supply
* a character string argument which will be put into LD_PRELOAD
* for you. Unfortunately, this string gets appended AFTER
* libtnfprobe.so by the tnfctl library(and also hence by the
* prex -l option).
* Luckily, when libtnfprobe is added by the tnfctl library, it is
* added AFTER an existing contents of the LD_PRELOAD variable.
*
* Therefore, if you are using an interposition library to collect
* thr_exit and pthread_exit events, THEN you should NOT use 'prex -l'
* or the 'ld_preload' argument to tnfctl_exec_open(), instead, you
* should be sure to put the interposition library into the LD_PRELOAD
* variable yourself.
*
*/
((*real_thr_exit)(status));
}
void
{
/* use dlsym to find the address of the "real" pthread_create func */
if (real_pthread_exit == NULL) {
}
/* see the comment in thr_exit about tnf_thread_disable() */
((*real_pthread_exit)(status));
}
/*
* function to be interposed in front of _resume. We invalidate the
* schedule record in case the lwpid changes the next time this
* thread is scheduled.
*/
void
{
static void (*real_resume_ret)(void *) = NULL;
if (real_resume_ret == NULL) {
"_resume_ret");
}
ops = tnf_get_ops();
if (ops) {
/*
* invalidate the schedule record. This forces it
* to get re-initialized with the new lwpid the next
* time this thread gets scheduled
*/
}
}
/*
* Functions to be interposed in front of fork and fork1.
*
* NOTE: we can't handle vfork, because the child would ruin the parent's
* data structures. We therefore don't interpose, letting the child's
* events appear as though they were the parent's. A slightly cleaner
* way to handle vfork would be to interpose on vfork separately to
* change the pid and anything else needed to show any events caused
* by the child as its events, and then interpose on the exec's as
* well to set things back to the way they should be for the parent.
* But this is a lot of work, and it makes almost no difference, since the
* child typically exec's very quickly after a vfork.
*/
_tnf_fork(void)
{
}
return (common_fork(real_fork));
}
_tnf_fork1(void)
{
}
return (common_fork(real_fork));
}
#ifdef sparc
/*
* Function to be interposed in front of thr_stksegment
* _tnf_thr_stksegment() - used to hide the probestart() allocated data
* on the thread stack, ensuring that the caller receives a pointer to the
* true bottom (ie, usable) portion of the stack, and the size thereof.
*
* NOTE: On sparc systems, failure to allow for the presense of tnf data
* on the stack would cause TNF probes to fail across doorfs calls. The
* i386 version of door_return decides to "skip over some slop", so no
* interpose function is required for x86; if the 512 byte 'slop skip'
* is ever removed from the i386 door_return, then it will also need
* interpose function intervention.
*
* Note: Instead of making this function static, we reduce it to local
* scope in the mapfile. That allows the linker to prevent it from
* appearing in the .SUNW_dynsymsort section.
*/
int
{
int err;
#ifdef VERYVERBOSE
#endif
if (real_thr_stksegment == NULL) {
}
err = ((*real_thr_stksegment)(s));
if (err == 0) {
s->ss_size -= tnf_probe_dsize;
}
return (err);
}
#endif /* sparc */
/* ---------------------------------------------------------------- */
/* ----------------------- Private Functions ---------------------- */
/* ---------------------------------------------------------------- */
/*
* tnf_probe_getfunc() - default test function if libthread is not
* present
*/
static tnf_ops_t *
tnf_probe_getfunc(void)
{
/* test function to be used if libthread is not linked in */
#ifdef DEBUGFUNCS
{
}
#endif
return (tpd);
} /* end tnf_probe_getfunc */
/*
* probestart() - this function is called as the start_func by the
* interposed thr_create() and pthread_create(). It calls the real start
* function.
*/
static void *
{
void *real_arg;
void *real_retval;
#ifdef VERYVERBOSE
#endif
#ifdef sparc
/*
* if the size of the probe data has not yet been calculated,
* initialize a jmpbuffer and calculate the amount of stack space
* used by probestart: %fp - %sp from jmp_buf
* Not expecting anything to actually longjmp here, so that is
* handled as an error condition.
*/
if (tnf_probe_dsize == 0) {
if (setjmp(tnf_jmpbuf) != 0) {
(void) write(2,
"probestart: unexpected longjmp\n", 32);
assert(0);
}
}
#endif /* sparc */
/* initialize ops */
/* copy (and free) the allocated arg block */
/* paranoia: thr_probe_setup should be defined */
assert(thr_probe_setup != 0);
#ifdef VERYVERBOSE
#endif
/*
* we need to write a NULL into the tpd pointer to disable
* tracing for this thread.
* CAUTION: never make this function tail recursive because
* tpd is allocated on stack.
*/
/* This should be handled by the call to tnf_thread_disable() */
/* if (thr_probe_setup != 0) */
/* thr_probe_setup(NULL); */
/* see the comment in thr_exit about tnf_thread_disable */
return (real_retval);
} /* end probestart */
/*
* tnf_thread_disable: API to disable a thread
*/
void
tnf_thread_disable(void)
{
if (thr_probe_setup != 0) {
/* threaded client */
/* REMIND: destructor function ? */
/* get the tpd */
/* check ops to ensure function is idempotent */
/* unlock currently held blocks */
/* disable the thread */
/* stash the tpd */
}
} else {
/* non-threaded client */
/* get the tpd */
ops = tnf_probe_getfunc();
/* disable the process */
/* stash the tpd */
stashed_tpd = ops;
}
}
}
/*
* tnf_thread_enable: API to enable a thread
*/
void
tnf_thread_enable(void)
{
if (thr_probe_setup != 0) {
/* threaded client */
if (ops)
} else {
/* non-threaded client */
ops = stashed_tpd;
if (ops)
}
}
/*
* common_fork - code that is common among the interpositions of
* fork, fork1, and vfork
*/
static pid_t
{
#ifdef DEBUGFUNCS
{
}
#endif
(tnf_trace_file_name[0] != '\0')) {
/*
* if no buffer has been allocated yet, and prex plugged in
* name...
*/
ops = tnf_get_ops();
/*
* get it from stashed location
* don't enable thread though
*/
if (thr_probe_setup != 0) {
/* threaded client */
} else {
/* non-threaded client */
ops = stashed_tpd;
}
}
/*
* ops shouldn't be NULL. But, if it is, then we don't
* initialize tracing. In the child, tracing will be
* set to broken.
*/
if (ops) {
/* initialize tracing */
/* commit the data */
}
}
if (retval == 0) {
/* child process */
(tnf_trace_file_name[0] != '\0')) {
/*
* race condition, prex attached after condition was
* checked in parent, so both parent and child point at
* the same file name and will overwrite each other.
* So, we set tracing to broken in child. We could
* invent a new state called RACE and use prex to
* reset it, if needed...
*/
tnf_trace_file_name[0] = '\0';
/* normal expected condition */
}
}
return (retval);
}
/*
* tnf_threaded_test
*/
/*ARGSUSED0*/
static void *
{
if (tpd_p) {
}
return (NULL);
}
/*
* tnf_non_threaded_test
*/
/*ARGSUSED0*/
static void *
{
tpd_p = tnf_probe_getfunc();
if (tpd_p) {
}
return (NULL);
}
/*
* tnf_get_ops() returns the ops pointer (thread-private data), or NULL
* if tracing is disabled for this thread.
*/
static tnf_ops_t *
{
/*
* IMPORTANT: this test to see whether thr_probe_getfunc_addr
* is bound is tricky. The compiler currently has a bug
* (1263684) that causes the test to be optimized away unless
* coded with an intermediate pointer (test_func_p). This
* causes the process to SEGV when the variable is not bound.
*/
return ((*test_func)());
}