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