Line data Source code
1 : /* $OpenBSD: kcov.c,v 1.4 2018/08/27 15:57:39 anton Exp $ */
2 :
3 : /*
4 : * Copyright (c) 2018 Anton Lindqvist <anton@openbsd.org>
5 : *
6 : * Permission to use, copy, modify, and distribute this software for any
7 : * purpose with or without fee is hereby granted, provided that the above
8 : * copyright notice and this permission notice appear in all copies.
9 : *
10 : * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 : * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 : * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 : * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 : * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 : * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 : * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 : */
18 :
19 : #include <sys/param.h>
20 : #include <sys/systm.h>
21 : #include <sys/proc.h>
22 : #include <sys/kcov.h>
23 : #include <sys/malloc.h>
24 : #include <sys/stdint.h>
25 : #include <sys/queue.h>
26 :
27 : #include <uvm/uvm_extern.h>
28 :
29 : /* #define KCOV_DEBUG */
30 : #ifdef KCOV_DEBUG
31 : #define DPRINTF(x...) do { if (kcov_debug) printf(x); } while (0)
32 : #else
33 : #define DPRINTF(x...)
34 : #endif
35 :
36 : struct kcov_dev {
37 : enum {
38 : KCOV_MODE_DISABLED,
39 : KCOV_MODE_INIT,
40 : KCOV_MODE_TRACE_PC,
41 : KCOV_MODE_DYING,
42 : } kd_mode;
43 : int kd_unit; /* device minor */
44 : uintptr_t *kd_buf; /* traced coverage */
45 : size_t kd_nmemb;
46 : size_t kd_size;
47 :
48 : TAILQ_ENTRY(kcov_dev) kd_entry;
49 : };
50 :
51 : void kcovattach(int);
52 :
53 : int kd_alloc(struct kcov_dev *, unsigned long);
54 : void kd_free(struct kcov_dev *);
55 : struct kcov_dev *kd_lookup(int);
56 :
57 : static inline int inintr(void);
58 :
59 : TAILQ_HEAD(, kcov_dev) kd_list = TAILQ_HEAD_INITIALIZER(kd_list);
60 :
61 : #ifdef KCOV_DEBUG
62 : int kcov_debug = 1;
63 : #endif
64 :
65 : /*
66 : * Compiling the kernel with the `-fsanitize-coverage=trace-pc' option will
67 : * cause the following function to be called upon function entry and before
68 : * each block instructions that maps to a single line in the original source
69 : * code.
70 : *
71 : * If kcov is enabled for the current thread, the kernel program counter will
72 : * be stored in its corresponding coverage buffer.
73 : * The first element in the coverage buffer holds the index of next available
74 : * element.
75 : */
76 : void
77 0 : __sanitizer_cov_trace_pc(void)
78 : {
79 : extern int cold;
80 : struct kcov_dev *kd;
81 : uint64_t idx;
82 :
83 : /* Do not trace during boot. */
84 0 : if (cold)
85 0 : return;
86 :
87 : /* Do not trace in interrupts to prevent noisy coverage. */
88 0 : if (inintr())
89 0 : return;
90 :
91 0 : kd = curproc->p_kd;
92 0 : if (kd == NULL || kd->kd_mode != KCOV_MODE_TRACE_PC)
93 0 : return;
94 :
95 0 : idx = kd->kd_buf[0];
96 0 : if (idx < kd->kd_nmemb) {
97 0 : kd->kd_buf[idx + 1] = (uintptr_t)__builtin_return_address(0);
98 0 : kd->kd_buf[0] = idx + 1;
99 0 : }
100 0 : }
101 :
102 : void
103 0 : kcovattach(int count)
104 : {
105 0 : }
106 :
107 : int
108 0 : kcovopen(dev_t dev, int flag, int mode, struct proc *p)
109 : {
110 : struct kcov_dev *kd;
111 :
112 0 : if (kd_lookup(minor(dev)) != NULL)
113 0 : return (EBUSY);
114 :
115 : DPRINTF("%s: unit=%d\n", __func__, minor(dev));
116 :
117 0 : kd = malloc(sizeof(*kd), M_SUBPROC, M_WAITOK | M_ZERO);
118 0 : kd->kd_unit = minor(dev);
119 0 : TAILQ_INSERT_TAIL(&kd_list, kd, kd_entry);
120 0 : return (0);
121 0 : }
122 :
123 : int
124 0 : kcovclose(dev_t dev, int flag, int mode, struct proc *p)
125 : {
126 : struct kcov_dev *kd;
127 :
128 0 : kd = kd_lookup(minor(dev));
129 0 : if (kd == NULL)
130 0 : return (EINVAL);
131 :
132 : DPRINTF("%s: unit=%d\n", __func__, minor(dev));
133 :
134 0 : if (kd->kd_mode == KCOV_MODE_TRACE_PC)
135 0 : kd->kd_mode = KCOV_MODE_DYING;
136 : else
137 0 : kd_free(kd);
138 :
139 0 : return (0);
140 0 : }
141 :
142 : int
143 0 : kcovioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
144 : {
145 : struct kcov_dev *kd;
146 : int error = 0;
147 :
148 0 : kd = kd_lookup(minor(dev));
149 0 : if (kd == NULL)
150 0 : return (ENXIO);
151 :
152 0 : switch (cmd) {
153 : case KIOSETBUFSIZE:
154 0 : if (kd->kd_mode != KCOV_MODE_DISABLED) {
155 : error = EBUSY;
156 0 : break;
157 : }
158 0 : error = kd_alloc(kd, *((unsigned long *)data));
159 0 : if (error == 0)
160 0 : kd->kd_mode = KCOV_MODE_INIT;
161 : break;
162 : case KIOENABLE:
163 : /* Only one kcov descriptor can be enabled per thread. */
164 0 : if (p->p_kd != NULL || kd->kd_mode != KCOV_MODE_INIT) {
165 : error = EBUSY;
166 0 : break;
167 : }
168 0 : kd->kd_mode = KCOV_MODE_TRACE_PC;
169 0 : p->p_kd = kd;
170 0 : break;
171 : case KIODISABLE:
172 : /* Only the enabled thread may disable itself. */
173 0 : if (p->p_kd != kd || kd->kd_mode != KCOV_MODE_TRACE_PC) {
174 : error = EBUSY;
175 0 : break;
176 : }
177 0 : kd->kd_mode = KCOV_MODE_INIT;
178 0 : p->p_kd = NULL;
179 0 : break;
180 : default:
181 : error = EINVAL;
182 : DPRINTF("%s: %lu: unknown command\n", __func__, cmd);
183 0 : }
184 :
185 : DPRINTF("%s: unit=%d, mode=%d, error=%d\n",
186 : __func__, kd->kd_unit, kd->kd_mode, error);
187 :
188 0 : return (error);
189 0 : }
190 :
191 : paddr_t
192 0 : kcovmmap(dev_t dev, off_t offset, int prot)
193 : {
194 : struct kcov_dev *kd;
195 0 : paddr_t pa;
196 : vaddr_t va;
197 :
198 0 : kd = kd_lookup(minor(dev));
199 0 : if (kd == NULL)
200 0 : return (paddr_t)(-1);
201 :
202 0 : if (offset < 0 || offset >= kd->kd_nmemb * sizeof(uintptr_t))
203 0 : return (paddr_t)(-1);
204 :
205 0 : va = (vaddr_t)kd->kd_buf + offset;
206 0 : if (pmap_extract(pmap_kernel(), va, &pa) == FALSE)
207 0 : return (paddr_t)(-1);
208 0 : return (pa);
209 0 : }
210 :
211 : void
212 0 : kcov_exit(struct proc *p)
213 : {
214 : struct kcov_dev *kd;
215 :
216 0 : kd = p->p_kd;
217 0 : if (kd == NULL)
218 0 : return;
219 :
220 : DPRINTF("%s: unit=%d\n", __func__, kd->kd_unit);
221 :
222 0 : if (kd->kd_mode == KCOV_MODE_DYING)
223 0 : kd_free(kd);
224 : else
225 0 : kd->kd_mode = KCOV_MODE_INIT;
226 0 : p->p_kd = NULL;
227 0 : }
228 :
229 : struct kcov_dev *
230 0 : kd_lookup(int unit)
231 : {
232 : struct kcov_dev *kd;
233 :
234 0 : TAILQ_FOREACH(kd, &kd_list, kd_entry) {
235 0 : if (kd->kd_unit == unit)
236 0 : return (kd);
237 : }
238 0 : return (NULL);
239 0 : }
240 :
241 : int
242 0 : kd_alloc(struct kcov_dev *kd, unsigned long nmemb)
243 : {
244 : size_t size;
245 :
246 0 : KASSERT(kd->kd_buf == NULL);
247 :
248 0 : if (nmemb == 0 || nmemb > KCOV_BUF_MAX_NMEMB)
249 0 : return (EINVAL);
250 :
251 0 : size = roundup(nmemb * sizeof(uintptr_t), PAGE_SIZE);
252 0 : kd->kd_buf = malloc(size, M_SUBPROC, M_WAITOK | M_ZERO);
253 : /* The first element is reserved to hold the number of used elements. */
254 0 : kd->kd_nmemb = nmemb - 1;
255 0 : kd->kd_size = size;
256 0 : return (0);
257 0 : }
258 :
259 : void
260 0 : kd_free(struct kcov_dev *kd)
261 : {
262 : DPRINTF("%s: unit=%d mode=%d\n", __func__, kd->kd_unit, kd->kd_mode);
263 :
264 0 : TAILQ_REMOVE(&kd_list, kd, kd_entry);
265 0 : free(kd->kd_buf, M_SUBPROC, kd->kd_size);
266 0 : free(kd, M_SUBPROC, sizeof(*kd));
267 0 : }
268 :
269 : static inline int
270 0 : inintr(void)
271 : {
272 : #if defined(__amd64__) || defined(__i386__)
273 0 : return (curcpu()->ci_idepth > 0);
274 : #else
275 : return (0);
276 : #endif
277 : }
|