summaryrefslogtreecommitdiff
path: root/sys/dev/kcov.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/kcov.c')
-rw-r--r--sys/dev/kcov.c277
1 files changed, 277 insertions, 0 deletions
diff --git a/sys/dev/kcov.c b/sys/dev/kcov.c
new file mode 100644
index 00000000000..cc8686a7927
--- /dev/null
+++ b/sys/dev/kcov.c
@@ -0,0 +1,277 @@
+/* $OpenBSD: kcov.c,v 1.1 2018/08/19 11:42:33 anton Exp $ */
+
+/*
+ * Copyright (c) 2018 Anton Lindqvist <anton@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/proc.h>
+#include <sys/kcov.h>
+#include <sys/malloc.h>
+#include <sys/stdint.h>
+#include <sys/queue.h>
+
+#include <uvm/uvm_extern.h>
+
+/* #define KCOV_DEBUG */
+#ifdef KCOV_DEBUG
+#define DPRINTF(x...) do { if (kcov_debug) printf(x); } while (0)
+#else
+#define DPRINTF(x...)
+#endif
+
+/* kcov descriptor */
+struct kd {
+ enum {
+ KCOV_MODE_DISABLED,
+ KCOV_MODE_INIT,
+ KCOV_MODE_TRACE_PC,
+ } kd_mode;
+ int kd_unit; /* device minor */
+ pid_t kd_pid; /* process being traced */
+ uintptr_t *kd_buf; /* traced coverage */
+ size_t kd_nmemb;
+ size_t kd_size;
+
+ TAILQ_ENTRY(kd) kd_entry;
+};
+
+void kcovattach(int);
+
+int kd_alloc(struct kd *, unsigned long);
+struct kd *kd_lookup(int);
+
+static inline struct kd *kd_lookup_pid(pid_t);
+static inline int inintr(void);
+
+TAILQ_HEAD(, kd) kd_list = TAILQ_HEAD_INITIALIZER(kd_list);
+
+#ifdef KCOV_DEBUG
+int kcov_debug = 1;
+#endif
+
+/*
+ * Compiling the kernel with the `-fsanitize-coverage=trace-pc' option will
+ * cause the following function to be called upon function entry and before
+ * each block instructions that maps to a single line in the original source
+ * code.
+ *
+ * If kcov is enabled for the current process, the executed address will be
+ * stored in the corresponding coverage buffer.
+ * The first element in the coverage buffer holds the index of next available
+ * element.
+ */
+void
+__sanitizer_cov_trace_pc(void)
+{
+ extern int cold;
+ struct kd *kd;
+ uint64_t idx;
+
+ /* Do not trace during boot. */
+ if (cold)
+ return;
+
+ /* Do not trace in interrupts to prevent noisy coverage. */
+ if (inintr())
+ return;
+
+ kd = kd_lookup_pid(curproc->p_p->ps_pid);
+ if (kd == NULL)
+ return;
+
+ idx = kd->kd_buf[0];
+ if (idx < kd->kd_nmemb) {
+ kd->kd_buf[idx + 1] = (uintptr_t)__builtin_return_address(0);
+ kd->kd_buf[0] = idx + 1;
+ }
+}
+
+void
+kcovattach(int count)
+{
+}
+
+int
+kcovopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+#ifdef KCOV
+ struct kd *kd;
+
+ if (kd_lookup(minor(dev)) != NULL)
+ return (EBUSY);
+
+ DPRINTF("%s: unit=%d\n", __func__, minor(dev));
+
+ kd = malloc(sizeof(*kd), M_SUBPROC, M_WAITOK | M_ZERO);
+ kd->kd_unit = minor(dev);
+ TAILQ_INSERT_TAIL(&kd_list, kd, kd_entry);
+ return (0);
+#else
+ return (ENXIO);
+#endif
+}
+
+int
+kcovclose(dev_t dev, int flag, int mode, struct proc *p)
+{
+ struct kd *kd;
+
+ kd = kd_lookup(minor(dev));
+ if (kd == NULL)
+ return (EINVAL);
+
+ DPRINTF("%s: unit=%d\n", __func__, minor(dev));
+
+ TAILQ_REMOVE(&kd_list, kd, kd_entry);
+ free(kd->kd_buf, M_SUBPROC, kd->kd_size);
+ free(kd, M_SUBPROC, sizeof(struct kd));
+ return (0);
+}
+
+int
+kcovioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct kd *kd;
+ int error = 0;
+
+ kd = kd_lookup(minor(dev));
+ if (kd == NULL)
+ return (ENXIO);
+
+ switch (cmd) {
+ case KIOSETBUFSIZE:
+ if (kd->kd_mode != KCOV_MODE_DISABLED) {
+ error = EBUSY;
+ break;
+ }
+ error = kd_alloc(kd, *((unsigned long *)data));
+ if (error == 0)
+ kd->kd_mode = KCOV_MODE_INIT;
+ break;
+ case KIOENABLE:
+ if (kd->kd_mode != KCOV_MODE_INIT) {
+ error = EBUSY;
+ break;
+ }
+ kd->kd_mode = KCOV_MODE_TRACE_PC;
+ kd->kd_pid = p->p_p->ps_pid;
+ break;
+ case KIODISABLE:
+ /* Only the enabled process may disable itself. */
+ if (kd->kd_pid != p->p_p->ps_pid ||
+ kd->kd_mode != KCOV_MODE_TRACE_PC) {
+ error = EBUSY;
+ break;
+ }
+ kd->kd_mode = KCOV_MODE_INIT;
+ kd->kd_pid = 0;
+ break;
+ default:
+ error = EINVAL;
+ DPRINTF("%s: %lu: unknown command\n", __func__, cmd);
+ }
+
+ DPRINTF("%s: unit=%d, mode=%d, pid=%d, error=%d\n",
+ __func__, kd->kd_unit, kd->kd_mode, kd->kd_pid, error);
+
+ return (error);
+}
+
+paddr_t
+kcovmmap(dev_t dev, off_t offset, int prot)
+{
+ struct kd *kd;
+ paddr_t pa;
+ vaddr_t va;
+
+ kd = kd_lookup(minor(dev));
+ if (kd == NULL)
+ return (paddr_t)(-1);
+
+ if (offset < 0 || offset >= kd->kd_nmemb * sizeof(uintptr_t))
+ return (paddr_t)(-1);
+
+ va = (vaddr_t)kd->kd_buf + offset;
+ if (pmap_extract(pmap_kernel(), va, &pa) == FALSE)
+ return (paddr_t)(-1);
+ return (pa);
+}
+
+void
+kcov_exit(struct proc *p)
+{
+ struct kd *kd;
+
+ kd = kd_lookup_pid(p->p_p->ps_pid);
+ if (kd == NULL)
+ return;
+
+ kd->kd_mode = KCOV_MODE_INIT;
+ kd->kd_pid = 0;
+}
+
+struct kd *
+kd_lookup(int unit)
+{
+ struct kd *kd;
+
+ TAILQ_FOREACH(kd, &kd_list, kd_entry) {
+ if (kd->kd_unit == unit)
+ return (kd);
+ }
+ return (NULL);
+}
+
+int
+kd_alloc(struct kd *kd, unsigned long nmemb)
+{
+ size_t size;
+
+ KASSERT(kd->kd_buf == NULL);
+
+ if (nmemb == 0 || nmemb > KCOV_BUF_MAX_NMEMB)
+ return (EINVAL);
+
+ size = roundup(nmemb * sizeof(uintptr_t), PAGE_SIZE);
+ kd->kd_buf = malloc(size, M_SUBPROC, M_WAITOK | M_ZERO);
+ /* The first element is reserved to hold the number of used elements. */
+ kd->kd_nmemb = nmemb - 1;
+ kd->kd_size = size;
+ return (0);
+}
+
+static inline struct kd *
+kd_lookup_pid(pid_t pid)
+{
+ struct kd *kd;
+
+ TAILQ_FOREACH(kd, &kd_list, kd_entry) {
+ if (kd->kd_pid == pid && kd->kd_mode == KCOV_MODE_TRACE_PC)
+ return (kd);
+ }
+ return (NULL);
+}
+
+static inline int
+inintr(void)
+{
+#if defined(__amd64__) || defined(__i386__)
+ return (curcpu()->ci_idepth > 0);
+#else
+ return (0);
+#endif
+}