feat(verify): combine ABI checks with fuzzing for deep-path testing
This commit is contained in:
+22
-9
@@ -633,6 +633,7 @@ With -profile, the static basic-block structure is listed for each function.
|
|||||||
`)
|
`)
|
||||||
smoke := fs.Bool("smoke", false, "call each NOSPLIT function with zeroed args")
|
smoke := fs.Bool("smoke", false, "call each NOSPLIT function with zeroed args")
|
||||||
abi := fs.Bool("abi", false, "run ABI-checking calls (sentinel registers + red zone)")
|
abi := fs.Bool("abi", false, "run ABI-checking calls (sentinel registers + red zone)")
|
||||||
|
abiN := fs.Int("abi-n", 100, "number of ABI check iterations with varied inputs")
|
||||||
profile := fs.Bool("profile", false, "list basic-block structure per function")
|
profile := fs.Bool("profile", false, "list basic-block structure per function")
|
||||||
groundTruth := fs.Bool("ground-truth", false, "compare machine code byte-for-byte against go tool asm")
|
groundTruth := fs.Bool("ground-truth", false, "compare machine code byte-for-byte against go tool asm")
|
||||||
fuzz := fs.Bool("fuzz", false, "differential fuzz: JIT both gasm and go-tool-asm versions, compare outputs")
|
fuzz := fs.Bool("fuzz", false, "differential fuzz: JIT both gasm and go-tool-asm versions, compare outputs")
|
||||||
@@ -823,16 +824,28 @@ With -profile, the static basic-block structure is listed for each function.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *abi && fl.NoSplit {
|
if *abi && fl.NoSplit {
|
||||||
args := make([]byte, fl.Args)
|
// Try varied-input ABI fuzzing first.
|
||||||
_, report, err := k.CallFuncChecked(name, args)
|
if src, err := readSource(path); err == nil {
|
||||||
if err != nil {
|
result := k.FuzzFuncCheckedByName(name, src, *abiN, int64(*abiN))
|
||||||
fmt.Printf(" abi: FAIL — %v\n", err)
|
if result.Mismatches > 0 {
|
||||||
rc = 1
|
fmt.Printf(" abi: %s\n", result)
|
||||||
} else if !report.OK() {
|
rc = 1
|
||||||
fmt.Printf(" abi: %s\n", report)
|
} else {
|
||||||
rc = 1
|
fmt.Printf(" abi: clean (%d varied inputs)\n", result.Matches)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" abi: clean\n")
|
// Fallback: single zeroed-arg call.
|
||||||
|
args := make([]byte, fl.Args)
|
||||||
|
_, report, err := k.CallFuncChecked(name, args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(" abi: FAIL — %v\n", err)
|
||||||
|
rc = 1
|
||||||
|
} else if !report.OK() {
|
||||||
|
fmt.Printf(" abi: %s\n", report)
|
||||||
|
rc = 1
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" abi: clean\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package verify
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
||||||
@@ -114,6 +115,66 @@ func (k *Kernel) CallFuncChecked(name string, args []byte) ([]byte, ABIReport, e
|
|||||||
return CallChecked(fnAddr, args)
|
return CallChecked(fnAddr, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FuzzFuncCheckedByName is like FuzzFuncChecked but extracts the signature
|
||||||
|
// from the source code internally.
|
||||||
|
func (k *Kernel) FuzzFuncCheckedByName(name, src string, iterations int, seed int64) FuzzResult {
|
||||||
|
result := FuzzResult{Func: name, Iterations: iterations}
|
||||||
|
sig, ok := ExtractSignatures(src)[name]
|
||||||
|
if !ok {
|
||||||
|
result.Mismatches = iterations
|
||||||
|
result.FirstFail = "no // func signature found"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return k.FuzzFuncChecked(name, sig, iterations, seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzFuncChecked combines fuzzing with ABI checks: it generates varied
|
||||||
|
// inputs and verifies that callee-saved registers and the red zone are
|
||||||
|
// preserved even on deep execution paths (not just early exits).
|
||||||
|
func (k *Kernel) FuzzFuncChecked(name string, sig funcSig, iterations int, seed int64) FuzzResult {
|
||||||
|
result := FuzzResult{Func: name, Iterations: iterations}
|
||||||
|
rng := rand.New(rand.NewSource(seed))
|
||||||
|
|
||||||
|
fl, err := k.Func(name)
|
||||||
|
if err != nil {
|
||||||
|
result.Mismatches = iterations
|
||||||
|
result.FirstFail = err.Error()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
violations := 0
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
gasmArgs, _, bufs := genDualArgs(rng, sig, fl.Args)
|
||||||
|
result.CrashInput = gasmArgs
|
||||||
|
|
||||||
|
_, report, err := k.CallFuncChecked(name, gasmArgs)
|
||||||
|
if err != nil {
|
||||||
|
result.Mismatches++
|
||||||
|
if result.FirstFail == "" {
|
||||||
|
result.FirstFail = fmt.Sprintf("iter %d: call: %v", i, err)
|
||||||
|
}
|
||||||
|
releaseBufs(bufs)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !report.OK() {
|
||||||
|
violations++
|
||||||
|
result.Mismatches++
|
||||||
|
if result.FirstFail == "" {
|
||||||
|
result.FirstFail = fmt.Sprintf("iter %d: %s", i, report.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.Matches++
|
||||||
|
}
|
||||||
|
releaseBufs(bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if violations > 0 && result.FirstFail == "" {
|
||||||
|
result.FirstFail = fmt.Sprintf("%d ABI violations across %d iterations", violations, iterations)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Close releases the executable mapping.
|
// Close releases the executable mapping.
|
||||||
func (k *Kernel) Close() {
|
func (k *Kernel) Close() {
|
||||||
if k.exec != nil {
|
if k.exec != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user