Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F137910522
D14599.id52334.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
31 KB
Referenced Files
None
Subscribers
None
D14599.id52334.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D14599: Implement kernel code coverage (kcov)
Attached
Detach File
Event Timeline
Log In to Comment