xref: /openbsd-src/sys/kern/subr_prof.c (revision fc405d53b73a2d73393cb97f684863d17b583e38)
1 /*	$OpenBSD: subr_prof.c,v 1.33 2023/04/25 01:32:36 cheloha Exp $	*/
2 /*	$NetBSD: subr_prof.c,v 1.12 1996/04/22 01:38:50 christos Exp $	*/
3 
4 /*-
5  * Copyright (c) 1982, 1986, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  *	@(#)subr_prof.c	8.3 (Berkeley) 9/23/93
33  */
34 
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/proc.h>
38 #include <sys/resourcevar.h>
39 #include <sys/mount.h>
40 #include <sys/sysctl.h>
41 #include <sys/syscallargs.h>
42 
43 
44 #if defined(GPROF) || defined(DDBPROF)
45 #include <sys/malloc.h>
46 #include <sys/gmon.h>
47 
48 #include <uvm/uvm_extern.h>
49 
50 #include <machine/db_machdep.h>
51 #include <ddb/db_extern.h>
52 
53 /*
54  * Flag to prevent CPUs from executing the mcount() monitor function
55  * until we're sure they are in a sane state.
56  */
57 int gmoninit = 0;
58 u_int gmon_cpu_count;		/* [K] number of CPUs with profiling enabled */
59 
60 extern char etext[];
61 
62 void
63 prof_init(void)
64 {
65 	CPU_INFO_ITERATOR cii;
66 	struct cpu_info *ci;
67 	struct gmonparam *p;
68 	u_long lowpc, highpc, textsize;
69 	u_long kcountsize, fromssize, tossize;
70 	long tolimit;
71 	char *cp;
72 	int size;
73 
74 	/*
75 	 * Round lowpc and highpc to multiples of the density we're using
76 	 * so the rest of the scaling (here and in gprof) stays in ints.
77 	 */
78 	lowpc = ROUNDDOWN(KERNBASE, HISTFRACTION * sizeof(HISTCOUNTER));
79 	highpc = ROUNDUP((u_long)etext, HISTFRACTION * sizeof(HISTCOUNTER));
80 	textsize = highpc - lowpc;
81 #ifdef GPROF
82 	printf("Profiling kernel, textsize=%ld [%lx..%lx]\n",
83 	    textsize, lowpc, highpc);
84 #endif
85 	kcountsize = textsize / HISTFRACTION;
86 	fromssize = textsize / HASHFRACTION;
87 	tolimit = textsize * ARCDENSITY / 100;
88 	if (tolimit < MINARCS)
89 		tolimit = MINARCS;
90 	else if (tolimit > MAXARCS)
91 		tolimit = MAXARCS;
92 	tossize = tolimit * sizeof(struct tostruct);
93 	size = sizeof(*p) + kcountsize + fromssize + tossize;
94 
95 	/* Allocate and initialize one profiling buffer per CPU. */
96 	CPU_INFO_FOREACH(cii, ci) {
97 		cp = km_alloc(round_page(size), &kv_any, &kp_zero, &kd_nowait);
98 		if (cp == NULL) {
99 			printf("No memory for profiling.\n");
100 			return;
101 		}
102 
103 		p = (struct gmonparam *)cp;
104 		cp += sizeof(*p);
105 		p->tos = (struct tostruct *)cp;
106 		cp += tossize;
107 		p->kcount = (u_short *)cp;
108 		cp += kcountsize;
109 		p->froms = (u_short *)cp;
110 
111 		p->state = GMON_PROF_OFF;
112 		p->lowpc = lowpc;
113 		p->highpc = highpc;
114 		p->textsize = textsize;
115 		p->hashfraction = HASHFRACTION;
116 		p->kcountsize = kcountsize;
117 		p->fromssize = fromssize;
118 		p->tolimit = tolimit;
119 		p->tossize = tossize;
120 
121 		ci->ci_gmon = p;
122 	}
123 }
124 
125 int
126 prof_state_toggle(struct gmonparam *gp, int oldstate)
127 {
128 	int error = 0;
129 
130 	KERNEL_ASSERT_LOCKED();
131 
132 	if (gp->state == oldstate)
133 		return (0);
134 
135 	switch (gp->state) {
136 	case GMON_PROF_ON:
137 #if !defined(GPROF)
138 		/*
139 		 * If this is not a profiling kernel, we need to patch
140 		 * all symbols that can be instrummented.
141 		 */
142 		error = db_prof_enable();
143 #endif
144 		if (error == 0) {
145 			if (++gmon_cpu_count == 1)
146 				startprofclock(&process0);
147 		}
148 		break;
149 	default:
150 		error = EINVAL;
151 		gp->state = GMON_PROF_OFF;
152 		/* FALLTHROUGH */
153 	case GMON_PROF_OFF:
154 		if (--gmon_cpu_count == 0)
155 			stopprofclock(&process0);
156 #if !defined(GPROF)
157 		db_prof_disable();
158 #endif
159 		break;
160 	}
161 
162 	return (error);
163 }
164 
165 /*
166  * Return kernel profiling information.
167  */
168 int
169 sysctl_doprof(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
170     size_t newlen)
171 {
172 	CPU_INFO_ITERATOR cii;
173 	struct cpu_info *ci;
174 	struct gmonparam *gp = NULL;
175 	int error, cpuid, op, state;
176 
177 	/* all sysctl names at this level are name and field */
178 	if (namelen != 2)
179 		return (ENOTDIR);		/* overloaded */
180 
181 	op = name[0];
182 	cpuid = name[1];
183 
184 	CPU_INFO_FOREACH(cii, ci) {
185 		if (cpuid == CPU_INFO_UNIT(ci)) {
186 			gp = ci->ci_gmon;
187 			break;
188 		}
189 	}
190 
191 	if (gp == NULL)
192 		return (EOPNOTSUPP);
193 
194 	/* Assume that if we're here it is safe to execute profiling. */
195 	gmoninit = 1;
196 
197 	switch (op) {
198 	case GPROF_STATE:
199 		state = gp->state;
200 		error = sysctl_int(oldp, oldlenp, newp, newlen, &gp->state);
201 		if (error)
202 			return (error);
203 		return (prof_state_toggle(gp, state));
204 	case GPROF_COUNT:
205 		return (sysctl_struct(oldp, oldlenp, newp, newlen,
206 		    gp->kcount, gp->kcountsize));
207 	case GPROF_FROMS:
208 		return (sysctl_struct(oldp, oldlenp, newp, newlen,
209 		    gp->froms, gp->fromssize));
210 	case GPROF_TOS:
211 		return (sysctl_struct(oldp, oldlenp, newp, newlen,
212 		    gp->tos, gp->tossize));
213 	case GPROF_GMONPARAM:
214 		return (sysctl_rdstruct(oldp, oldlenp, newp, gp, sizeof *gp));
215 	default:
216 		return (EOPNOTSUPP);
217 	}
218 	/* NOTREACHED */
219 }
220 #endif /* GPROF || DDBPROF */
221 
222 /*
223  * Profiling system call.
224  *
225  * The scale factor is a fixed point number with 16 bits of fraction, so that
226  * 1.0 is represented as 0x10000.  A scale factor of 0 turns off profiling.
227  */
228 int
229 sys_profil(struct proc *p, void *v, register_t *retval)
230 {
231 	struct sys_profil_args /* {
232 		syscallarg(caddr_t) samples;
233 		syscallarg(size_t) size;
234 		syscallarg(u_long) offset;
235 		syscallarg(u_int) scale;
236 	} */ *uap = v;
237 	struct process *pr = p->p_p;
238 	struct uprof *upp;
239 	int s;
240 
241 	if (SCARG(uap, scale) > (1 << 16))
242 		return (EINVAL);
243 	if (SCARG(uap, scale) == 0) {
244 		stopprofclock(pr);
245 		return (0);
246 	}
247 	upp = &pr->ps_prof;
248 
249 	/* Block profile interrupts while changing state. */
250 	s = splstatclock();
251 	upp->pr_off = SCARG(uap, offset);
252 	upp->pr_scale = SCARG(uap, scale);
253 	upp->pr_base = (caddr_t)SCARG(uap, samples);
254 	upp->pr_size = SCARG(uap, size);
255 	startprofclock(pr);
256 	splx(s);
257 
258 	return (0);
259 }
260 
261 /*
262  * Scale is a fixed-point number with the binary point 16 bits
263  * into the value, and is <= 1.0.  pc is at most 32 bits, so the
264  * intermediate result is at most 48 bits.
265  */
266 #define	PC_TO_INDEX(pc, prof) \
267 	((int)(((u_quad_t)((pc) - (prof)->pr_off) * \
268 	    (u_quad_t)((prof)->pr_scale)) >> 16) & ~1)
269 
270 /*
271  * Collect user-level profiling statistics; called on a profiling tick,
272  * when a process is running in user-mode.  This routine may be called
273  * from an interrupt context. Schedule an AST that will vector us to
274  * trap() with a context in which copyin and copyout will work.
275  * Trap will then call addupc_task().
276  */
277 void
278 addupc_intr(struct proc *p, u_long pc, u_long nticks)
279 {
280 	struct uprof *prof;
281 
282 	prof = &p->p_p->ps_prof;
283 	if (pc < prof->pr_off || PC_TO_INDEX(pc, prof) >= prof->pr_size)
284 		return;			/* out of range; ignore */
285 
286 	p->p_prof_addr = pc;
287 	p->p_prof_ticks += nticks;
288 	atomic_setbits_int(&p->p_flag, P_OWEUPC);
289 	need_proftick(p);
290 }
291 
292 
293 /*
294  * Much like before, but we can afford to take faults here.  If the
295  * update fails, we simply turn off profiling.
296  */
297 void
298 addupc_task(struct proc *p, u_long pc, u_int nticks)
299 {
300 	struct process *pr = p->p_p;
301 	struct uprof *prof;
302 	caddr_t addr;
303 	u_int i;
304 	u_short v;
305 
306 	/* Testing PS_PROFIL may be unnecessary, but is certainly safe. */
307 	if ((pr->ps_flags & PS_PROFIL) == 0 || nticks == 0)
308 		return;
309 
310 	prof = &pr->ps_prof;
311 	if (pc < prof->pr_off ||
312 	    (i = PC_TO_INDEX(pc, prof)) >= prof->pr_size)
313 		return;
314 
315 	addr = prof->pr_base + i;
316 	if (copyin(addr, (caddr_t)&v, sizeof(v)) == 0) {
317 		v += nticks;
318 		if (copyout((caddr_t)&v, addr, sizeof(v)) == 0)
319 			return;
320 	}
321 	stopprofclock(pr);
322 }
323