OpenSolaris_b135/lib/libtnfprobe/probe_cntl.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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
 * or http://www.opensolaris.org/os/licensing.
 * 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
#define	NDEBUG	1
#endif

#include <thread.h>
#include <pthread.h>
#include <sys/lwp.h>
#include <synch.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.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
 */

typedef tnf_ops_t *(*tnf_context_t)(void);

typedef void * (*start_func_t)(void *arg);

typedef int (*tnf_thr_create_func_t)(void		*stk,
				size_t			stksize,
				start_func_t		startfunc,
				void			*arg,
				long			flags,
				thread_t		*newthread);

typedef int (*tnf_pthread_create_func_t)(pthread_t	*thr,
				const pthread_attr_t	*attr,
				start_func_t		startfunc,
				void *			arg);

typedef void (*tnf_thr_exit_func_t)(void *) __NORETURN;

typedef void (*tnf_pthread_exit_func_t)(void *) __NORETURN;

typedef pid_t (*fork_t)(void);

typedef int (*tnf_thr_stksegment_func_t)(stack_t *s);

typedef struct args {
	start_func_t		real_func;
	void 			*real_arg;
} args_t;

/*
 * Local Declarations
 */

static void * tnf_threaded_test(void 			*dummy,
				tnf_probe_control_t 	*probe_p,
				tnf_probe_setup_t 	*set_p);
static void * tnf_non_threaded_test(void 			*dummy,
					tnf_probe_control_t	*probe_p,
					tnf_probe_setup_t	*set_p);
static tnf_ops_t *tnf_probe_getfunc(void);
static void *probestart(void *arg);
static pid_t common_fork(fork_t real_fork);
static void probe_setup(void *data);
static tnf_ops_t *tnf_get_ops();

/*
 * Static Globals
 */

extern tnf_ops_t 	tnf_trace_initial_tpd;
static void 		*tpd = &tnf_trace_initial_tpd;
#ifdef sparc
static size_t		tnf_probe_dsize = 0;
#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 */
tnf_probe_control_t	*__tnf_probe_list_head		= NULL;
int			__tnf_probe_list_valid		= 0;

/* notify function that libthread calls after primordial thread is created */
void __tnf_probe_notify(void);

tnf_probe_test_func_t tnf_threaded_test_addr = tnf_threaded_test;
tnf_probe_test_func_t tnf_non_threaded_test_addr = tnf_non_threaded_test;


/*
 * Externs
 */
#pragma weak thr_probe_getfunc_addr
extern tnf_context_t	thr_probe_getfunc_addr;

#pragma weak thr_probe_setup
extern void thr_probe_setup(void *);

/* ---------------------------------------------------------------- */
/* ----------------------- Public Functions ----------------------- */
/* ---------------------------------------------------------------- */

/*
 * probe_setup() - the thread probe setup function for the non-threaded
 * case.
 */
static void
probe_setup(void *data)
{
#ifdef DEBUG
	/* #### - TEMPORARY */
	fprintf(stderr, "probe_setup: \n");
#endif
	tpd = data;

}   /* 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)
{
	tnf_probe_control_t		*prbctl_p;
	tnf_probe_test_func_t		test_func;

	/* paranoia: thr_probe_setup should be defined */
	assert(thr_probe_setup != 0);
	if (thr_probe_setup != 0) thr_probe_setup(tpd);

	/*
	 * no race with prex if we set flag first
	 *		- this is an idempotent operation
	 */
	__tnf_probe_thr_sync = 1;

#ifdef DEBUG
	{
		char tmp_buf[512];
		(void) sprintf(tmp_buf, "__tnf_probe_notify: \n");
		(void) write(2, tmp_buf, strlen(tmp_buf));
	}
