Add support for trace type selection in kcov(4)

Allow to specify mode in KCOV_IOC_ENABLE synchronizing the functionality
with Linux, FreeBSD and OpenBSD. As a NetBSD (and OpenBSD) specific of
the ioctl(2) interface, the mode argument has to be specified as &value
rather than value.

There are 3 modes available:
 1. KCOV_MODE_NONE       -- no trace specified, useful for testing purposes
 2. KCOV_MODE_TRACE_PC   -- trace the kernel program counter
 3. KCOV_MODE_TRACE_CMP  -- trace comparison instructions and switch statements

Adapt the ATF tests and documentation for new API.

The KCOV_MODE_TRACE_CMP mode is implemented but still awaits for the
GCC 8.x upgrade or selection of Clang/LLVM as the kernel compiler.

Obtained from OpenBSD and adapted for NetBSD by myself.
This commit is contained in:
kamil 2019-03-10 17:51:00 +00:00
parent ffd738cfac
commit f43e07b7f7
4 changed files with 293 additions and 23 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: kcov.4,v 1.3 2019/03/10 12:54:39 kamil Exp $
.\" $NetBSD: kcov.4,v 1.4 2019/03/10 17:51:00 kamil Exp $
.\"
.\" Copyright (c) 2018 Anton Lindqvist <anton@openbsd.org>
.\"
@ -82,8 +82,46 @@ entries.
Note that kcov_int_t is volatile.
The first entry contains the number of entries in the array,
excluding the first entry.
.It Dv KCOV_IOC_ENABLE Fa void
.It Dv KCOV_IOC_ENABLE Fa int *mode
Enable code coverage tracing for the current thread.
The
.Fa mode
must be one of the following:
.Bl -ohang
.It Dv KCOV_MODE_NONE
No trace selected.
This option is useful for testing the
.Nm
device.
.It Dv KCOV_MODE_TRACE_PC
Trace the kernel program counter.
.It Dv KCOV_MODE_TRACE_CMP
Trace comparison instructions and switch statements.
For switch statements, the number of traced comparison instructions is equal to
the number of switch cases.
Each traced comparison instruction is represented by 4 entries in the coverage
buffer:
.Bl -enum
.It
A mask where the least significant bit is set if one of the comparison operands
is a compile-time constant, which is always true for switch statements.
The remaining bits represents the log2 size of the operands, ranging from 0 to
3.
.It
First comparison operand.
For switch statements, this operand corresponds to the case value.
.It
Second comparison operand.
For switch statements, this operand corresponds to the value passed to switch.
.It
Kernel program counter where the comparison instruction took place.
.El
.Pp
In this mode, the first entry in the coverage buffer reflects the number of
traced comparison instructions.
Thus, the effective number of entries in the coverage buffer is given by
multiplying the first entry by 4.
.El
.It Dv KCOV_IOC_DISABLE Fa void
Disable code coverage tracing for the current thread.
.El
@ -119,6 +157,7 @@ main(void)
kcov_int_t *cover, i, n;
uint64_t size = 1024 * 100;
int fd;
int mode;
fd = open("/dev/kcov", O_RDWR);
if (fd == -1)
@ -129,7 +168,8 @@ main(void)
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
if (cover == MAP_FAILED)
err(1, "mmap");
if (ioctl(fd, KCOV_IOC_ENABLE) == -1)
mode = KCOV_MODE_TRACE_PC;
if (ioctl(fd, KCOV_IOC_ENABLE, &mode) == -1)
err(1, "ioctl: KCOV_IOC_ENABLE");
KCOV_STORE(cover[0], 0);
read(-1, NULL, 0); /* syscall paths to be traced */

View File

