github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/coverage/coverage.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package coverage provides an interface through which Go coverage data can 16 // be collected, converted to kcov format, and exposed to userspace. 17 // 18 // Coverage can be enabled by calling bazel {build,test} with 19 // --collect_coverage_data and --instrumentation_filter with the desired 20 // coverage surface. This causes bazel to use the Go cover tool manually to 21 // generate instrumented files. It injects a hook that registers all coverage 22 // data with the coverdata package. 23 package coverage 24 25 import ( 26 "fmt" 27 "io" 28 "sort" 29 "sync/atomic" 30 "testing" 31 32 "github.com/SagerNet/gvisor/pkg/hostarch" 33 "github.com/SagerNet/gvisor/pkg/sync" 34 35 "github.com/bazelbuild/rules_go/go/tools/coverdata" 36 ) 37 38 var ( 39 // coverageMu must be held while accessing coverdata.Cover. This prevents 40 // concurrent reads/writes from multiple threads collecting coverage data. 41 coverageMu sync.RWMutex 42 43 // reportOutput is the place to write out a coverage report. It should be 44 // closed after the report is written. It is protected by reportOutputMu. 45 reportOutput io.WriteCloser 46 reportOutputMu sync.Mutex 47 ) 48 49 // blockBitLength is the number of bits used to represent coverage block index 50 // in a synthetic PC (the rest are used to represent the file index). Even 51 // though a PC has 64 bits, we only use the lower 32 bits because some users 52 // (e.g., syzkaller) may truncate that address to a 32-bit value. 53 // 54 // As of this writing, there are ~1200 files that can be instrumented and at 55 // most ~1200 blocks per file, so 16 bits is more than enough to represent every 56 // file and every block. 57 const blockBitLength = 16 58 59 // Available returns whether any coverage data is available. 60 func Available() bool { 61 return len(coverdata.Cover.Blocks) > 0 62 } 63 64 // EnableReport sets up coverage reporting. 65 func EnableReport(w io.WriteCloser) { 66 reportOutputMu.Lock() 67 defer reportOutputMu.Unlock() 68 reportOutput = w 69 } 70 71 // KcovSupported returns whether the kcov interface should be made available. 72 // 73 // If coverage reporting is on, do not turn on kcov, which will consume 74 // coverage data. 75 func KcovSupported() bool { 76 return (reportOutput == nil) && Available() 77 } 78 79 var globalData struct { 80 // files is the set of covered files sorted by filename. It is calculated at 81 // startup. 82 files []string 83 84 // syntheticPCs are a set of PCs calculated at startup, where the PC 85 // at syntheticPCs[i][j] corresponds to file i, block j. 86 syntheticPCs [][]uint64 87 88 // once ensures that globalData is only initialized once. 89 once sync.Once 90 } 91 92 // ClearCoverageData clears existing coverage data. 93 // 94 //go:norace 95 func ClearCoverageData() { 96 coverageMu.Lock() 97 defer coverageMu.Unlock() 98 99 // We do not use atomic operations while reading/writing to the counters, 100 // which would drastically degrade performance. Slight discrepancies due to 101 // racing is okay for the purposes of kcov. 102 for _, counters := range coverdata.Cover.Counters { 103 for index := 0; index < len(counters); index++ { 104 counters[index] = 0 105 } 106 } 107 } 108 109 var coveragePool = sync.Pool{ 110 New: func() interface{} { 111 return make([]byte, 0) 112 }, 113 } 114 115 // ConsumeCoverageData builds and writes the collection of covered PCs. It 116 // returns the number of bytes written. 117 // 118 // In Linux, a kernel configuration is set that compiles the kernel with a 119 // custom function that is called at the beginning of every basic block, which 120 // updates the memory-mapped coverage information. The Go coverage tool does not 121 // allow us to inject arbitrary instructions into basic blocks, but it does 122 // provide data that we can convert to a kcov-like format and transfer them to 123 // userspace through a memory mapping. 124 // 125 // Note that this is not a strict implementation of kcov, which is especially 126 // tricky to do because we do not have the same coverage tools available in Go 127 // that that are available for the actual Linux kernel. In Linux, a kernel 128 // configuration is set that compiles the kernel with a custom function that is 129 // called at the beginning of every basic block to write program counters to the 130 // kcov memory mapping. In Go, however, coverage tools only give us a count of 131 // basic blocks as they are executed. Every time we return to userspace, we 132 // collect the coverage information and write out PCs for each block that was 133 // executed, providing userspace with the illusion that the kcov data is always 134 // up to date. For convenience, we also generate a unique synthetic PC for each 135 // block instead of using actual PCs. Finally, we do not provide thread-specific 136 // coverage data (each kcov instance only contains PCs executed by the thread 137 // owning it); instead, we will supply data for any file specified by -- 138 // instrumentation_filter. 139 // 140 // Note that we "consume", i.e. clear, coverdata when this function is run, to 141 // ensure that each event is only reported once. Due to the limitations of Go 142 // coverage tools, we reset the global coverage data every time this function is 143 // run. 144 // 145 //go:norace 146 func ConsumeCoverageData(w io.Writer) int { 147 InitCoverageData() 148 149 coverageMu.Lock() 150 defer coverageMu.Unlock() 151 152 total := 0 153 var pcBuffer [8]byte 154 for fileNum, file := range globalData.files { 155 counters := coverdata.Cover.Counters[file] 156 for index := 0; index < len(counters); index++ { 157 // We do not use atomic operations while reading/writing to the counters, 158 // which would drastically degrade performance. Slight discrepancies due to 159 // racing is okay for the purposes of kcov. 160 if counters[index] == 0 { 161 continue 162 } 163 // Non-zero coverage data found; consume it and report as a PC. 164 counters[index] = 0 165 pc := globalData.syntheticPCs[fileNum][index] 166 hostarch.ByteOrder.PutUint64(pcBuffer[:], pc) 167 n, err := w.Write(pcBuffer[:]) 168 if err != nil { 169 if err == io.EOF { 170 // Simply stop writing if we encounter EOF; it's ok if we attempted to 171 // write more than we can hold. 172 return total + n 173 } 174 panic(fmt.Sprintf("Internal error writing PCs to kcov area: %v", err)) 175 } 176 total += n 177 } 178 } 179 180 if total == 0 { 181 // An empty profile indicates that coverage is not enabled, in which case 182 // there shouldn't be any task work registered. 183 panic("kcov task work is registered, but no coverage data was found") 184 } 185 return total 186 } 187 188 // InitCoverageData initializes globalData. It should be called before any kcov 189 // data is written. 190 func InitCoverageData() { 191 globalData.once.Do(func() { 192 // First, order all files. Then calculate synthetic PCs for every block 193 // (using the well-defined ordering for files as well). 194 for file := range coverdata.Cover.Blocks { 195 globalData.files = append(globalData.files, file) 196 } 197 sort.Strings(globalData.files) 198 199 for fileNum, file := range globalData.files { 200 blocks := coverdata.Cover.Blocks[file] 201 pcs := make([]uint64, 0, len(blocks)) 202 for blockNum := range blocks { 203 pcs = append(pcs, calculateSyntheticPC(fileNum, blockNum)) 204 } 205 globalData.syntheticPCs = append(globalData.syntheticPCs, pcs) 206 } 207 }) 208 } 209 210 // reportOnce ensures that a coverage report is written at most once. For a 211 // complete coverage report, Report should be called during the sandbox teardown 212 // process. Report is called from multiple places (which may overlap) so that a 213 // coverage report is written in different sandbox exit scenarios. 214 var reportOnce sync.Once 215 216 // Report writes out a coverage report with all blocks that have been covered. 217 // 218 // TODO(b/144576401): Decide whether this should actually be in LCOV format 219 func Report() error { 220 if reportOutput == nil { 221 return nil 222 } 223 224 var err error 225 reportOnce.Do(func() { 226 for file, counters := range coverdata.Cover.Counters { 227 blocks := coverdata.Cover.Blocks[file] 228 for i := 0; i < len(counters); i++ { 229 if atomic.LoadUint32(&counters[i]) > 0 { 230 err = writeBlock(reportOutput, file, blocks[i]) 231 if err != nil { 232 return 233 } 234 } 235 } 236 } 237 reportOutput.Close() 238 }) 239 return err 240 } 241 242 // Symbolize prints information about the block corresponding to pc. 243 func Symbolize(out io.Writer, pc uint64) error { 244 fileNum, blockNum := syntheticPCToIndexes(pc) 245 file, err := fileFromIndex(fileNum) 246 if err != nil { 247 return err 248 } 249 block, err := blockFromIndex(file, blockNum) 250 if err != nil { 251 return err 252 } 253 return writeBlockWithPC(out, pc, file, block) 254 } 255 256 // WriteAllBlocks prints all information about all blocks along with their 257 // corresponding synthetic PCs. 258 func WriteAllBlocks(out io.Writer) error { 259 for fileNum, file := range globalData.files { 260 for blockNum, block := range coverdata.Cover.Blocks[file] { 261 if err := writeBlockWithPC(out, calculateSyntheticPC(fileNum, blockNum), file, block); err != nil { 262 return err 263 } 264 } 265 } 266 return nil 267 } 268 269 func writeBlockWithPC(out io.Writer, pc uint64, file string, block testing.CoverBlock) error { 270 if _, err := io.WriteString(out, fmt.Sprintf("%#x\n", pc)); err != nil { 271 return err 272 } 273 return writeBlock(out, file, block) 274 } 275 276 func writeBlock(out io.Writer, file string, block testing.CoverBlock) error { 277 _, err := io.WriteString(out, fmt.Sprintf("%s:%d.%d,%d.%d\n", file, block.Line0, block.Col0, block.Line1, block.Col1)) 278 return err 279 } 280 281 func calculateSyntheticPC(fileNum int, blockNum int) uint64 { 282 return (uint64(fileNum) << blockBitLength) + uint64(blockNum) 283 } 284 285 func syntheticPCToIndexes(pc uint64) (fileNum int, blockNum int) { 286 return int(pc >> blockBitLength), int(pc & ((1 << blockBitLength) - 1)) 287 } 288 289 // fileFromIndex returns the name of the file in the sorted list of instrumented files. 290 func fileFromIndex(i int) (string, error) { 291 total := len(globalData.files) 292 if i < 0 || i >= total { 293 return "", fmt.Errorf("file index out of range: [%d] with length %d", i, total) 294 } 295 return globalData.files[i], nil 296 } 297 298 // blockFromIndex returns the i-th block in the given file. 299 func blockFromIndex(file string, i int) (testing.CoverBlock, error) { 300 blocks, ok := coverdata.Cover.Blocks[file] 301 if !ok { 302 return testing.CoverBlock{}, fmt.Errorf("instrumented file %s does not exist", file) 303 } 304 total := len(blocks) 305 if i < 0 || i >= total { 306 return testing.CoverBlock{}, fmt.Errorf("block index out of range: [%d] with length %d", i, total) 307 } 308 return blocks[i], nil 309 }