Page MenuHomeFreeBSD

D14599.id52334.diff
No OneTemporary

D14599.id52334.diff

Index: sys/amd64/conf/GENERIC
===================================================================
--- sys/amd64/conf/GENERIC
+++ sys/amd64/conf/GENERIC
@@ -102,6 +102,7 @@
options VERBOSE_SYSINIT=0 # Support debug.verbose_sysinit, off by default
# Warning: KUBSAN can result in a kernel too large for loader to load
#options KUBSAN # Kernel Undefined Behavior Sanitizer
+options KCOV # Kernel Coverage Sanitizer
# Kernel dump features.
options EKCD # Support for encrypted kernel dumps
Index: sys/arm64/conf/GENERIC
===================================================================
--- sys/arm64/conf/GENERIC
+++ sys/arm64/conf/GENERIC
@@ -94,6 +94,7 @@
options VERBOSE_SYSINIT=0 # Support debug.verbose_sysinit, off by default
# Warning: KUBSAN can result in a kernel too large for loader to load
#options KUBSAN # Kernel Undefined Behavior Sanitizer
+options KCOV # Kernel Coverage Sanitizer
# Kernel dump features.
options EKCD # Support for encrypted kernel dumps
Index: sys/conf/files
===================================================================
--- sys/conf/files
+++ sys/conf/files
@@ -3789,6 +3789,8 @@
kern/kern_idle.c standard
kern/kern_intr.c standard
kern/kern_jail.c standard
+kern/kern_kcov.c optional kcov \
+ compile-with "${NORMAL_C} -fno-sanitize-coverage=trace-pc,trace-cmp"
kern/kern_khelp.c standard
kern/kern_kthread.c standard
kern/kern_ktr.c optional ktr
Index: sys/conf/kern.pre.mk
===================================================================
--- sys/conf/kern.pre.mk
+++ sys/conf/kern.pre.mk
@@ -117,6 +117,18 @@
.if !empty(KUBSAN_ENABLED)
SAN_CFLAGS+= -fsanitize=undefined
.endif
+
+KCOV_ENABLED!= grep KCOV opt_kcov.h || true ; echo
+.if !empty(KCOV_ENABLED)
+SAN_CFLAGS+= -fsanitize-coverage=trace-pc,trace-cmp
+.endif
+
+CFLAGS+= ${SAN_CFLAGS}
+
+KCOV_ENABLED!= grep KCOV opt_kcov.h || true ; echo
+.if !empty(KCOV_ENABLED)
+SAN_CFLAGS+= -fsanitize-coverage=trace-pc,trace-cmp
+.endif
CFLAGS+= ${SAN_CFLAGS}
# Put configuration-specific C flags last (except for ${PROF}) so that they
Index: sys/conf/options
===================================================================
--- sys/conf/options
+++ sys/conf/options
@@ -57,6 +57,7 @@
DDB_NUMSYM opt_ddb.h
FULL_BUF_TRACKING opt_global.h
GDB
+KCOV opt_kcov.h
KDB opt_global.h
KDB_TRACE opt_kdb.h
KDB_UNATTENDED opt_kdb.h
Index: sys/kern/kern_kcov.c
===================================================================
--- /dev/null
+++ sys/kern/kern_kcov.c
@@ -0,0 +1,632 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (C) 2018 The FreeBSD Foundation. All rights reserved.
+ * Copyright (C) 2018 Andrew Turner
+ *
+ * This software was developed by Mitchell Horne under sponsorship of
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/kcov.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mman.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/rwlock.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+
+#include <vm/vm.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_object.h>
+#include <vm/vm_page.h>
+#include <vm/vm_pager.h>
+
+#include <machine/cpufunc.h>
+
+#include <vm/pmap.h>
+
+MALLOC_DEFINE(M_KCOV_INFO, "kcovinfo", "KCOV info type");
+
+#define KCOV_ELEMENT_SIZE sizeof(uint64_t)
+
+/*
+ * - Only move away from the running state in the current thread. This is to
+ * ensure we are not currently recording a trace while this happens.
+ * - There need to be barriers before moving to the running state and after
+ * leaving it. This ensures a consistent state if an interrupt happens
+ * when enabling or disabling.
+ */
+
+typedef enum {
+ KCOV_STATE_INVALID,
+ KCOV_STATE_OPEN, /* The device is open, but with no buffer */
+ KCOV_STATE_READY, /* The buffer has been allocated */
+ KCOV_STATE_RUNNING, /* Recording trace data */
+ KCOV_STATE_DYING, /* The thread exited or the fd was closed */
+} kcov_state_t;
+
+struct kcov_info {
+ vm_object_t bufobj;
+ vm_offset_t kvaddr;
+ size_t size;
+ size_t bufsize;
+ kcov_state_t state;
+ int mode;
+ bool mmap;
+};
+
+/* Prototypes */
+static d_open_t kcov_open;
+static d_close_t kcov_close;
+static d_mmap_single_t kcov_mmap_single;
+static d_ioctl_t kcov_ioctl;
+
+void __sanitizer_cov_trace_pc(void);
+void __sanitizer_cov_trace_cmp1(uint8_t, uint8_t);
+void __sanitizer_cov_trace_cmp2(uint16_t, uint16_t);
+void __sanitizer_cov_trace_cmp4(uint32_t, uint32_t);
+void __sanitizer_cov_trace_cmp8(uint64_t, uint64_t);
+void __sanitizer_cov_trace_const_cmp1(uint8_t, uint8_t);
+void __sanitizer_cov_trace_const_cmp2(uint16_t, uint16_t);
+void __sanitizer_cov_trace_const_cmp4(uint32_t, uint32_t);
+void __sanitizer_cov_trace_const_cmp8(uint64_t, uint64_t);
+void __sanitizer_cov_trace_switch(uint64_t, uint64_t *);
+
+static int kcov_alloc(struct kcov_info *info, size_t size);
+static void kcov_init(const void *unused);
+
+static struct cdevsw kcov_cdevsw = {
+ .d_version = D_VERSION,
+ .d_open = kcov_open,
+ .d_close = kcov_close,
+ .d_mmap_single = kcov_mmap_single,
+ .d_ioctl = kcov_ioctl,
+ .d_name = "kcov",
+};
+
+SYSCTL_NODE(_kern, OID_AUTO, kcov, CTLFLAG_RW, 0, "Kernel coverage");
+
+static u_int kcov_max_size = KCOV_MAXENTRIES;
+SYSCTL_UINT(_kern_kcov, OID_AUTO, max_size, CTLFLAG_RW,
+ &kcov_max_size, 0,
+ "Maximum size of the kcov buffer");
+
+static struct mtx kcov_lock;
+
+static struct kcov_info *
+get_kinfo(struct thread *td)
+{
+ struct kcov_info *info;
+
+ /* We might have a NULL thread when releasing the secondary CPUs */
+ if (td == NULL)
+ return (NULL);
+
+ /*
+ * We are in an interrupt, stop tracing as it is not explicitly
+ * part of a syscall.
+ */
+ if (td->td_intr_nesting_level > 0 || td->td_intr_frame != NULL)
+ return (NULL);
+
+ /*
+ * If info is NULL or the state is not running we are not tracing.
+ */
+ info = td->td_kcov_info;
+ if (info == NULL || info->state != KCOV_STATE_RUNNING)
+ return (NULL);
+
+ return (info);
+}
+
+/*
+ * Main entry point. A call to this function will be inserted
+ * at every edge, and if coverage is enabled for the thread
+ * this function will add the PC to the buffer.
+ */
+void
+__sanitizer_cov_trace_pc(void)
+{
+ struct thread *td;
+ struct kcov_info *info;
+ uint64_t *buf, index;
+
+ /*
+ * To guarantee curthread is properly set, we exit early
+ * until the driver has been initialized
+ */
+ if (cold)
+ return;
+
+ td = curthread;
+ info = get_kinfo(td);
+ if (info == NULL)
+ return;
+
+ /*
+ * Check we are in the PC-trace mode.
+ */
+ if (info->mode != KCOV_MODE_TRACE_PC)
+ return;
+
+ KASSERT(info->kvaddr != 0,
+ ("__sanitizer_cov_trace_pc: NULL buf while running"));
+
+ buf = (uint64_t *)info->kvaddr;
+
+ /* The first entry of the buffer holds the index */
+ index = buf[0];
+ if (index + 2 >= info->size / KCOV_ELEMENT_SIZE)
+ return;
+
+ buf[index + 1] = (uint64_t)__builtin_return_address(0);
+ buf[0] = index + 1;
+}
+
+static bool
+trace_cmp(uint64_t type, uint64_t arg1, uint64_t arg2, uint64_t ret)
+{
+ struct thread *td;
+ struct kcov_info *info;
+ uint64_t *buf, index;
+
+ /*
+ * To guarantee curthread is properly set, we exit early
+ * until the driver has been initialized
+ */
+ if (cold)
+ return (false);
+
+ td = curthread;
+ info = get_kinfo(td);
+ if (info == NULL)
+ return (false);
+
+ /*
+ * Check we are in the comparison-trace mode.
+ */
+ if (info->mode != KCOV_MODE_TRACE_CMP)
+ return (false);
+
+ KASSERT(info->kvaddr != 0,
+ ("__sanitizer_cov_trace_pc: NULL buf while running"));
+
+ buf = (uint64_t *)info->kvaddr;
+
+ /* The first entry of the buffer holds the index */
+ index = buf[0];
+
+ /* Check we have space to store all elements */
+ if (index * 4 + 5 >= info->size / KCOV_ELEMENT_SIZE)
+ return (false);
+
+ buf[index * 4 + 1] = type;
+ buf[index * 4 + 2] = arg1;
+ buf[index * 4 + 3] = arg2;
+ buf[index * 4 + 4] = ret;
+ buf[0] = index + 1;
+
+ return (true);
+}
+
+void
+__sanitizer_cov_trace_cmp1(uint8_t arg1, uint8_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(0), arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+void
+__sanitizer_cov_trace_cmp2(uint16_t arg1, uint16_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(1), arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+void
+__sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(2), arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+void
+__sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(3), arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+void
+__sanitizer_cov_trace_const_cmp1(uint8_t arg1, uint8_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(0) | KCOV_CMP_CONST, arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+void
+__sanitizer_cov_trace_const_cmp2(uint16_t arg1, uint16_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(1) | KCOV_CMP_CONST, arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+void
+__sanitizer_cov_trace_const_cmp4(uint32_t arg1, uint32_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(2) | KCOV_CMP_CONST, arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+void
+__sanitizer_cov_trace_const_cmp8(uint64_t arg1, uint64_t arg2)
+{
+
+ trace_cmp(KCOV_CMP_SIZE(3) | KCOV_CMP_CONST, arg1, arg2,
+ (uint64_t)__builtin_return_address(0));
+}
+
+/*
+ * val is the switch operand
+ * cases[0] is the number of case constants
+ * cases[1] is the size of val in bits
+ * cases[2..n] are the case constants
+ */
+void
+__sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases)
+{
+ uint64_t i, count, ret, type;
+
+ count = cases[0];
+ ret = (uint64_t)__builtin_return_address(0);
+
+ switch (cases[1]) {
+ case 8:
+ type = KCOV_CMP_SIZE(0);
+ break;
+ case 16:
+ type = KCOV_CMP_SIZE(1);
+ break;
+ case 32:
+ type = KCOV_CMP_SIZE(2);
+ break;
+ case 64:
+ type = KCOV_CMP_SIZE(3);
+ break;
+ default:
+ return;
+ }
+
+ val |= KCOV_CMP_CONST;
+
+ for (i = 0; i < count; i++)
+ if (!trace_cmp(type, val, cases[i + 2], ret))
+ return;
+}
+
+/*
+ * The fd is being closed, cleanup everything we can.
+ */
+static void
+kcov_mmap_cleanup(void *arg)
+{
+ struct kcov_info *info = arg;
+
+ mtx_lock_spin(&kcov_lock);
+ if (info->state != KCOV_STATE_DYING) {
+ /*
+ * The thread is still running. Put it into the dying state
+ * to be cleaned up later.
+ */
+ info->state = KCOV_STATE_DYING;
+ atomic_thread_fence_seq_cst();
+ mtx_unlock_spin(&kcov_lock);
+ return;
+ }
+ mtx_unlock_spin(&kcov_lock);
+
+ /*
+ * The thread has exited, clean up the old state now the
+ * user has closed the fd.
+ */
+
+ if (info->kvaddr != 0) {
+ pmap_qremove(info->kvaddr, info->bufsize / PAGE_SIZE);
+ kva_free(info->kvaddr, info->bufsize);
+ }
+ if (info->bufobj != NULL && !info->mmap)
+ vm_object_deallocate(info->bufobj);
+ free(info, M_KCOV_INFO);
+}
+
+static int
+kcov_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+ struct kcov_info *info;
+ int error;
+
+ info = malloc(sizeof(struct kcov_info), M_KCOV_INFO, M_ZERO | M_WAITOK);
+ info->state = KCOV_STATE_OPEN;
+ info->mode = -1;
+ info->mmap = false;
+
+ if ((error = devfs_set_cdevpriv(info, kcov_mmap_cleanup)) != 0)
+ kcov_mmap_cleanup(info);
+
+ return (error);
+}
+
+static int
+kcov_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
+{
+ struct kcov_info *info;
+ int error;
+
+ if ((error = devfs_get_cdevpriv((void **)&info)) != 0)
+ return (error);
+
+ KASSERT(info != NULL, ("kcov_close with no kcov_info structure"));
+
+ /* Trying to close, but haven't disabled */
+ if (info->state == KCOV_STATE_RUNNING)
+ return (EBUSY);
+
+ return (0);
+}
+
+static int
+kcov_mmap_single(struct cdev *dev, vm_ooffset_t *offset, vm_size_t size,
+ struct vm_object **object, int nprot)
+{
+ struct kcov_info *info;
+ int error;
+
+ if ((nprot & (PROT_EXEC | PROT_READ | PROT_WRITE)) !=
+ (PROT_READ | PROT_WRITE))
+ return (EINVAL);
+
+ if ((error = devfs_get_cdevpriv((void **)&info)) != 0)
+ return (error);
+
+ if (info->kvaddr == 0 || size != info->size || info->mmap != false)
+ return (EINVAL);
+
+ info->mmap = true;
+ *offset = 0;
+ *object = info->bufobj;
+ return (0);
+}
+
+static int
+kcov_alloc(struct kcov_info *info, size_t size)
+{
+ size_t n, pages;
+ vm_page_t *m;
+
+ KASSERT(info->kvaddr == 0, ("kcov_alloc: Already have a buffer"));
+ KASSERT(info->state == KCOV_STATE_OPEN,
+ ("kcov_alloc: Not in open state (%x)", info->state));
+
+ if (size < 2 * KCOV_ELEMENT_SIZE || size > kcov_max_size)
+ return (EINVAL);
+
+ /* Align to page size so mmap can't access other kernel memory */
+ info->bufsize = roundup2(size, PAGE_SIZE);
+ pages = info->bufsize / PAGE_SIZE;
+
+ if ((info->kvaddr = kva_alloc(info->bufsize)) == 0)
+ return (ENOMEM);
+
+ info->bufobj = vm_pager_allocate(OBJT_PHYS, 0, info->bufsize,
+ PROT_READ | PROT_WRITE, 0, curthread->td_ucred);
+
+ m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
+ VM_OBJECT_WLOCK(info->bufobj);
+ for (n = 0; n < pages; n++) {
+ m[n] = vm_page_grab(info->bufobj, n,
+ VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
+ m[n]->valid = VM_PAGE_BITS_ALL;
+ }
+ VM_OBJECT_WUNLOCK(info->bufobj);
+ pmap_qenter(info->kvaddr, m, pages);
+ free(m, M_TEMP);
+
+ info->size = size;
+
+ return (0);
+}
+
+static int
+kcov_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag __unused,
+ struct thread *td)
+{
+ struct kcov_info *info;
+ int mode, error;
+
+ if ((error = devfs_get_cdevpriv((void **)&info)) != 0)
+ return (error);
+
+ if (cmd == KIOSETBUFSIZE) {
+ /*
+ * Set the size of the coverage buffer. Should be called
+ * before enabling coverage collection for that thread.
+ */
+ if (info->state != KCOV_STATE_OPEN) {
+ return (EBUSY);
+ }
+ error = kcov_alloc(info, *(u_int *)data);
+ if (error == 0)
+ info->state = KCOV_STATE_READY;
+ return (error);
+ }
+
+ mtx_lock_spin(&kcov_lock);
+ switch (cmd) {
+ case KIOENABLE:
+ if (info->state != KCOV_STATE_READY) {
+ error = EBUSY;
+ break;
+ }
+ if (td->td_kcov_info != NULL) {
+ error = EINVAL;
+ break;
+ }
+ mode = *(int *)data;
+ if (mode != KCOV_MODE_TRACE_PC && mode != KCOV_MODE_TRACE_CMP) {
+ error = EINVAL;
+ break;
+ }
+ td->td_kcov_info = info;
+ info->mode = mode;
+ /*
+ * Atomically store the pointer to the info struct to protect
+ * against an interrupt happening at the wrong time.
+ */
+ atomic_thread_fence_seq_cst();
+ info->state = KCOV_STATE_RUNNING;
+ break;
+ case KIODISABLE:
+ /* Only the currently enabled thread may disable itself */
+ if (info->state != KCOV_STATE_RUNNING ||
+ info != td->td_kcov_info) {
+ error = EINVAL;
+ break;
+ }
+ info->state = KCOV_STATE_READY;
+ atomic_thread_fence_seq_cst();
+ td->td_kcov_info = NULL;
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+ mtx_unlock_spin(&kcov_lock);
+
+ return (error);
+}
+
+static void
+kcov_thread_dtor(void *arg __unused, struct thread *td)
+{
+ struct kcov_info *info;
+
+ info = td->td_kcov_info;
+ if (info == NULL)
+ return;
+
+ mtx_lock_spin(&kcov_lock);
+ if (info->state != KCOV_STATE_DYING) {
+ /*
+ * The kcov file is still open. Mark it as unused and
+ * wait for it to be closed before cleaning up.
+ */
+ info->state = KCOV_STATE_DYING;
+ atomic_thread_fence_seq_cst();
+ td->td_kcov_info = NULL;
+ mtx_unlock_spin(&kcov_lock);
+ return;
+ }
+ mtx_unlock_spin(&kcov_lock);
+
+ /* We should have entered KCOV_STATE_DYING in kcov_thread_exit */
+ KASSERT(info->state != KCOV_STATE_RUNNING,
+ ("kcov_thread_dtor: Clean up while running"));
+
+ if (info->kvaddr != 0) {
+ pmap_qremove(info->kvaddr, info->bufsize / PAGE_SIZE);
+ kva_free(info->kvaddr, info->bufsize);
+ }
+ if (info->bufobj != NULL && !info->mmap)
+ vm_object_deallocate(info->bufobj);
+ free(info, M_KCOV_INFO);
+}
+
+static void
+kcov_init(const void *unused)
+{
+ struct make_dev_args args;
+ struct cdev *dev;
+
+ mtx_init(&kcov_lock, "kcov lock", NULL, MTX_SPIN);
+
+ make_dev_args_init(&args);
+ args.mda_devsw = &kcov_cdevsw;
+ args.mda_uid = UID_ROOT;
+ args.mda_gid = GID_WHEEL;
+ args.mda_mode = 0600;
+ if (make_dev_s(&args, &dev, "kcov") != 0) {
+ printf("%s", "Failed to create kcov device");
+ return;
+ }
+
+ EVENTHANDLER_REGISTER(thread_dtor, kcov_thread_dtor, NULL,
+ EVENTHANDLER_PRI_ANY);
+}
+
+/*
+ * thread_exit() hook
+ */
+void
+kcov_thread_exit(struct thread *td)
+{
+ struct kcov_info *info;
+ bool need_free;
+
+ info = td->td_kcov_info;
+ if (info != NULL) {
+ mtx_lock_spin(&kcov_lock);
+ need_free = false;
+ if (info->state != KCOV_STATE_DYING) {
+ KASSERT(info->state == KCOV_STATE_RUNNING,
+ ("%s: td_kcov_info set but not running", __func__));
+ info->state = KCOV_STATE_DYING;
+ atomic_thread_fence_seq_cst();
+ td->td_kcov_info = NULL;
+ }
+ mtx_unlock_spin(&kcov_lock);
+ }
+}
+
+SYSINIT(kcovdev, SI_SUB_DEVFS, SI_ORDER_ANY, kcov_init, NULL);
Index: sys/kern/kern_thread.c
===================================================================
--- sys/kern/kern_thread.c
+++ sys/kern/kern_thread.c
@@ -30,12 +30,14 @@
#include "opt_witness.h"
#include "opt_hwpmc_hooks.h"
+#include "opt_kcov.h"
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
+#include <sys/kcov.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
@@ -82,9 +84,9 @@
"struct thread KBI td_flags");
_Static_assert(offsetof(struct thread, td_pflags) == 0x104,
"struct thread KBI td_pflags");
-_Static_assert(offsetof(struct thread, td_frame) == 0x470,
+_Static_assert(offsetof(struct thread, td_frame) == 0x478,
"struct thread KBI td_frame");
-_Static_assert(offsetof(struct thread, td_emuldata) == 0x528,
+_Static_assert(offsetof(struct thread, td_emuldata) == 0x530,
"struct thread KBI td_emuldata");
_Static_assert(offsetof(struct proc, p_flag) == 0xb0,
"struct proc KBI p_flag");
@@ -535,6 +537,10 @@
SDT_PROBE0(proc, , , lwp__exit);
KASSERT(TAILQ_EMPTY(&td->td_sigqueue.sq_list), ("signal pending"));
+#ifdef KCOV
+ kcov_thread_exit(td);
+#endif
+
/*
* drop FPU & debug register state storage, or any other
* architecture specific resources that
Index: sys/sys/kcov.h
===================================================================
--- /dev/null
+++ sys/sys/kcov.h
@@ -0,0 +1,58 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (C) 2018 The FreeBSD Foundation. All rights reserved.
+ *
+ * This software was developed by Mitchell Horne under sponsorship of
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _SYS_KCOV_H_
+#define _SYS_KCOV_H_
+
+#include <sys/ioccom.h>
+
+#define KCOV_MAXENTRIES (1 << 24) /* 16M */
+
+#define KCOV_MODE_TRACE_PC 0
+#define KCOV_MODE_TRACE_CMP 1
+
+/* KCOV ioctls */
+#define KIOENABLE _IOWINT('c', 2) /* Enable coverage recording */
+#define KIODISABLE _IO('c', 3) /* Disable coverage recording */
+#define KIOSETBUFSIZE _IOWINT('c', 4) /* Set the buffer size */
+
+#define KCOV_CMP_CONST (1 << 0)
+#define KCOV_CMP_SIZE(x) ((x) << 1)
+#define KCOV_CMP_MASK (3 << 1)
+#define KCOV_CMP_GET_SIZE(x) (((x) >> 1) & 3)
+
+#ifdef _KERNEL
+
+void kcov_thread_exit(struct thread *);
+
+#endif /* _KERNEL */
+#endif /* _SYS_KCOV_H_ */
Index: sys/sys/proc.h
===================================================================
--- sys/sys/proc.h
+++ sys/sys/proc.h
@@ -175,6 +175,7 @@
struct filemon;
struct kaioinfo;
struct kaudit_record;
+struct kcov_info;
struct kdtrace_proc;
struct kdtrace_thread;
struct mqueue_notifier;
@@ -300,6 +301,7 @@
sbintime_t td_sleeptimo; /* (t) Sleep timeout. */
int td_rtcgen; /* (s) rtc_generation of abs. sleep */
size_t td_vslock_sz; /* (k) amount of vslock-ed space */
+ struct kcov_info *td_kcov_info; /* (*) Kernel code coverage data */
#define td_endzero td_sigmask
/* Copied during fork1() or create_thread(). */
Index: tests/sys/kern/Makefile
===================================================================
--- tests/sys/kern/Makefile
+++ tests/sys/kern/Makefile
@@ -5,6 +5,7 @@
TESTSDIR= ${TESTSBASE}/sys/kern
+ATF_TESTS_C+= kcov
ATF_TESTS_C+= kern_copyin
ATF_TESTS_C+= kern_descrip_test
ATF_TESTS_C+= ptrace_test
@@ -32,6 +33,7 @@
LIBADD.sys_getrandom+= pthread
LIBADD.ptrace_test+= pthread
LIBADD.unix_seqpacket_test+= pthread
+LIBADD.kcov+= pthread
NETBSD_ATF_TESTS_C+= lockf_test
NETBSD_ATF_TESTS_C+= mqueue_test
Index: tests/sys/kern/kcov.c
===================================================================
--- /dev/null
+++ tests/sys/kern/kcov.c
@@ -0,0 +1,352 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2018 Andrew Turner
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/kcov.h>
+#include <sys/mman.h>
+
+#include <machine/atomic.h>
+
+#include <fcntl.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <atf-c.h>
+
+static const char *modes[] = {
+ "PC tracing",
+ "comparison tracing",
+};
+
+static int
+open_kcov(void)
+{
+ int fd;
+
+ fd = open("/dev/kcov", O_RDWR);
+ if (fd == -1)
+ atf_tc_skip("Failed to open /dev/kcov");
+
+ return (fd);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_bufsize);
+ATF_TC_BODY(kcov_bufsize, tc)
+{
+ int fd;
+
+ fd = open_kcov();
+
+ ATF_CHECK(ioctl(fd, KIOSETBUFSIZE, 0) == -1);
+ ATF_CHECK(ioctl(fd, KIOSETBUFSIZE, 1) == -1);
+ ATF_CHECK(ioctl(fd, KIOSETBUFSIZE, sizeof(uint64_t)) == -1);
+ ATF_CHECK(ioctl(fd, KIOSETBUFSIZE, 2 * sizeof(uint64_t) - 1) == -1);
+ ATF_CHECK(ioctl(fd, KIOSETBUFSIZE, 2 * sizeof(uint64_t)) == 0);
+ ATF_CHECK(ioctl(fd, KIOSETBUFSIZE, 2 * sizeof(uint64_t)) == -1);
+
+ close(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_mmap);
+ATF_TC_BODY(kcov_mmap, tc)
+{
+ void *data;
+ int fd;
+
+ fd = open_kcov();
+
+ ATF_CHECK(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0) == MAP_FAILED);
+
+ ATF_REQUIRE(ioctl(fd, KIOSETBUFSIZE, 2 * PAGE_SIZE) == 0);
+
+ ATF_CHECK(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0) == MAP_FAILED);
+ ATF_CHECK(mmap(NULL, 3 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0) == MAP_FAILED);
+ ATF_REQUIRE((data = mmap(NULL, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0)) != MAP_FAILED);
+ ATF_CHECK(mmap(NULL, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0) == MAP_FAILED);
+
+ munmap(data, 2 * PAGE_SIZE);
+
+ close(fd);
+}
+
+/* This shouldn't panic */
+ATF_TC_WITHOUT_HEAD(kcov_mmap_no_munmap);
+ATF_TC_BODY(kcov_mmap_no_munmap, tc)
+{
+ int fd;
+
+ fd = open_kcov();
+
+ ATF_REQUIRE(ioctl(fd, KIOSETBUFSIZE, PAGE_SIZE) == 0);
+
+ ATF_CHECK(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0) != MAP_FAILED);
+
+ close(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_mmap_no_munmap_no_close);
+ATF_TC_BODY(kcov_mmap_no_munmap_no_close, tc)
+{
+ int fd;
+
+ fd = open_kcov();
+
+ ATF_REQUIRE(ioctl(fd, KIOSETBUFSIZE, PAGE_SIZE) == 0);
+
+ ATF_CHECK(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0) != MAP_FAILED);
+}
+
+static sem_t sem1, sem2;
+
+static void *
+kcov_mmap_enable_thread(void *data)
+{
+ int fd;
+
+ fd = open_kcov();
+ *(int *)data = fd;
+
+ ATF_REQUIRE(ioctl(fd, KIOSETBUFSIZE, PAGE_SIZE) == 0);
+ ATF_CHECK(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, 0) != MAP_FAILED);
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_PC) == 0);
+
+ sem_post(&sem1);
+ sem_wait(&sem2);
+
+ return (NULL);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_mmap_enable_thread_close);
+ATF_TC_BODY(kcov_mmap_enable_thread_close, tc)
+{
+ pthread_t thread;
+ int fd;
+
+ sem_init(&sem1, 0, 0);
+ sem_init(&sem2, 0, 0);
+ pthread_create(&thread, NULL,
+ kcov_mmap_enable_thread, &fd);
+ sem_wait(&sem1);
+ close(fd);
+ sem_post(&sem2);
+ pthread_join(thread, NULL);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_enable);
+ATF_TC_BODY(kcov_enable, tc)
+{
+ int fd;
+
+ fd = open_kcov();
+
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_PC) == -1);
+
+ ATF_REQUIRE(ioctl(fd, KIOSETBUFSIZE, PAGE_SIZE) == 0);
+
+ /* We need to enable before disable */
+ ATF_CHECK(ioctl(fd, KIODISABLE, 0) == -1);
+
+ /* Check enabling works only with a valid trace method */
+ ATF_CHECK(ioctl(fd, KIOENABLE, -1) == -1);
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_PC) == 0);
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_PC) == -1);
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_CMP) == -1);
+
+ /* Disable should only be called once */
+ ATF_CHECK(ioctl(fd, KIODISABLE, 0) == 0);
+ ATF_CHECK(ioctl(fd, KIODISABLE, 0) == -1);
+
+ /* Re-enabling should also work */
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_CMP) == 0);
+ ATF_CHECK(ioctl(fd, KIODISABLE, 0) == 0);
+
+ close(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_enable_no_disable);
+ATF_TC_BODY(kcov_enable_no_disable, tc)
+{
+ int fd;
+
+ fd = open_kcov();
+ ATF_REQUIRE(ioctl(fd, KIOSETBUFSIZE, PAGE_SIZE) == 0);
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_PC) == 0);
+ close(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_enable_no_disable_no_close);
+ATF_TC_BODY(kcov_enable_no_disable_no_close, tc)
+{
+ int fd;
+
+ fd = open_kcov();
+ ATF_REQUIRE(ioctl(fd, KIOSETBUFSIZE, PAGE_SIZE) == 0);
+ ATF_CHECK(ioctl(fd, KIOENABLE, KCOV_MODE_TRACE_PC) == 0);
+}
+
+static void *
+common_head(int *fdp)
+{
+ void *data;
+ int fd;
+
+ fd = open_kcov();
+
+ ATF_REQUIRE_MSG(ioctl(fd, KIOSETBUFSIZE, PAGE_SIZE) == 0,
+ "Unable to set the kcov buffer size");
+
+ data = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ ATF_REQUIRE_MSG(data != MAP_FAILED, "Unable to mmap the kcov buffer");
+
+ *fdp = fd;
+ return (data);
+}
+
+static void
+common_tail(int fd, void *data)
+{
+
+ ATF_REQUIRE_MSG(munmap(data, PAGE_SIZE) == 0,
+ "Unable to unmap the kcov biffer");
+
+ close (fd);
+}
+
+static void
+basic_test(u_int mode)
+{
+ uint64_t *buf;
+ int fd;
+
+ buf = common_head(&fd);
+ ATF_REQUIRE_MSG(ioctl(fd, KIOENABLE, mode) == 0,
+ "Unable to enable kcov %s",
+ mode < nitems(modes) ? modes[mode] : "unknown mode");
+
+ atomic_store_64(&buf[0], 0);
+
+ sleep(0);
+ ATF_REQUIRE_MSG(atomic_load_64(&buf[0]) != 0, "No records found");
+
+ ATF_REQUIRE_MSG(ioctl(fd, KIODISABLE, 0) == 0,
+ "Unable to disable kcov");
+
+ common_tail(fd, buf);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_basic_pc);
+ATF_TC_BODY(kcov_basic_pc, tc)
+{
+ basic_test(KCOV_MODE_TRACE_PC);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_basic_cmp);
+ATF_TC_BODY(kcov_basic_cmp, tc)
+{
+ basic_test(KCOV_MODE_TRACE_CMP);
+}
+
+static void *
+thread_test_helper(void *ptr)
+{
+ uint64_t *buf = ptr;
+
+ atomic_store_64(&buf[0], 0);
+ sleep(0);
+ ATF_REQUIRE_MSG(atomic_load_64(&buf[0]) == 0,
+ "Records changed in blocked thread");
+
+ return (NULL);
+}
+
+static void
+thread_test(u_int mode)
+{
+ pthread_t thread;
+ uint64_t *buf;
+ int fd;
+
+ buf = common_head(&fd);
+
+ ATF_REQUIRE_MSG(ioctl(fd, KIOENABLE, mode) == 0,
+ "Unable to enable kcov %s",
+ mode < nitems(modes) ? modes[mode] : "unknown mode");
+
+ pthread_create(&thread, NULL, thread_test_helper, buf);
+ pthread_join(thread, NULL);
+
+ ATF_REQUIRE_MSG(ioctl(fd, KIODISABLE, 0) == 0,
+ "Unable to disable kcov");
+
+ common_tail(fd, buf);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_thread_pc);
+ATF_TC_BODY(kcov_thread_pc, tc)
+{
+ thread_test(KCOV_MODE_TRACE_PC);
+}
+
+ATF_TC_WITHOUT_HEAD(kcov_thread_cmp);
+ATF_TC_BODY(kcov_thread_cmp, tc)
+{
+ thread_test(KCOV_MODE_TRACE_CMP);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, kcov_bufsize);
+ ATF_TP_ADD_TC(tp, kcov_mmap);
+ ATF_TP_ADD_TC(tp, kcov_mmap_no_munmap);
+ ATF_TP_ADD_TC(tp, kcov_mmap_no_munmap_no_close);
+ ATF_TP_ADD_TC(tp, kcov_enable);
+ ATF_TP_ADD_TC(tp, kcov_enable_no_disable);
+ ATF_TP_ADD_TC(tp, kcov_enable_no_disable_no_close);
+ ATF_TP_ADD_TC(tp, kcov_mmap_enable_thread_close);
+ ATF_TP_ADD_TC(tp, kcov_basic_pc);
+ ATF_TP_ADD_TC(tp, kcov_basic_cmp);
+ ATF_TP_ADD_TC(tp, kcov_thread_pc);
+ ATF_TP_ADD_TC(tp, kcov_thread_cmp);
+ return (atf_no_error());
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 28, 3:29 AM (6 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
26273102
Default Alt Text
D14599.id52334.diff (31 KB)

Event Timeline