github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/coverage/encodecounter/encode.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 encodecounter 6 7 import ( 8 "bufio" 9 "encoding/binary" 10 "fmt" 11 "io" 12 "os" 13 "sort" 14 15 "github.com/go-asm/go/coverage" 16 "github.com/go-asm/go/coverage/slicewriter" 17 "github.com/go-asm/go/coverage/stringtab" 18 "github.com/go-asm/go/coverage/uleb128" 19 ) 20 21 // This package contains APIs and helpers for encoding initial portions 22 // of the counter data files emitted at runtime when coverage instrumentation 23 // is enabled. Counter data files may contain multiple segments; the file 24 // header and first segment are written via the "Write" method below, and 25 // additional segments can then be added using "AddSegment". 26 27 type CoverageDataWriter struct { 28 stab *stringtab.Writer 29 w *bufio.Writer 30 csh coverage.CounterSegmentHeader 31 tmp []byte 32 cflavor coverage.CounterFlavor 33 segs uint32 34 debug bool 35 } 36 37 func NewCoverageDataWriter(w io.Writer, flav coverage.CounterFlavor) *CoverageDataWriter { 38 r := &CoverageDataWriter{ 39 stab: &stringtab.Writer{}, 40 w: bufio.NewWriter(w), 41 42 tmp: make([]byte, 64), 43 cflavor: flav, 44 } 45 r.stab.InitWriter() 46 r.stab.Lookup("") 47 return r 48 } 49 50 // CounterVisitor describes a helper object used during counter file 51 // writing; when writing counter data files, clients pass a 52 // CounterVisitor to the write/emit routines, then the expectation is 53 // that the VisitFuncs method will then invoke the callback "f" with 54 // data for each function to emit to the file. 55 type CounterVisitor interface { 56 VisitFuncs(f CounterVisitorFn) error 57 } 58 59 // CounterVisitorFn describes a callback function invoked when writing 60 // coverage counter data. 61 type CounterVisitorFn func(pkid uint32, funcid uint32, counters []uint32) error 62 63 // Write writes the contents of the count-data file to the writer 64 // previously supplied to NewCoverageDataWriter. Returns an error 65 // if something went wrong somewhere with the write. 66 func (cfw *CoverageDataWriter) Write(metaFileHash [16]byte, args map[string]string, visitor CounterVisitor) error { 67 if err := cfw.writeHeader(metaFileHash); err != nil { 68 return err 69 } 70 return cfw.AppendSegment(args, visitor) 71 } 72 73 func padToFourByteBoundary(ws *slicewriter.WriteSeeker) error { 74 sz := len(ws.BytesWritten()) 75 zeros := []byte{0, 0, 0, 0} 76 rem := uint32(sz) % 4 77 if rem != 0 { 78 pad := zeros[:(4 - rem)] 79 if nw, err := ws.Write(pad); err != nil { 80 return err 81 } else if nw != len(pad) { 82 return fmt.Errorf("error: short write") 83 } 84 } 85 return nil 86 } 87 88 func (cfw *CoverageDataWriter) patchSegmentHeader(ws *slicewriter.WriteSeeker) error { 89 // record position 90 off, err := ws.Seek(0, io.SeekCurrent) 91 if err != nil { 92 return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) 93 } 94 // seek back to start so that we can update the segment header 95 if _, err := ws.Seek(0, io.SeekStart); err != nil { 96 return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) 97 } 98 if cfw.debug { 99 fmt.Fprintf(os.Stderr, "=-= writing counter segment header: %+v", cfw.csh) 100 } 101 if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil { 102 return err 103 } 104 // ... and finally return to the original offset. 105 if _, err := ws.Seek(off, io.SeekStart); err != nil { 106 return fmt.Errorf("error seeking in patchSegmentHeader: %v", err) 107 } 108 return nil 109 } 110 111 func (cfw *CoverageDataWriter) writeSegmentPreamble(args map[string]string, ws *slicewriter.WriteSeeker) error { 112 if err := binary.Write(ws, binary.LittleEndian, cfw.csh); err != nil { 113 return err 114 } 115 hdrsz := uint32(len(ws.BytesWritten())) 116 117 // Write string table and args to a byte slice (since we need 118 // to capture offsets at various points), then emit the slice 119 // once we are done. 120 cfw.stab.Freeze() 121 if err := cfw.stab.Write(ws); err != nil { 122 return err 123 } 124 cfw.csh.StrTabLen = uint32(len(ws.BytesWritten())) - hdrsz 125 126 akeys := make([]string, 0, len(args)) 127 for k := range args { 128 akeys = append(akeys, k) 129 } 130 sort.Strings(akeys) 131 132 wrULEB128 := func(v uint) error { 133 cfw.tmp = cfw.tmp[:0] 134 cfw.tmp = uleb128.AppendUleb128(cfw.tmp, v) 135 if _, err := ws.Write(cfw.tmp); err != nil { 136 return err 137 } 138 return nil 139 } 140 141 // Count of arg pairs. 142 if err := wrULEB128(uint(len(args))); err != nil { 143 return err 144 } 145 // Arg pairs themselves. 146 for _, k := range akeys { 147 ki := uint(cfw.stab.Lookup(k)) 148 if err := wrULEB128(ki); err != nil { 149 return err 150 } 151 v := args[k] 152 vi := uint(cfw.stab.Lookup(v)) 153 if err := wrULEB128(vi); err != nil { 154 return err 155 } 156 } 157 if err := padToFourByteBoundary(ws); err != nil { 158 return err 159 } 160 cfw.csh.ArgsLen = uint32(len(ws.BytesWritten())) - (cfw.csh.StrTabLen + hdrsz) 161 162 return nil 163 } 164 165 // AppendSegment appends a new segment to a counter data, with a new 166 // args section followed by a payload of counter data clauses. 167 func (cfw *CoverageDataWriter) AppendSegment(args map[string]string, visitor CounterVisitor) error { 168 cfw.stab = &stringtab.Writer{} 169 cfw.stab.InitWriter() 170 cfw.stab.Lookup("") 171 172 var err error 173 for k, v := range args { 174 cfw.stab.Lookup(k) 175 cfw.stab.Lookup(v) 176 } 177 178 ws := &slicewriter.WriteSeeker{} 179 if err = cfw.writeSegmentPreamble(args, ws); err != nil { 180 return err 181 } 182 if err = cfw.writeCounters(visitor, ws); err != nil { 183 return err 184 } 185 if err = cfw.patchSegmentHeader(ws); err != nil { 186 return err 187 } 188 if err := cfw.writeBytes(ws.BytesWritten()); err != nil { 189 return err 190 } 191 if err = cfw.writeFooter(); err != nil { 192 return err 193 } 194 if err := cfw.w.Flush(); err != nil { 195 return fmt.Errorf("write error: %v", err) 196 } 197 cfw.stab = nil 198 return nil 199 } 200 201 func (cfw *CoverageDataWriter) writeHeader(metaFileHash [16]byte) error { 202 // Emit file header. 203 ch := coverage.CounterFileHeader{ 204 Magic: coverage.CovCounterMagic, 205 Version: coverage.CounterFileVersion, 206 MetaHash: metaFileHash, 207 CFlavor: cfw.cflavor, 208 BigEndian: false, 209 } 210 if err := binary.Write(cfw.w, binary.LittleEndian, ch); err != nil { 211 return err 212 } 213 return nil 214 } 215 216 func (cfw *CoverageDataWriter) writeBytes(b []byte) error { 217 if len(b) == 0 { 218 return nil 219 } 220 nw, err := cfw.w.Write(b) 221 if err != nil { 222 return fmt.Errorf("error writing counter data: %v", err) 223 } 224 if len(b) != nw { 225 return fmt.Errorf("error writing counter data: short write") 226 } 227 return nil 228 } 229 230 func (cfw *CoverageDataWriter) writeCounters(visitor CounterVisitor, ws *slicewriter.WriteSeeker) error { 231 // Notes: 232 // - this version writes everything little-endian, which means 233 // a call is needed to encode every value (expensive) 234 // - we may want to move to a model in which we just blast out 235 // all counters, or possibly mmap the file and do the write 236 // implicitly. 237 ctrb := make([]byte, 4) 238 wrval := func(val uint32) error { 239 var buf []byte 240 var towr int 241 if cfw.cflavor == coverage.CtrRaw { 242 binary.LittleEndian.PutUint32(ctrb, val) 243 buf = ctrb 244 towr = 4 245 } else if cfw.cflavor == coverage.CtrULeb128 { 246 cfw.tmp = cfw.tmp[:0] 247 cfw.tmp = uleb128.AppendUleb128(cfw.tmp, uint(val)) 248 buf = cfw.tmp 249 towr = len(buf) 250 } else { 251 panic("internal error: bad counter flavor") 252 } 253 if sz, err := ws.Write(buf); err != nil { 254 return err 255 } else if sz != towr { 256 return fmt.Errorf("writing counters: short write") 257 } 258 return nil 259 } 260 261 // Write out entries for each live function. 262 emitter := func(pkid uint32, funcid uint32, counters []uint32) error { 263 cfw.csh.FcnEntries++ 264 if err := wrval(uint32(len(counters))); err != nil { 265 return err 266 } 267 268 if err := wrval(pkid); err != nil { 269 return err 270 } 271 272 if err := wrval(funcid); err != nil { 273 return err 274 } 275 for _, val := range counters { 276 if err := wrval(val); err != nil { 277 return err 278 } 279 } 280 return nil 281 } 282 if err := visitor.VisitFuncs(emitter); err != nil { 283 return err 284 } 285 return nil 286 } 287 288 func (cfw *CoverageDataWriter) writeFooter() error { 289 cfw.segs++ 290 cf := coverage.CounterFileFooter{ 291 Magic: coverage.CovCounterMagic, 292 NumSegments: cfw.segs, 293 } 294 if err := binary.Write(cfw.w, binary.LittleEndian, cf); err != nil { 295 return err 296 } 297 return nil 298 }