decodecounterfile.mx raw

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