// Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package decodecounter import ( "encoding/binary" "fmt" "internal/coverage" "internal/coverage/slicereader" "internal/coverage/stringtab" "io" "os" "strconv" "unsafe" ) // This file contains helpers for reading counter data files created // during the executions of a coverage-instrumented binary. type CounterDataReader struct { stab *stringtab.Reader args map[string]string osargs []string goarch string // GOARCH setting from run that produced counter data goos string // GOOS setting from run that produced counter data mr io.ReadSeeker hdr coverage.CounterFileHeader ftr coverage.CounterFileFooter shdr coverage.CounterSegmentHeader u32b []byte u8b []byte fcnCount uint32 segCount uint32 debug bool } func NewCounterDataReader(fn string, rs io.ReadSeeker) (*CounterDataReader, error) { cdr := &CounterDataReader{ mr: rs, u32b: make([]byte, 4), u8b: make([]byte, 1), } // Read header if err := binary.Read(rs, binary.LittleEndian, &cdr.hdr); err != nil { return nil, err } if cdr.debug { fmt.Fprintf(os.Stderr, "=-= counter file header: %+v\n", cdr.hdr) } if !checkMagic(cdr.hdr.Magic) { return nil, fmt.Errorf("invalid magic string: not a counter data file") } if cdr.hdr.Version > coverage.CounterFileVersion { return nil, fmt.Errorf("version data incompatibility: reader is %d data is %d", coverage.CounterFileVersion, cdr.hdr.Version) } // Read footer. if err := cdr.readFooter(); err != nil { return nil, err } // Seek back to just past the file header. hsz := int64(unsafe.Sizeof(cdr.hdr)) if _, err := cdr.mr.Seek(hsz, io.SeekStart); err != nil { return nil, err } // Read preamble for first segment. if err := cdr.readSegmentPreamble(); err != nil { return nil, err } return cdr, nil } func checkMagic(v [4]byte) bool { g := coverage.CovCounterMagic return v[0] == g[0] && v[1] == g[1] && v[2] == g[2] && v[3] == g[3] } func (cdr *CounterDataReader) readFooter() error { ftrSize := int64(unsafe.Sizeof(cdr.ftr)) if _, err := cdr.mr.Seek(-ftrSize, io.SeekEnd); err != nil { return err } if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.ftr); err != nil { return err } if !checkMagic(cdr.ftr.Magic) { return fmt.Errorf("invalid magic string (not a counter data file)") } if cdr.ftr.NumSegments == 0 { return fmt.Errorf("invalid counter data file (no segments)") } return nil } // readSegmentPreamble reads and consumes the segment header, segment string // table, and segment args table. func (cdr *CounterDataReader) readSegmentPreamble() error { // Read segment header. if err := binary.Read(cdr.mr, binary.LittleEndian, &cdr.shdr); err != nil { return err } if cdr.debug { fmt.Fprintf(os.Stderr, "=-= read counter segment header: %+v", cdr.shdr) fmt.Fprintf(os.Stderr, " FcnEntries=0x%x StrTabLen=0x%x ArgsLen=0x%x\n", cdr.shdr.FcnEntries, cdr.shdr.StrTabLen, cdr.shdr.ArgsLen) } // Read string table and args. if err := cdr.readStringTable(); err != nil { return err } if err := cdr.readArgs(); err != nil { return err } // Seek past any padding to bring us up to a 4-byte boundary. if of, err := cdr.mr.Seek(0, io.SeekCurrent); err != nil { return err } else { rem := of % 4 if rem != 0 { pad := 4 - rem if _, err := cdr.mr.Seek(pad, io.SeekCurrent); err != nil { return err } } } return nil } func (cdr *CounterDataReader) readStringTable() error { b := make([]byte, cdr.shdr.StrTabLen) nr, err := cdr.mr.Read(b) if err != nil { return err } if nr != int(cdr.shdr.StrTabLen) { return fmt.Errorf("error: short read on string table") } slr := slicereader.NewReader(b, false /* not readonly */) cdr.stab = stringtab.NewReader(slr) cdr.stab.Read() return nil } func (cdr *CounterDataReader) readArgs() error { b := make([]byte, cdr.shdr.ArgsLen) nr, err := cdr.mr.Read(b) if err != nil { return err } if nr != int(cdr.shdr.ArgsLen) { return fmt.Errorf("error: short read on args table") } slr := slicereader.NewReader(b, false /* not readonly */) sget := func() (string, error) { kidx := slr.ReadULEB128() if int(kidx) >= cdr.stab.Entries() { return "", fmt.Errorf("malformed string table ref") } return cdr.stab.Get(uint32(kidx)), nil } nents := slr.ReadULEB128() cdr.args = make(map[string]string, int(nents)) for i := uint64(0); i < nents; i++ { k, errk := sget() if errk != nil { return errk } v, errv := sget() if errv != nil { return errv } if _, ok := cdr.args[k]; ok { return fmt.Errorf("malformed args table") } cdr.args[k] = v } if argcs, ok := cdr.args["argc"]; ok { argc, err := strconv.Atoi(argcs) if err != nil { return fmt.Errorf("malformed argc in counter data file args section") } cdr.osargs = make([]string, 0, argc) for i := 0; i < argc; i++ { arg := cdr.args[fmt.Sprintf("argv%d", i)] cdr.osargs = append(cdr.osargs, arg) } } if goos, ok := cdr.args["GOOS"]; ok { cdr.goos = goos } if goarch, ok := cdr.args["GOARCH"]; ok { cdr.goarch = goarch } return nil } // OsArgs returns the program arguments (saved from os.Args during // the run of the instrumented binary) read from the counter // data file. Not all coverage data files will have os.Args values; // for example, if a data file is produced by merging coverage // data from two distinct runs, no os args will be available (an // empty list is returned). func (cdr *CounterDataReader) OsArgs() []string { return cdr.osargs } // Goos returns the GOOS setting in effect for the "-cover" binary // that produced this counter data file. The GOOS value may be // empty in the case where the counter data file was produced // from a merge in which more than one GOOS value was present. func (cdr *CounterDataReader) Goos() string { return cdr.goos } // Goarch returns the GOARCH setting in effect for the "-cover" binary // that produced this counter data file. The GOARCH value may be // empty in the case where the counter data file was produced // from a merge in which more than one GOARCH value was present. func (cdr *CounterDataReader) Goarch() string { return cdr.goarch } // FuncPayload encapsulates the counter data payload for a single // function as read from a counter data file. type FuncPayload struct { PkgIdx uint32 FuncIdx uint32 Counters []uint32 } // NumSegments returns the number of execution segments in the file. func (cdr *CounterDataReader) NumSegments() uint32 { return cdr.ftr.NumSegments } // BeginNextSegment sets up the reader to read the next segment, // returning TRUE if we do have another segment to read, or FALSE // if we're done with all the segments (also an error if // something went wrong). func (cdr *CounterDataReader) BeginNextSegment() (bool, error) { if cdr.segCount >= cdr.ftr.NumSegments { return false, nil } cdr.segCount++ cdr.fcnCount = 0 // Seek past footer from last segment. ftrSize := int64(unsafe.Sizeof(cdr.ftr)) if _, err := cdr.mr.Seek(ftrSize, io.SeekCurrent); err != nil { return false, err } // Read preamble for this segment. if err := cdr.readSegmentPreamble(); err != nil { return false, err } return true, nil } // NumFunctionsInSegment returns the number of live functions // in the currently selected segment. func (cdr *CounterDataReader) NumFunctionsInSegment() uint32 { return uint32(cdr.shdr.FcnEntries) } const supportDeadFunctionsInCounterData = false // NextFunc reads data for the next function in this current segment // into "p", returning TRUE if the read was successful or FALSE // if we've read all the functions already (also an error if // something went wrong with the read or we hit a premature // EOF). func (cdr *CounterDataReader) NextFunc(p *FuncPayload) (bool, error) { if cdr.fcnCount >= uint32(cdr.shdr.FcnEntries) { return false, nil } cdr.fcnCount++ var rdu32 func() (uint32, error) if cdr.hdr.CFlavor == coverage.CtrULeb128 { rdu32 = func() (uint32, error) { var shift uint var value uint64 for { _, err := cdr.mr.Read(cdr.u8b) if err != nil { return 0, err } b := cdr.u8b[0] value |= (uint64(b&0x7F) << shift) if b&0x80 == 0 { break } shift += 7 } return uint32(value), nil } } else if cdr.hdr.CFlavor == coverage.CtrRaw { if cdr.hdr.BigEndian { rdu32 = func() (uint32, error) { n, err := cdr.mr.Read(cdr.u32b) if err != nil { return 0, err } if n != 4 { return 0, io.EOF } return binary.BigEndian.Uint32(cdr.u32b), nil } } else { rdu32 = func() (uint32, error) { n, err := cdr.mr.Read(cdr.u32b) if err != nil { return 0, err } if n != 4 { return 0, io.EOF } return binary.LittleEndian.Uint32(cdr.u32b), nil } } } else { panic("internal error: unknown counter flavor") } // Alternative/experimental path: one way we could handling writing // out counter data would be to just memcpy the counter segment // out to a file, meaning that a region in the counter memory // corresponding to a dead (never-executed) function would just be // zeroes. The code path below handles this case. var nc uint32 var err error if supportDeadFunctionsInCounterData { for { nc, err = rdu32() if err == io.EOF { return false, io.EOF } else if err != nil { break } if nc != 0 { break } } } else { nc, err = rdu32() } if err != nil { return false, err } // Read package and func indices. p.PkgIdx, err = rdu32() if err != nil { return false, err } p.FuncIdx, err = rdu32() if err != nil { return false, err } if cap(p.Counters) < 1024 { p.Counters = make([]uint32, 0, 1024) } p.Counters = p.Counters[:0] for i := uint32(0); i < nc; i++ { v, err := rdu32() if err != nil { return false, err } p.Counters = append(p.Counters, v) } return true, nil }