#endif
	/*
	 * Use dlsym to test for the present of "thr_probe_getfunc_addr" .
	 */

	test_func = (((int(*)())dlsym(RTLD_DEFAULT,
		"thr_probe_getfunc_addr")) != NULL) ? tnf_threaded_test : 0;

	assert(test_func);

	/*
	 * 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 */
	for (prbctl_p = __tnf_probe_list_head; prbctl_p;
					prbctl_p = prbctl_p->next)
		if (prbctl_p->test_func)
			prbctl_p->test_func = 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)
{
	tnf_ops_t	*ops;

#ifdef DEBUGFUNCS
	{
		char tmp_buf[512];
		(void) sprintf(tmp_buf, "in _tnf_fork_thread_setup: \n");
		(void) write(2, tmp_buf, strlen(tmp_buf));
	}
#endif
	/* get the tpd */
	ops = tnf_get_ops();
	if (!ops)
		return;
	/* null out tag_index, so that a new one is initialized and written */
	ops->schedule.record_p = 0;
	return;

}

/* ---------------------------------------------------------------- */
/* ---------------------- Interposed Functions -------------------- */
/* ---------------------------------------------------------------- */

/*
 * thr_create() - this function is interposed in front of the
 * actual thread create function in libthread.
 */

int
thr_create(void 		*stk,
	size_t		stksize,
	void *		(*real_func)(void *),
	void		*real_arg,
	long		flags,
	thread_t	*new_thread)
{
	static tnf_thr_create_func_t real_thr_create = NULL;
	args_t *arg_p;

#ifdef VERYVERBOSE
	fprintf(stderr, "hello from the interposed thr_create parent\n");
#endif

	/* use dlsym to find the address of the "real" thr_create function */
	if (real_thr_create == NULL) {
		real_thr_create = (tnf_thr_create_func_t)
					dlsym(RTLD_NEXT, "thr_create");
	}
	assert(real_thr_create);

	/* set up the interposed argument block */
	arg_p = (args_t *)malloc(sizeof (args_t));
	assert(arg_p);
	arg_p->real_func = real_func;
	arg_p->real_arg  = real_arg;

	return ((*real_thr_create)(stk, stksize, probestart, (void *) arg_p,
					flags, new_thread));

}   /* end thr_create */


int
pthread_create(pthread_t *new_thread_id,
	const pthread_attr_t *attr,
	void *		(*real_func)(void *),
	void		*real_arg)
{
	static tnf_pthread_create_func_t real_pthread_create = NULL;
	args_t *arg_p;

#ifdef VERYVERBOSE
	fprintf(stderr, "hello from the interposed pthread_create parent\n");
#endif

	/* use dlsym to find the address of the "real" pthread_create func */
	if (real_pthread_create == NULL) {
		real_pthread_create = (tnf_pthread_create_func_t)
					dlsym(RTLD_NEXT, "pthread_create");
	}
	assert(real_pthread_create);

	/* set up the interposed argument block */
	arg_p = (args_t *)malloc(sizeof (args_t));
	assert(arg_p);
	arg_p->real_func = real_func;
	arg_p->real_arg  = real_arg;

	return ((*real_pthread_create)(new_thread_id, attr, probestart,
			(void *) arg_p));

}   /* end pthread_create */

void
thr_exit(void * status)
{
	static tnf_thr_exit_func_t real_thr_exit = NULL;
	/* use dlsym to find the address of the "real" pthread_create func */
	if (real_thr_exit == NULL) {
		real_thr_exit = (tnf_thr_exit_func_t)
		dlsym(RTLD_NEXT, "thr_exit");
	}
	assert(real_thr_exit);


	/*
	 * 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.
	 *
	 */

	tnf_thread_disable();

	((*real_thr_exit)(status));
}

