Files
Astral/kernel-src/sys/kcov.c
T
Matheus e4a4896b4b 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.
2026-10-06 11:51:02 -03:00

130 lines
3.0 KiB
C

#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