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