1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
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
33static const 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#if 0
40static char sccsid[] = "@(#)kgmon.c	8.1 (Berkeley) 6/6/93";
41#endif
42#endif /* not lint */
43
44#include <sys/cdefs.h>
45__FBSDID("$FreeBSD$");
46
47#include <sys/param.h>
48#include <sys/file.h>
49#include <sys/time.h>
50#include <sys/sysctl.h>
51#include <sys/gmon.h>
52#include <ctype.h>
53#include <err.h>
54#include <errno.h>
55#include <kvm.h>
56#include <limits.h>
57#include <nlist.h>
58#include <paths.h>
59#include <stddef.h>
60#include <stdio.h>
61#include <stdlib.h>
62#include <string.h>
63#include <unistd.h>
64
65struct nlist nl[] = {
66#define	N_GMONPARAM	0
67	{ "__gmonparam" },
68#define	N_PROFHZ	1
69	{ "_profhz" },
70	{ NULL },
71};
72
73struct kvmvars {
74	kvm_t	*kd;
75	struct gmonparam gpm;
76};
77
78int	Bflag, bflag, hflag, kflag, rflag, pflag;
79int	debug = 0;
80int	getprof(struct kvmvars *);
81int	getprofhz(struct kvmvars *);
82void	kern_readonly(int);
83int	openfiles(const char *, char *, struct kvmvars *);
84void	setprof(struct kvmvars *kvp, int state);
85void	dumpstate(struct kvmvars *kvp);
86void	reset(struct kvmvars *kvp);
87static void usage(void);
88
89int
90main(int argc, char **argv)
91{
92	int ch, mode, disp, accessmode;
93	struct kvmvars kvmvars;
94	const char *systemname;
95	char *kmemf;
96
97	if (seteuid(getuid()) != 0) {
98		err(1, "seteuid failed\n");
99	}
100	kmemf = NULL;
101	systemname = NULL;
102	while ((ch = getopt(argc, argv, "M:N:Bbhpr")) != -1) {
103		switch((char)ch) {
104
105		case 'M':
106			kmemf = optarg;
107			kflag = 1;
108			break;
109
110		case 'N':
111			systemname = optarg;
112			break;
113
114		case 'B':
115			Bflag = 1;
116			break;
117
118		case 'b':
119			bflag = 1;
120			break;
121
122		case 'h':
123			hflag = 1;
124			break;
125
126		case 'p':
127			pflag = 1;
128			break;
129
130		case 'r':
131			rflag = 1;
132			break;
133
134		default:
135			usage();
136		}
137	}
138	argc -= optind;
139	argv += optind;
140
141#define BACKWARD_COMPATIBILITY
142#ifdef	BACKWARD_COMPATIBILITY
143	if (*argv) {
144		systemname = *argv;
145		if (*++argv) {
146			kmemf = *argv;
147			++kflag;
148		}
149	}
150#endif
151	if (systemname == NULL)
152		systemname = getbootfile();
153	accessmode = openfiles(systemname, kmemf, &kvmvars);
154	mode = getprof(&kvmvars);
155	if (hflag)
156		disp = GMON_PROF_OFF;
157	else if (Bflag)
158		disp = GMON_PROF_HIRES;
159	else if (bflag)
160		disp = GMON_PROF_ON;
161	else
162		disp = mode;
163	if (pflag)
164		dumpstate(&kvmvars);
165	if (rflag)
166		reset(&kvmvars);
167	if (accessmode == O_RDWR)
168		setprof(&kvmvars, disp);
169	(void)fprintf(stdout, "kgmon: kernel profiling is %s.\n",
170		      disp == GMON_PROF_OFF ? "off" :
171		      disp == GMON_PROF_HIRES ? "running (high resolution)" :
172		      disp == GMON_PROF_ON ? "running" :
173		      disp == GMON_PROF_BUSY ? "busy" :
174		      disp == GMON_PROF_ERROR ? "off (error)" :
175		      "in an unknown state");
176	return (0);
177}
178
179static void
180usage(void)
181{
182	fprintf(stderr, "usage: kgmon [-Bbhrp] [-M core] [-N system]\n");
183	exit(1);
184}
185
186/*
187 * Check that profiling is enabled and open any necessary files.
188 */
189int
190openfiles(const char *systemname, char *kmemf, struct kvmvars *kvp)
191{
192	size_t size;
193	int mib[3], state, openmode;
194	char errbuf[_POSIX2_LINE_MAX];
195
196	if (!kflag) {
197		mib[0] = CTL_KERN;
198		mib[1] = KERN_PROF;
199		mib[2] = GPROF_STATE;
200		size = sizeof state;
201		if (sysctl(mib, 3, &state, &size, NULL, 0) < 0)
202			errx(20, "profiling not defined in kernel");
203		if (!(Bflag || bflag || hflag || rflag ||
204		    (pflag &&
205		     (state == GMON_PROF_HIRES || state == GMON_PROF_ON))))
206			return (O_RDONLY);
207		(void)seteuid(0);
208		if (sysctl(mib, 3, NULL, NULL, &state, size) >= 0)
209			return (O_RDWR);
210		(void)seteuid(getuid());
211		kern_readonly(state);
212		return (O_RDONLY);
213	}
214	openmode = (Bflag || bflag || hflag || pflag || rflag)
215		   ? O_RDWR : O_RDONLY;
216	kvp->kd = kvm_openfiles(systemname, kmemf, NULL, openmode, errbuf);
217	if (kvp->kd == NULL) {
218		if (openmode == O_RDWR) {
219			openmode = O_RDONLY;
220			kvp->kd = kvm_openfiles(systemname, kmemf, NULL, O_RDONLY,
221			    errbuf);
222		}
223		if (kvp->kd == NULL)
224			errx(2, "kvm_openfiles: %s", errbuf);
225		kern_readonly(GMON_PROF_ON);
226	}
227	if (kvm_nlist(kvp->kd, nl) < 0)
228		errx(3, "%s: no namelist", systemname);
229	if (!nl[N_GMONPARAM].n_value)
230		errx(20, "profiling not defined in kernel");
231	return (openmode);
232}
233
234/*
235 * Suppress options that require a writable kernel.
236 */
237void
238kern_readonly(int mode)
239{
240
241	(void)fprintf(stderr, "kgmon: kernel read-only: ");
242	if (pflag && (mode == GMON_PROF_HIRES || mode == GMON_PROF_ON))
243		(void)fprintf(stderr, "data may be inconsistent\n");
244	if (rflag)
245		(void)fprintf(stderr, "-r suppressed\n");
246	if (Bflag)
247		(void)fprintf(stderr, "-B suppressed\n");
248	if (bflag)
249		(void)fprintf(stderr, "-b suppressed\n");
250	if (hflag)
251		(void)fprintf(stderr, "-h suppressed\n");
252	rflag = Bflag = bflag = hflag = 0;
253}
254
255/*
256 * Get the state of kernel profiling.
257 */
258int
259getprof(struct kvmvars *kvp)
260{
261	size_t size;
262	int mib[3];
263
264	if (kflag) {
265		size = kvm_read(kvp->kd, nl[N_GMONPARAM].n_value, &kvp->gpm,
266		    sizeof kvp->gpm);
267	} else {
268		mib[0] = CTL_KERN;
269		mib[1] = KERN_PROF;
270		mib[2] = GPROF_GMONPARAM;
271		size = sizeof kvp->gpm;
272		if (sysctl(mib, 3, &kvp->gpm, &size, NULL, 0) < 0)
273			size = 0;
274	}
275
276	/*
277	 * Accept certain undersized "structs" from old kernels.  We need
278	 * everything up to hashfraction, and want profrate and
279	 * histcounter_type.  Assume that the kernel doesn't put garbage
280	 * in any padding that is returned instead of profrate and
281	 * histcounter_type.  This is a bad assumption for dead kernels,
282	 * since kvm_read() will normally return garbage for bytes beyond
283	 * the end of the actual kernel struct, if any.
284	 */
285	if (size < offsetof(struct gmonparam, hashfraction) +
286	    sizeof(kvp->gpm.hashfraction) || size > sizeof(kvp->gpm))
287		errx(4, "cannot get gmonparam: %s",
288		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
289	bzero((char *)&kvp->gpm + size, sizeof(kvp->gpm) - size);
290	if (kvp->gpm.profrate == 0)
291		kvp->gpm.profrate = getprofhz(kvp);
292#ifdef __i386__
293	if (kvp->gpm.histcounter_type == 0) {
294		/*
295		 * This fixup only works for not-so-old i386 kernels.  The
296		 * magic 16 is the kernel FUNCTION_ALIGNMENT.  64-bit
297		 * counters are signed; smaller counters are unsigned.
298		 */
299		kvp->gpm.histcounter_type = 16 /
300		    (kvp->gpm.textsize / kvp->gpm.kcountsize) * CHAR_BIT;
301		if (kvp->gpm.histcounter_type == 64)
302			kvp->gpm.histcounter_type = -64;
303	}
304#endif
305
306	return (kvp->gpm.state);
307}
308
309/*
310 * Enable or disable kernel profiling according to the state variable.
311 */
312void
313setprof(struct kvmvars *kvp, int state)
314{
315	struct gmonparam *p = (struct gmonparam *)nl[N_GMONPARAM].n_value;
316	size_t sz;
317	int mib[3], oldstate;
318
319	sz = sizeof(state);
320	if (!kflag) {
321		mib[0] = CTL_KERN;
322		mib[1] = KERN_PROF;
323		mib[2] = GPROF_STATE;
324		if (sysctl(mib, 3, &oldstate, &sz, NULL, 0) < 0)
325			goto bad;
326		if (oldstate == state)
327			return;
328		(void)seteuid(0);
329		if (sysctl(mib, 3, NULL, NULL, &state, sz) >= 0) {
330			(void)seteuid(getuid());
331			return;
332		}
333		(void)seteuid(getuid());
334	} else if (kvm_write(kvp->kd, (u_long)&p->state, (void *)&state, sz)
335	    == (ssize_t)sz)
336		return;
337bad:
338	warnx("warning: cannot turn profiling %s",
339	    state == GMON_PROF_OFF ? "off" : "on");
340}
341
342/*
343 * Build the gmon.out file.
344 */
345void
346dumpstate(struct kvmvars *kvp)
347{
348	register FILE *fp;
349	struct rawarc rawarc;
350	struct tostruct *tos;
351	u_long frompc;
352	u_short *froms, *tickbuf;
353	size_t i;
354	int mib[3];
355	struct gmonhdr h;
356	int fromindex, endfrom, toindex;
357
358	setprof(kvp, GMON_PROF_OFF);
359	fp = fopen("gmon.out", "w");
360	if (fp == NULL) {
361		warn("gmon.out");
362		return;
363	}
364
365	/*
366	 * Build the gmon header and write it to a file.
367	 */
368	bzero(&h, sizeof(h));
369	h.lpc = kvp->gpm.lowpc;
370	h.hpc = kvp->gpm.highpc;
371	h.ncnt = kvp->gpm.kcountsize + sizeof(h);
372	h.version = GMONVERSION;
373	h.profrate = kvp->gpm.profrate;
374	h.histcounter_type = kvp->gpm.histcounter_type;
375	fwrite((char *)&h, sizeof(h), 1, fp);
376
377	/*
378	 * Write out the tick buffer.
379	 */
380	mib[0] = CTL_KERN;
381	mib[1] = KERN_PROF;
382	if ((tickbuf = (u_short *)malloc(kvp->gpm.kcountsize)) == NULL)
383		errx(5, "cannot allocate kcount space");
384	if (kflag) {
385		i = kvm_read(kvp->kd, (u_long)kvp->gpm.kcount, (void *)tickbuf,
386		    kvp->gpm.kcountsize);
387	} else {
388		mib[2] = GPROF_COUNT;
389		i = kvp->gpm.kcountsize;
390		if (sysctl(mib, 3, tickbuf, &i, NULL, 0) < 0)
391			i = 0;
392	}
393	if (i != kvp->gpm.kcountsize)
394		errx(6, "read ticks: read %lu, got %ld: %s",
395		    kvp->gpm.kcountsize, (long)i,
396		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
397	if ((fwrite(tickbuf, kvp->gpm.kcountsize, 1, fp)) != 1)
398		err(7, "writing tocks to gmon.out");
399	free(tickbuf);
400
401	/*
402	 * Write out the arc info.
403	 */
404	if ((froms = (u_short *)malloc(kvp->gpm.fromssize)) == NULL)
405		errx(8, "cannot allocate froms space");
406	if (kflag) {
407		i = kvm_read(kvp->kd, (u_long)kvp->gpm.froms, (void *)froms,
408		    kvp->gpm.fromssize);
409	} else {
410		mib[2] = GPROF_FROMS;
411		i = kvp->gpm.fromssize;
412		if (sysctl(mib, 3, froms, &i, NULL, 0) < 0)
413			i = 0;
414	}
415	if (i != kvp->gpm.fromssize)
416		errx(9, "read froms: read %lu, got %ld: %s",
417		    kvp->gpm.fromssize, (long)i,
418		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
419	if ((tos = (struct tostruct *)malloc(kvp->gpm.tossize)) == NULL)
420		errx(10, "cannot allocate tos space");
421	if (kflag) {
422		i = kvm_read(kvp->kd, (u_long)kvp->gpm.tos, (void *)tos,
423		    kvp->gpm.tossize);
424	} else {
425		mib[2] = GPROF_TOS;
426		i = kvp->gpm.tossize;
427		if (sysctl(mib, 3, tos, &i, NULL, 0) < 0)
428			i = 0;
429	}
430	if (i != kvp->gpm.tossize)
431		errx(11, "read tos: read %lu, got %ld: %s",
432		    kvp->gpm.tossize, (long)i,
433		    kflag ? kvm_geterr(kvp->kd) : strerror(errno));
434	if (debug)
435		warnx("lowpc 0x%lx, textsize 0x%lx",
436		    (unsigned long)kvp->gpm.lowpc, kvp->gpm.textsize);
437	endfrom = kvp->gpm.fromssize / sizeof(*froms);
438	for (fromindex = 0; fromindex < endfrom; ++fromindex) {
439		if (froms[fromindex] == 0)
440			continue;
441		frompc = (u_long)kvp->gpm.lowpc +
442		    (fromindex * kvp->gpm.hashfraction * sizeof(*froms));
443		for (toindex = froms[fromindex]; toindex != 0;
444		     toindex = tos[toindex].link) {
445			if (debug)
446				warnx("[mcleanup] frompc 0x%lx selfpc 0x%lx "
447				    "count %ld", frompc, tos[toindex].selfpc,
448				    tos[toindex].count);
449			rawarc.raw_frompc = frompc;
450			rawarc.raw_selfpc = (u_long)tos[toindex].selfpc;
451			rawarc.raw_count = tos[toindex].count;
452			fwrite((char *)&rawarc, sizeof(rawarc), 1, fp);
453		}
454	}
455	fclose(fp);
456}
457
458/*
459 * Get the profiling rate.
460 */
461int
462getprofhz(struct kvmvars *kvp)
463{
464	size_t size;
465	int mib[2], profrate;
466	struct clockinfo clockrate;
467
468	if (kflag) {
469		profrate = 1;
470		if (kvm_read(kvp->kd, nl[N_PROFHZ].n_value, &profrate,
471		    sizeof profrate) != sizeof profrate)
472			warnx("get clockrate: %s", kvm_geterr(kvp->kd));
473		return (profrate);
474	}
475	mib[0] = CTL_KERN;
476	mib[1] = KERN_CLOCKRATE;
477	clockrate.profhz = 1;
478	size = sizeof clockrate;
479	if (sysctl(mib, 2, &clockrate, &size, NULL, 0) < 0)
480		warn("get clockrate");
481	return (clockrate.profhz);
482}
483
484/*
485 * Reset the kernel profiling date structures.
486 */
487void
488reset(struct kvmvars *kvp)
489{
490	char *zbuf;
491	u_long biggest;
492	int mib[3];
493
494	setprof(kvp, GMON_PROF_OFF);
495
496	biggest = kvp->gpm.kcountsize;
497	if (kvp->gpm.fromssize > biggest)
498		biggest = kvp->gpm.fromssize;
499	if (kvp->gpm.tossize > biggest)
500		biggest = kvp->gpm.tossize;
501	if ((zbuf = (char *)malloc(biggest)) == NULL)
502		errx(12, "cannot allocate zbuf space");
503	bzero(zbuf, biggest);
504	if (kflag) {
505		if (kvm_write(kvp->kd, (u_long)kvp->gpm.kcount, zbuf,
506		    kvp->gpm.kcountsize) != (ssize_t)kvp->gpm.kcountsize)
507			errx(13, "tickbuf zero: %s", kvm_geterr(kvp->kd));
508		if (kvm_write(kvp->kd, (u_long)kvp->gpm.froms, zbuf,
509		    kvp->gpm.fromssize) != (ssize_t)kvp->gpm.fromssize)
510			errx(14, "froms zero: %s", kvm_geterr(kvp->kd));
511		if (kvm_write(kvp->kd, (u_long)kvp->gpm.tos, zbuf,
512		    kvp->gpm.tossize) != (ssize_t)kvp->gpm.tossize)
513			errx(15, "tos zero: %s", kvm_geterr(kvp->kd));
514		free(zbuf);
515		return;
516	}
517	(void)seteuid(0);
518	mib[0] = CTL_KERN;
519	mib[1] = KERN_PROF;
520	mib[2] = GPROF_COUNT;
521	if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.kcountsize) < 0)
522		err(13, "tickbuf zero");
523	mib[2] = GPROF_FROMS;
524	if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.fromssize) < 0)
525		err(14, "froms zero");
526	mib[2] = GPROF_TOS;
527	if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.tossize) < 0)
528		err(15, "tos zero");
529	(void)seteuid(getuid());
530	free(zbuf);
531}
532