xref: /openbsd/usr.sbin/kgmon/kgmon.c (revision 404b540a)
1 /*	$OpenBSD: kgmon.c,v 1.14 2007/11/26 09:28:34 martynas Exp $	*/
2 
3 /*
4  * Copyright (c) 1983, 1992, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #ifndef lint
33 static char copyright[] =
34 "@(#) Copyright (c) 1983, 1992, 1993\n\
35 	The Regents of the University of California.  All rights reserved.\n";
36 #endif /* not lint */
37 
38 #ifndef lint
39 /*static char sccsid[] = "from: @(#)kgmon.c	8.1 (Berkeley) 6/6/93";*/
40 static char *rcsid = "$OpenBSD: kgmon.c,v 1.14 2007/11/26 09:28:34 martynas Exp $";
41 #endif /* not lint */
42 
43 #include <sys/param.h>
44 #include <sys/file.h>
45 #include <sys/sysctl.h>
46 #include <sys/gmon.h>
47 #include <errno.h>
48 #include <err.h>
49 #include <kvm.h>
50 #include <limits.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <unistd.h>
54 #include <string.h>
55 #include <nlist.h>
56 #include <ctype.h>
57 #include <paths.h>
58 
59 struct nlist nl[] = {
60 #define	N_GMONPARAM	0
61 	{ "__gmonparam" },
62 #define	N_PROFHZ	1
63 	{ "_profhz" },
64 	{ NULL }
65 };
66 
67 struct kvmvars {
68 	kvm_t	*kd;
69 	struct gmonparam gpm;
70 };
71 
72 int	bflag, hflag, kflag, rflag, pflag;
73 int	debug = 0;
74 void	setprof(struct kvmvars *, int);
75 void	dumpstate(struct kvmvars *);
76 void	reset(struct kvmvars *);
77 void	kern_readonly(int);
78 int	getprof(struct kvmvars *);
79 int	getprofhz(struct kvmvars *);
80 int	openfiles(char *, char *, struct kvmvars *);
81 
82 int
83 main(int argc, char **argv)
84 {
85 	extern char *__progname;
86 	int ch, mode, disp, accessmode;
87 	struct kvmvars kvmvars;
88 	char *sys, *kmemf;
89 
90 	seteuid(getuid());
91 	kmemf = NULL;
92 	sys = NULL;
93 	while ((ch = getopt(argc, argv, "M:N:bhpr")) != -1) {
94 		switch((char)ch) {
95 
96 		case 'M':
97 			kmemf = optarg;
98 			kflag = 1;
99 			break;
100 
101 		case 'N':
102 			sys = optarg;
103 			break;
104 
105 		case 'b':
106 			bflag = 1;
107 			break;
108 
109 		case 'h':
110 			hflag = 1;
111 			break;
112 
113 		case 'p':
114 			pflag = 1;
115 			break;
116 
117 		case 'r':
118 			rflag = 1;
119 			break;
120 
121 		default:
122 			fprintf(stderr,
123 			    "usage: %s [-bhpr] [-M core] [-N system]\n",
124 			    __progname);
125 			exit(1);
126 		}
127 	}
128 	argc -= optind;
129 	argv += optind;
130 
131 #define BACKWARD_COMPATIBILITY
132 #ifdef	BACKWARD_COMPATIBILITY
133 	if (*argv) {
134 		sys = *argv;
135 		if (*++argv) {
136 			kmemf = *argv;
137 			++kflag;
138 		}
139 	}
140 #endif
141 	accessmode = openfiles(sys, kmemf, &kvmvars);
142 	mode = getprof(&kvmvars);
143 	if (hflag)
144 		disp = GMON_PROF_OFF;
145 	else if (bflag)
146 		disp = GMON_PROF_ON;
147 	else
148 		disp = mode;
149 	if (pflag)
150 		dumpstate(&kvmvars);
151 	if (rflag)
152 		reset(&kvmvars);
153 	if (accessmode == O_RDWR)
154 		setprof(&kvmvars, disp);
155 	printf("%s: kernel profiling is %s.\n", __progname,
156 	    disp == GMON_PROF_OFF ? "off" : "running");
157 	return (0);
158 }
159 
160 /*
161  * Check that profiling is enabled and open any ncessary files.
162  */
163 int
164 openfiles(char *sys, char *kmemf, struct kvmvars *kvp)
165 {
166 	int mib[3], state, openmode;
167 	size_t size;
168 	char errbuf[_POSIX2_LINE_MAX];
169 
170 	if (!kflag) {
171 		mib[0] = CTL_KERN;
172 		mib[1] = KERN_PROF;
173 		mib[2] = GPROF_STATE;
174 		size = sizeof state;
175 		if (sysctl(mib, 3, &state, &size, NULL, 0) < 0)
176 			errx(20, "profiling not defined in kernel.");
177 		if (!(bflag || hflag || rflag ||
178 		    (pflag && state == GMON_PROF_ON)))
179 			return (O_RDONLY);
180 		(void)seteuid(0);
181 		if (sysctl(mib, 3, NULL, NULL, &state, size) >= 0)
182 			return (O_RDWR);
183 		(void)seteuid(getuid());
184 		kern_readonly(state);
185 		return (O_RDONLY);
186 	}
187 	openmode = (bflag || hflag || pflag || rflag) ? O_RDWR : O_RDONLY;
188 	kvp->kd = kvm_openfiles(sys, kmemf, NULL, openmode, errbuf);
189 	if (kvp->kd == NULL) {
190 		if (openmode == O_RDWR) {
191 			openmode = O_RDONLY;
192 			kvp->kd = kvm_openfiles(sys, kmemf, NULL, O_RDONLY,
193 			    errbuf);
194 		}
195 		if (kvp->kd == NULL)
196 			errx(2, "kvm_openfiles: %s", errbuf);
197 		kern_readonly(GMON_PROF_ON);
198 	}
199 	if (kvm_nlist(kvp->kd, nl) < 0)
200 		errx(3, "%s: no namelist", sys ? sys : _PATH_UNIX);
201 	if (!nl[N_GMONPARAM].n_value)
202 		errx(20, "profiling not defined in kernel.");
203 	return (openmode);
204 }
205 
206 /*
207  * Suppress options that require a writable kernel.
208  */
209 void
210 kern_readonly(int mode)
211 {
212 	extern char *__progname;
213 
214 	(void)fprintf(stderr, "%s: kernel read-only: ", __progname);
215 	if (pflag && mode == GMON_PROF_ON)
216 		(void)fprintf(stderr, "data may be inconsistent\n");
217 	if (rflag)
218 		(void)fprintf(stderr, "-r suppressed\n");
219 	if (bflag)
220 		(void)fprintf(stderr, "-b suppressed\n");
221 	if (hflag)
222 		(void)fprintf(stderr, "-h suppressed\n");
223 	rflag = bflag = hflag = 0;
224 }
225 
226 /*
227  * Get the state of kernel profiling.
228  */
229 int
230 getprof(struct kvmvars *kvp)
231 {
232 	int mib[3];
233 	size_t size;
234 
235 	if (kflag) {
236 		size = kvm_read(kvp->kd, nl[N_GMONPARAM].n_value, &kvp->gpm,
237 		    sizeof kvp->gpm);
238 	} else {
239 		mib[0] = CTL_KERN;
240 		mib[1] = KERN_PROF;
241 		mib[2] = GPROF_GMONPARAM;
242 		size = sizeof kvp->gpm;
243 		if (sysctl(mib, 3, &kvp->gpm, &size, NULL, 0) < 0)
244 			size = 0;
245 	}
246 	if (size != sizeof kvp->gpm)
247 		errx(4, "cannot get gmonparam: %s",
248 		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
249 	return (kvp->gpm.state);
250 }
251 
252 /*
253  * Enable or disable kernel profiling according to the state variable.
254  */
255 void
256 setprof(struct kvmvars *kvp, int state)
257 {
258 	struct gmonparam *p = (struct gmonparam *)nl[N_GMONPARAM].n_value;
259 	int mib[3], oldstate;
260 	size_t sz;
261 
262 	sz = sizeof(state);
263 	if (!kflag) {
264 		mib[0] = CTL_KERN;
265 		mib[1] = KERN_PROF;
266 		mib[2] = GPROF_STATE;
267 		if (sysctl(mib, 3, &oldstate, &sz, NULL, 0) < 0)
268 			goto bad;
269 		if (oldstate == state)
270 			return;
271 		(void)seteuid(0);
272 		if (sysctl(mib, 3, NULL, NULL, &state, sz) >= 0) {
273 			(void)seteuid(getuid());
274 			return;
275 		}
276 		(void)seteuid(getuid());
277 	} else if (kvm_write(kvp->kd, (u_long)&p->state, (void *)&state, sz)
278 	    == sz)
279 		return;
280 bad:
281 	warnx("warning: cannot turn profiling %s",
282 	    state == GMON_PROF_OFF ? "off" : "on");
283 }
284 
285 /*
286  * Build the gmon.out file.
287  */
288 void
289 dumpstate(struct kvmvars *kvp)
290 {
291 	FILE *fp;
292 	struct rawarc rawarc;
293 	struct tostruct *tos;
294 	u_long frompc;
295 	u_short *froms, *tickbuf;
296 	int mib[3];
297 	size_t i;
298 	struct gmonhdr h;
299 	int fromindex, endfrom, toindex;
300 
301 	setprof(kvp, GMON_PROF_OFF);
302 	fp = fopen("gmon.out", "w");
303 	if (fp == 0) {
304 		perror("gmon.out");
305 		return;
306 	}
307 
308 	/*
309 	 * Build the gmon header and write it to a file.
310 	 */
311 	bzero(&h, sizeof(h));
312 	h.lpc = kvp->gpm.lowpc;
313 	h.hpc = kvp->gpm.highpc;
314 	h.ncnt = kvp->gpm.kcountsize + sizeof(h);
315 	h.version = GMONVERSION;
316 	h.profrate = getprofhz(kvp);
317 	fwrite((char *)&h, sizeof(h), 1, fp);
318 
319 	/*
320 	 * Write out the tick buffer.
321 	 */
322 	mib[0] = CTL_KERN;
323 	mib[1] = KERN_PROF;
324 	if ((tickbuf = (u_short *)malloc(kvp->gpm.kcountsize)) == NULL)
325 		errx(5, "cannot allocate kcount space");
326 	if (kflag) {
327 		i = kvm_read(kvp->kd, (u_long)kvp->gpm.kcount, (void *)tickbuf,
328 		    kvp->gpm.kcountsize);
329 	} else {
330 		mib[2] = GPROF_COUNT;
331 		i = kvp->gpm.kcountsize;
332 		if (sysctl(mib, 3, tickbuf, &i, NULL, 0) < 0)
333 			i = 0;
334 	}
335 	if (i != kvp->gpm.kcountsize)
336 		errx(6, "read ticks: read %lu, got %zu: %s",
337 		    kvp->gpm.kcountsize, i,
338 		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
339 	if ((fwrite(tickbuf, kvp->gpm.kcountsize, 1, fp)) != 1)
340 		err(7, "writing tocks to gmon.out");
341 	free(tickbuf);
342 
343 	/*
344 	 * Write out the arc info.
345 	 */
346 	if ((froms = (u_short *)malloc(kvp->gpm.fromssize)) == NULL)
347 		errx(8, "cannot allocate froms space");
348 	if (kflag) {
349 		i = kvm_read(kvp->kd, (u_long)kvp->gpm.froms, (void *)froms,
350 		    kvp->gpm.fromssize);
351 	} else {
352 		mib[2] = GPROF_FROMS;
353 		i = kvp->gpm.fromssize;
354 		if (sysctl(mib, 3, froms, &i, NULL, 0) < 0)
355 			i = 0;
356 	}
357 	if (i != kvp->gpm.fromssize)
358 		errx(9, "read froms: read %lu, got %zu: %s",
359 		    kvp->gpm.fromssize, i,
360 		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
361 	if ((tos = (struct tostruct *)malloc(kvp->gpm.tossize)) == NULL)
362 		errx(10, "cannot allocate tos space");
363 	if (kflag) {
364 		i = kvm_read(kvp->kd, (u_long)kvp->gpm.tos, (void *)tos,
365 		    kvp->gpm.tossize);
366 	} else {
367 		mib[2] = GPROF_TOS;
368 		i = kvp->gpm.tossize;
369 		if (sysctl(mib, 3, tos, &i, NULL, 0) < 0)
370 			i = 0;
371 	}
372 	if (i != kvp->gpm.tossize)
373 		errx(11, "read tos: read %lu, got %zu: %s",
374 		    kvp->gpm.tossize, i,
375 		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
376 	if (debug)
377 		warnx("lowpc 0x%lx, textsize 0x%lx",
378 		    kvp->gpm.lowpc, kvp->gpm.textsize);
379 	endfrom = kvp->gpm.fromssize / sizeof(*froms);
380 	for (fromindex = 0; fromindex < endfrom; ++fromindex) {
381 		if (froms[fromindex] == 0)
382 			continue;
383 		frompc = (u_long)kvp->gpm.lowpc +
384 		    (fromindex * kvp->gpm.hashfraction * sizeof(*froms));
385 		for (toindex = froms[fromindex]; toindex != 0;
386 		   toindex = tos[toindex].link) {
387 			if (debug)
388 			  warnx("[mcleanup] frompc 0x%lx selfpc 0x%lx count %ld",
389 			    frompc, tos[toindex].selfpc, tos[toindex].count);
390 			rawarc.raw_frompc = frompc;
391 			rawarc.raw_selfpc = (u_long)tos[toindex].selfpc;
392 			rawarc.raw_count = tos[toindex].count;
393 			fwrite((char *)&rawarc, sizeof(rawarc), 1, fp);
394 		}
395 	}
396 	fclose(fp);
397 }
398 
399 /*
400  * Get the profiling rate.
401  */
402 int
403 getprofhz(struct kvmvars *kvp)
404 {
405 	int mib[2], profrate;
406 	size_t size;
407 	struct clockinfo clockrate;
408 
409 	if (kflag) {
410 		profrate = 1;
411 		if (kvm_read(kvp->kd, nl[N_PROFHZ].n_value, &profrate,
412 		    sizeof profrate) != sizeof profrate)
413 			warnx("get clockrate: %s", kvm_geterr(kvp->kd));
414 		return (profrate);
415 	}
416 	mib[0] = CTL_KERN;
417 	mib[1] = KERN_CLOCKRATE;
418 	clockrate.profhz = 1;
419 	size = sizeof clockrate;
420 	if (sysctl(mib, 2, &clockrate, &size, NULL, 0) < 0)
421 		warn("get clockrate");
422 	return (clockrate.profhz);
423 }
424 
425 /*
426  * Reset the kernel profiling date structures.
427  */
428 void
429 reset(struct kvmvars *kvp)
430 {
431 	char *zbuf;
432 	u_long biggest;
433 	int mib[3];
434 
435 	setprof(kvp, GMON_PROF_OFF);
436 
437 	biggest = kvp->gpm.kcountsize;
438 	if (kvp->gpm.fromssize > biggest)
439 		biggest = kvp->gpm.fromssize;
440 	if (kvp->gpm.tossize > biggest)
441 		biggest = kvp->gpm.tossize;
442 	if ((zbuf = (char *)malloc(biggest)) == NULL)
443 		errx(12, "cannot allocate zbuf space");
444 	bzero(zbuf, biggest);
445 	if (kflag) {
446 		if (kvm_write(kvp->kd, (u_long)kvp->gpm.kcount, zbuf,
447 		    kvp->gpm.kcountsize) != kvp->gpm.kcountsize)
448 			errx(13, "tickbuf zero: %s", kvm_geterr(kvp->kd));
449 		if (kvm_write(kvp->kd, (u_long)kvp->gpm.froms, zbuf,
450 		    kvp->gpm.fromssize) != kvp->gpm.fromssize)
451 			errx(14, "froms zero: %s", kvm_geterr(kvp->kd));
452 		if (kvm_write(kvp->kd, (u_long)kvp->gpm.tos, zbuf,
453 		    kvp->gpm.tossize) != kvp->gpm.tossize)
454 			errx(15, "tos zero: %s", kvm_geterr(kvp->kd));
455 		return;
456 	}
457 	(void)seteuid(0);
458 	mib[0] = CTL_KERN;
459 	mib[1] = KERN_PROF;
460 	mib[2] = GPROF_COUNT;
461 	if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.kcountsize) < 0)
462 		err(13, "tickbuf zero");
463 	mib[2] = GPROF_FROMS;
464 	if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.fromssize) < 0)
465 		err(14, "froms zero");
466 	mib[2] = GPROF_TOS;
467 	if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.tossize) < 0)
468 		err(15, "tos zero");
469 	(void)seteuid(getuid());
470 	free(zbuf);
471 }
472