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  }