Documentation
¶
Index ¶
- func ApplyCoverMapToBlockList(coverMap *ebpf.Map, blockList [][]CoverBlock) error
- func BlockListToGoCover(blockList [][]CoverBlock, out io.Writer, mode string)
- func BlockListToHTML(blockList [][]CoverBlock, out io.Writer, mode string) error
- func CFGToBlockList(cfg []*ProgBlock) [][]CoverBlock
- func HtmlOutput(profiles []*cover.Profile, out io.Writer) error
- type CoverBlock
- type ProgBlock
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApplyCoverMapToBlockList ¶
func ApplyCoverMapToBlockList(coverMap *ebpf.Map, blockList [][]CoverBlock) error
ApplyCoverMapToBlockList reads from the coverage map and applies the counts inside the map to the block list. The blocklist can be iterated after this to create a go-cover coverage file.
func BlockListToGoCover ¶
func BlockListToGoCover(blockList [][]CoverBlock, out io.Writer, mode string)
BlockListToGoCover convert a block-list into a go-cover file which can be interpreted by `go tool cover`. `mode` value can be `set` or `count`, see `go tool cover -h` for details.
func BlockListToHTML ¶
func BlockListToHTML(blockList [][]CoverBlock, out io.Writer, mode string) error
BlockListToHTML converts a block-list into a HTML coverage report.
func CFGToBlockList ¶
func CFGToBlockList(cfg []*ProgBlock) [][]CoverBlock
CFGToBlockList convert a CFG to a "BlockList", the outer slice indexed by BlockID which maps to an inner slice, each element of which is a reference to a specific block of code inside a source file. Thus the resulting block list can be used to translate blockID's into the pieces of source code to apply coverage mapping.
Types ¶
type CoverBlock ¶
type CoverBlock struct { Filename string ProfileBlock cover.ProfileBlock }
CoverBlock wraps the ProfileBlock, adding a filename so each CoverBlock can be turned into a line on a go coverage file.
func (CoverBlock) String ¶
func (cb CoverBlock) String() string
type ProgBlock ¶
type ProgBlock struct { Index int // The current block of code Block asm.Instructions // The next block of we don't branch NoBranch *ProgBlock // The next block if we do branch Branch *ProgBlock }
func InstrumentAndLoadCollection ¶
func InstrumentAndLoadCollection( coll *ebpf.CollectionSpec, opts ebpf.CollectionOptions, logWriter io.Writer, ) (*ebpf.Collection, []*ProgBlock, error)
InstrumentAndLoadCollection adds instrumentation instructions to all programs contained within the given collection. This "instrumentation" is nothing more than incrementing a 16-bit number within a map value, at an index unique to the location within the program(Block ID/index). After updating the program, it is loaded into the kernel, the loaded collection and a list of program blocks is returned. The index of the returned program blocks matches the index of blocks in the coverage map.
Steps of the function:
- Load the original programs and collect the verbose verifier log
- Parse the verifier log, which tells us which registers and stack slots are occupied at any given time.
- Convert the program into a CFG(Control Flow Graph)
- At the start of each program and bpf-to-bpf function, load the cover-map's index 0 and store the map value in a available slot on the stack.
- At the start of each block, load an offset into the cover-map value, increment it, write it back. This requires 2 registers which can be clobbered. If only 1 or no registers are unused, store the register values to the stack and restore values afterwards.
- Move symbols of the original code to the instrumented code so jumps and functions calls first pass by the instrumentation.
- Load all modified program into the kernel.
func ProgramBlocks ¶
func ProgramBlocks(prog asm.Instructions) []*ProgBlock
ProgramBlocks takes a list of instructions and converts it into a a CFG(Control Flow Graph). Which works as follows:
- Construct a translation map from RawOffsets to the instructions(since index within the slice doesn't account for LDIMM64 instructions which use two instructions).
- Apply a label to every jump target and set that label as a reference in the branching instruction. This does two things. First, it makes it easy to find all block boundaries since each block has a function name or jump label. The second is that cilium/ebpf will recalculate the offsets of the jumps based on the symbols when loading, so we can easily add instructions to blocks without fear of breaking offsets.
- Loop over all instructions, creating a block at each branching instruction or symbol/jump label.
- Build a translation map from symbol/jump label to block.
- Loop over all blocks, using the map from step 4 to link blocks together on the branching and non-branching edges.