Module Name: src Committed By: kamil Date: Sun Mar 10 17:51:00 UTC 2019
Modified Files: src/share/man/man4: kcov.4 src/sys/kern: subr_kcov.c src/sys/sys: kcov.h src/tests/modules: t_kcov.c Log Message: 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. To generate a diff of this commit: cvs rdiff -u -r1.3 -r1.4 src/share/man/man4/kcov.4 cvs rdiff -u -r1.4 -r1.5 src/sys/kern/subr_kcov.c cvs rdiff -u -r1.3 -r1.4 src/sys/sys/kcov.h cvs rdiff -u -r1.6 -r1.7 src/tests/modules/t_kcov.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/share/man/man4/kcov.4 diff -u src/share/man/man4/kcov.4:1.3 src/share/man/man4/kcov.4:1.4 --- src/share/man/man4/kcov.4:1.3 Sun Mar 10 12:54:39 2019 +++ src/share/man/man4/kcov.4 Sun Mar 10 17:51:00 2019 @@ -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 <an...@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 */ Index: src/sys/kern/subr_kcov.c diff -u src/sys/kern/subr_kcov.c:1.4 src/sys/kern/subr_kcov.c:1.5 --- src/sys/kern/subr_kcov.c:1.4 Sun Mar 10 12:54:39 2019 +++ src/sys/kern/subr_kcov.c Sun Mar 10 17:51:00 2019 @@ -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, 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); Index: src/sys/sys/kcov.h diff -u src/sys/sys/kcov.h:1.3 src/sys/sys/kcov.h:1.4 --- src/sys/sys/kcov.h:1.3 Mon Feb 25 13:19:14 2019 +++ src/sys/sys/kcov.h Sun Mar 10 17:51:00 2019 @@ -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) Index: src/tests/modules/t_kcov.c diff -u src/tests/modules/t_kcov.c:1.6 src/tests/modules/t_kcov.c:1.7 --- src/tests/modules/t_kcov.c:1.6 Sun Mar 10 13:24:50 2019 +++ src/tests/modules/t_kcov.c Sun Mar 10 17:51:00 2019 @@ -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_cl { 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_cust 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 __unus { 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);