kcov: initial implementation

Implement a device (/dev/kcov) that exposes coverage info for the
kernel. This will be especially useful for coverage-based fuzzing (e.g.
syzkaller).

Exclude a lot of functions which would crash/otherwise interfere in the
coverage, including only processing coverage callbacks when not handling
an irq.
This commit is contained in:
Matheus
2026-10-06 11:05:58 -03:00
parent 111a8696d8
commit e4a4896b4b
14 changed files with 581 additions and 25 deletions
+4
View File
@@ -14,8 +14,12 @@ UACPIINCDIR=$(shell pwd)/io/acpi/uacpi/include
U80211INCDIR=$(shell pwd)/io/net/u80211/include U80211INCDIR=$(shell pwd)/io/net/u80211/include
U80211DRVINCDIR=$(shell pwd)/io/net/u80211_drv/include U80211DRVINCDIR=$(shell pwd)/io/net/u80211_drv/include
UACPIOPTS=-DUACPI_OVERRIDE_LIBC -DUACPI_KERNEL_INITIALIZATION -DUACPI_NATIVE_ALLOC_ZEROED UACPIOPTS=-DUACPI_OVERRIDE_LIBC -DUACPI_KERNEL_INITIALIZATION -DUACPI_NATIVE_ALLOC_ZEROED
KCOV_ENABLED=n
KERNELCONFIG=-DX86_64_ENABLE_E9 #-DSYSCALL_LOGGING #-DENABLE_PROFILING KERNELCONFIG=-DX86_64_ENABLE_E9 #-DSYSCALL_LOGGING #-DENABLE_PROFILING
CFLAGS=-g -ffreestanding -mcmodel=kernel -O2 -mno-red-zone -mgeneral-regs-only -mno-mmx -mno-sse -mno-sse2 -nostdlib -Wall -MMD -I "$(INCDIR)" -I "$(ARCHINCDIR)" -I "$(FLANTERMINCDIR)" -I "$(UACPIINCDIR)" -I "$(U80211INCDIR)" -I"$(U80211DRVINCDIR)" $(UACPIOPTS) $(PRINTFOPTS) $(KERNELCONFIG) $(FLANTERMOPTS) -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -std=gnu17 -Wno-error=implicit-function-declaration CFLAGS=-g -ffreestanding -mcmodel=kernel -O2 -mno-red-zone -mgeneral-regs-only -mno-mmx -mno-sse -mno-sse2 -nostdlib -Wall -MMD -I "$(INCDIR)" -I "$(ARCHINCDIR)" -I "$(FLANTERMINCDIR)" -I "$(UACPIINCDIR)" -I "$(U80211INCDIR)" -I"$(U80211DRVINCDIR)" $(UACPIOPTS) $(PRINTFOPTS) $(KERNELCONFIG) $(FLANTERMOPTS) -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -std=gnu17 -Wno-error=implicit-function-declaration
ifeq ($(KCOV_ENABLED),y)
CFLAGS += -DKCOV_ENABLED -fsanitize-coverage=trace-pc,trace-cmp
endif
ASFLAGS=-felf64 ASFLAGS=-felf64
DEPS=$(CSOURCES:.c=.d) DEPS=$(CSOURCES:.c=.d)
+1 -1
View File
@@ -116,7 +116,7 @@ void arch_extracontext_save(extracontext_t *context) {
} }
} }
void arch_extracontext_load(extracontext_t *context) { KCOV_DISABLED void arch_extracontext_load(extracontext_t *context) {
if (current_thread()->proc && current_thread()->proc->ldt) if (current_thread()->proc && current_thread()->proc->ldt)
arch_gdt_set_ldt(current_thread()->proc->ldt, 0xffff); arch_gdt_set_ldt(current_thread()->proc->ldt, 0xffff);
else else
+1 -1
View File
@@ -140,7 +140,7 @@ bool arch_cpu_smap_enabled(void) {
return has_smap; return has_smap;
} }
void arch_nmi_isr(context_t *ctx) { KCOV_DISABLED void arch_nmi_isr(context_t *ctx) {
// we are in an NMI context, running on IST0 // we are in an NMI context, running on IST0
// first load the proper gsbase to get access to cpu-local variables. // first load the proper gsbase to get access to cpu-local variables.
// the pointer to cpu's data is at the beggining of the stack so after the context struct. // the pointer to cpu's data is at the beggining of the stack so after the context struct.
+3 -2
View File
@@ -9,6 +9,7 @@
#include <kernel/alloc.h> #include <kernel/alloc.h>
#include <kernel/init.h> #include <kernel/init.h>
#include <arch/context.h> #include <arch/context.h>
#include <kernel/kcov.h>
#include <arch/prof.h> #include <arch/prof.h>
DEFINE_KERNEL_ARGUMENT(nosmp, bool); DEFINE_KERNEL_ARGUMENT(nosmp, bool);
@@ -21,12 +22,12 @@ static volatile struct limine_mp_request smprequest = {
size_t arch_smp_cpusawake = 1; size_t arch_smp_cpusawake = 1;
// for panic. if the nosmp argument is given to the kernel, this is what the APs will jump to // for panic. if the nosmp argument is given to the kernel, this is what the APs will jump to
static void cpuwakeuphalt(struct limine_mp_info *info) { KCOV_DISABLED static void cpuwakeuphalt(struct limine_mp_info *info) {
asm("cli"); asm("cli");
for (;;) CPU_HALT(); for (;;) CPU_HALT();
} }
static void cpuwakeup(struct limine_mp_info *info) { KCOV_DISABLED static void cpuwakeup(struct limine_mp_info *info) {
cpu_set((cpu_t *)info->extra_argument); cpu_set((cpu_t *)info->extra_argument);
arch_gdt_reload(); arch_gdt_reload();
+43
View File
@@ -0,0 +1,43 @@
#ifndef _KCOV_H
#define _KCOV_H
#include <stdint.h>
#include <stddef.h>
#include <mutex.h>
#ifdef KCOV_ENABLED
#define KCOV_DISABLED __attribute__((no_sanitize_coverage))
#else
#define KCOV_DISABLED
#endif
#define KCOV_MODE_DISABLED 0
#define KCOV_MODE_PC 1
#define KCOV_MODE_CMP 2
#define KCOV_CMP_FLAGS_CONST 1
#define KCOV_CMP_FLAGS_8BIT 0
#define KCOV_CMP_FLAGS_16BIT 2
#define KCOV_CMP_FLAGS_32BIT 4
#define KCOV_CMP_FLAGS_64BIT 6
typedef struct {
unsigned int mode;
mutex_t mapping_mutex;
unsigned int entry_count; // protected by mutex
uint64_t *buffer; // pointer protected by mutex
size_t mappings; // protected by mutex
unsigned int refcount;
unsigned int minor;
void *vnode_handle;
} kcov_t;
void kcov_publish_pc(uint64_t pc);
void kcov_publish_cmp(uint64_t pc, uint64_t v1, uint64_t v2, uint64_t flags);
int kcov_set_buffer_size(unsigned int entry_count);
int kcov_set_mode(unsigned int mode);
void kcov_hold(kcov_t *kcov);
void kcov_release(kcov_t *kcov);
void kcov_free_buffer(kcov_t *kcov);
#endif
+4
View File
@@ -6,6 +6,7 @@
#include <kernel/abi.h> #include <kernel/abi.h>
#include <kernel/signal.h> #include <kernel/signal.h>
#include <kernel/event.h> #include <kernel/event.h>
#include <kernel/kcov.h>
#define THREAD_FLAGS_QUEUED 1 #define THREAD_FLAGS_QUEUED 1
#define THREAD_FLAGS_RUNNING 2 #define THREAD_FLAGS_RUNNING 2
@@ -31,6 +32,9 @@ typedef struct thread_t {
struct cpu_t *cpu; struct cpu_t *cpu;
struct cpu_t *cputarget; struct cpu_t *cputarget;
struct cpu_t *last_cpu; struct cpu_t *last_cpu;
#ifdef KCOV_ENABLED
kcov_t *kcov;
#endif
context_t context; context_t context;
extracontext_t extracontext; extracontext_t extracontext;
void *kernelstack; void *kernelstack;
+13 -7
View File
@@ -122,41 +122,47 @@ void arch_cpu_user_access_end(void);
bool arch_cpu_smep_enabled(void); bool arch_cpu_smep_enabled(void);
bool arch_cpu_smap_enabled(void); bool arch_cpu_smap_enabled(void);
static inline __attribute__((always_inline)) thread_t *current_thread(void) { KCOV_DISABLED static inline __attribute__((always_inline)) thread_t *current_thread(void) {
thread_t *thread; thread_t *thread;
asm volatile ("mov %%gs:0, %%rax" : "=a"(thread) : : "memory"); asm volatile ("mov %%gs:0, %%rax" : "=a"(thread) : : "memory");
return thread; return thread;
} }
static inline __attribute__((always_inline)) cpu_t *current_cpu(void) { KCOV_DISABLED static inline __attribute__((always_inline)) cpu_t *current_cpu(void) {
cpu_t *cpu; cpu_t *cpu;
asm volatile ("mov %%gs:8, %%rax" : "=a"(cpu) : : "memory"); asm volatile ("mov %%gs:8, %%rax" : "=a"(cpu) : : "memory");
return cpu; return cpu;
} }
static inline __attribute__((always_inline)) mm_context_t *current_mm_context(void) { KCOV_DISABLED static inline __attribute__((always_inline)) mm_context_t *current_mm_context(void) {
mm_context_t *context; mm_context_t *context;
asm volatile ("mov %%gs:16, %%rax" : "=a"(context) : : "memory"); asm volatile ("mov %%gs:16, %%rax" : "=a"(context) : : "memory");
return context; return context;
} }
static inline __attribute__((always_inline)) void set_current_mm_context(mm_context_t *context) { KCOV_DISABLED static inline __attribute__((always_inline)) void set_current_mm_context(mm_context_t *context) {
asm volatile ("mov %%rax, %%gs:16" : : "a"(context) : "memory"); asm volatile ("mov %%rax, %%gs:16" : : "a"(context) : "memory");
} }
static inline __attribute__((always_inline)) long current_cpu_internal_id(void) { KCOV_DISABLED static inline __attribute__((always_inline)) long current_cpu_internal_id(void) {
long id; long id;
asm volatile ("mov %%gs:32, %%rax" : "=a"(id) : : "memory"); asm volatile ("mov %%gs:32, %%rax" : "=a"(id) : : "memory");
return id; return id;
} }
static inline __attribute__((always_inline)) long current_cpu_id(void) { KCOV_DISABLED static inline __attribute__((always_inline)) long current_cpu_id(void) {
long id; long id;
asm volatile ("mov %%gs:24, %%rax" : "=a"(id) : : "memory"); asm volatile ("mov %%gs:24, %%rax" : "=a"(id) : : "memory");
return id; return id;
} }
static inline void cpu_set(cpu_t *ptr) { KCOV_DISABLED static inline __attribute__((always_inline)) unsigned long current_irq_depth(void) {
long depth;
asm volatile ("mov %%gs:48, %%rax" : "=a"(depth) : : "memory");
return depth;
}
KCOV_DISABLED static inline __attribute__((always_inline)) void cpu_set(cpu_t *ptr) {
ptr->self = ptr; ptr->self = ptr;
wrmsr(MSR_GSBASE, (uint64_t)ptr); wrmsr(MSR_GSBASE, (uint64_t)ptr);
} }
+3 -2
View File
@@ -2,6 +2,7 @@
#define _MSR_H #define _MSR_H
#include <stdint.h> #include <stdint.h>
#include <kernel/kcov.h>
#define MSR_IA32APICBASE 0x1B #define MSR_IA32APICBASE 0x1B
#define MSR_PERF_FIXED_CTR2 0x30b #define MSR_PERF_FIXED_CTR2 0x30b
@@ -20,13 +21,13 @@
#define MSR_KVM_SYSTEM_TIME_NEW 0x4b564d01 #define MSR_KVM_SYSTEM_TIME_NEW 0x4b564d01
static inline uint64_t rdmsr(uint32_t which) { KCOV_DISABLED static inline uint64_t rdmsr(uint32_t which) {
uint64_t low,high; uint64_t low,high;
asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(which)); asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(which));
return (high << 32) | low; return (high << 32) | low;
} }
static inline void wrmsr(uint32_t which, uint64_t value) { KCOV_DISABLED static inline void wrmsr(uint32_t which, uint64_t value) {
uint32_t low = value & 0xFFFFFFFF; uint32_t low = value & 0xFFFFFFFF;
uint32_t high = (value >> 32) & 0xFFFFFFFF; uint32_t high = (value >> 32) & 0xFFFFFFFF;
asm volatile("wrmsr" : : "a"(low), "d"(high), "c"(which)); asm volatile("wrmsr" : : "a"(low), "d"(high), "c"(which));
+1 -1
View File
@@ -20,7 +20,7 @@ DEFINE_KERNEL_ARGUMENT(rootfs, char *);
DEFINE_KERNEL_ARGUMENT(initrd, bool); DEFINE_KERNEL_ARGUMENT(initrd, bool);
DEFINE_KERNEL_ARGUMENT(root_wait, bool); DEFINE_KERNEL_ARGUMENT(root_wait, bool);
void kernel_entry() { KCOV_DISABLED void kernel_entry() {
cpu_set(&bsp_cpu); cpu_set(&bsp_cpu);
#ifdef TERM_EARLY_INIT #ifdef TERM_EARLY_INIT
+14 -11
View File
@@ -2,6 +2,7 @@
#include <logging.h> #include <logging.h>
#include <arch/cpu.h> #include <arch/cpu.h>
#include <arch/context.h> #include <arch/context.h>
#include <kernel/kcov.h>
#include <panic.h> #include <panic.h>
void arch_interrupt_disable(); void arch_interrupt_disable();
@@ -9,7 +10,7 @@ void arch_interrupt_enable();
#define DOPENDING_SAVE() arch_context_saveandcall(dopending, NULL, NULL) #define DOPENDING_SAVE() arch_context_saveandcall(dopending, NULL, NULL)
static void removefromqueue(isr_t *isr) { KCOV_DISABLED static void removefromqueue(isr_t *isr) {
if (isr->prev) if (isr->prev)
isr->prev->next = isr->next; isr->prev->next = isr->next;
else else
@@ -19,7 +20,7 @@ static void removefromqueue(isr_t *isr) {
isr->next->prev = isr->prev; isr->next->prev = isr->prev;
} }
static void insertinqueue(isr_t *isr) { KCOV_DISABLED static void insertinqueue(isr_t *isr) {
if (isr->pending) if (isr->pending)
return; return;
@@ -29,7 +30,7 @@ static void insertinqueue(isr_t *isr) {
current_cpu()->isrqueue = isr; current_cpu()->isrqueue = isr;
} }
static void runisr(isr_t *isr, context_t *ctx) { KCOV_DISABLED static void runisr(isr_t *isr, context_t *ctx) {
long oldipl = -1; long oldipl = -1;
if (isr->priority != IPL_IGNORE) if (isr->priority != IPL_IGNORE)
oldipl = interrupt_raiseipl(isr->priority); oldipl = interrupt_raiseipl(isr->priority);
@@ -41,7 +42,7 @@ static void runisr(isr_t *isr, context_t *ctx) {
interrupt_loweripl(oldipl); interrupt_loweripl(oldipl);
} }
static void dopending(context_t *ctx, void *) { KCOV_DISABLED static void dopending(context_t *ctx, void *) {
arch_interrupt_disable(); arch_interrupt_disable();
bool entrystatus = current_cpu()->intstatus; bool entrystatus = current_cpu()->intstatus;
current_cpu()->intstatus = false; current_cpu()->intstatus = false;
@@ -100,7 +101,7 @@ __attribute__((no_caller_saved_registers)) void sched_userspacecheck(context_t *
uint64_t syscallret, uint64_t syscallret,
uint64_t syscallerrno); uint64_t syscallerrno);
void interrupt_isr(int vec, context_t *ctx) { KCOV_DISABLED void interrupt_isr(int vec, context_t *ctx) {
isr_t *isr = &current_cpu()->isr[vec]; isr_t *isr = &current_cpu()->isr[vec];
current_cpu()->intstatus = false; current_cpu()->intstatus = false;
@@ -133,7 +134,7 @@ void interrupt_isr(int vec, context_t *ctx) {
current_cpu()->intstatus = ARCH_CONTEXT_INTSTATUS(ctx); current_cpu()->intstatus = ARCH_CONTEXT_INTSTATUS(ctx);
} }
void interrupt_raise(isr_t *isr) { KCOV_DISABLED void interrupt_raise(isr_t *isr) {
bool entrystate = interrupt_set(false); bool entrystate = interrupt_set(false);
if (isr->pending) if (isr->pending)
@@ -146,7 +147,8 @@ void interrupt_raise(isr_t *isr) {
interrupt_set(entrystate); interrupt_set(entrystate);
} }
void interrupt_register(int vector, void (*func)(isr_t *self, context_t *ctx), void (*eoi)(isr_t *self), long priority) { KCOV_DISABLED void interrupt_register(int vector, void (*func)(isr_t *self, context_t *ctx),
void (*eoi)(isr_t *self), long priority) {
bool intstatus = interrupt_set(false); bool intstatus = interrupt_set(false);
isr_t *isr = &current_cpu()->isr[vector]; isr_t *isr = &current_cpu()->isr[vector];
@@ -159,7 +161,8 @@ void interrupt_register(int vector, void (*func)(isr_t *self, context_t *ctx), v
interrupt_set(intstatus); interrupt_set(intstatus);
} }
isr_t *interrupt_allocate(void (*func)(isr_t *self, context_t *ctx), void (*eoi)(isr_t *self), long priority) { KCOV_DISABLED isr_t *interrupt_allocate(void (*func)(isr_t *self, context_t *ctx), void (*eoi)(isr_t *self),
long priority) {
bool intstatus = interrupt_set(false); bool intstatus = interrupt_set(false);
isr_t *isr = NULL; isr_t *isr = NULL;
@@ -175,7 +178,7 @@ isr_t *interrupt_allocate(void (*func)(isr_t *self, context_t *ctx), void (*eoi)
return isr; return isr;
} }
long interrupt_loweripl(long ipl) { KCOV_DISABLED long interrupt_loweripl(long ipl) {
bool oldintstatus = interrupt_set(false); bool oldintstatus = interrupt_set(false);
long oldipl = current_cpu()->ipl; long oldipl = current_cpu()->ipl;
if (oldipl < ipl) if (oldipl < ipl)
@@ -185,7 +188,7 @@ long interrupt_loweripl(long ipl) {
return oldipl; return oldipl;
} }
long interrupt_raiseipl(long ipl) { KCOV_DISABLED long interrupt_raiseipl(long ipl) {
bool oldintstatus = interrupt_set(false); bool oldintstatus = interrupt_set(false);
long oldipl = current_cpu()->ipl; long oldipl = current_cpu()->ipl;
@@ -197,7 +200,7 @@ long interrupt_raiseipl(long ipl) {
return oldipl; return oldipl;
} }
bool interrupt_set(bool status) { KCOV_DISABLED bool interrupt_set(bool status) {
arch_interrupt_disable(); arch_interrupt_disable();
bool old = current_cpu()->intstatus; bool old = current_cpu()->intstatus;
+129
View File
@@ -0,0 +1,129 @@
#include <kernel/kcov.h>
#include <kernel/alloc.h>
#include <kernel/devfs.h>
#include <kernel/init.h>
#include <kernel/usercopy.h>
#include <arch/cpu.h>
#include <logging.h>
#ifdef KCOV_ENABLED
#define MAX_ENTRIES (1 << 24)
KCOV_DISABLED void kcov_publish_pc(uint64_t pc) {
kcov_t *kcov = current_thread()->kcov;
uint64_t entry = kcov->buffer[0];
if (entry >= kcov->entry_count)
return;
kcov->buffer[entry + 1] = pc;
kcov->buffer[0] = entry + 1;
__atomic_thread_fence(__ATOMIC_RELEASE);
}
KCOV_DISABLED void kcov_publish_cmp(uint64_t pc, uint64_t v1, uint64_t v2, uint64_t flags) {
kcov_t *kcov = current_thread()->kcov;
uint64_t entry = kcov->buffer[0];
if (entry >= kcov->entry_count / 4)
return;
kcov->buffer[entry * 4 + 1] = flags;
kcov->buffer[entry * 4 + 2] = v1;
kcov->buffer[entry * 4 + 3] = v2;
kcov->buffer[entry * 4 + 4] = pc;
kcov->buffer[0] = entry + 1;
__atomic_thread_fence(__ATOMIC_RELEASE);
}
// expects the mutex to be held OR this to be the last reference
KCOV_DISABLED void kcov_free_buffer(kcov_t *kcov) {
if (kcov->buffer == NULL)
return;
mm_unmap(kcov->buffer, sizeof(uint64_t) * (kcov->entry_count + 1), 0);
kcov->entry_count = 0;
kcov->buffer = NULL;
}
KCOV_DISABLED static int resize_buffer(kcov_t *kcov, unsigned int entry_count) {
if (entry_count == 0) {
kcov_free_buffer(kcov);
return 0;
}
void *tmp = mm_map(NULL, sizeof(uint64_t) * (entry_count + 1), MM_RANGE_FLAGS_ALLOCATE,
ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_NOEXEC, NULL);
if (tmp == NULL)
return ENOMEM;
void *old = kcov->buffer;
size_t old_entries = kcov->entry_count;
kcov->buffer = tmp;
kcov->entry_count = entry_count;
mm_unmap(old, (old_entries + 1) * sizeof(uint64_t), 0);
return 0;
}
KCOV_DISABLED int kcov_set_mode(unsigned int mode) {
kcov_t *kcov = current_thread()->kcov;
__assert(kcov);
if (mode != KCOV_MODE_DISABLED &&
mode != KCOV_MODE_PC &&
mode != KCOV_MODE_CMP)
return EINVAL;
if (mode == KCOV_MODE_DISABLED && kcov->mode == KCOV_MODE_DISABLED)
return EINVAL;
if (mode != KCOV_MODE_DISABLED && kcov->mode != KCOV_MODE_DISABLED)
return EINVAL;
if (kcov->buffer == NULL)
return EINVAL;
kcov->mode = mode;
return 0;
}
KCOV_DISABLED int kcov_set_buffer_size(unsigned int entry_count) {
if (entry_count < 0 || entry_count > MAX_ENTRIES)
return EINVAL;
kcov_t *kcov = current_thread()->kcov;
__assert(kcov);
MUTEX_ACQUIRE(&kcov->mapping_mutex);
int error = 0;
if (kcov->mappings || kcov->mode != KCOV_MODE_DISABLED) {
error = EBUSY;
goto cleanup;
}
if (kcov->buffer) {
error = resize_buffer(kcov, entry_count);
goto cleanup;
}
if (entry_count == 0)
goto cleanup;
kcov->buffer = mm_map(NULL, sizeof(uint64_t) * (entry_count + 1), MM_RANGE_FLAGS_ALLOCATE,
ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_NOEXEC, NULL);
if (kcov->buffer == NULL) {
error = ENOMEM;
goto cleanup;
}
kcov->entry_count = entry_count;
cleanup:
MUTEX_RELEASE(&kcov->mapping_mutex);
return error;
}
#endif
+255
View File
@@ -0,0 +1,255 @@
#ifdef KCOV_ENABLED
#include <kernel/kcov.h>
#include <kernel/alloc.h>
#include <kernel/devfs.h>
#include <kernel/init.h>
#include <kernel/usercopy.h>
#include <arch/cpu.h>
#include <logging.h>
#define KCOV_MAX 256
static MUTEX_DEFINE(map_mutex);
static kcov_t *kcov_map[KCOV_MAX];
static int allocate_kcov(int *out) {
kcov_t *kcov = alloc(sizeof(kcov_t));
if (kcov == NULL)
return ENOMEM;
MUTEX_INIT(&kcov->mapping_mutex);
__atomic_store_n(&kcov->refcount, 1, __ATOMIC_RELAXED);
MUTEX_ACQUIRE(&map_mutex);
int minor;
for (minor = 0; minor < KCOV_MAX; ++minor) {
if (kcov_map[minor] == NULL) {
kcov_map[minor] = kcov;
break;
}
}
if (minor < KCOV_MAX)
kcov->minor = minor;
MUTEX_RELEASE(&map_mutex);
if (minor == KCOV_MAX) {
free(kcov);
return EAGAIN;
}
*out = minor;
return 0;
}
static void free_kcov(int minor) {
MUTEX_ACQUIRE(&map_mutex);
kcov_t *kcov = kcov_map[minor];
kcov_map[minor] = NULL;
MUTEX_RELEASE(&map_mutex);
__assert(kcov);
free(kcov);
}
void kcov_hold(kcov_t *kcov) {
__atomic_add_fetch(&kcov->refcount, 1, __ATOMIC_RELAXED);
}
void kcov_release(kcov_t *kcov) {
if (__atomic_sub_fetch(&kcov->refcount, 1, __ATOMIC_RELAXED) == 0) {
__atomic_thread_fence(__ATOMIC_ACQ_REL);
// this is safe to do as we are the only ones holding a reference
kcov_free_buffer(kcov);
free_kcov(kcov->minor);
}
}
static kcov_t *get_kcov(int minor) {
if (minor >= KCOV_MAX)
return NULL;
MUTEX_ACQUIRE(&map_mutex);
kcov_t *kcov = kcov_map[minor];
MUTEX_RELEASE(&map_mutex);
return kcov;
}
static devops_t ops;
static int create_node_for_kcov(kcov_t *kcov, int minor, vnode_t **vnodep) {
vnode_t *vnode;
int error = devfs_register_anonymous(&ops, V_TYPE_CHDEV, DEV_MAJOR_KCOV, minor,
0, NULL, &vnode);
if (error)
return error;
kcov->vnode_handle = vnode;
devfs_remove_anonymous(DEV_MAJOR_KCOV, minor);
// a kcov will have two references initially:
// - one for the thread holding it
// - one for the vnode
kcov_hold(kcov);
*vnodep = vnode;
return 0;
}
static int open(int oldminor, vnode_t **vnodep, int flags) {
vnode_t *old = *vnodep;
if (oldminor != KCOV_MAX)
return ENODEV;
// if the thread already has a kcov attached, return it
if (current_thread()->kcov) {
kcov_t *kcov = current_thread()->kcov;
int error = create_node_for_kcov(kcov, kcov->minor, vnodep);
if (error)
return error;
VOP_RELEASE(old);
return 0;
}
int minor;
int error = allocate_kcov(&minor);
if (error)
return error;
kcov_t *kcov = get_kcov(minor);
error = create_node_for_kcov(kcov, minor, vnodep);
if (error) {
kcov_release(kcov);
return error;
}
current_thread()->kcov = kcov;
VOP_RELEASE(old);
return 0;
}
#define IOCTL_REQUEST_START 0xF1177000
#define IOCTL_REQUEST_STOP 0xF1177100
#define IOCTL_REQUEST_SET_SIZE 0xF1177200
static int ioctl(int minor, unsigned long request, void *arg, int *result, cred_t *cred) {
kcov_t *kcov = current_thread()->kcov;
if (kcov == NULL || kcov->minor != minor)
return EINVAL;
switch (request) {
case IOCTL_REQUEST_SET_SIZE: {
int size;
int error = USERCOPY_POSSIBLY_FROM_USER(&size, arg, sizeof(size));
if (error)
return error;
return kcov_set_buffer_size(size);
}
case IOCTL_REQUEST_START: {
if (kcov->mode != KCOV_MODE_DISABLED)
return EBUSY;
if (kcov->buffer == NULL)
return EINVAL;
int mode;
int error = USERCOPY_POSSIBLY_FROM_USER(&mode, arg, sizeof(mode));
if (error)
return error;
return kcov_set_mode(mode);
}
case IOCTL_REQUEST_STOP:
return kcov_set_mode(KCOV_MODE_DISABLED);
default:
return ENOTTY;
}
}
static int mmap(int minor, void *addr, uintmax_t offset, int flags) {
kcov_t *kcov = get_kcov(minor);
__assert(kcov); // mapped kcov that doesn't exist...?
if (offset % PAGE_SIZE)
return EINVAL;
// why would anyone do this?
if ((flags & V_FFLAGS_SHARED) == 0)
return EINVAL;
MUTEX_ACQUIRE(&kcov->mapping_mutex);
int error = EINVAL;
if (kcov->buffer == NULL)
goto cleanup;
size_t start_entry = offset / sizeof(uint64_t);
if (kcov->entry_count < start_entry)
goto cleanup;
void *phys = arch_mmu_getphysical(current_mm_context()->pagetable,
&kcov->buffer[start_entry]);
__assert(phys);
// this code is called holding the mm lock, so this is the way of doing this
if (arch_mmu_map(current_mm_context()->pagetable, phys, addr,
mm_vnode_flags_to_mmu_flags(flags)) == false) {
error = ENOMEM;
goto cleanup;
}
++kcov->mappings;
error = 0;
cleanup:
MUTEX_RELEASE(&kcov->mapping_mutex);
return error;
}
static int munmap(int minor, void *addr, uintmax_t offset, int flags) {
kcov_t *kcov = get_kcov(minor);
__assert(kcov); // mapped kcov that doesn't exist...?
if (offset % PAGE_SIZE)
return EINVAL;
// why would anyone do this?
if ((flags & V_FFLAGS_SHARED) == 0)
return EINVAL;
// this code is called holding the mm lock, so this is the way of doing this
arch_mmu_unmap(current_mm_context()->pagetable, addr);
arch_mmu_invalidate_range(addr, PAGE_SIZE);
MUTEX_ACQUIRE(&kcov->mapping_mutex);
--kcov->mappings;
MUTEX_RELEASE(&kcov->mapping_mutex);
return 0;
}
static void inactive(int minor) {
kcov_t *kcov = get_kcov(minor);
if (kcov == NULL)
return;
kcov_release(kcov);
}
static devops_t ops = {
.open = open,
.inactive = inactive,
.mmap = mmap,
.munmap = munmap,
.ioctl = ioctl
};
static void kcov_init(void) {
__assert(devfs_register(&ops, "kcov", V_TYPE_CHDEV, DEV_MAJOR_KCOV, KCOV_MAX, 0600,
NULL) == 0);
}
INIT_ROUTINE_DEFINE(kcov, INIT_ROUTINE_FLAGS_NONE, kcov_init, devfs);
#endif
+101
View File
@@ -0,0 +1,101 @@
#include <kernel/kcov.h>
#include <arch/cpu.h>
#ifdef KCOV_ENABLED
KCOV_DISABLED void __sanitizer_cov_trace_pc(void) {
if (current_thread() == NULL || current_thread()->kcov == NULL ||
current_thread()->kcov->mode != KCOV_MODE_PC)
return;
if (current_irq_depth() > 0)
return;
uint64_t pc = (uint64_t)__builtin_return_address(0);
kcov_publish_pc(pc);
}
KCOV_DISABLED static void publish_cmp(uint64_t pc, uint64_t v1, uint64_t v2, uint64_t flags) {
if (current_thread() == NULL || current_thread()->kcov == NULL ||
current_thread()->kcov->mode != KCOV_MODE_CMP)
return;
if (current_irq_depth() > 0)
return;
kcov_publish_cmp(pc, v1, v2, flags);
}
KCOV_DISABLED void __sanitizer_cov_trace_const_cmp1(uint8_t a1, uint8_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_CONST | KCOV_CMP_FLAGS_8BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_const_cmp2(uint16_t a1, uint16_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_CONST | KCOV_CMP_FLAGS_16BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_const_cmp4(uint32_t a1, uint32_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_CONST | KCOV_CMP_FLAGS_32BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_const_cmp8(uint64_t a1, uint64_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_CONST | KCOV_CMP_FLAGS_64BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_cmp1(uint8_t a1, uint8_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_8BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_cmp2(uint16_t a1, uint16_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_16BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_cmp4(uint32_t a1, uint32_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_32BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_cmp8(uint64_t a1, uint64_t a2) {
uint64_t pc = (uint64_t)__builtin_return_address(0);
publish_cmp(pc, a1, a2, KCOV_CMP_FLAGS_64BIT);
}
KCOV_DISABLED void __sanitizer_cov_trace_switch(uint64_t v, uint64_t *cases) {
if (current_thread() == NULL || current_thread()->kcov == NULL ||
current_thread()->kcov->mode != KCOV_MODE_CMP)
return;
if (current_irq_depth() > 0)
return;
uint64_t pc = (uint64_t)__builtin_return_address(0);
uint64_t flags = KCOV_CMP_FLAGS_CONST;
switch (cases[1]) {
case 8:
flags |= KCOV_CMP_FLAGS_8BIT;
break;
case 16:
flags |= KCOV_CMP_FLAGS_16BIT;
break;
case 32:
flags |= KCOV_CMP_FLAGS_32BIT;
break;
case 64:
flags |= KCOV_CMP_FLAGS_64BIT;
break;
default:
return;
}
for (int i = 0; i < cases[0]; ++i)
kcov_publish_cmp(pc, cases[2 + i], v, flags);
}
#endif
+9
View File
@@ -57,6 +57,10 @@ thread_t *sched_newthread(void *ip, size_t kstacksize, int nice, proc_t *proc, v
} }
void sched_destroythread(thread_t *thread) { void sched_destroythread(thread_t *thread) {
#ifdef KCOV_ENABLED
if (thread->kcov)
kcov_release(thread->kcov);
#endif
mm_unmap(thread->kernelstack, thread->kernelstacksize, 0); mm_unmap(thread->kernelstack, thread->kernelstacksize, 0);
arch_extracontext_free(&thread->extracontext); arch_extracontext_free(&thread->extracontext);
slab_free(thread_cache, thread); slab_free(thread_cache, thread);
@@ -101,6 +105,11 @@ __attribute__((noreturn)) void sched_threadexit() {
PROC_RELEASE(proc); PROC_RELEASE(proc);
} }
#ifdef KCOV_ENABLED
if (current_thread()->kcov)
kcov_set_mode(KCOV_MODE_DISABLED);
#endif
interrupt_set(false); interrupt_set(false);
arch_context_saveandcall(threadexit_internal, current_cpu()->schedulerstack, NULL); arch_context_saveandcall(threadexit_internal, current_cpu()->schedulerstack, NULL);
__builtin_unreachable(); __builtin_unreachable();