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  }