1 // Copyright 2021 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 package decodemeta
6 7 // This package contains APIs and helpers for reading and decoding
8 // meta-data output files emitted by the runtime when a
9 // coverage-instrumented binary executes. A meta-data file contains
10 // top-level info (counter mode, number of packages) and then a
11 // separate self-contained meta-data section for each Go package.
12 13 import (
14 "bufio"
15 "encoding/binary"
16 "fmt"
17 "hash/fnv"
18 "internal/coverage"
19 "internal/coverage/slicereader"
20 "internal/coverage/stringtab"
21 "io"
22 "os"
23 )
24 25 // CoverageMetaFileReader provides state and methods for reading
26 // a meta-data file from a code coverage run.
27 type CoverageMetaFileReader struct {
28 f *os.File
29 hdr coverage.MetaFileHeader
30 tmp []byte
31 pkgOffsets []uint64
32 pkgLengths []uint64
33 strtab *stringtab.Reader
34 fileRdr *bufio.Reader
35 fileView []byte
36 debug bool
37 }
38 39 // NewCoverageMetaFileReader returns a new helper object for reading
40 // the coverage meta-data output file 'f'. The param 'fileView' is a
41 // read-only slice containing the contents of 'f' obtained by mmap'ing
42 // the file read-only; 'fileView' may be nil, in which case the helper
43 // will read the contents of the file using regular file Read
44 // operations.
45 func NewCoverageMetaFileReader(f *os.File, fileView []byte) (*CoverageMetaFileReader, error) {
46 r := &CoverageMetaFileReader{
47 f: f,
48 fileView: fileView,
49 tmp: []byte{:256},
50 }
51 52 if err := r.readFileHeader(); err != nil {
53 return nil, err
54 }
55 return r, nil
56 }
57 58 func (r *CoverageMetaFileReader) readFileHeader() error {
59 var err error
60 61 r.fileRdr = bufio.NewReader(r.f)
62 63 // Read file header.
64 if err := binary.Read(r.fileRdr, binary.LittleEndian, &r.hdr); err != nil {
65 return err
66 }
67 68 // Verify magic string
69 m := r.hdr.Magic
70 g := coverage.CovMetaMagic
71 if m[0] != g[0] || m[1] != g[1] || m[2] != g[2] || m[3] != g[3] {
72 return fmt.Errorf("invalid meta-data file magic string")
73 }
74 75 // Vet the version. If this is a meta-data file from the future,
76 // we won't be able to read it.
77 if r.hdr.Version > coverage.MetaFileVersion {
78 return fmt.Errorf("meta-data file withn unknown version %d (expected %d)", r.hdr.Version, coverage.MetaFileVersion)
79 }
80 81 // Read package offsets for good measure
82 r.pkgOffsets = []uint64{:r.hdr.Entries}
83 for i := uint64(0); i < r.hdr.Entries; i++ {
84 if r.pkgOffsets[i], err = r.rdUint64(); err != nil {
85 return err
86 }
87 if r.pkgOffsets[i] > r.hdr.TotalLength {
88 return fmt.Errorf("insane pkg offset %d: %d > totlen %d",
89 i, r.pkgOffsets[i], r.hdr.TotalLength)
90 }
91 }
92 r.pkgLengths = []uint64{:r.hdr.Entries}
93 for i := uint64(0); i < r.hdr.Entries; i++ {
94 if r.pkgLengths[i], err = r.rdUint64(); err != nil {
95 return err
96 }
97 if r.pkgLengths[i] > r.hdr.TotalLength {
98 return fmt.Errorf("insane pkg length %d: %d > totlen %d",
99 i, r.pkgLengths[i], r.hdr.TotalLength)
100 }
101 }
102 103 // Read string table.
104 b := []byte{:r.hdr.StrTabLength}
105 nr, err := r.fileRdr.Read(b)
106 if err != nil {
107 return err
108 }
109 if nr != int(r.hdr.StrTabLength) {
110 return fmt.Errorf("error: short read on string table")
111 }
112 slr := slicereader.NewReader(b, false /* not readonly */)
113 r.strtab = stringtab.NewReader(slr)
114 r.strtab.Read()
115 116 if r.debug {
117 fmt.Fprintf(os.Stderr, "=-= read-in header is: %+v\n", *r)
118 }
119 120 return nil
121 }
122 123 func (r *CoverageMetaFileReader) rdUint64() (uint64, error) {
124 r.tmp = r.tmp[:0]
125 r.tmp = append(r.tmp, []byte{:8}...)
126 n, err := r.fileRdr.Read(r.tmp)
127 if err != nil {
128 return 0, err
129 }
130 if n != 8 {
131 return 0, fmt.Errorf("premature end of file on read")
132 }
133 v := binary.LittleEndian.Uint64(r.tmp)
134 return v, nil
135 }
136 137 // NumPackages returns the number of packages for which this file
138 // contains meta-data.
139 func (r *CoverageMetaFileReader) NumPackages() uint64 {
140 return r.hdr.Entries
141 }
142 143 // CounterMode returns the counter mode (set, count, atomic) used
144 // when building for coverage for the program that produce this
145 // meta-data file.
146 func (r *CoverageMetaFileReader) CounterMode() coverage.CounterMode {
147 return r.hdr.CMode
148 }
149 150 // CounterGranularity returns the counter granularity (single counter per
151 // function, or counter per block) selected when building for coverage
152 // for the program that produce this meta-data file.
153 func (r *CoverageMetaFileReader) CounterGranularity() coverage.CounterGranularity {
154 return r.hdr.CGranularity
155 }
156 157 // FileHash returns the hash computed for all of the package meta-data
158 // blobs. Coverage counter data files refer to this hash, and the
159 // hash will be encoded into the meta-data file name.
160 func (r *CoverageMetaFileReader) FileHash() [16]byte {
161 return r.hdr.MetaFileHash
162 }
163 164 // GetPackageDecoder requests a decoder object for the package within
165 // the meta-data file whose index is 'pkIdx'. If the
166 // CoverageMetaFileReader was set up with a read-only file view, a
167 // pointer into that file view will be returned, otherwise the buffer
168 // 'payloadbuf' will be written to (or if it is not of sufficient
169 // size, a new buffer will be allocated). Return value is the decoder,
170 // a byte slice with the encoded meta-data, and an error.
171 func (r *CoverageMetaFileReader) GetPackageDecoder(pkIdx uint32, payloadbuf []byte) (*CoverageMetaDataDecoder, []byte, error) {
172 pp, err := r.GetPackagePayload(pkIdx, payloadbuf)
173 if r.debug {
174 h := fnv.New128a()
175 h.Write(pp)
176 fmt.Fprintf(os.Stderr, "=-= pkidx=%d payload length is %d hash=%s\n",
177 pkIdx, len(pp), fmt.Sprintf("%x", h.Sum(nil)))
178 }
179 if err != nil {
180 return nil, nil, err
181 }
182 mdd, err := NewCoverageMetaDataDecoder(pp, r.fileView != nil)
183 if err != nil {
184 return nil, nil, err
185 }
186 return mdd, pp, nil
187 }
188 189 // GetPackagePayload returns the raw (encoded) meta-data payload for the
190 // package with index 'pkIdx'. As with GetPackageDecoder, if the
191 // CoverageMetaFileReader was set up with a read-only file view, a
192 // pointer into that file view will be returned, otherwise the buffer
193 // 'payloadbuf' will be written to (or if it is not of sufficient
194 // size, a new buffer will be allocated). Return value is the decoder,
195 // a byte slice with the encoded meta-data, and an error.
196 func (r *CoverageMetaFileReader) GetPackagePayload(pkIdx uint32, payloadbuf []byte) ([]byte, error) {
197 198 // Determine correct offset/length.
199 if uint64(pkIdx) >= r.hdr.Entries {
200 return nil, fmt.Errorf("GetPackagePayload: illegal pkg index %d", pkIdx)
201 }
202 off := r.pkgOffsets[pkIdx]
203 len := r.pkgLengths[pkIdx]
204 205 if r.debug {
206 fmt.Fprintf(os.Stderr, "=-= for pk %d, off=%d len=%d\n", pkIdx, off, len)
207 }
208 209 if r.fileView != nil {
210 return r.fileView[off : off+len], nil
211 }
212 213 payload := payloadbuf[:0]
214 if cap(payload) < int(len) {
215 payload = []byte{:0:len}
216 }
217 payload = append(payload, []byte{:len}...)
218 if _, err := r.f.Seek(int64(off), io.SeekStart); err != nil {
219 return nil, err
220 }
221 if _, err := io.ReadFull(r.f, payload); err != nil {
222 return nil, err
223 }
224 return payload, nil
225 }
226