@ -1,4 +1,4 @@
/* $NetBSD: subr_kcov.c,v 1.4 2019/03/10 12:54:39 kamil Exp $ */
/* $NetBSD: subr_kcov.c,v 1.5 2019/03/10 17:51:00 kamil Exp $ */
/*
* Copyright (c) 2019 The NetBSD Foundation, Inc.
@ -50,6 +50,9 @@
#define KCOV_BUF_MAX_ENTRIES (256 << 10)
#define KCOV_CMP_CONST 1
#define KCOV_CMP_SIZE(x) ((x) << 1)
static dev_type_open(kcov_open);
const struct cdevsw kcov_cdevsw = {
@ -107,6 +110,7 @@ typedef struct kcov_desc {
struct uvm_object *uobj;
size_t bufnent;
size_t bufsize;
int mode;
bool enabled;
bool lwpfree;
} kcov_t;
@ -231,6 +235,7 @@ static int
kcov_fops_ioctl(file_t *fp, u_long cmd, void *addr)
{
int error = 0;
int mode;
kcov_t *kd;
kd = fp->f_data;
@ -259,6 +264,20 @@ kcov_fops_ioctl(file_t *fp, u_long cmd, void *addr)
error = ENOBUFS;
break;
}
mode = *((int *)addr);
switch (mode) {
case KCOV_MODE_NONE:
case KCOV_MODE_TRACE_PC:
case KCOV_MODE_TRACE_CMP:
kd->mode = mode;
break;
default:
error = EINVAL;
}
if (error)
break;
lwp_setspecific(kcov_lwp_key, kd);
kd->enabled = true;
break;
@ -356,6 +375,11 @@ __sanitizer_cov_trace_pc(void)
return;
}
if (kd->mode != KCOV_MODE_TRACE_PC) {
/* PC tracing mode not enabled */
return;
}
idx = KCOV_LOAD(kd->buf[0]);
if (idx < kd->bufnent) {
KCOV_STORE(kd->buf[idx+1],
@ -364,6 +388,163 @@ __sanitizer_cov_trace_pc(void)
}
}
static void
trace_cmp(uint64_t type, uint64_t arg1, uint64_t arg2, intptr_t pc)
{
extern int cold;
uint64_t idx;
kcov_t *kd;
if (__predict_false(cold)) {
/* Do not trace during boot. */
return;
}
if (in_interrupt()) {
/* Do not trace in interrupts. */
return;
}
kd = lwp_getspecific(kcov_lwp_key);
if (__predict_true(kd == NULL)) {
/* Not traced. */
return;
}
if (!kd->enabled) {
/* Tracing not enabled */
return;
}
if (kd->mode != KCOV_MODE_TRACE_CMP) {
/* PC tracing mode not enabled */
return;
}
idx = KCOV_LOAD(kd->buf[0]);
if ((idx * 4 + 4) <= kd->bufnent) {
KCOV_STORE(kd->buf[idx * 4 + 1], type);
KCOV_STORE(kd->buf[idx * 4 + 2], arg1);
KCOV_STORE(kd->buf[idx * 4 + 3], arg2);
KCOV_STORE(kd->buf[idx * 4 + 4], pc);
KCOV_STORE(kd->buf[0], idx + 1);
}
}
void __sanitizer_cov_trace_cmp1(uint8_t arg1, uint8_t arg2);
void
__sanitizer_cov_trace_cmp1(uint8_t arg1, uint8_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(0), arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_cmp2(uint16_t arg1, uint16_t arg2);
void
__sanitizer_cov_trace_cmp2(uint16_t arg1, uint16_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(1), arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2);
void
__sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(2), arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2);
void
__sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(3), arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_const_cmp1(uint8_t arg1, uint8_t arg2);
void
__sanitizer_cov_trace_const_cmp1(uint8_t arg1, uint8_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(0) | KCOV_CMP_CONST, arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_const_cmp2(uint16_t arg1, uint16_t arg2);
void
__sanitizer_cov_trace_const_cmp2(uint16_t arg1, uint16_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(1) | KCOV_CMP_CONST, arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_const_cmp4(uint32_t arg1, uint32_t arg2);
void
__sanitizer_cov_trace_const_cmp4(uint32_t arg1, uint32_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(2) | KCOV_CMP_CONST, arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_const_cmp8(uint64_t arg1, uint64_t arg2);
void
__sanitizer_cov_trace_const_cmp8(uint64_t arg1, uint64_t arg2)
{
trace_cmp(KCOV_CMP_SIZE(3) | KCOV_CMP_CONST, arg1, arg2,
(intptr_t)__builtin_return_address(0));
}
void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases);
void
__sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases)
{
uint64_t i, nbits, ncases, type;
intptr_t pc;
pc = (intptr_t)__builtin_return_address(0);
ncases = cases[0];
nbits = cases[1];
switch (nbits) {
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;
}
type |= KCOV_CMP_CONST;
for (i = 0; i < ncases; i++)
trace_cmp(type, cases[i + 2], val, pc);
}
/* -------------------------------------------------------------------------- */
MODULE(MODULE_CLASS_ANY, kcov, NULL);

View File

@ -1,4 +1,4 @@
/* $NetBSD: kcov.h,v 1.3 2019/02/25 13:19:14 kamil Exp $ */
/* $NetBSD: kcov.h,v 1.4 2019/03/10 17:51:00 kamil Exp $ */
/*
* Copyright (c) 2019 The NetBSD Foundation, Inc.
@ -37,9 +37,13 @@
#include <sys/atomic.h>
#define KCOV_IOC_SETBUFSIZE _IOW('K', 1, uint64_t)
#define KCOV_IOC_ENABLE _IO('K', 2)
#define KCOV_IOC_ENABLE _IOW('K', 2, int)
#define KCOV_IOC_DISABLE _IO('K', 3)
#define KCOV_MODE_NONE 0
#define KCOV_MODE_TRACE_PC 1
#define KCOV_MODE_TRACE_CMP 2
typedef volatile uint64_t kcov_int_t;
#define KCOV_ENTRY_SIZE sizeof(kcov_int_t)

View File

@ -39,6 +39,7 @@
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <atf-c.h>
@ -157,6 +158,7 @@ kcov_mmap_enable_thread(void *data)
{
int fd;
uint64_t size = PAGE_SIZE / KCOV_ENTRY_SIZE;
int mode;
fd = open_kcov();
*(int *)data = fd;
@ -164,7 +166,8 @@ kcov_mmap_enable_thread(void *data)
ATF_REQUIRE(ioctl(fd, KCOV_IOC_SETBUFSIZE, &size) ==0);
ATF_CHECK(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, 0) != MAP_FAILED);
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE) == 0);
mode = KCOV_MODE_NONE;
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
sem_post(&sem1);
sem_wait(&sem2);
@ -193,10 +196,12 @@ ATF_TC_BODY(kcov_enable, tc)
{
int fd;
uint64_t size = PAGE_SIZE / KCOV_ENTRY_SIZE;
int mode;
fd = open_kcov();
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE) == -1);
mode = KCOV_MODE_NONE;
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == -1);
ATF_REQUIRE(ioctl(fd, KCOV_IOC_SETBUFSIZE, &size) ==0);
@ -204,15 +209,26 @@ ATF_TC_BODY(kcov_enable, tc)
ATF_CHECK(ioctl(fd, KCOV_IOC_DISABLE) == -1);
/* Check enabling works only with a valid trace method */
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE) == -1);
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == -1);
/* Disable should only be called once */
ATF_CHECK(ioctl(fd, KCOV_IOC_DISABLE) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_DISABLE) == -1);
/* Re-enabling should also work */
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_DISABLE) == 0);
/* Re-enablibling and changing mode should also work */
mode = KCOV_MODE_NONE;
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_DISABLE) == 0);
mode = KCOV_MODE_TRACE_PC;
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_DISABLE) == 0);
mode = KCOV_MODE_TRACE_CMP;
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
ATF_CHECK(ioctl(fd, KCOV_IOC_DISABLE) == 0);
close(fd);
@ -223,10 +239,12 @@ ATF_TC_BODY(kcov_enable_no_disable, tc)
{
int fd;
uint64_t size = PAGE_SIZE / KCOV_ENTRY_SIZE;
int mode;
fd = open_kcov();
ATF_REQUIRE(ioctl(fd, KCOV_IOC_SETBUFSIZE, &size) ==0);
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE) == 0);
mode = KCOV_MODE_NONE;
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
close(fd);
}
@ -235,10 +253,12 @@ ATF_TC_BODY(kcov_enable_no_disable_no_close, tc)
{
int fd;
uint64_t size = PAGE_SIZE / KCOV_ENTRY_SIZE;
int mode;
fd = open_kcov();
ATF_REQUIRE(ioctl(fd, KCOV_IOC_SETBUFSIZE, &size) ==0);
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE) == 0);
mode = KCOV_MODE_NONE;
ATF_CHECK(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0);
}
static void *
@ -270,19 +290,19 @@ common_tail(int fd, kcov_int_t *data)
close(fd);
}
ATF_TC_WITHOUT_HEAD(kcov_basic);
ATF_TC_BODY(kcov_basic, tc)
static void
kcov_basic(int mode)
{
kcov_int_t *buf;
int fd;
buf = common_head(&fd);
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE) == 0,
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0,
"Unable to enable kcov ");
KCOV_STORE(buf[0], 0);
sleep(0);
sleep(0); /* XXX: Is it enough for all trace types? */
ATF_REQUIRE_MSG(KCOV_LOAD(buf[0]) != 0, "No records found");
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_DISABLE) == 0,
@ -291,17 +311,35 @@ ATF_TC_BODY(kcov_basic, tc)
common_tail(fd, buf);
}
ATF_TC_WITHOUT_HEAD(kcov_basic_pc);
ATF_TC_BODY(kcov_basic_pc, tc)
{
kcov_basic(KCOV_MODE_TRACE_PC);
}
ATF_TC_WITHOUT_HEAD(kcov_basic_cmp);
ATF_TC_BODY(kcov_basic_cmp, tc)
{
atf_tc_skip("XXX: GCC8 needed");
kcov_basic(KCOV_MODE_TRACE_CMP);
}
ATF_TC_WITHOUT_HEAD(kcov_multienable_on_the_same_thread);
ATF_TC_BODY(kcov_multienable_on_the_same_thread, tc)
{
kcov_int_t *buf1, *buf2;
int fd1, fd2;
int mode;
buf1 = common_head(&fd1);
buf2 = common_head(&fd2);
ATF_REQUIRE_MSG(ioctl(fd1, KCOV_IOC_ENABLE) == 0,
mode = KCOV_MODE_NONE;
ATF_REQUIRE_MSG(ioctl(fd1, KCOV_IOC_ENABLE, &mode) == 0,
"Unable to enable kcov");
ATF_REQUIRE_ERRNO(EBUSY, ioctl(fd2, KCOV_IOC_ENABLE) != 0);
ATF_REQUIRE_ERRNO(EBUSY, ioctl(fd2, KCOV_IOC_ENABLE, &mode) != 0);
ATF_REQUIRE_MSG(ioctl(fd1, KCOV_IOC_DISABLE) == 0,
"Unable to disable kcov");
@ -327,10 +365,12 @@ ATF_TC_BODY(kcov_buffer_access_from_custom_thread, tc)
pthread_t thread;
kcov_int_t *buf;
int fd;
int mode;
buf = common_head(&fd);
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE) == 0,
mode = KCOV_MODE_TRACE_PC;
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0,
"Unable to enable kcov ");
pthread_create(&thread, NULL, thread_buffer_access_test_helper,
@ -363,11 +403,13 @@ ATF_TC_BODY(kcov_thread, tc)
pthread_t thread;
kcov_int_t *buf;
int fd;
int mode;
volatile int i;
buf = common_head(&fd);
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE) == 0,
mode = KCOV_MODE_TRACE_PC;
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0,
"Unable to enable kcov ");
/* The thread does something, does not matter what exactly. */
@ -392,9 +434,11 @@ multiple_threads_helper(void *ptr __unused)
{
kcov_int_t *buf;
int fd;
int mode;
buf = common_head(&fd);
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE) == 0,
mode = KCOV_MODE_TRACE_PC;
ATF_REQUIRE_MSG(ioctl(fd, KCOV_IOC_ENABLE, &mode) == 0,
"Unable to enable kcov ");
KCOV_STORE(buf[0], 0);
@ -452,7 +496,8 @@ ATF_TP_ADD_TCS(tp)
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);
ATF_TP_ADD_TC(tp, kcov_basic_pc);
ATF_TP_ADD_TC(tp, kcov_basic_cmp);
ATF_TP_ADD_TC(tp, kcov_multienable_on_the_same_thread);
ATF_TP_ADD_TC(tp, kcov_buffer_access_from_custom_thread);
ATF_TP_ADD_TC(tp, kcov_thread);