| // 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 |
| } |