xref: /illumos-gate/usr/src/lib/libc/port/gen/mon.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1988 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  * University Copyright- Copyright (c) 1982, 1986, 1988
32  * The Regents of the University of California
33  * All Rights Reserved
34  *
35  * University Acknowledgment- Portions of this document are derived from
36  * software developed by the University of California, Berkeley, and its
37  * contributors.
38  */
39 
40 /*
41  *	Environment variable PROFDIR added such that:
42  *		If PROFDIR doesn't exist, "mon.out" is produced as before.
43  *		If PROFDIR = NULL, no profiling output is produced.
44  *		If PROFDIR = string, "string/pid.progname" is produced,
45  *		  where name consists of argv[0] suitably massaged.
46  *
47  *
48  *	Routines:
49  *		(global) monitor	init, cleanup for prof(1)iling
50  *		(global) _mcount	function call counter
51  *		(global) _mcount_newent	call count entry manager
52  *		(static) _mnewblock	call count block allocator
53  *
54  *
55  *	Monitor(), coordinating with mcount(), mcount_newent() and mnewblock(),
56  *	maintains a series of one or more blocks of prof-profiling
57  *	information.  These blocks are added in response to calls to
58  *	monitor() (explicitly or via mcrt[01]'s _start) and, via mcount()'s
59  *	calls to mcount_newent() thence to mnewblock().
60  *	The blocks are tracked via a linked list of block anchors,
61  *	which each point to a block.
62  *
63  *
64  *	An anchor points forward, backward and 'down' (to a block).
65  *	A block has the profiling information, and consists of
66  *	three regions: a header, a function call count array region,
67  *	and an optional execution histogram region, as illustrated below.
68  *
69  *
70  *		 "anchor"
71  *		+========+
72  *	prior<--|        |-->next anchor
73  *	anchor	|        |
74  *		+========+
75  *		 |
76  *		 |
77  *		 V "block"
78  *		+-----------+
79  *		+  header   +
80  *		+-----------+
81  *		+           +
82  *		+ fcn call  +	// data collected by mcount
83  *		+  counts   +
84  *		+  array    +
85  *		+           +
86  *		+-----------+
87  *		+           +
88  *		+ execution +	// data collected by system call,
89  *		+ profile   +	// profil(2) (assumed ALWAYS specified
90  *		+ histogram +	// by monitor()-caller, even if small;
91  *		+           +	// never specified by mnewblock()).
92  *		+-----------+
93  *
94  *	The first time monitor() is called, it sets up the chain
95  *	by allocating an anchor and initializing countbase and countlimit
96  *	to zero.  Everyone assumes that they start out zeroed.
97  *
98  *	When a user (or _start from mcrt[01]) calls monitor(), they
99  *	register a buffer which contains the third region (either with
100  *	a meaningful size, or so short that profil-ing is being shut off).
101  *
102  *	For each fcn, the first time it calls mcount(), mcount calls
103  *	mcount_newent(), which parcels out the fcn call count entries
104  *	from the current block, until they are exausted; then it calls
105  *	mnewblock().
106  *
107  *	Mnewbloc() allocates a block Without a third region, and
108  *	links in a new associated anchor, adding a new anchor&block pair
109  *	to the linked list.  Each new mnewblock() block or user block,
110  *	is added to the list as it comes in, FIFO.
111  *
112  *	When monitor() is called to close up shop, it writes out
113  *	a summarizing header, ALL the fcn call counts from ALL
114  *	the blocks, and the Last specified execution histogram
115  *	(currently there is no neat way to accumulate that info).
116  *	This preserves all call count information, even when
117  *	new blocks are specified.
118  *
119  *	NOTE - no block passed to monitor() may be freed, until
120  *	it is called to clean up!!!!
121  *
122  */
123 
124 #pragma weak _monitor = monitor
125 
126 #include "lint.h"
127 #include "mtlib.h"
128 #include "libc.h"
129 #include <sys/types.h>
130 #include <string.h>
131 #include <stdlib.h>
132 #include <stdio.h>
133 #include <errno.h>
134 #include <mon.h>
135 #include <fcntl.h>
136 #include <unistd.h>
137 #include <thread.h>
138 #include <synch.h>
139 
140 #define	PROFDIR	"PROFDIR"
141 
142 static mutex_t mon_lock = DEFAULTMUTEX;
143 
144 char **___Argv = NULL; /* initialized to argv array by mcrt0 (if loaded) */
145 
146 /*
147  * countbase and countlimit are used to parcel out
148  * the pc,count cells from the current block one at
149  * a time to each profiled function, the first time
150  * that function is called.
151  * When countbase reaches countlimit, mcount() calls
152  * mnewblock() to link in a new block.
153  *
154  * Only monitor/mcount/mcount_newent/mnewblock() should change these!!
155  * Correct that: only these routines are ABLE to change these;
156  * countbase/countlimit are now STATIC!
157  */
158 static char *countbase;		/* addr of next pc,count cell to use in block */
159 static char *countlimit;	/* addr lim for cells (addr after last cell) */
160 
161 typedef struct anchor	ANCHOR;
162 
163 struct anchor {
164 	ANCHOR  *next, *prior;	/* forward, backward ptrs for list */
165 	struct hdr  *monBuffer;	/* 'down' ptr, to block */
166 	short  flags;		/* indicators - has histogram designation */
167 
168 	int  histSize;		/* if has region3, this is size. */
169 };
170 
171 #define	HAS_HISTOGRAM	0x0001		/* this buffer has a histogram */
172 
173 static ANCHOR 	*curAnchor = NULL;	/* addr of anchor for current block */
174 static ANCHOR    firstAnchor;		/* the first anchor to use */
175 					/* - hopefully the Only one needed */
176 					/* a speedup for most cases. */
177 static char *mon_out;
178 
179 static int writeBlocks(void);
180 static void _mnewblock(void);
181 struct cnt *_mcount_newent(void);
182 
183 /*
184  * int (*alowpc)(), (*ahighpc)(); boundaries of text to be monitored
185  * WORD *buffer;	ptr to space for monitor data(WORDs)
186  * size_t bufsize;	size of above space(in WORDs)
187  * size_t nfunc;	max no. of functions whose calls are counted
188  *			(default nfunc is 300 on PDP11, 600 on others)
189  */
190 void
191 monitor(int (*alowpc)(void), int (*ahighpc)(void), WORD *buffer,
192 	size_t bufsize, size_t nfunc)
193 {
194 	uint_t scale;
195 	long text;
196 	char *s;
197 	struct hdr *hdrp;
198 	ANCHOR  *newanchp;
199 	size_t	ssiz;
200 	int error;
201 	char	*lowpc = (char *)alowpc;
202 	char	*highpc = (char *)ahighpc;
203 
204 	lmutex_lock(&mon_lock);
205 
206 	if (lowpc == NULL) {		/* true only at the end */
207 		error = 0;
208 		if (curAnchor != NULL) { /* if anything was collected!.. */
209 			profil(NULL, 0, 0, 0);
210 			if (writeBlocks() == 0)
211 				error = errno;
212 		}
213 		lmutex_unlock(&mon_lock);
214 		if (error) {
215 			errno = error;
216 			perror(mon_out);
217 		}
218 		return;
219 	}
220 
221 	/*
222 	 * Ok - they want to submit a block for immediate use, for
223 	 *	function call count consumption, and execution profile
224 	 *	histogram computation.
225 	 * If the block fails sanity tests, just bag it.
226 	 * Next thing - get name to use. If PROFDIR is NULL, let's
227 	 *	get out now - they want No Profiling done.
228 	 *
229 	 * Otherwise:
230 	 * Set the block hdr cells.
231 	 * Get an anchor for the block, and link the anchor+block onto
232 	 *	the end of the chain.
233 	 * Init the grabba-cell externs (countbase/limit) for this block.
234 	 * Finally, call profil and return.
235 	 */
236 
237 	ssiz = ((sizeof (struct hdr) + nfunc * sizeof (struct cnt)) /
238 	    sizeof (WORD));
239 	if (ssiz >= bufsize || lowpc >= highpc) {
240 		lmutex_unlock(&mon_lock);
241 		return;
242 	}
243 
244 	if ((s = getenv(PROFDIR)) == NULL) { /* PROFDIR not in environment */
245 		mon_out = MON_OUT; /* use default "mon.out" */
246 	} else if (*s == '\0') { /* value of PROFDIR is NULL */
247 		lmutex_unlock(&mon_lock);
248 		return; /* no profiling on this run */
249 	} else { /* construct "PROFDIR/pid.progname" */
250 		int n;
251 		pid_t pid;
252 		char *name;
253 		size_t len;
254 
255 		len = strlen(s);
256 		/* 15 is space for /pid.mon.out\0, if necessary */
257 		if ((mon_out = libc_malloc(len + strlen(___Argv[0]) + 15))
258 		    == NULL) {
259 			lmutex_unlock(&mon_lock);
260 			perror("");
261 			return;
262 		}
263 		(void) strcpy(mon_out, s);
264 		name = mon_out + len;
265 		*name++ = '/'; /* two slashes won't hurt */
266 
267 		if ((pid = getpid()) <= 0) /* extra test just in case */
268 			pid = 1; /* getpid returns something inappropriate */
269 
270 		/* suppress leading zeros */
271 		for (n = 10000; n > pid; n /= 10)
272 			;
273 		for (; ; n /= 10) {
274 			*name++ = pid/n + '0';
275 			if (n == 1)
276 				break;
277 			pid %= n;
278 		}
279 		*name++ = '.';
280 
281 		if (___Argv != NULL) {	/* mcrt0.s executed */
282 			if ((s = strrchr(___Argv[0], '/')) != NULL)
283 				(void) strcpy(name, s + 1);
284 			else
285 				(void) strcpy(name, ___Argv[0]);
286 		} else {
287 			(void) strcpy(name, MON_OUT);
288 		}
289 	}
290 
291 
292 	hdrp = (struct hdr *)(uintptr_t)buffer;	/* initialize 1st region */
293 	hdrp->lpc = lowpc;
294 	hdrp->hpc = highpc;
295 	hdrp->nfns = nfunc;
296 
297 	/* get an anchor for the block */
298 	newanchp = (curAnchor == NULL) ? &firstAnchor :
299 	    (ANCHOR *)libc_malloc(sizeof (ANCHOR));
300 
301 	if (newanchp == NULL) {
302 		lmutex_unlock(&mon_lock);
303 		perror("monitor");
304 		return;
305 	}
306 
307 	/* link anchor+block into chain */
308 	newanchp->monBuffer = hdrp;		/* new, down. */
309 	newanchp->next  = NULL;			/* new, forward to NULL. */
310 	newanchp->prior = curAnchor;		/* new, backward. */
311 	if (curAnchor != NULL)
312 		curAnchor->next = newanchp;	/* old, forward to new. */
313 	newanchp->flags = HAS_HISTOGRAM;	/* note it has a histgm area */
314 
315 	/* got it - enable use by mcount() */
316 	countbase  = (char *)buffer + sizeof (struct hdr);
317 	countlimit = countbase + (nfunc * sizeof (struct cnt));
318 
319 	/* (set size of region 3) */
320 	newanchp->histSize = (int)
321 	    (bufsize * sizeof (WORD) - (countlimit - (char *)buffer));
322 
323 
324 	/* done w/regions 1 + 2: setup 3  to activate profil processing. */
325 	buffer += ssiz;			/* move ptr past 2'nd region */
326 	bufsize -= ssiz;		/* no. WORDs in third region */
327 					/* no. WORDs of text */
328 	text = (highpc - lowpc + sizeof (WORD) - 1) / sizeof (WORD);
329 
330 	/*
331 	 * scale is a 16 bit fixed point fraction with the decimal
332 	 * point at the left
333 	 */
334 	if (bufsize < text) {
335 		/* make sure cast is done first! */
336 		double temp = (double)bufsize;
337 		scale = (uint_t)((temp * (long)0200000L) / text);
338 	} else {
339 		/* scale must be less than 1 */
340 		scale = 0xffff;
341 	}
342 	bufsize *= sizeof (WORD);	/* bufsize into # bytes */
343 	profil(buffer, bufsize, (ulong_t)lowpc, scale);
344 
345 
346 	curAnchor = newanchp;	/* make latest addition, the cur anchor */
347 	lmutex_unlock(&mon_lock);
348 }
349 
350 /*
351  * writeBlocks() - write accumulated profiling info, std fmt.
352  *
353  * This routine collects the function call counts, and the
354  * last specified profil buffer, and writes out one combined
355  * 'pseudo-block', as expected by current and former versions
356  * of prof.
357  */
358 static int
359 writeBlocks(void)
360 {
361 	int fd;
362 	int ok;
363 	ANCHOR *ap;		/* temp anchor ptr */
364 	struct hdr sum;		/* summary header (for 'pseudo' block) */
365 	ANCHOR *histp;		/* anchor with histogram to use */
366 
367 	if ((fd = creat(mon_out, 0666)) < 0)
368 		return (0);
369 
370 	/*
371 	 * this loop (1) computes # funct cts total
372 	 *  (2) finds anchor of last block w / hist(histp)
373 	 */
374 	histp = NULL;
375 	for (sum.nfns = 0, ap = &firstAnchor; ap != NULL; ap = ap->next) {
376 		sum.nfns += ap->monBuffer->nfns; /* accum num of cells */
377 		if (ap->flags & HAS_HISTOGRAM)
378 			histp = ap;	 /* remember lastone with a histgm */
379 	}
380 
381 
382 	/* copy pc range from effective histgm */
383 	sum.lpc = histp->monBuffer->lpc;
384 	sum.hpc = histp->monBuffer->hpc;
385 
386 	ok = (write(fd, (char *)&sum, sizeof (sum)) == sizeof (sum));
387 
388 	if (ok) {		/* if the hdr went out ok.. */
389 		size_t amt;
390 		char *p;
391 
392 		/* write out the count arrays (region 2's) */
393 		for (ap = &firstAnchor; ok && ap != NULL; ap = ap->next) {
394 			amt = ap->monBuffer->nfns * sizeof (struct cnt);
395 			p = (char *)ap->monBuffer + sizeof (struct hdr);
396 
397 			ok = (write(fd, p, amt) == amt);
398 		}
399 
400 		/* count arrays out; write out histgm area */
401 		if (ok) {
402 			p = (char *)histp->monBuffer + sizeof (struct hdr) +
403 			    (histp->monBuffer->nfns * sizeof (struct cnt));
404 			amt = histp->histSize;
405 
406 			ok = (write(fd, p, amt) == amt);
407 
408 		}
409 	}
410 
411 	(void) close(fd);
412 
413 	return (ok);	/* indicate success */
414 }
415 
416 
417 /*
418  * mnewblock()-allocate and link in a new region1&2 block.
419  *
420  * This routine, called by mcount_newent(), allocates a new block
421  * containing only regions 1 & 2 (hdr and fcn call count array),
422  * and an associated anchor (see header comments), inits the
423  * header (region 1) of the block, links the anchor into the
424  * list, and resets the countbase/limit pointers.
425  *
426  * This routine cannot be called recursively, since (each) mcount
427  * has a local lock which prevents recursive calls to mcount_newent.
428  * See mcount_newent for more details.
429  *
430  */
431 
432 #define	THISMANYFCNS	(MPROGS0*2)
433 
434 /*
435  * call libc_malloc() to get an anchor & a regn1&2 block, together
436  */
437 #define	GETTHISMUCH	(sizeof (ANCHOR) + 	/* get an ANCHOR */  \
438 			(sizeof (struct hdr) +	/* get Region 1 */   \
439 			THISMANYFCNS * sizeof (struct cnt))) /* Region 2 */  \
440 						/* but No region 3 */
441 
442 
443 static void
444 _mnewblock(void)
445 {
446 	struct hdr *hdrp;
447 	ANCHOR	*newanchp;
448 	ANCHOR	*p;
449 
450 					/* get anchor And block, together */
451 	p = libc_malloc(GETTHISMUCH);
452 	if (p == NULL) {
453 		perror("mcount(mnewblock)");
454 		return;
455 	}
456 
457 	newanchp = p;
458 	hdrp = (struct hdr *)(p + 1);
459 
460 					/* initialize 1st region to dflts */
461 	hdrp->lpc = 0;
462 	hdrp->hpc = 0;
463 	hdrp->nfns = THISMANYFCNS;
464 
465 					/* link anchor+block into chain */
466 	newanchp->monBuffer = hdrp;		/* new, down. */
467 	newanchp->next  = NULL;			/* new, forward to NULL. */
468 	newanchp->prior = curAnchor;		/* new, backward. */
469 	if (curAnchor != NULL)
470 		curAnchor->next = newanchp;	/* old, forward to new. */
471 	newanchp->flags = 0;		/* note that it has NO histgm area */
472 
473 					/* got it - enable use by mcount() */
474 	countbase  = (char *)hdrp + sizeof (struct hdr);
475 	countlimit = countbase + (THISMANYFCNS * sizeof (struct cnt));
476 
477 	newanchp->histSize = 0;	/* (set size of region 3.. to 0) */
478 
479 
480 	curAnchor = newanchp;		/* make latest addition, cur anchor */
481 }
482 
483 /*
484  * mcount_newent() -- call to get a new mcount call count entry.
485  *
486  * this function is called by _mcount to get a new call count entry
487  * (struct cnt, in the region allocated by monitor()), or to return
488  * zero if profiling is off.
489  *
490  * This function acts as a funnel, an access function to make sure
491  * that all instances of mcount (the one in the a.out, and any in
492  * any shared objects) all get entries from the same array, and
493  * all know when profiling is off.
494  *
495  * NOTE: when mcount calls this function, it sets a private flag
496  * so that it does not call again until this function returns,
497  * thus preventing recursion.
498  *
499  * At Worst, the mcount in either a shared object or the a.out
500  * could call once, and then the mcount living in the shared object
501  * with monitor could call a second time (i.e. libc.so.1, although
502  * presently it does not have mcount in it).  This worst case
503  * would involve Two active calls to mcount_newent, which it can
504  * handle, since the second one would find a already-set value
505  * in countbase.
506  *
507  * The only unfortunate result is that No new call counts
508  * will be handed out until this function returns.
509  * Thus if libc_malloc or other routines called inductively by
510  * this routine have not yet been provided with a call count entry,
511  * they will not get one until this function call is completed.
512  * Thus a few calls to library routines during the course of
513  * profiling setup, may not be counted.
514  *
515  * NOTE: countbase points at the next available entry, and
516  * countlimit points past the last valid entry, in the current
517  * function call counts array.
518  *
519  *
520  * if profiling is off		// countbase==0
521  *   just return 0
522  *
523  * else
524  *   if need more entries	// because countbase points last valid entry
525  *     link in a new block, resetting countbase and countlimit
526  *   endif
527  *   if Got more entries
528  *     return pointer to the next available entry, and
529  *     update pointer-to-next-slot before you return.
530  *
531  *   else			// failed to get more entries
532  *     just return 0
533  *
534  *   endif
535  * endif
536  */
537 
538 struct cnt *
539 _mcount_newent(void)
540 {
541 	if (countbase == 0)
542 		return (NULL);
543 
544 	if (countbase >= countlimit)
545 		_mnewblock();		/* get a new block; set countbase */
546 
547 	if (countbase != 0) {
548 		struct cnt *cur_countbase = (struct cnt *)(uintptr_t)countbase;
549 
550 		countbase += sizeof (struct cnt);
551 		return (cur_countbase);
552 	}
553 	return (NULL);
554 }
555