github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/kcov/kcov.go (about) 1 // Copyright 2025 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 //go:build linux 5 6 // Package kcov provides Go native code for collecting kernel coverage (KCOV) 7 // information. 8 package kcov 9 10 import ( 11 "os" 12 "runtime" 13 "sync/atomic" 14 "unsafe" 15 16 "golang.org/x/sys/unix" 17 ) 18 19 const ( 20 kcovPath = "/sys/kernel/debug/kcov" 21 // This is the same value used by the linux executor, see executor_linux.h. 22 kcovCoverSize = 512 << 10 23 ) 24 25 // Holds resources for a single traced thread. 26 type KCOVState struct { 27 file *os.File 28 cover []byte 29 } 30 31 type KCOVTraceResult struct { 32 Result error // Result of the call. 33 Coverage []uintptr // Collected program counters. 34 } 35 36 // Trace invokes `f` and returns a KCOVTraceResult. 37 func (st *KCOVState) Trace(f func() error) KCOVTraceResult { 38 // First 8 bytes holds the number of collected PCs since last poll. 39 countPtr := (*uintptr)(unsafe.Pointer(&st.cover[0])) 40 // Reset coverage for this run. 41 atomic.StoreUintptr(countPtr, 0) 42 // Trigger call. 43 err := f() 44 // Load the number of PCs that were hit during trigger. 45 n := atomic.LoadUintptr(countPtr) 46 47 pcDataPtr := (*uintptr)(unsafe.Pointer(&st.cover[sizeofUintPtr])) 48 pcs := unsafe.Slice(pcDataPtr, n) 49 pcsCopy := make([]uintptr, n) 50 copy(pcsCopy, pcs) 51 return KCOVTraceResult{Result: err, Coverage: pcsCopy} 52 } 53 54 // EnableTracingForCurrentGoroutine prepares the current goroutine for kcov tracing. 55 // It must be paired with a call to DisableTracing. 56 func EnableTracingForCurrentGoroutine() (st *KCOVState, err error) { 57 st = &KCOVState{} 58 defer func() { 59 if err != nil { 60 // The original error is more important, so we ignore any potential 61 // errors that result from cleaning up. 62 _ = st.DisableTracing() 63 } 64 }() 65 66 // KCOV is per-thread, so lock goroutine to its current OS thread. 67 runtime.LockOSThread() 68 69 file, err := os.OpenFile(kcovPath, os.O_RDWR, 0) 70 if err != nil { 71 return nil, err 72 } 73 st.file = file 74 75 // Setup trace mode and size. 76 if err := unix.IoctlSetInt(int(st.file.Fd()), uint(kcovInitTrace), kcovCoverSize); err != nil { 77 return nil, err 78 } 79 80 // Mmap buffer shared between kernel- and user-space. For more information, 81 // see the Linux KCOV documentation: https://docs.kernel.org/dev-tools/kcov.html. 82 st.cover, err = unix.Mmap( 83 int(st.file.Fd()), 84 0, // Offset. 85 kcovCoverSize*sizeofUintPtr, 86 unix.PROT_READ|unix.PROT_WRITE, 87 unix.MAP_SHARED, 88 ) 89 if err != nil { 90 return nil, err 91 } 92 93 // Enable coverage collection on the current thread. 94 if err := unix.IoctlSetInt(int(st.file.Fd()), uint(kcovEnable), kcovTracePC); err != nil { 95 return nil, err 96 } 97 return st, nil 98 } 99 100 // DisableTracing disables KCOV tracing for the current Go routine. On failure, 101 // it returns the first error that occurred during cleanup. 102 func (st *KCOVState) DisableTracing() error { 103 var firstErr error 104 if err := unix.IoctlSetInt(int(st.file.Fd()), uint(kcovDisable), kcovTracePC); err != nil { 105 firstErr = err 106 } 107 if err := unix.Munmap(st.cover); err != nil && firstErr == nil { 108 firstErr = err 109 } 110 if err := st.file.Close(); err != nil && firstErr == nil { 111 firstErr = err 112 } 113 runtime.UnlockOSThread() 114 return firstErr 115 }