aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEthan Graham <ethangraham@google.com>2025-09-15 12:45:43 +0000
committerAleksandr Nogikh <nogikh@google.com>2025-09-22 09:11:54 +0000
commitc9f0a99247f6d9a6df877720609cbce3dca73b55 (patch)
treeefc0ce5a8aed7519ba206dcc1ce62fb33258d5c5
parent770ff59fb77d138f63af2893d3f1c97da2625d05 (diff)
pkg/kcov: add pkg/kcov
Add a Go-native KCOV package, with a helper functions for tracing a a function. This is in preparation for a standalone KFuzzTest tool, which should be written in Go in order to take advantage of existing fuzzing infrastructure. The hard-coded coverage buffer size is the same as the executor program, defined as `512 << 10` in `executor/executor.cc`. Signed-off-by: Ethan Graham <ethangraham@google.com>
-rw-r--r--pkg/kcov/cdefs.go45
-rw-r--r--pkg/kcov/kcov.go115
2 files changed, 160 insertions, 0 deletions
diff --git a/pkg/kcov/cdefs.go b/pkg/kcov/cdefs.go
new file mode 100644
index 000000000..1272360b9
--- /dev/null
+++ b/pkg/kcov/cdefs.go
@@ -0,0 +1,45 @@
+// Copyright 2025 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+package kcov
+
+// This file defines values required for KCOV ioctl calls. More information on
+// the values and their semantics can be found in the kernel documentation under
+// Documentation/dev-tools/kcov.rst, or at docs.kernel.org/dev-tools/kcov.html.
+
+import "unsafe"
+
+const (
+ sizeofUintPtr = int(unsafe.Sizeof((*int)(nil)))
+
+ iocNrBits = 8
+ iocTypeBits = 8
+ iocSizeBits = 14
+ iocDirBits = 2
+
+ iocNrShift = 0
+ iocTypeshift = iocNrShift + iocNrBits
+ iocSizeShift = iocTypeshift + iocTypeBits
+ iocDirShift = iocSizeShift + iocSizeBits
+
+ iocNone = 0
+ iocWrite = 1
+ iocRead = 2
+
+ // kcovInitTrace initializes KCOV tracing.
+ // #define kcovInitTrace _IOR('c', 1, unsigned long)
+ kcovInitTrace uintptr = (iocRead << iocDirShift) |
+ (unsafe.Sizeof(uint64(0)) << iocSizeShift) | ('c' << iocTypeshift) | (1 << iocNrShift) // 0x80086301.
+
+ // kcovEnable enables kcov for the current thread.
+ // #define kcovEnable _IO('c', 100)
+ kcovEnable uintptr = (iocNone << iocDirShift) |
+ (0 << iocSizeShift) | ('c' << iocTypeshift) | (100 << iocNrShift) // 0x6364.
+
+ // kcovDisable disables kcov for the current thread.
+ // #define kcovDisable _IO('c', 101)
+ kcovDisable uintptr = (iocNone << iocDirShift) |
+ (0 << iocSizeShift) | ('c' << iocTypeshift) | (101 << iocNrShift) // 0x6365.
+
+ kcovTracePC = 0
+ kcovTraceCMP = 1
+)
diff --git a/pkg/kcov/kcov.go b/pkg/kcov/kcov.go
new file mode 100644
index 000000000..0400a32ff
--- /dev/null
+++ b/pkg/kcov/kcov.go
@@ -0,0 +1,115 @@
+// Copyright 2025 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+//go:build linux
+
+// Package kcov provides Go native code for collecting kernel coverage (KCOV)
+// information.
+package kcov
+
+import (
+ "os"
+ "runtime"
+ "sync/atomic"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+const (
+ kcovPath = "/sys/kernel/debug/kcov"
+ // This is the same value used by the linux executor, see executor_linux.h.
+ kcovCoverSize = 512 << 10
+)
+
+// Holds resources for a single traced thread.
+type KCOVState struct {
+ file *os.File
+ cover []byte
+}
+
+type KCOVTraceResult struct {
+ Result error // Result of the call.
+ Coverage []uintptr // Collected program counters.
+}
+
+// Trace invokes `f` and returns a KCOVTraceResult.
+func (st *KCOVState) Trace(f func() error) KCOVTraceResult {
+ // First 8 bytes holds the number of collected PCs since last poll.
+ countPtr := (*uintptr)(unsafe.Pointer(&st.cover[0]))
+ // Reset coverage for this run.
+ atomic.StoreUintptr(countPtr, 0)
+ // Trigger call.
+ err := f()
+ // Load the number of PCs that were hit during trigger.
+ n := atomic.LoadUintptr(countPtr)
+
+ pcDataPtr := (*uintptr)(unsafe.Pointer(&st.cover[sizeofUintPtr]))
+ pcs := unsafe.Slice(pcDataPtr, n)
+ pcsCopy := make([]uintptr, n)
+ copy(pcsCopy, pcs)
+ return KCOVTraceResult{Result: err, Coverage: pcsCopy}
+}
+
+// EnableTracingForCurrentGoroutine prepares the current goroutine for kcov tracing.
+// It must be paired with a call to DisableTracing.
+func EnableTracingForCurrentGoroutine() (st *KCOVState, err error) {
+ st = &KCOVState{}
+ defer func() {
+ if err != nil {
+ // The original error is more important, so we ignore any potential
+ // errors that result from cleaning up.
+ _ = st.DisableTracing()
+ }
+ }()
+
+ // KCOV is per-thread, so lock goroutine to its current OS thread.
+ runtime.LockOSThread()
+
+ file, err := os.OpenFile(kcovPath, os.O_RDWR, 0)
+ if err != nil {
+ return nil, err
+ }
+ st.file = file
+
+ // Setup trace mode and size.
+ if err := unix.IoctlSetInt(int(st.file.Fd()), uint(kcovInitTrace), kcovCoverSize); err != nil {
+ return nil, err
+ }
+
+ // Mmap buffer shared between kernel- and user-space. For more information,
+ // see the Linux KCOV documentation: https://docs.kernel.org/dev-tools/kcov.html.
+ st.cover, err = unix.Mmap(
+ int(st.file.Fd()),
+ 0, // Offset.
+ kcovCoverSize*sizeofUintPtr,
+ unix.PROT_READ|unix.PROT_WRITE,
+ unix.MAP_SHARED,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ // Enable coverage collection on the current thread.
+ if err := unix.IoctlSetInt(int(st.file.Fd()), uint(kcovEnable), kcovTracePC); err != nil {
+ return nil, err
+ }
+ return st, nil
+}
+
+// DisableTracing disables KCOV tracing for the current Go routine. On failure,
+// it returns the first error that occurred during cleanup.
+func (st *KCOVState) DisableTracing() error {
+ var firstErr error
+ if err := unix.IoctlSetInt(int(st.file.Fd()), uint(kcovDisable), kcovTracePC); err != nil {
+ firstErr = err
+ }
+ if err := unix.Munmap(st.cover); err != nil && firstErr == nil {
+ firstErr = err
+ }
+ if err := st.file.Close(); err != nil && firstErr == nil {
+ firstErr = err
+ }
+ runtime.UnlockOSThread()
+ return firstErr
+}