github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/coverage/decodecounter/decodecounterfile.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 decodecounter 6 7 import ( 8 "encoding/binary" 9 "fmt" 10 "io" 11 "os" 12 "strconv" 13 "unsafe" 14 15 "github.com/go-asm/go/coverage" 16 "github.com/go-asm/go/coverage/slicereader" 17 "github.com/go-asm/go/coverage/stringtab" 18 ) 19 20 // This file contains helpers for reading counter data files created 21 // during the executions of a coverage-instrumented binary. 22 23 type CounterDataReader struct { 24 stab *stringtab.Reader 25 args map[string]string 26 osargs []string 27 goarch string // GOARCH setting from run that produced counter data 28 goos string // GOOS setting from run that produced counter data 29 mr io.ReadSeeker 30 hdr coverage.CounterFileHeader 31 ftr coverage.CounterFileFooter 32 shdr coverage.CounterSegmentHeader 33 u32b []byte 34 u8b []byte 35 fcnCount uint32 36 segCount uint32 37 debug bool 38 } 39 40 func NewCounterDataReader(fn string, rs io.ReadSeeker) (*CounterDataReader, error) { 41 cdr := &CounterDataReader{ 42 mr: rs, 43 u32b: make([]byte, 4), 44 u8b: make([]byte, 1), 45 } 46 // Read header 47 if err := binary.Read(rs, binary.LittleEndian, &cdr.hdr); err != nil { 48 return nil, err 49 } 50 if cdr.debug { 51 fmt.Fprintf(os.Stderr, "=-= counter file header: %+v\n", cdr.hdr) 52 } 53 if !checkMagic(cdr.hdr.Magic) { 54 return nil, fmt.Errorf("invalid magic string: not a counter data file") 55 } 56 if cdr.hdr.Version > coverage.CounterFileVersion { 57 return nil, fmt.Errorf("version data incompatibility: reader is %d data is %d", coverage.CounterFileVersion, cdr.hdr.Version) 58 } 59 60 // Read footer. 61 if err := cdr.readFooter(); err != nil { 62 return nil, err 63 } 64 // Seek back to just past the file header. 65 hsz := int64(unsafe.Sizeof(cdr.hdr)) 66 if _, err := cdr.mr.Seek(hsz, io.SeekStart); err != nil { 67 return nil, err 68 } 69 // Read preamble for first segment. 70 if err := cdr.readSegmentPreamble(); err != nil { 71 return nil, err 72 } 73 return cdr, nil 74 } 75 76 func checkMagic(v [4]byte) bool { 77 g := coverage.CovCounterMagic 78 return v[0] == g[0] && v[1] == g[1] && v[2] == g[2] && v[3] == g[3] 79 } 80 81 func (cdr *CounterDataReader) readFooter() error { 82 ftrSize := int64(unsafe.Sizeof(cdr.ftr)) 83 if _, err := cdr.mr.Seek(-ftrSize, io.SeekEnd); err != nil { 84 return err 85 } 86 if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.ftr); err != nil { 87 return err 88 } 89 if !checkMagic(cdr.ftr.Magic) { 90 return fmt.Errorf("invalid magic string (not a counter data file)") 91 } 92 if cdr.ftr.NumSegments == 0 { 93 return fmt.Errorf("invalid counter data file (no segments)") 94 } 95 return nil 96 } 97 98 // readSegmentPreamble reads and consumes the segment header, segment string 99 // table, and segment args table. 100 func (cdr *CounterDataReader) readSegmentPreamble() error { 101 // Read segment header. 102 if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.shdr); err != nil { 103 return err 104 } 105 if cdr.debug { 106 fmt.Fprintf(os.Stderr, "=-= read counter segment header: %+v", cdr.shdr) 107 fmt.Fprintf(os.Stderr, " FcnEntries=0x%x StrTabLen=0x%x ArgsLen=0x%x\n", 108 cdr.shdr.FcnEntries, cdr.shdr.StrTabLen, cdr.shdr.ArgsLen) 109 } 110 111 // Read string table and args. 112 if err := cdr.readStringTable(); err != nil { 113 return err 114 } 115 if err := cdr.readArgs(); err != nil { 116 return err 117 } 118 // Seek past any padding to bring us up to a 4-byte boundary. 119 if of, err := cdr.mr.Seek(0, io.SeekCurrent); err != nil { 120 return err 121 } else { 122 rem := of % 4 123 if rem != 0 { 124 pad := 4 - rem 125 if _, err := cdr.mr.Seek(pad, io.SeekCurrent); err != nil { 126 return err 127 } 128 } 129 } 130 return nil 131 } 132 133 func (cdr *CounterDataReader) readStringTable() error { 134 b := make([]byte, cdr.shdr.StrTabLen) 135 nr, err := cdr.mr.Read(b) 136 if err != nil { 137 return err 138 } 139 if nr != int(cdr.shdr.StrTabLen) { 140 return fmt.Errorf("error: short read on string table") 141 } 142 slr := slicereader.NewReader(b, false /* not readonly */) 143 cdr.stab = stringtab.NewReader(slr) 144 cdr.stab.Read() 145 return nil 146 } 147 148 func (cdr *CounterDataReader) readArgs() error { 149 b := make([]byte, cdr.shdr.ArgsLen) 150 nr, err := cdr.mr.Read(b) 151 if err != nil { 152 return err 153 } 154 if nr != int(cdr.shdr.ArgsLen) { 155 return fmt.Errorf("error: short read on args table") 156 } 157 slr := slicereader.NewReader(b, false /* not readonly */) 158 sget := func() (string, error) { 159 kidx := slr.ReadULEB128() 160 if int(kidx) >= cdr.stab.Entries() { 161 return "", fmt.Errorf("malformed string table ref") 162 } 163 return cdr.stab.Get(uint32(kidx)), nil 164 } 165 nents := slr.ReadULEB128() 166 cdr.args = make(map[string]string, int(nents)) 167 for i := uint64(0); i < nents; i++ { 168 k, errk := sget() 169 if errk != nil { 170 return errk 171 } 172 v, errv := sget() 173 if errv != nil { 174 return errv 175 } 176 if _, ok := cdr.args[k]; ok { 177 return fmt.Errorf("malformed args table") 178 } 179 cdr.args[k] = v 180 } 181 if argcs, ok := cdr.args["argc"]; ok { 182 argc, err := strconv.Atoi(argcs) 183 if err != nil { 184 return fmt.Errorf("malformed argc in counter data file args section") 185 } 186 cdr.osargs = make([]string, 0, argc) 187 for i := 0; i < argc; i++ { 188 arg := cdr.args[fmt.Sprintf("argv%d", i)] 189 cdr.osargs = append(cdr.osargs, arg) 190 } 191 } 192 if goos, ok := cdr.args["GOOS"]; ok { 193 cdr.goos = goos 194 } 195 if goarch, ok := cdr.args["GOARCH"]; ok { 196 cdr.goarch = goarch 197 } 198 return nil 199 } 200 201 // OsArgs returns the program arguments (saved from os.Args during 202 // the run of the instrumented binary) read from the counter 203 // data file. Not all coverage data files will have os.Args values; 204 // for example, if a data file is produced by merging coverage 205 // data from two distinct runs, no os args will be available (an 206 // empty list is returned). 207 func (cdr *CounterDataReader) OsArgs() []string { 208 return cdr.osargs 209 } 210 211 // Goos returns the GOOS setting in effect for the "-cover" binary 212 // that produced this counter data file. The GOOS value may be 213 // empty in the case where the counter data file was produced 214 // from a merge in which more than one GOOS value was present. 215 func (cdr *CounterDataReader) Goos() string { 216 return cdr.goos 217 } 218 219 // Goarch returns the GOARCH setting in effect for the "-cover" binary 220 // that produced this counter data file. The GOARCH value may be 221 // empty in the case where the counter data file was produced 222 // from a merge in which more than one GOARCH value was present. 223 func (cdr *CounterDataReader) Goarch() string { 224 return cdr.goarch 225 } 226 227 // FuncPayload encapsulates the counter data payload for a single 228 // function as read from a counter data file. 229 type FuncPayload struct { 230 PkgIdx uint32 231 FuncIdx uint32 232 Counters []uint32 233 } 234 235 // NumSegments returns the number of execution segments in the file. 236 func (cdr *CounterDataReader) NumSegments() uint32 { 237 return cdr.ftr.NumSegments 238 } 239 240 // BeginNextSegment sets up the reader to read the next segment, 241 // returning TRUE if we do have another segment to read, or FALSE 242 // if we're done with all the segments (also an error if 243 // something went wrong). 244 func (cdr *CounterDataReader) BeginNextSegment() (bool, error) { 245 if cdr.segCount >= cdr.ftr.NumSegments { 246 return false, nil 247 } 248 cdr.segCount++ 249 cdr.fcnCount = 0 250 // Seek past footer from last segment. 251 ftrSize := int64(unsafe.Sizeof(cdr.ftr)) 252 if _, err := cdr.mr.Seek(ftrSize, io.SeekCurrent); err != nil { 253 return false, err 254 } 255 // Read preamble for this segment. 256 if err := cdr.readSegmentPreamble(); err != nil { 257 return false, err 258 } 259 return true, nil 260 } 261 262 // NumFunctionsInSegment returns the number of live functions 263 // in the currently selected segment. 264 func (cdr *CounterDataReader) NumFunctionsInSegment() uint32 { 265 return uint32(cdr.shdr.FcnEntries) 266 } 267 268 const supportDeadFunctionsInCounterData = false 269 270 // NextFunc reads data for the next function in this current segment 271 // into "p", returning TRUE if the read was successful or FALSE 272 // if we've read all the functions already (also an error if 273 // something went wrong with the read or we hit a premature 274 // EOF). 275 func (cdr *CounterDataReader) NextFunc(p *FuncPayload) (bool, error) { 276 if cdr.fcnCount >= uint32(cdr.shdr.FcnEntries) { 277 return false, nil 278 } 279 cdr.fcnCount++ 280 var rdu32 func() (uint32, error) 281 if cdr.hdr.CFlavor == coverage.CtrULeb128 { 282 rdu32 = func() (uint32, error) { 283 var shift uint 284 var value uint64 285 for { 286 _, err := cdr.mr.Read(cdr.u8b) 287 if err != nil { 288 return 0, err 289 } 290 b := cdr.u8b[0] 291 value |= (uint64(b&0x7F) << shift) 292 if b&0x80 == 0 { 293 break 294 } 295 shift += 7 296 } 297 return uint32(value), nil 298 } 299 } else if cdr.hdr.CFlavor == coverage.CtrRaw { 300 if cdr.hdr.BigEndian { 301 rdu32 = func() (uint32, error) { 302 n, err := cdr.mr.Read(cdr.u32b) 303 if err != nil { 304 return 0, err 305 } 306 if n != 4 { 307 return 0, io.EOF 308 } 309 return binary.BigEndian.Uint32(cdr.u32b), nil 310 } 311 } else { 312 rdu32 = func() (uint32, error) { 313 n, err := cdr.mr.Read(cdr.u32b) 314 if err != nil { 315 return 0, err 316 } 317 if n != 4 { 318 return 0, io.EOF 319 } 320 return binary.LittleEndian.Uint32(cdr.u32b), nil 321 } 322 } 323 } else { 324 panic("internal error: unknown counter flavor") 325 } 326 327 // Alternative/experimental path: one way we could handling writing 328 // out counter data would be to just memcpy the counter segment 329 // out to a file, meaning that a region in the counter memory 330 // corresponding to a dead (never-executed) function would just be 331 // zeroes. The code path below handles this case. 332 var nc uint32 333 var err error 334 if supportDeadFunctionsInCounterData { 335 for { 336 nc, err = rdu32() 337 if err == io.EOF { 338 return false, io.EOF 339 } else if err != nil { 340 break 341 } 342 if nc != 0 { 343 break 344 } 345 } 346 } else { 347 nc, err = rdu32() 348 } 349 if err != nil { 350 return false, err 351 } 352 353 // Read package and func indices. 354 p.PkgIdx, err = rdu32() 355 if err != nil { 356 return false, err 357 } 358 p.FuncIdx, err = rdu32() 359 if err != nil { 360 return false, err 361 } 362 if cap(p.Counters) < 1024 { 363 p.Counters = make([]uint32, 0, 1024) 364 } 365 p.Counters = p.Counters[:0] 366 for i := uint32(0); i < nc; i++ { 367 v, err := rdu32() 368 if err != nil { 369 return false, err 370 } 371 p.Counters = append(p.Counters, v) 372 } 373 return true, nil 374 }