104 lines
3.1 KiB
Go
104 lines
3.1 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
|
||
|
|
package verify
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"sort"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Block describes one basic block within a function: a maximal sequence of
|
||
|
|
// instructions with a single entry point (a label or the function start) and
|
||
|
|
// a single exit (a jump, conditional jump or RET).
|
||
|
|
type Block struct {
|
||
|
|
Offset int // byte offset within the function
|
||
|
|
Label string // label name ("" for the entry block)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Blocks identifies the basic blocks of a function from its local labels.
|
||
|
|
// Each label is a potential jump target and therefore a block boundary; the
|
||
|
|
// function entry (offset 0) is always a block. The blocks are returned in
|
||
|
|
// ascending offset order.
|
||
|
|
func (k *Kernel) Blocks(name string) ([]Block, error) {
|
||
|
|
idx, ok := k.funcs[name]
|
||
|
|
if !ok {
|
||
|
|
return nil, fmt.Errorf("verify: function %q not found", name)
|
||
|
|
}
|
||
|
|
fl := k.img.Funcs[idx]
|
||
|
|
|
||
|
|
blocks := []Block{{Offset: 0, Label: "(entry)"}}
|
||
|
|
// Build a reverse map: offset → label name.
|
||
|
|
offToLabel := make(map[int]string, len(fl.Labels))
|
||
|
|
for label, off := range fl.Labels {
|
||
|
|
if off > 0 && off < fl.Size {
|
||
|
|
offToLabel[off] = label
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Collect and sort offsets.
|
||
|
|
offsets := make([]int, 0, len(offToLabel))
|
||
|
|
for off := range offToLabel {
|
||
|
|
offsets = append(offsets, off)
|
||
|
|
}
|
||
|
|
sort.Ints(offsets)
|
||
|
|
for _, off := range offsets {
|
||
|
|
blocks = append(blocks, Block{Offset: off, Label: offToLabel[off]})
|
||
|
|
}
|
||
|
|
return blocks, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// BlockCount returns the number of identified basic blocks for the function.
|
||
|
|
func (k *Kernel) BlockCount(name string) (int, error) {
|
||
|
|
blocks, err := k.Blocks(name)
|
||
|
|
if err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
return len(blocks), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// PathFingerprint is the observable output of one function execution: the
|
||
|
|
// values written back into the result slots of the argument block. Two
|
||
|
|
// executions that produce the same fingerprint took observationally
|
||
|
|
// equivalent paths (though they may differ internally).
|
||
|
|
type PathFingerprint struct {
|
||
|
|
Results []uint64 // the result words from the arg block
|
||
|
|
}
|
||
|
|
|
||
|
|
// ProfilePaths runs the function with each of the given argument blocks and
|
||
|
|
// collects the distinct output fingerprints. This measures path diversity:
|
||
|
|
// how many observationally different execution paths the input corpus
|
||
|
|
// exercises. Combined with Blocks (the static block count), it gives a
|
||
|
|
// lower bound on code coverage.
|
||
|
|
func (k *Kernel) ProfilePaths(name string, argSets [][]byte, resultOffsets []int) ([]PathFingerprint, error) {
|
||
|
|
idx, ok := k.funcs[name]
|
||
|
|
if !ok {
|
||
|
|
return nil, fmt.Errorf("verify: function %q not found", name)
|
||
|
|
}
|
||
|
|
fl := k.img.Funcs[idx]
|
||
|
|
|
||
|
|
seen := map[string]bool{}
|
||
|
|
var paths []PathFingerprint
|
||
|
|
|
||
|
|
for _, args := range argSets {
|
||
|
|
if len(args) < fl.Args {
|
||
|
|
return nil, fmt.Errorf("verify: %s: arg block too small", name)
|
||
|
|
}
|
||
|
|
out, err := k.CallFunc(name, args)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
fp := PathFingerprint{}
|
||
|
|
key := ""
|
||
|
|
for _, off := range resultOffsets {
|
||
|
|
v := GetUint64(out, off)
|
||
|
|
fp.Results = append(fp.Results, v)
|
||
|
|
key += fmt.Sprintf("%016x", v)
|
||
|
|
}
|
||
|
|
if !seen[key] {
|
||
|
|
seen[key] = true
|
||
|
|
paths = append(paths, fp)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return paths, nil
|
||
|
|
}
|