void
pthread_exit(void * status)
{
	static tnf_pthread_exit_func_t real_pthread_exit = NULL;
	/* use dlsym to find the address of the "real" pthread_create func */
	if (real_pthread_exit == NULL) {
		real_pthread_exit = (tnf_pthread_exit_func_t)
		dlsym(RTLD_NEXT, "pthread_exit");
	}
	assert(real_pthread_exit);
	/* see the comment in thr_exit about tnf_thread_disable() */
	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.
 */

#pragma weak _resume_ret = _tnf_resume_ret
void
_tnf_resume_ret(void *arg1)
{
	static void (*real_resume_ret)(void *) = NULL;
	tnf_ops_t	*ops;

	if (real_resume_ret == NULL) {
		real_resume_ret = (void (*)(void *)) dlsym(RTLD_NEXT,
					"_resume_ret");
	}
	assert(real_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
		 */
		if (ops->schedule.lwpid != _lwp_self())
			ops->schedule.record_p = 0;
	}

	real_resume_ret(arg1);
}

/*
 * 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.
 */

#pragma weak fork = _tnf_fork
pid_t
_tnf_fork(void)
{
	static fork_t real_fork = NULL;

	if (real_fork == NULL) {
		real_fork = (fork_t)dlsym(RTLD_NEXT, "fork");
	}
	assert(real_fork);
	return (common_fork(real_fork));
}

#pragma weak fork1 = _tnf_fork1
pid_t
_tnf_fork1(void)
{
	static fork_t real_fork = NULL;

	if (real_fork == NULL) {
		real_fork = (fork_t)dlsym(RTLD_NEXT, "fork1");
	}
	assert(real_fork);
	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.
 */
#pragma weak thr_stksegment = _tnf_thr_stksegment
int
_tnf_thr_stksegment(stack_t *s)
{
	static tnf_thr_stksegment_func_t real_thr_stksegment = NULL;
	int	err;

#ifdef VERYVERBOSE
	fprintf(stderr, "hello from the interposed thr_stksegment\n");
#endif

	if (real_thr_stksegment == NULL) {
		real_thr_stksegment = (tnf_thr_stksegment_func_t)
		    dlsym(RTLD_NEXT, "thr_stksegment");
	}
	assert(real_thr_stksegment);

	err = ((*real_thr_stksegment)(s));
	if (err == 0) {
		s->ss_sp = (void *)((caddr_t)s->ss_sp - tnf_probe_dsize);
		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
	{
		char tmp_buf[512];
		(void) sprintf(tmp_buf, "tnf_probe_getfunc: \n");
		(void) write(2, tmp_buf, strlen(tmp_buf));
	}
#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 *
probestart(void * arg)
{
	args_t 		*args_p = (args_t *)arg;
	start_func_t	real_func;
	void		*real_arg;
	tnf_ops_t	ops;		/* allocated on stack */
	void		*real_retval;

#ifdef VERYVERBOSE
	fprintf(stderr, "hello from the interposed thr_create child\n");
#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) {
		jmp_buf tnf_jmpbuf;
		if (setjmp(tnf_jmpbuf) != 0) {
			(void) write(2,
			    "probestart:  unexpected longjmp\n", 32);
			assert(0);
		}
		tnf_probe_dsize = (size_t)(tnf_jmpbuf[3] - tnf_jmpbuf[1]);
	}
#endif /* sparc */

	/* initialize ops */
	(void) memset(&ops, 0, sizeof (ops));	/* zero ops */
	ops.mode = TNF_ALLOC_REUSABLE;
	ops.alloc = tnfw_b_alloc;
	ops.commit = tnfw_b_xcommit;
	ops.rollback = tnfw_b_xabort;

	/* copy (and free) the allocated arg block */
	real_func = args_p->real_func;
	real_arg  = args_p->real_arg;
	free(args_p);

	/* paranoia: thr_probe_setup should be defined */
	assert(thr_probe_setup != 0);
	if (thr_probe_setup != 0) thr_probe_setup(&ops);

#ifdef VERYVERBOSE
	fprintf(stderr, "in middle of interposed start procedure\n");
#endif

	real_retval = (*real_func)(real_arg);

	/*
	 * 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 */
	tnf_thread_disable();

	return (real_retval);

}   /* end probestart */


static thread_key_t tpd_key = THR_ONCE_KEY;
static tnf_ops_t *stashed_tpd = NULL;

/*
 * tnf_thread_disable: API to disable a thread
 */
void
tnf_thread_disable(void)
{
	tnf_ops_t		*ops;

	if (thr_probe_setup != 0) {
		/* threaded client */

		/* REMIND: destructor function ? */
		(void) thr_keycreate_once(&tpd_key, NULL);
		/* get the tpd */
		ops = thr_probe_getfunc_addr();
		/* check ops to ensure function is idempotent */
		if (ops != NULL) {
			/* unlock currently held blocks */
			tnfw_b_release_block(&ops->wcb);
			/* disable the thread */
			thr_probe_setup(NULL);
			/* stash the tpd */
			(void) thr_setspecific(tpd_key, ops);
		}
	} else {
		/* non-threaded client */

		/* get the tpd */
		ops = tnf_probe_getfunc();
		if (ops != NULL) {
			/* disable the process */
			probe_setup(NULL);
			/* stash the tpd */
			stashed_tpd = ops;
		}
	}
}

/*
 * tnf_thread_enable: API to enable a thread
 */
void
tnf_thread_enable(void)
{
	tnf_ops_t *ops;

	if (thr_probe_setup != 0) {
		/* threaded client */

		ops = pthread_getspecific(tpd_key);
		if (ops)
			thr_probe_setup(ops);
	} else {
		/* non-threaded client */

		ops = stashed_tpd;
		if (ops)
			probe_setup(ops);
	}
}

/*
 * common_fork - code that is common among the interpositions of
 * fork, fork1, and vfork
 */
static pid_t
common_fork(fork_t real_fork)
{
	pid_t		 retval;
	tnf_ops_t	*ops;
	tnf_tag_data_t	*metatag_data;

#ifdef DEBUGFUNCS
	{
		char tmp_buf[512];
		(void) sprintf(tmp_buf, "in interposed fork: \n");
		(void) write(2, tmp_buf, strlen(tmp_buf));
	}
#endif
	if ((_tnfw_b_control->tnf_state == TNFW_B_NOBUFFER) &&
				(tnf_trace_file_name[0] != '\0')) {
		/*
		 * if no buffer has been allocated yet, and prex plugged in
		 * name...
		 */
		ops = tnf_get_ops();
		if (ops == NULL) {
			/*
			 * get it from stashed location
			 * don't enable thread though
			 */
			if (thr_probe_setup != 0) {
				/* threaded client */
				ops = pthread_getspecific(tpd_key);
			} 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 */
			ops->busy = 1;
			metatag_data = TAG_DATA(tnf_struct_type);
			metatag_data->tag_desc(ops, metatag_data);
			/* commit the data */
			(void) ops->commit(&(ops->wcb));
			ops->busy = 0;
		}
	}

	retval = real_fork();
	if (retval == 0) {
		/* child process */
		_tnfw_b_control->tnf_pid = getpid();
		if ((_tnfw_b_control->tnf_state == TNFW_B_NOBUFFER) &&
				(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';
			_tnfw_b_control->tnf_state = TNFW_B_BROKEN;
		} else if (_tnfw_b_control->tnf_state == TNFW_B_RUNNING) {
			/* normal expected condition */
			_tnfw_b_control->tnf_state = TNFW_B_FORKED;
		}
	}
	return (retval);
}

/*
 * tnf_threaded_test
 */
/*ARGSUSED0*/
static void *
tnf_threaded_test(void *dummy, tnf_probe_control_t *probe_p,
			tnf_probe_setup_t *set_p)
{
	tnf_ops_t *tpd_p;

	tpd_p = thr_probe_getfunc_addr();
	if (tpd_p) {
		return (probe_p->alloc_func(tpd_p, probe_p, set_p));
	}
	return (NULL);
}


/*
 * tnf_non_threaded_test
 */
/*ARGSUSED0*/
static void *
tnf_non_threaded_test(void *dummy, tnf_probe_control_t *probe_p,
				tnf_probe_setup_t *set_p)
{
	tnf_ops_t *tpd_p;

	tpd_p = tnf_probe_getfunc();
	if (tpd_p) {
		return (probe_p->alloc_func(tpd_p, probe_p, set_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 *
tnf_get_ops()
{
	tnf_context_t	*test_func_p = &thr_probe_getfunc_addr;
	tnf_context_t	 test_func;

	/*
	 * 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.
	 */

	test_func = test_func_p ? *test_func_p : tnf_probe_getfunc;
	return ((*test_func)());
}