package decodecounter
import (
"encoding/binary"
"fmt"
"internal/coverage"
"internal/coverage/slicereader"
"internal/coverage/stringtab"
"io"
"os"
"strconv"
"unsafe"
)
type CounterDataReader struct {
stab *stringtab .Reader
args map [string ]string
osargs []string
goarch string
goos string
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 ),
}
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 )
}
if err := cdr .readFooter (); err != nil {
return nil , err
}
hsz := int64 (unsafe .Sizeof (cdr .hdr ))
if _ , err := cdr .mr .Seek (hsz , io .SeekStart ); err != nil {
return nil , err
}
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
}
func (cdr *CounterDataReader ) readSegmentPreamble () error {
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 )
}
if err := cdr .readStringTable (); err != nil {
return err
}
if err := cdr .readArgs (); err != nil {
return err
}
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 )
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 )
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
}
func (cdr *CounterDataReader ) OsArgs () []string {
return cdr .osargs
}
func (cdr *CounterDataReader ) Goos () string {
return cdr .goos
}
func (cdr *CounterDataReader ) Goarch () string {
return cdr .goarch
}
type FuncPayload struct {
PkgIdx uint32
FuncIdx uint32
Counters []uint32
}
func (cdr *CounterDataReader ) NumSegments () uint32 {
return cdr .ftr .NumSegments
}
func (cdr *CounterDataReader ) BeginNextSegment () (bool , error ) {
if cdr .segCount >= cdr .ftr .NumSegments {
return false , nil
}
cdr .segCount ++
cdr .fcnCount = 0
ftrSize := int64 (unsafe .Sizeof (cdr .ftr ))
if _ , err := cdr .mr .Seek (ftrSize , io .SeekCurrent ); err != nil {
return false , err
}
if err := cdr .readSegmentPreamble (); err != nil {
return false , err
}
return true , nil
}
func (cdr *CounterDataReader ) NumFunctionsInSegment () uint32 {
return uint32 (cdr .shdr .FcnEntries )
}
const supportDeadFunctionsInCounterData = false
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" )
}
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
}
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
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .