subr_kcov.c revision 1.3 1 1.3 kamil /* $NetBSD: subr_kcov.c,v 1.3 2019/02/23 12:07:40 kamil Exp $ */
2 1.1 kamil
3 1.1 kamil /*
4 1.1 kamil * Copyright (c) 2019 The NetBSD Foundation, Inc.
5 1.1 kamil * All rights reserved.
6 1.1 kamil *
7 1.1 kamil * This code is derived from software contributed to The NetBSD Foundation
8 1.1 kamil * by Siddharth Muralee.
9 1.1 kamil *
10 1.1 kamil * Redistribution and use in source and binary forms, with or without
11 1.1 kamil * modification, are permitted provided that the following conditions
12 1.1 kamil * are met:
13 1.1 kamil * 1. Redistributions of source code must retain the above copyright
14 1.1 kamil * notice, this list of conditions and the following disclaimer.
15 1.1 kamil * 2. Redistributions in binary form must reproduce the above copyright
16 1.1 kamil * notice, this list of conditions and the following disclaimer in the
17 1.1 kamil * documentation and/or other materials provided with the distribution.
18 1.1 kamil *
19 1.1 kamil * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 1.1 kamil * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 1.1 kamil * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 1.1 kamil * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 1.1 kamil * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 1.1 kamil * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 1.1 kamil * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 1.1 kamil * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 1.1 kamil * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 1.1 kamil * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 1.1 kamil * POSSIBILITY OF SUCH DAMAGE.
30 1.1 kamil */
31 1.1 kamil
32 1.1 kamil #include <sys/cdefs.h>
33 1.1 kamil
34 1.1 kamil #include <sys/module.h>
35 1.1 kamil #include <sys/param.h>
36 1.1 kamil #include <sys/systm.h>
37 1.1 kamil #include <sys/kernel.h>
38 1.1 kamil
39 1.1 kamil #include <sys/conf.h>
40 1.1 kamil #include <sys/condvar.h>
41 1.1 kamil #include <sys/kmem.h>
42 1.1 kamil #include <sys/mutex.h>
43 1.1 kamil #include <sys/queue.h>
44 1.1 kamil
45 1.1 kamil #include <uvm/uvm_extern.h>
46 1.1 kamil #include <sys/kcov.h>
47 1.1 kamil
48 1.1 kamil #define KCOV_BUF_MAX_ENTRIES (256 << 10)
49 1.1 kamil
50 1.1 kamil /*
51 1.1 kamil * The KCOV descriptors are allocated during open(), and are associated with
52 1.1 kamil * the calling proc. They are freed lazily when their refcount reaches zero,
53 1.1 kamil * only when the process exits; this guarantees that kd->buf is not mmapped
54 1.1 kamil * in a currently running LWP. A KCOV descriptor is active on only one LWP
55 1.1 kamil * at the same time within the proc.
56 1.1 kamil *
57 1.1 kamil * In the refcount, one ref is for the proc, and one ref is for the LWP where
58 1.1 kamil * the descriptor is active. In each case, the descriptor is pointed to in
59 1.1 kamil * the proc's and LWP's specificdata.
60 1.1 kamil */
61 1.1 kamil
62 1.1 kamil typedef struct kcov_desc {
63 1.1 kamil kmutex_t lock;
64 1.1 kamil int refcnt;
65 1.1 kamil kcov_int_t *buf;
66 1.1 kamil size_t bufnent;
67 1.1 kamil size_t bufsize;
68 1.1 kamil TAILQ_ENTRY(kcov_desc) entry;
69 1.1 kamil } kcov_t;
70 1.1 kamil
71 1.1 kamil static specificdata_key_t kcov_proc_key;
72 1.1 kamil static specificdata_key_t kcov_lwp_key;
73 1.1 kamil
74 1.1 kamil static void
75 1.1 kamil kcov_lock(kcov_t *kd)
76 1.1 kamil {
77 1.1 kamil
78 1.1 kamil mutex_enter(&kd->lock);
79 1.1 kamil KASSERT(kd->refcnt > 0);
80 1.1 kamil }
81 1.1 kamil
82 1.1 kamil static void
83 1.1 kamil kcov_unlock(kcov_t *kd)
84 1.1 kamil {
85 1.1 kamil
86 1.1 kamil mutex_exit(&kd->lock);
87 1.1 kamil }
88 1.1 kamil
89 1.1 kamil static void
90 1.1 kamil kcov_lwp_take(kcov_t *kd)
91 1.1 kamil {
92 1.1 kamil
93 1.1 kamil kd->refcnt++;
94 1.1 kamil KASSERT(kd->refcnt == 2);
95 1.1 kamil lwp_setspecific(kcov_lwp_key, kd);
96 1.1 kamil }
97 1.1 kamil
98 1.1 kamil static void
99 1.1 kamil kcov_lwp_release(kcov_t *kd)
100 1.1 kamil {
101 1.1 kamil
102 1.1 kamil KASSERT(kd->refcnt == 2);
103 1.1 kamil kd->refcnt--;
104 1.1 kamil lwp_setspecific(kcov_lwp_key, NULL);
105 1.1 kamil }
106 1.1 kamil
107 1.1 kamil static inline bool
108 1.1 kamil kcov_is_owned(kcov_t *kd)
109 1.1 kamil {
110 1.1 kamil
111 1.1 kamil return (kd->refcnt > 1);
112 1.1 kamil }
113 1.1 kamil
114 1.1 kamil static void
115 1.1 kamil kcov_free(void *arg)
116 1.1 kamil {
117 1.1 kamil kcov_t *kd = (kcov_t *)arg;
118 1.1 kamil bool dofree;
119 1.1 kamil
120 1.1 kamil if (kd == NULL) {
121 1.1 kamil return;
122 1.1 kamil }
123 1.1 kamil
124 1.1 kamil kcov_lock(kd);
125 1.1 kamil kd->refcnt--;
126 1.1 kamil kcov_unlock(kd);
127 1.1 kamil dofree = (kd->refcnt == 0);
128 1.1 kamil
129 1.1 kamil if (!dofree) {
130 1.1 kamil return;
131 1.1 kamil }
132 1.1 kamil if (kd->buf != NULL) {
133 1.1 kamil uvm_km_free(kernel_map, (vaddr_t)kd->buf, kd->bufsize,
134 1.1 kamil UVM_KMF_WIRED);
135 1.1 kamil }
136 1.1 kamil mutex_destroy(&kd->lock);
137 1.1 kamil kmem_free(kd, sizeof(*kd));
138 1.1 kamil }
139 1.1 kamil
140 1.1 kamil static int
141 1.1 kamil kcov_allocbuf(kcov_t *kd, uint64_t nent)
142 1.1 kamil {
143 1.1 kamil size_t size;
144 1.1 kamil
145 1.1 kamil if (nent < 2 || nent > KCOV_BUF_MAX_ENTRIES)
146 1.1 kamil return EINVAL;
147 1.1 kamil if (kd->buf != NULL)
148 1.1 kamil return EEXIST;
149 1.1 kamil
150 1.1 kamil size = roundup(nent * KCOV_ENTRY_SIZE, PAGE_SIZE);
151 1.1 kamil kd->buf = (kcov_int_t *)uvm_km_alloc(kernel_map, size, 0,
152 1.1 kamil UVM_KMF_WIRED|UVM_KMF_ZERO);
153 1.1 kamil if (kd->buf == NULL)
154 1.1 kamil return ENOMEM;
155 1.1 kamil
156 1.1 kamil kd->bufnent = nent - 1;
157 1.1 kamil kd->bufsize = size;
158 1.1 kamil
159 1.1 kamil return 0;
160 1.1 kamil }
161 1.1 kamil
162 1.1 kamil /* -------------------------------------------------------------------------- */
163 1.1 kamil
164 1.1 kamil static int
165 1.1 kamil kcov_open(dev_t dev, int flag, int mode, struct lwp *l)
166 1.1 kamil {
167 1.1 kamil struct proc *p = l->l_proc;
168 1.1 kamil kcov_t *kd;
169 1.1 kamil
170 1.1 kamil kd = proc_getspecific(p, kcov_proc_key);
171 1.1 kamil if (kd != NULL)
172 1.1 kamil return EBUSY;
173 1.1 kamil
174 1.1 kamil kd = kmem_zalloc(sizeof(*kd), KM_SLEEP);
175 1.1 kamil mutex_init(&kd->lock, MUTEX_DEFAULT, IPL_NONE);
176 1.1 kamil kd->refcnt = 1;
177 1.1 kamil proc_setspecific(p, kcov_proc_key, kd);
178 1.1 kamil
179 1.1 kamil return 0;
180 1.1 kamil }
181 1.1 kamil
182 1.1 kamil static int
183 1.1 kamil kcov_close(dev_t dev, int flag, int mode, struct lwp *l)
184 1.1 kamil {
185 1.1 kamil
186 1.1 kamil return 0;
187 1.1 kamil }
188 1.1 kamil
189 1.1 kamil static int
190 1.1 kamil kcov_ioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
191 1.1 kamil {
192 1.1 kamil struct proc *p = l->l_proc;
193 1.1 kamil int error = 0;
194 1.1 kamil kcov_t *kd;
195 1.1 kamil
196 1.1 kamil kd = proc_getspecific(p, kcov_proc_key);
197 1.1 kamil if (kd == NULL)
198 1.1 kamil return ENXIO;
199 1.1 kamil kcov_lock(kd);
200 1.1 kamil
201 1.1 kamil switch (cmd) {
202 1.1 kamil case KCOV_IOC_SETBUFSIZE:
203 1.1 kamil if (kcov_is_owned(kd)) {
204 1.1 kamil error = EBUSY;
205 1.1 kamil break;
206 1.1 kamil }
207 1.1 kamil error = kcov_allocbuf(kd, *((uint64_t *)addr));
208 1.1 kamil break;
209 1.1 kamil case KCOV_IOC_ENABLE:
210 1.1 kamil if (kcov_is_owned(kd)) {
211 1.1 kamil error = EBUSY;
212 1.1 kamil break;
213 1.1 kamil }
214 1.1 kamil if (kd->buf == NULL) {
215 1.1 kamil error = ENOBUFS;
216 1.1 kamil break;
217 1.1 kamil }
218 1.1 kamil KASSERT(l == curlwp);
219 1.1 kamil kcov_lwp_take(kd);
220 1.1 kamil break;
221 1.1 kamil case KCOV_IOC_DISABLE:
222 1.1 kamil if (lwp_getspecific(kcov_lwp_key) == NULL) {
223 1.1 kamil error = ENOENT;
224 1.1 kamil break;
225 1.1 kamil }
226 1.1 kamil KASSERT(l == curlwp);
227 1.1 kamil kcov_lwp_release(kd);
228 1.1 kamil break;
229 1.1 kamil default:
230 1.1 kamil error = EINVAL;
231 1.1 kamil }
232 1.1 kamil
233 1.1 kamil kcov_unlock(kd);
234 1.1 kamil return error;
235 1.1 kamil }
236 1.1 kamil
237 1.1 kamil static paddr_t
238 1.1 kamil kcov_mmap(dev_t dev, off_t offset, int prot)
239 1.1 kamil {
240 1.1 kamil kcov_t *kd;
241 1.1 kamil paddr_t pa;
242 1.1 kamil vaddr_t va;
243 1.1 kamil
244 1.1 kamil kd = proc_getspecific(curproc, kcov_proc_key);
245 1.1 kamil KASSERT(kd != NULL);
246 1.1 kamil
247 1.1 kamil if ((offset < 0) || (offset >= kd->bufnent * KCOV_ENTRY_SIZE)) {
248 1.1 kamil return (paddr_t)-1;
249 1.1 kamil }
250 1.1 kamil if (offset & PAGE_MASK) {
251 1.1 kamil return (paddr_t)-1;
252 1.1 kamil }
253 1.1 kamil va = (vaddr_t)kd->buf + offset;
254 1.1 kamil if (!pmap_extract(pmap_kernel(), va, &pa)) {
255 1.1 kamil return (paddr_t)-1;
256 1.1 kamil }
257 1.1 kamil
258 1.1 kamil return atop(pa);
259 1.1 kamil }
260 1.1 kamil
261 1.1 kamil static inline bool
262 1.1 kamil in_interrupt(void)
263 1.1 kamil {
264 1.1 kamil return curcpu()->ci_idepth >= 0;
265 1.1 kamil }
266 1.1 kamil
267 1.1 kamil void __sanitizer_cov_trace_pc(void);
268 1.1 kamil
269 1.1 kamil void
270 1.1 kamil __sanitizer_cov_trace_pc(void)
271 1.1 kamil {
272 1.1 kamil extern int cold;
273 1.1 kamil uint64_t idx;
274 1.1 kamil kcov_t *kd;
275 1.1 kamil
276 1.1 kamil if (__predict_false(cold)) {
277 1.1 kamil /* Do not trace during boot. */
278 1.1 kamil return;
279 1.1 kamil }
280 1.1 kamil
281 1.1 kamil if (in_interrupt()) {
282 1.1 kamil /* Do not trace in interrupts. */
283 1.1 kamil return;
284 1.1 kamil }
285 1.1 kamil
286 1.1 kamil kd = lwp_getspecific(kcov_lwp_key);
287 1.1 kamil if (__predict_true(kd == NULL)) {
288 1.1 kamil /* Not traced. */
289 1.1 kamil return;
290 1.1 kamil }
291 1.1 kamil
292 1.1 kamil idx = kd->buf[0];
293 1.1 kamil if (idx < kd->bufnent) {
294 1.2 kamil kd->buf[idx+1] = (intptr_t)__builtin_return_address(0);
295 1.1 kamil kd->buf[0]++;
296 1.1 kamil }
297 1.1 kamil }
298 1.1 kamil
299 1.1 kamil /* -------------------------------------------------------------------------- */
300 1.1 kamil
301 1.1 kamil const struct cdevsw kcov_cdevsw = {
302 1.1 kamil .d_open = kcov_open,
303 1.1 kamil .d_close = kcov_close,
304 1.1 kamil .d_read = noread,
305 1.1 kamil .d_write = nowrite,
306 1.1 kamil .d_ioctl = kcov_ioctl,
307 1.1 kamil .d_stop = nostop,
308 1.1 kamil .d_tty = notty,
309 1.1 kamil .d_poll = nopoll,
310 1.1 kamil .d_mmap = kcov_mmap,
311 1.1 kamil .d_kqfilter = nokqfilter,
312 1.1 kamil .d_discard = nodiscard,
313 1.1 kamil .d_flag = D_OTHER | D_MPSAFE
314 1.1 kamil };
315 1.1 kamil
316 1.1 kamil MODULE(MODULE_CLASS_ANY, kcov, NULL);
317 1.1 kamil
318 1.1 kamil static void
319 1.1 kamil kcov_init(void)
320 1.1 kamil {
321 1.1 kamil
322 1.1 kamil proc_specific_key_create(&kcov_proc_key, kcov_free);
323 1.1 kamil lwp_specific_key_create(&kcov_lwp_key, kcov_free);
324 1.1 kamil }
325 1.1 kamil
326 1.1 kamil static int
327 1.1 kamil kcov_modcmd(modcmd_t cmd, void *arg)
328 1.1 kamil {
329 1.1 kamil
330 1.1 kamil switch (cmd) {
331 1.1 kamil case MODULE_CMD_INIT:
332 1.1 kamil kcov_init();
333 1.1 kamil return 0;
334 1.1 kamil case MODULE_CMD_FINI:
335 1.1 kamil return EINVAL;
336 1.1 kamil default:
337 1.1 kamil return ENOTTY;
338 1.1 kamil }
339 1.1 kamil }
340