1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29/*
30 * Includes
31 */
32
33#ifndef DEBUG
34#define	NDEBUG	1
35#endif
36
37#include <thread.h>
38#include <pthread.h>
39#include <sys/lwp.h>
40#include <synch.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43#include <sys/param.h>
44#include <fcntl.h>
45#include <dlfcn.h>
46#include <string.h>
47#include <unistd.h>
48#include <stdlib.h>
49#include <assert.h>
50#include <stdio.h>
51#include <errno.h>
52#ifdef sparc
53#include <setjmp.h>
54#endif /* sparc */
55
56#include "tnf_trace.h"
57
58/*
59 * Typedefs
60 */
61
62typedef tnf_ops_t *(*tnf_context_t)(void);
63
64typedef void * (*start_func_t)(void *arg);
65
66typedef int (*tnf_thr_create_func_t)(void		*stk,
67				size_t			stksize,
68				start_func_t		startfunc,
69				void			*arg,
70				long			flags,
71				thread_t		*newthread);
72
73typedef int (*tnf_pthread_create_func_t)(pthread_t	*thr,
74				const pthread_attr_t	*attr,
75				start_func_t		startfunc,
76				void *			arg);
77
78typedef void (*tnf_thr_exit_func_t)(void *) __NORETURN;
79
80typedef void (*tnf_pthread_exit_func_t)(void *) __NORETURN;
81
82typedef pid_t (*fork_t)(void);
83
84typedef int (*tnf_thr_stksegment_func_t)(stack_t *s);
85
86typedef struct args {
87	start_func_t		real_func;
88	void 			*real_arg;
89} args_t;
90
91/*
92 * Local Declarations
93 */
94
95static void * tnf_threaded_test(void 			*dummy,
96				tnf_probe_control_t 	*probe_p,
97				tnf_probe_setup_t 	*set_p);
98static void * tnf_non_threaded_test(void 			*dummy,
99					tnf_probe_control_t	*probe_p,
100					tnf_probe_setup_t	*set_p);
101static tnf_ops_t *tnf_probe_getfunc(void);
102static void *probestart(void *arg);
103static pid_t common_fork(fork_t real_fork);
104static void probe_setup(void *data);
105static tnf_ops_t *tnf_get_ops();
106
107/*
108 * Static Globals
109 */
110
111extern tnf_ops_t 	tnf_trace_initial_tpd;
112static void 		*tpd = &tnf_trace_initial_tpd;
113#ifdef sparc
114static size_t		tnf_probe_dsize = 0;
115#endif /* sparc */
116
117/*
118 * Project Private interfaces:
119 * 	These are interfaces between prex and libtnfw, or
120 * 	between libtnfw and libtthread.
121 */
122
123/* variable indicates if libtnfw has sync'ed up with libthread or not */
124long			__tnf_probe_thr_sync		= 0;
125
126/* head of the list that is used to chain all probes */
127tnf_probe_control_t	*__tnf_probe_list_head		= NULL;
128int			__tnf_probe_list_valid		= 0;
129
130/* notify function that libthread calls after primordial thread is created */
131void __tnf_probe_notify(void);
132
133tnf_probe_test_func_t tnf_threaded_test_addr = tnf_threaded_test;
134tnf_probe_test_func_t tnf_non_threaded_test_addr = tnf_non_threaded_test;
135
136
137/*
138 * Externs
139 */
140#pragma weak thr_probe_getfunc_addr
141extern tnf_context_t	thr_probe_getfunc_addr;
142
143#pragma weak thr_probe_setup
144extern void thr_probe_setup(void *);
145
146/* ---------------------------------------------------------------- */
147/* ----------------------- Public Functions ----------------------- */
148/* ---------------------------------------------------------------- */
149
150/*
151 * probe_setup() - the thread probe setup function for the non-threaded
152 * case.
153 */
154static void
155probe_setup(void *data)
156{
157#ifdef DEBUG
158	/* #### - TEMPORARY */
159	fprintf(stderr, "probe_setup: \n");
160#endif
161	tpd = data;
162
163}   /* end probe_setup */
164
165/*
166 * __tnf_probe_notify() - libthread calls this function to notify us
167 * that the primordial thread has been created.
168 */
169
170void
171__tnf_probe_notify(void)
172{
173	tnf_probe_control_t		*prbctl_p;
174	tnf_probe_test_func_t		test_func;
175
176	/* paranoia: thr_probe_setup should be defined */
177	assert(thr_probe_setup != 0);
178	if (thr_probe_setup != 0) thr_probe_setup(tpd);
179
180	/*
181	 * no race with prex if we set flag first
182	 *		- this is an idempotent operation
183	 */
184	__tnf_probe_thr_sync = 1;
185
186#ifdef DEBUG
187	{
188		char tmp_buf[512];
189		(void) sprintf(tmp_buf, "__tnf_probe_notify: \n");
190		(void) write(2, tmp_buf, strlen(tmp_buf));
191	}
192#endif
193	/*
194	 * Use dlsym to test for the present of "thr_probe_getfunc_addr" .
195	 */
196
197	test_func = (((int(*)())dlsym(RTLD_DEFAULT,
198		"thr_probe_getfunc_addr")) != NULL) ? tnf_threaded_test : 0;
199
200	assert(test_func);
201
202	/*
203	 * I think in this case that we do not need to check the
204	 * __tnf_probe_list_valid flag since __tnf_probe_notify is
205	 * called very early.
206	 */
207
208	/* replace all existing test functions with libthread's test func */
209	for (prbctl_p = __tnf_probe_list_head; prbctl_p;
210					prbctl_p = prbctl_p->next)
211		if (prbctl_p->test_func)
212			prbctl_p->test_func = test_func;
213
214	return;
215
216}   /* end __tnf_probe_notify */
217
218/*
219 * _tnf_fork_thread_setup - function called by buffering layer
220 * whenever it finds a thread in the newly forked process that
221 * hasn't been re-initialized in this process.
222 */
223void
224_tnf_fork_thread_setup(void)
225{
226	tnf_ops_t	*ops;
227
228#ifdef DEBUGFUNCS
229	{
230		char tmp_buf[512];
231		(void) sprintf(tmp_buf, "in _tnf_fork_thread_setup: \n");
232		(void) write(2, tmp_buf, strlen(tmp_buf));
233	}
234#endif
235	/* get the tpd */
236	ops = tnf_get_ops();
237	if (!ops)
238		return;
239	/* null out tag_index, so that a new one is initialized and written */
240	ops->schedule.record_p = 0;
241	return;
242
243}
244
245/* ---------------------------------------------------------------- */
246/* ---------------------- Interposed Functions -------------------- */
247/* ---------------------------------------------------------------- */
248
249/*
250 * thr_create() - this function is interposed in front of the
251 * actual thread create function in libthread.
252 */
253
254int
255thr_create(void 		*stk,
256	size_t		stksize,
257	void *		(*real_func)(void *),
258	void		*real_arg,
259	long		flags,
260	thread_t	*new_thread)
261{
262	static tnf_thr_create_func_t real_thr_create = NULL;
263	args_t *arg_p;
264
265#ifdef VERYVERBOSE
266	fprintf(stderr, "hello from the interposed thr_create parent\n");
267#endif
268
269	/* use dlsym to find the address of the "real" thr_create function */
270	if (real_thr_create == NULL) {
271		real_thr_create = (tnf_thr_create_func_t)
272					dlsym(RTLD_NEXT, "thr_create");
273	}
274	assert(real_thr_create);
275
276	/* set up the interposed argument block */
277	arg_p = (args_t *)malloc(sizeof (args_t));
278	assert(arg_p);
279	arg_p->real_func = real_func;
280	arg_p->real_arg  = real_arg;
281
282	return ((*real_thr_create)(stk, stksize, probestart, (void *) arg_p,
283					flags, new_thread));
284
285}   /* end thr_create */
286
287
288int
289pthread_create(pthread_t *new_thread_id,
290	const pthread_attr_t *attr,
291	void *		(*real_func)(void *),
292	void		*real_arg)
293{
294	static tnf_pthread_create_func_t real_pthread_create = NULL;
295	args_t *arg_p;
296
297#ifdef VERYVERBOSE
298	fprintf(stderr, "hello from the interposed pthread_create parent\n");
299#endif
300
301	/* use dlsym to find the address of the "real" pthread_create func */
302	if (real_pthread_create == NULL) {
303		real_pthread_create = (tnf_pthread_create_func_t)
304					dlsym(RTLD_NEXT, "pthread_create");
305	}
306	assert(real_pthread_create);
307
308	/* set up the interposed argument block */
309	arg_p = (args_t *)malloc(sizeof (args_t));
310	assert(arg_p);
311	arg_p->real_func = real_func;
312	arg_p->real_arg  = real_arg;
313
314	return ((*real_pthread_create)(new_thread_id, attr, probestart,
315			(void *) arg_p));
316
317}   /* end pthread_create */
318
319void
320thr_exit(void * status)
321{
322	static tnf_thr_exit_func_t real_thr_exit = NULL;
323	/* use dlsym to find the address of the "real" pthread_create func */
324	if (real_thr_exit == NULL) {
325		real_thr_exit = (tnf_thr_exit_func_t)
326		dlsym(RTLD_NEXT, "thr_exit");
327	}
328	assert(real_thr_exit);
329
330
331	/*
332	 * Calling tnf_thread_disable() whenever a thread exits...
333	 * This has the side-effect of unlocking our currently
334	 * locked block in the trace buffer.  This keeps a dying
335	 * thread from taking a block with it when it dies, but
336	 * it means that we won't be able to trace events from
337	 * the thread-specific data destructors.  We will lose
338	 * out on any events a thread spits out AFTER is calls thr_exit().
339	 * This code was added to fix a bug where tracing breaks when trying
340	 * to trace a program with large numbers of thread-ids.
341	 *
342	 * Addendum:
343	 * Now you can't get events for thr_exit using an interposition library.
344	 * Since thr_exit is a really helpful event, this is a problem.
345	 * Also, breaking this interposition will probably break
346	 * BAT, the DevPro TNF perf tool.
347	 *
348	 * Addendum:
349	 * Correction:  You can get interposition events if the interposition
350	 * library comes BEFORE libtnfprobe.so.  But not, if the interp.
351	 * library comes AFTER libtnfprobe.so.  This is a more difficult
352	 * constraint that it might sound like because of the following:
353	 * The tnfctl functional interface and the prex command line
354	 * interface provide convenience features where you can supply
355	 * a character string argument which will be put into LD_PRELOAD
356	 * for you.  Unfortunately, this string gets appended AFTER
357	 * libtnfprobe.so by the tnfctl library(and also hence by the
358	 * prex -l option).
359	 * Luckily, when libtnfprobe is added by the tnfctl library, it is
360	 * added AFTER an existing contents of the LD_PRELOAD variable.
361	 *
362	 * Therefore, if you are using an interposition library to collect
363	 * thr_exit and pthread_exit events, THEN you should NOT use 'prex -l'
364	 * or the 'ld_preload' argument to tnfctl_exec_open(), instead, you
365	 * should be sure to put the interposition library into the LD_PRELOAD
366	 * variable yourself.
367	 *
368	 */
369
370	tnf_thread_disable();
371
372	((*real_thr_exit)(status));
373}
374
375void
376pthread_exit(void * status)
377{
378	static tnf_pthread_exit_func_t real_pthread_exit = NULL;
379	/* use dlsym to find the address of the "real" pthread_create func */
380	if (real_pthread_exit == NULL) {
381		real_pthread_exit = (tnf_pthread_exit_func_t)
382		dlsym(RTLD_NEXT, "pthread_exit");
383	}
384	assert(real_pthread_exit);
385	/* see the comment in thr_exit about tnf_thread_disable() */
386	tnf_thread_disable();
387	((*real_pthread_exit)(status));
388}
389
390/*
391 * function to be interposed in front of _resume.  We invalidate the
392 * schedule record in case the lwpid changes the next time this
393 * thread is scheduled.
394 */
395
396#pragma weak _resume_ret = _tnf_resume_ret
397void
398_tnf_resume_ret(void *arg1)
399{
400	static void (*real_resume_ret)(void *) = NULL;
401	tnf_ops_t	*ops;
402
403	if (real_resume_ret == NULL) {
404		real_resume_ret = (void (*)(void *)) dlsym(RTLD_NEXT,
405					"_resume_ret");
406	}
407	assert(real_resume_ret);
408
409	ops = tnf_get_ops();
410	if (ops) {
411		/*
412		 * invalidate the schedule record.  This forces it
413		 * to get re-initialized with the new lwpid the next
414		 * time this thread gets scheduled
415		 */
416		if (ops->schedule.lwpid != _lwp_self())
417			ops->schedule.record_p = 0;
418	}
419
420	real_resume_ret(arg1);
421}
422
423/*
424 * Functions to be interposed in front of fork and fork1.
425 *
426 * NOTE: we can't handle vfork, because the child would ruin the parent's
427 * data structures.  We therefore don't interpose, letting the child's
428 * events appear as though they were the parent's.  A slightly cleaner
429 * way to handle vfork would be to interpose on vfork separately to
430 * change the pid and anything else needed to show any events caused
431 * by the child as its events, and then interpose on the exec's as
432 * well to set things back to the way they should be for the parent.
433 * But this is a lot of work, and it makes almost no difference, since the
434 * child typically exec's very quickly after a vfork.
435 */
436
437#pragma weak fork = _tnf_fork
438pid_t
439_tnf_fork(void)
440{
441	static fork_t real_fork = NULL;
442
443	if (real_fork == NULL) {
444		real_fork = (fork_t)dlsym(RTLD_NEXT, "fork");
445	}
446	assert(real_fork);
447	return (common_fork(real_fork));
448}
449
450#pragma weak fork1 = _tnf_fork1
451pid_t
452_tnf_fork1(void)
453{
454	static fork_t real_fork = NULL;
455
456	if (real_fork == NULL) {
457		real_fork = (fork_t)dlsym(RTLD_NEXT, "fork1");
458	}
459	assert(real_fork);
460	return (common_fork(real_fork));
461}
462
463#ifdef sparc
464/*
465 * Function to be interposed in front of thr_stksegment
466 * _tnf_thr_stksegment() - used to hide the probestart() allocated data
467 * on the thread stack, ensuring that the caller receives a pointer to the
468 * true bottom (ie, usable) portion of the stack, and the size thereof.
469 *
470 * NOTE:  On sparc systems, failure to allow for the presense of tnf data
471 * on the stack would cause TNF probes to fail across doorfs calls.  The
472 * i386 version of door_return decides to "skip over some slop", so no
473 * interpose function is required for x86;  if the 512 byte 'slop skip'
474 * is ever removed from the i386 door_return, then it will also need
475 * interpose function intervention.
476 *
477 * Note: Instead of making this function static, we reduce it to local
478 * scope in the mapfile. That allows the linker to prevent it from
479 * appearing in the .SUNW_dynsymsort section.
480 */
481#pragma weak thr_stksegment = _tnf_thr_stksegment
482int
483_tnf_thr_stksegment(stack_t *s)
484{
485	static tnf_thr_stksegment_func_t real_thr_stksegment = NULL;
486	int	err;
487
488#ifdef VERYVERBOSE
489	fprintf(stderr, "hello from the interposed thr_stksegment\n");
490#endif
491
492	if (real_thr_stksegment == NULL) {
493		real_thr_stksegment = (tnf_thr_stksegment_func_t)
494		    dlsym(RTLD_NEXT, "thr_stksegment");
495	}
496	assert(real_thr_stksegment);
497
498	err = ((*real_thr_stksegment)(s));
499	if (err == 0) {
500		s->ss_sp = (void *)((caddr_t)s->ss_sp - tnf_probe_dsize);
501		s->ss_size -= tnf_probe_dsize;
502	}
503	return (err);
504}
505#endif /* sparc */
506
507/* ---------------------------------------------------------------- */
508/* ----------------------- Private Functions ---------------------- */
509/* ---------------------------------------------------------------- */
510
511/*
512 * tnf_probe_getfunc() - default test function if libthread is not
513 * present
514 */
515static tnf_ops_t *
516tnf_probe_getfunc(void)
517{
518	/* test function to be used if libthread is not linked in */
519#ifdef DEBUGFUNCS
520	{
521		char tmp_buf[512];
522		(void) sprintf(tmp_buf, "tnf_probe_getfunc: \n");
523		(void) write(2, tmp_buf, strlen(tmp_buf));
524	}
525#endif
526	return (tpd);
527}   /* end tnf_probe_getfunc */
528
529
530/*
531 * probestart() - this function is called as the start_func by the
532 * interposed thr_create() and pthread_create().  It calls the real start
533 * function.
534 */
535
536static void *
537probestart(void * arg)
538{
539	args_t 		*args_p = (args_t *)arg;
540	start_func_t	real_func;
541	void		*real_arg;
542	tnf_ops_t	ops;		/* allocated on stack */
543	void		*real_retval;
544
545#ifdef VERYVERBOSE
546	fprintf(stderr, "hello from the interposed thr_create child\n");
547#endif
548#ifdef sparc
549	/*
550	 * if the size of the probe data has not yet been calculated,
551	 * initialize a jmpbuffer and calculate the amount of stack space
552	 * used by probestart:  %fp - %sp from jmp_buf
553	 * Not expecting anything to actually longjmp here, so that is
554	 * handled as an error condition.
555	 */
556	if (tnf_probe_dsize == 0) {
557		jmp_buf tnf_jmpbuf;
558		if (setjmp(tnf_jmpbuf) != 0) {
559			(void) write(2,
560			    "probestart:  unexpected longjmp\n", 32);
561			assert(0);
562		}
563		tnf_probe_dsize = (size_t)(tnf_jmpbuf[3] - tnf_jmpbuf[1]);
564	}
565#endif /* sparc */
566
567	/* initialize ops */
568	(void) memset(&ops, 0, sizeof (ops));	/* zero ops */
569	ops.mode = TNF_ALLOC_REUSABLE;
570	ops.alloc = tnfw_b_alloc;
571	ops.commit = tnfw_b_xcommit;
572	ops.rollback = tnfw_b_xabort;
573
574	/* copy (and free) the allocated arg block */
575	real_func = args_p->real_func;
576	real_arg  = args_p->real_arg;
577	free(args_p);
578
579	/* paranoia: thr_probe_setup should be defined */
580	assert(thr_probe_setup != 0);
581	if (thr_probe_setup != 0) thr_probe_setup(&ops);
582
583#ifdef VERYVERBOSE
584	fprintf(stderr, "in middle of interposed start procedure\n");
585#endif
586
587	real_retval = (*real_func)(real_arg);
588
589	/*
590	 * we need to write a NULL into the tpd pointer to disable
591	 * tracing for this thread.
592	 * CAUTION: never make this function tail recursive because
593	 * tpd is allocated on stack.
594	 */
595
596	/* This should be handled by the call to tnf_thread_disable() */
597	/* if (thr_probe_setup != 0) */
598	/* thr_probe_setup(NULL); */
599
600	/* see the comment in thr_exit about tnf_thread_disable */
601	tnf_thread_disable();
602
603	return (real_retval);
604
605}   /* end probestart */
606
607
608static thread_key_t tpd_key = THR_ONCE_KEY;
609static tnf_ops_t *stashed_tpd = NULL;
610
611/*
612 * tnf_thread_disable: API to disable a thread
613 */
614void
615tnf_thread_disable(void)
616{
617	tnf_ops_t		*ops;
618
619	if (thr_probe_setup != 0) {
620		/* threaded client */
621
622		/* REMIND: destructor function ? */
623		(void) thr_keycreate_once(&tpd_key, NULL);
624		/* get the tpd */
625		ops = thr_probe_getfunc_addr();
626		/* check ops to ensure function is idempotent */
627		if (ops != NULL) {
628			/* unlock currently held blocks */
629			tnfw_b_release_block(&ops->wcb);
630			/* disable the thread */
631			thr_probe_setup(NULL);
632			/* stash the tpd */
633			(void) thr_setspecific(tpd_key, ops);
634		}
635	} else {
636		/* non-threaded client */
637
638		/* get the tpd */
639		ops = tnf_probe_getfunc();
640		if (ops != NULL) {
641			/* disable the process */
642			probe_setup(NULL);
643			/* stash the tpd */
644			stashed_tpd = ops;
645		}
646	}
647}
648
649/*
650 * tnf_thread_enable: API to enable a thread
651 */
652void
653tnf_thread_enable(void)
654{
655	tnf_ops_t *ops;
656
657	if (thr_probe_setup != 0) {
658		/* threaded client */
659
660		ops = pthread_getspecific(tpd_key);
661		if (ops)
662			thr_probe_setup(ops);
663	} else {
664		/* non-threaded client */
665
666		ops = stashed_tpd;
667		if (ops)
668			probe_setup(ops);
669	}
670}
671
672/*
673 * common_fork - code that is common among the interpositions of
674 * fork, fork1, and vfork
675 */
676static pid_t
677common_fork(fork_t real_fork)
678{
679	pid_t		 retval;
680	tnf_ops_t	*ops;
681	tnf_tag_data_t	*metatag_data;
682
683#ifdef DEBUGFUNCS
684	{
685		char tmp_buf[512];
686		(void) sprintf(tmp_buf, "in interposed fork: \n");
687		(void) write(2, tmp_buf, strlen(tmp_buf));
688	}
689#endif
690	if ((_tnfw_b_control->tnf_state == TNFW_B_NOBUFFER) &&
691				(tnf_trace_file_name[0] != '\0')) {
692		/*
693		 * if no buffer has been allocated yet, and prex plugged in
694		 * name...
695		 */
696		ops = tnf_get_ops();
697		if (ops == NULL) {
698			/*
699			 * get it from stashed location
700			 * don't enable thread though
701			 */
702			if (thr_probe_setup != 0) {
703				/* threaded client */
704				ops = pthread_getspecific(tpd_key);
705			} else {
706				/* non-threaded client */
707				ops = stashed_tpd;
708			}
709		}
710
711		/*
712		 * ops shouldn't be NULL.  But, if it is, then we don't
713		 * initialize tracing.  In the child, tracing will be
714		 * set to broken.
715		 */
716		if (ops) {
717			/* initialize tracing */
718			ops->busy = 1;
719			metatag_data = TAG_DATA(tnf_struct_type);
720			metatag_data->tag_desc(ops, metatag_data);
721			/* commit the data */
722			(void) ops->commit(&(ops->wcb));
723			ops->busy = 0;
724		}
725	}
726
727	retval = real_fork();
728	if (retval == 0) {
729		/* child process */
730		_tnfw_b_control->tnf_pid = getpid();
731		if ((_tnfw_b_control->tnf_state == TNFW_B_NOBUFFER) &&
732				(tnf_trace_file_name[0] != '\0')) {
733			/*
734			 * race condition, prex attached after condition was
735			 * checked in parent, so both parent and child point at
736			 * the same file name and will overwrite each other.
737			 * So, we set tracing to broken in child.  We could
738			 * invent a new state called RACE and use prex to
739			 * reset it, if needed...
740			 */
741			tnf_trace_file_name[0] = '\0';
742			_tnfw_b_control->tnf_state = TNFW_B_BROKEN;
743		} else if (_tnfw_b_control->tnf_state == TNFW_B_RUNNING) {
744			/* normal expected condition */
745			_tnfw_b_control->tnf_state = TNFW_B_FORKED;
746		}
747	}
748	return (retval);
749}
750
751/*
752 * tnf_threaded_test
753 */
754/*ARGSUSED0*/
755static void *
756tnf_threaded_test(void *dummy, tnf_probe_control_t *probe_p,
757			tnf_probe_setup_t *set_p)
758{
759	tnf_ops_t *tpd_p;
760
761	tpd_p = thr_probe_getfunc_addr();
762	if (tpd_p) {
763		return (probe_p->alloc_func(tpd_p, probe_p, set_p));
764	}
765	return (NULL);
766}
767
768
769/*
770 * tnf_non_threaded_test
771 */
772/*ARGSUSED0*/
773static void *
774tnf_non_threaded_test(void *dummy, tnf_probe_control_t *probe_p,
775				tnf_probe_setup_t *set_p)
776{
777	tnf_ops_t *tpd_p;
778
779	tpd_p = tnf_probe_getfunc();
780	if (tpd_p) {
781		return (probe_p->alloc_func(tpd_p, probe_p, set_p));
782	}
783	return (NULL);
784}
785
786/*
787 * tnf_get_ops() returns the ops pointer (thread-private data), or NULL
788 * if tracing is disabled for this thread.
789 */
790static tnf_ops_t *
791tnf_get_ops()
792{
793	tnf_context_t	*test_func_p = &thr_probe_getfunc_addr;
794	tnf_context_t	 test_func;
795
796	/*
797	 * IMPORTANT: this test to see whether thr_probe_getfunc_addr
798	 * is bound is tricky.  The compiler currently has a bug
799	 * (1263684) that causes the test to be optimized away unless
800	 * coded with an intermediate pointer (test_func_p).  This
801	 * causes the process to SEGV when the variable is not bound.
802	 */
803
804	test_func = test_func_p ? *test_func_p : tnf_probe_getfunc;
805	return ((*test_func)());
806}
807