internal/coverage: add coverage meta-data encoder

Add a new package with APIs for encoding coverage meta-data. This
provides support for accumulating information about each function
during the compilation process, and then encoding and emitting a
payload for a coverage meta-data symbol.  Not yet connected to the
rest of the coverage machinery (that will appear in a later patch).

Updates #51430.

Change-Id: I61054ce87f205b25fb1bfedaa740fd7425c34de4
Reviewed-on: https://go-review.googlesource.com/c/go/+/353453
Run-TryBot: Than McIntosh <thanm@google.com>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Than McIntosh 2021-09-29 16:41:49 -04:00
parent a3434b71a3
commit f951f697c4
6 changed files with 839 additions and 1 deletions

View file

@ -40,7 +40,8 @@ var depsRules = `
# No dependencies allowed for any of these packages.
NONE
< constraints, container/list, container/ring,
internal/cfg, internal/cpu, internal/goarch,
internal/cfg, internal/cpu, internal/coverage,
internal/coverage/uleb128, internal/goarch,
internal/goexperiment, internal/goos,
internal/goversion, internal/nettrace,
unicode/utf8, unicode/utf16, unicode,
@ -547,6 +548,13 @@ var depsRules = `
FMT
< internal/diff, internal/txtar;
FMT, io, internal/coverage/uleb128
< internal/coverage/stringtab;
FMT, encoding/binary, internal/coverage, internal/coverage/stringtab,
io, os, bufio, crypto/md5
< internal/coverage/encodemeta;
`
// listStdPkgs returns the same list of packages as "go list std".

View file

@ -0,0 +1,374 @@
// Copyright 2022 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 coverage
// Types and constants related to the output files files written
// by code coverage tooling. When a coverage-instrumented binary
// is run, it emits two output files: a meta-data output file, and
// a counter data output file.
//.....................................................................
//
// Meta-data definitions:
//
// The meta-data file is composed of a file header, a series of
// meta-data blobs/sections (one per instrumented package), and an offsets
// area storing the offsets of each section. Format of the meta-data
// file looks like:
//
// --header----------
// | magic: [4]byte magic string
// | version
// | total length of meta-data file in bytes
// | numPkgs: number of package entries in file
// | hash: [16]byte hash of entire meta-data payload
// | offset to string table section
// | length of string table
// | number of entries in string table
// | counter mode
// | counter granularity
// --package offsets table------
// <offset to pkg 0>
// <offset to pkg 1>
// ...
// --package lengths table------
// <length of pkg 0>
// <length of pkg 1>
// ...
// --string table------
// <uleb128 len> 8
// <data> "somestring"
// ...
// --package payloads------
// <meta-symbol for pkg 0>
// <meta-symbol for pkg 1>
// ...
//
// Each package payload is a stand-alone blob emitted by the compiler,
// and does not depend on anything else in the meta-data file. In
// particular, each blob has it's own string table. Note that the
// file-level string table is expected to be very short (most strings
// will be in the meta-data blobs themselves).
// CovMetaMagic holds the magic string for a meta-data file.
var CovMetaMagic = [4]byte{'\x00', '\x63', '\x76', '\x6d'}
// MetaFilePref is a prefix used when emitting meta-data files; these
// files are of the form "covmeta.<hash>", where hash is a hash
// computed from the hashes of all the package meta-data symbols in
// the program.
const MetaFilePref = "covmeta"
// MetaFileVersion contains the current (most recent) meta-data file version.
const MetaFileVersion = 1
// MetaFileHeader stores file header information for a meta-data file.
type MetaFileHeader struct {
Magic [4]byte
Version uint32
TotalLength uint64
Entries uint64
MetaFileHash [16]byte
StrTabOffset uint32
StrTabLength uint32
CMode CounterMode
CGranularity CounterGranularity
_ [6]byte // padding
}
// MetaSymbolHeader stores header information for a single
// meta-data blob, e.g. the coverage meta-data payload
// computed for a given Go package.
type MetaSymbolHeader struct {
Length uint32 // size of meta-symbol payload in bytes
PkgName uint32 // string table index
PkgPath uint32 // string table index
ModulePath uint32 // string table index
MetaHash [16]byte
_ byte // currently unused
_ [3]byte // padding
NumFiles uint32
NumFuncs uint32
}
const CovMetaHeaderSize = 16 + 4 + 4 + 4 + 4 + 4 + 4 + 4 // keep in sync with above
// As an example, consider the following Go package:
//
// 01: package p
// 02:
// 03: var v, w, z int
// 04:
// 05: func small(x, y int) int {
// 06: v++
// 07: // comment
// 08: if y == 0 {
// 09: return x
// 10: }
// 11: return (x << 1) ^ (9 / y)
// 12: }
// 13:
// 14: func Medium(q, r int) int {
// 15: s1 := small(q, r)
// 16: z += s1
// 17: s2 := small(r, q)
// 18: w -= s2
// 19: return w + z
// 20: }
//
// The meta-data blob for the single package above might look like the
// following:
//
// -- MetaSymbolHeader header----------
// | size: size of this blob in bytes
// | packagepath: <path to p>
// | modulepath: <modpath for p>
// | nfiles: 1
// | nfunctions: 2
// --func offsets table------
// <offset to func 0>
// <offset to func 1>
// --string table (contains all files and functions)------
// | <uleb128 len> 4
// | <data> "p.go"
// | <uleb128 len> 5
// | <data> "small"
// | <uleb128 len> 6
// | <data> "Medium"
// --func 0------
// | <uleb128> num units: 3
// | <uleb128> func name: S1 (index into string table)
// | <uleb128> file: S0 (index into string table)
// | <unit 0>: S0 L6 L8 2
// | <unit 1>: S0 L9 L9 1
// | <unit 2>: S0 L11 L11 1
// --func 1------
// | <uleb128> num units: 1
// | <uleb128> func name: S2 (index into string table)
// | <uleb128> file: S0 (index into string table)
// | <unit 0>: S0 L15 L19 5
// ---end-----------
// The following types and constants used by the meta-data encoder/decoder.
// FuncDesc encapsulates the meta-data definitions for a single Go function.
// This version assumes that we're looking at a function before inlining;
// if we want to capture a post-inlining view of the world, the
// representations of source positions would need to be a good deal more
// complicated.
type FuncDesc struct {
Funcname string
Srcfile string
Units []CoverableUnit
Lit bool // true if this is a function literal
}
// CoverableUnit describes the source characteristics of a single
// program unit for which we want to gather coverage info. Coverable
// units are either "simple" or "intraline"; a "simple" coverable unit
// corresponds to a basic block (region of straight-line code with no
// jumps or control transfers). An "intraline" unit corresponds to a
// logical clause nested within some other simple unit. A simple unit
// will have a zero Parent value; for an intraline unit NxStmts will
// be zero and and Parent will be set to 1 plus the index of the
// containing simple statement. Example:
//
// L7: q := 1
// L8: x := (y == 101 || launch() == false)
// L9: r := x * 2
//
// For the code above we would have three simple units (one for each
// line), then an intraline unit describing the "launch() == false"
// clause in line 8, with Parent pointing to the index of the line 8
// unit in the units array.
//
// Note: in the initial version of the coverage revamp, only simple
// units will be in use.
type CoverableUnit struct {
StLine, StCol uint32
EnLine, EnCol uint32
NxStmts uint32
Parent uint32
}
// CounterMode tracks the "flavor" of the coverage counters being
// used in a given coverage-instrumented program.
type CounterMode uint8
const (
CtrModeInvalid CounterMode = iota
CtrModeSet // "set" mode
CtrModeCount // "count" mode
CtrModeAtomic // "atomic" mode
CtrModeRegOnly // registration-only pseudo-mode
CtrModeTestMain // testmain pseudo-mode
)
func (cm CounterMode) String() string {
switch cm {
case CtrModeSet:
return "set"
case CtrModeCount:
return "count"
case CtrModeAtomic:
return "atomic"
case CtrModeRegOnly:
return "regonly"
case CtrModeTestMain:
return "testmain"
}
return "<invalid>"
}
func ParseCounterMode(mode string) CounterMode {
var cm CounterMode
switch mode {
case "set":
cm = CtrModeSet
case "count":
cm = CtrModeCount
case "atomic":
cm = CtrModeAtomic
case "regonly":
cm = CtrModeRegOnly
case "testmain":
cm = CtrModeTestMain
default:
cm = CtrModeInvalid
}
return cm
}
// CounterGranularity tracks the granularity of the coverage counters being
// used in a given coverage-instrumented program.
type CounterGranularity uint8
const (
CtrGranularityInvalid CounterGranularity = iota
CtrGranularityPerBlock
CtrGranularityPerFunc
)
func (cm CounterGranularity) String() string {
switch cm {
case CtrGranularityPerBlock:
return "perblock"
case CtrGranularityPerFunc:
return "perfunc"
}
return "<invalid>"
}
//.....................................................................
//
// Counter data definitions:
//
// A counter data file is composed of a file header followed by one or
// more "segments" (each segment representing a given run or partial
// run of a give binary) followed by a footer.
// CovCounterMagic holds the magic string for a coverage counter-data file.
var CovCounterMagic = [4]byte{'\x00', '\x63', '\x77', '\x6d'}
// CounterFileVersion stores the most recent counter data file version.
const CounterFileVersion = 1
// CounterFileHeader stores files header information for a counter-data file.
type CounterFileHeader struct {
Magic [4]byte
Version uint32
MetaHash [16]byte
CFlavor CounterFlavor
BigEndian bool
_ [6]byte // padding
}
// CounterSegmentHeader encapsulates information about a specific
// segment in a counter data file, which at the moment contains
// counters data from a single execution of a coverage-instrumented
// program. Following the segment header will be the string table and
// args table, and then (possibly) padding bytes to bring the byte
// size of the preamble up to a multiple of 4. Immediately following
// that will be the counter payloads.
//
// The "args" section of a segment is used to store annotations
// describing where the counter data came from; this section is
// basically a series of key-value pairs (can be thought of as an
// encoded 'map[string]string'). At the moment we only write os.Args()
// data to this section, using pairs of the form "argc=<integer>",
// "argv0=<os.Args[0]>", "argv1=<os.Args[1]>", and so on. In the
// future the args table may also include things like GOOS/GOARCH
// values, and/or tags indicating which tests were run to generate the
// counter data.
type CounterSegmentHeader struct {
FcnEntries uint64
StrTabLen uint32
ArgsLen uint32
}
// CounterFileFooter appears at the tail end of a counter data file,
// and stores the number of segments it contains.
type CounterFileFooter struct {
Magic [4]byte
_ [4]byte // padding
NumSegments uint32
_ [4]byte // padding
}
// CounterFilePref is the file prefix used when emitting coverage data
// output files. CounterFileTemplate describes the format of the file
// name: prefix followed by meta-file hash followed by process ID
// followed by emit UnixNanoTime.
const CounterFilePref = "covcounters"
const CounterFileTempl = "%s.%x.%d.%d"
const CounterFileRegexp = `^%s\.(\S+)\.(\d+)\.(\d+)+$`
// CounterFlavor describes how function and counters are
// stored/represented in the counter section of the file.
type CounterFlavor uint8
const (
// "Raw" representation: all values (pkg ID, func ID, num counters,
// and counters themselves) are stored as uint32's.
CtrRaw CounterFlavor = iota + 1
// "ULeb" representation: all values (pkg ID, func ID, num counters,
// and counters themselves) are stored with ULEB128 encoding.
CtrULeb128
)
func Round4(x int) int {
return (x + 3) &^ 3
}
//.....................................................................
//
// Runtime counter data definitions.
//
// At runtime within a coverage-instrumented program, the "counters"
// object we associated with instrumented function can be thought of
// as a struct of the following form:
//
// struct {
// numCtrs uint32
// pkgid uint32
// funcid uint32
// counterArray [numBlocks]uint32
// }
//
// where "numCtrs" is the number of blocks / coverable units within the
// function, "pkgid" is the unique index assigned to this package by
// the runtime, "funcid" is the index of this function within its containing
// packge, and "counterArray" stores the actual counters.
//
// The counter variable itself is created not as a struct but as a flat
// array of uint32's; we then use the offsets below to index into it.
const NumCtrsOffset = 0
const PkgIdOffset = 1
const FuncIdOffset = 2
const FirstCtrOffset = 3

View file

@ -0,0 +1,215 @@
// 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 encodemeta
// This package contains APIs and helpers for encoding the meta-data
// "blob" for a single Go package, created when coverage
// instrumentation is turned on.
import (
"crypto/md5"
"encoding/binary"
"fmt"
"hash"
"internal/coverage"
"internal/coverage/stringtab"
"internal/coverage/uleb128"
"io"
"os"
)
type CoverageMetaDataBuilder struct {
stab stringtab.Writer
funcs []funcDesc
tmp []byte // temp work slice
h hash.Hash
pkgpath uint32
pkgname uint32
modpath uint32
debug bool
werr error
}
func NewCoverageMetaDataBuilder(pkgpath string, pkgname string, modulepath string) (*CoverageMetaDataBuilder, error) {
if pkgpath == "" {
return nil, fmt.Errorf("invalid empty package path")
}
x := &CoverageMetaDataBuilder{
tmp: make([]byte, 0, 256),
h: md5.New(),
}
x.stab.InitWriter()
x.stab.Lookup("")
x.pkgpath = x.stab.Lookup(pkgpath)
x.pkgname = x.stab.Lookup(pkgname)
x.modpath = x.stab.Lookup(modulepath)
io.WriteString(x.h, pkgpath)
io.WriteString(x.h, pkgname)
io.WriteString(x.h, modulepath)
return x, nil
}
func h32(x uint32, h hash.Hash, tmp []byte) {
tmp = tmp[:0]
tmp = append(tmp, []byte{0, 0, 0, 0}...)
binary.LittleEndian.PutUint32(tmp, x)
h.Write(tmp)
}
type funcDesc struct {
encoded []byte
}
// AddFunc registers a new function with the meta data builder.
func (b *CoverageMetaDataBuilder) AddFunc(f coverage.FuncDesc) uint {
hashFuncDesc(b.h, &f, b.tmp)
fd := funcDesc{}
b.tmp = b.tmp[:0]
b.tmp = uleb128.AppendUleb128(b.tmp, uint(len(f.Units)))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Funcname)))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Srcfile)))
for _, u := range f.Units {
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StLine))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StCol))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnLine))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnCol))
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.NxStmts))
}
lit := uint(0)
if f.Lit {
lit = 1
}
b.tmp = uleb128.AppendUleb128(b.tmp, lit)
fd.encoded = make([]byte, len(b.tmp))
copy(fd.encoded, b.tmp)
rv := uint(len(b.funcs))
b.funcs = append(b.funcs, fd)
return rv
}
func (b *CoverageMetaDataBuilder) emitFuncOffsets(w io.WriteSeeker, off int64) int64 {
nFuncs := len(b.funcs)
var foff int64 = coverage.CovMetaHeaderSize + int64(b.stab.Size()) + int64(nFuncs)*4
for idx := 0; idx < nFuncs; idx++ {
b.wrUint32(w, uint32(foff))
foff += int64(len(b.funcs[idx].encoded))
}
return off + (int64(len(b.funcs)) * 4)
}
func (b *CoverageMetaDataBuilder) emitFunc(w io.WriteSeeker, off int64, f funcDesc) (int64, error) {
ew := len(f.encoded)
if nw, err := w.Write(f.encoded); err != nil {
return 0, err
} else if ew != nw {
return 0, fmt.Errorf("short write emitting coverage meta-data")
}
return off + int64(ew), nil
}
func (b *CoverageMetaDataBuilder) reportWriteError(err error) {
if b.werr != nil {
b.werr = err
}
}
func (b *CoverageMetaDataBuilder) wrUint32(w io.WriteSeeker, v uint32) {
b.tmp = b.tmp[:0]
b.tmp = append(b.tmp, []byte{0, 0, 0, 0}...)
binary.LittleEndian.PutUint32(b.tmp, v)
if nw, err := w.Write(b.tmp); err != nil {
b.reportWriteError(err)
} else if nw != 4 {
b.reportWriteError(fmt.Errorf("short write"))
}
}
// Emit writes the meta-data accumulated so far in this builder to 'w'.
// Returns a hash of the meta-data payload and an error.
func (b *CoverageMetaDataBuilder) Emit(w io.WriteSeeker) ([16]byte, error) {
// Emit header. Length will initially be zero, we'll
// back-patch it later.
var digest [16]byte
copy(digest[:], b.h.Sum(nil))
mh := coverage.MetaSymbolHeader{
// hash and length initially zero, will be back-patched
PkgPath: uint32(b.pkgpath),
PkgName: uint32(b.pkgname),
ModulePath: uint32(b.modpath),
NumFiles: uint32(b.stab.Nentries()),
NumFuncs: uint32(len(b.funcs)),
MetaHash: digest,
}
if b.debug {
fmt.Fprintf(os.Stderr, "=-= writing header: %+v\n", mh)
}
if err := binary.Write(w, binary.LittleEndian, mh); err != nil {
return digest, fmt.Errorf("error writing meta-file header: %v\n", err)
}
off := int64(coverage.CovMetaHeaderSize)
// Write function offsets section
off = b.emitFuncOffsets(w, off)
// Check for any errors up to this point.
if b.werr != nil {
return digest, b.werr
}
// Write string table.
if err := b.stab.Write(w); err != nil {
return digest, err
}
off += int64(b.stab.Size())
// Write functions
for _, f := range b.funcs {
var err error
off, err = b.emitFunc(w, off, f)
if err != nil {
return digest, err
}
}
// Back-patch the length.
totalLength := uint32(off)
if _, err := w.Seek(0, os.SEEK_SET); err != nil {
return digest, err
}
b.wrUint32(w, totalLength)
if b.werr != nil {
return digest, b.werr
}
return digest, nil
}
// HashFuncDesc computes an md5 sum of a coverage.FuncDesc and returns
// a digest for it.
func HashFuncDesc(f *coverage.FuncDesc) [16]byte {
h := md5.New()
tmp := make([]byte, 0, 32)
hashFuncDesc(h, f, tmp)
var r [16]byte
copy(r[:], h.Sum(nil))
return r
}
// hashFuncDesc incorporates a given function 'f' into the hash 'h'.
func hashFuncDesc(h hash.Hash, f *coverage.FuncDesc, tmp []byte) {
io.WriteString(h, f.Funcname)
io.WriteString(h, f.Srcfile)
for _, u := range f.Units {
h32(u.StLine, h, tmp)
h32(u.StCol, h, tmp)
h32(u.EnLine, h, tmp)
h32(u.EnCol, h, tmp)
h32(u.NxStmts, h, tmp)
}
lit := uint32(0)
if f.Lit {
lit = 1
}
h32(lit, h, tmp)
}

View file

@ -0,0 +1,132 @@
// 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 encodemeta
import (
"bufio"
"crypto/md5"
"encoding/binary"
"fmt"
"internal/coverage"
"internal/coverage/stringtab"
"io"
"os"
"unsafe"
)
// This package contains APIs and helpers for writing out a meta-data
// file (composed of a file header, offsets/lengths, and then a series of
// meta-data blobs emitted by the compiler, one per Go package).
type CoverageMetaFileWriter struct {
stab stringtab.Writer
mfname string
w *bufio.Writer
tmp []byte
debug bool
}
func NewCoverageMetaFileWriter(mfname string, w io.Writer) *CoverageMetaFileWriter {
r := &CoverageMetaFileWriter{
mfname: mfname,
w: bufio.NewWriter(w),
tmp: make([]byte, 64),
}
r.stab.InitWriter()
r.stab.Lookup("")
return r
}
func (m *CoverageMetaFileWriter) Write(finalHash [16]byte, blobs [][]byte, mode coverage.CounterMode, granularity coverage.CounterGranularity) error {
mhsz := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
stSize := m.stab.Size()
stOffset := mhsz + uint64(16*len(blobs))
preambleLength := stOffset + uint64(stSize)
if m.debug {
fmt.Fprintf(os.Stderr, "=+= sizeof(MetaFileHeader)=%d\n", mhsz)
fmt.Fprintf(os.Stderr, "=+= preambleLength=%d stSize=%d\n", preambleLength, stSize)
}
// Compute total size
tlen := preambleLength
for i := 0; i < len(blobs); i++ {
tlen += uint64(len(blobs[i]))
}
// Emit header
mh := coverage.MetaFileHeader{
Magic: coverage.CovMetaMagic,
Version: coverage.MetaFileVersion,
TotalLength: tlen,
Entries: uint64(len(blobs)),
MetaFileHash: finalHash,
StrTabOffset: uint32(stOffset),
StrTabLength: stSize,
CMode: mode,
CGranularity: granularity,
}
var err error
if err = binary.Write(m.w, binary.LittleEndian, mh); err != nil {
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
}
if m.debug {
fmt.Fprintf(os.Stderr, "=+= len(blobs) is %d\n", mh.Entries)
}
// Emit package offsets section followed by package lengths section.
off := preambleLength
off2 := mhsz
buf := make([]byte, 8)
for _, blob := range blobs {
binary.LittleEndian.PutUint64(buf, off)
if _, err = m.w.Write(buf); err != nil {
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
}
if m.debug {
fmt.Fprintf(os.Stderr, "=+= pkg offset %d 0x%x\n", off, off)
}
off += uint64(len(blob))
off2 += 8
}
for _, blob := range blobs {
bl := uint64(len(blob))
binary.LittleEndian.PutUint64(buf, bl)
if _, err = m.w.Write(buf); err != nil {
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
}
if m.debug {
fmt.Fprintf(os.Stderr, "=+= pkg len %d 0x%x\n", bl, bl)
}
off2 += 8
}
// Emit string table
if err = m.stab.Write(m.w); err != nil {
return err
}
// Now emit blobs themselves.
for k, blob := range blobs {
if m.debug {
fmt.Fprintf(os.Stderr, "=+= writing blob %d len %d at off=%d hash %s\n", k, len(blob), off2, fmt.Sprintf("%x", md5.Sum(blob)))
}
if _, err = m.w.Write(blob); err != nil {
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
}
if m.debug {
fmt.Fprintf(os.Stderr, "=+= wrote package payload of %d bytes\n",
len(blob))
}
off2 += uint64(len(blob))
}
// Flush writer, and we're done.
if err = m.w.Flush(); err != nil {
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
}
return nil
}

View file

@ -0,0 +1,89 @@
// Copyright 2022 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 stringtab
import (
"fmt"
"internal/coverage/uleb128"
"io"
)
// This package implements a string table writer utility for use in
// emitting coverage meta-data and counter-data files.
type Writer struct {
stab map[string]uint32
strs []string
tmp []byte
frozen bool
}
// InitWriter initializes a stringtab.Writer.
func (stw *Writer) InitWriter() {
stw.stab = make(map[string]uint32)
stw.tmp = make([]byte, 64)
}
// Nentries returns the number of strings interned so far.
func (stw *Writer) Nentries() uint32 {
return uint32(len(stw.strs))
}
// Lookup looks up string 's' in the writer's table, adding
// a new entry if need be, and returning an index into the table.
func (stw *Writer) Lookup(s string) uint32 {
if idx, ok := stw.stab[s]; ok {
return idx
}
idx := uint32(len(stw.strs))
stw.stab[s] = idx
stw.strs = append(stw.strs, s)
return idx
}
// Size computes the memory in bytes needed for the serialized
// version of a stringtab.Writer.
func (stw *Writer) Size() uint32 {
rval := uint32(0)
stw.tmp = stw.tmp[:0]
stw.tmp = uleb128.AppendUleb128(stw.tmp, uint(len(stw.strs)))
rval += uint32(len(stw.tmp))
for _, s := range stw.strs {
stw.tmp = stw.tmp[:0]
slen := uint(len(s))
stw.tmp = uleb128.AppendUleb128(stw.tmp, slen)
rval += uint32(len(stw.tmp)) + uint32(slen)
}
return rval
}
// Write writes the string table in serialized form to the specified
// io.Writer.
func (stw *Writer) Write(w io.Writer) error {
wr128 := func(v uint) error {
stw.tmp = stw.tmp[:0]
stw.tmp = uleb128.AppendUleb128(stw.tmp, v)
if nw, err := w.Write(stw.tmp); err != nil {
return fmt.Errorf("writing string table: %v", err)
} else if nw != len(stw.tmp) {
return fmt.Errorf("short write emitting stringtab uleb")
}
return nil
}
if err := wr128(uint(len(stw.strs))); err != nil {
return err
}
for _, s := range stw.strs {
if err := wr128(uint(len(s))); err != nil {
return err
}
if nw, err := w.Write([]byte(s)); err != nil {
return fmt.Errorf("writing string table: %v\n", err)
} else if nw != len([]byte(s)) {
return fmt.Errorf("short write emitting stringtab")
}
}
return nil
}

View file

@ -0,0 +1,20 @@
// 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 uleb128
func AppendUleb128(b []byte, v uint) []byte {
for {
c := uint8(v & 0x7f)
v >>= 7
if v != 0 {
c |= 0x80
}
b = append(b, c)
if c&0x80 == 0 {
break
}
}
return b
}