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