fix(verify): subprocess isolation for --fuzz, partial functions report CRASH gracefully
Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
+83
-5
@@ -14,8 +14,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
||||||
@@ -30,7 +33,7 @@ import (
|
|||||||
|
|
||||||
// version is the release version, stamped at build time via
|
// version is the release version, stamped at build time via
|
||||||
// -ldflags "-X main.version=…" (defaulting to the current release).
|
// -ldflags "-X main.version=…" (defaulting to the current release).
|
||||||
var version = "0.26.0"
|
var version = "0.27.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
@@ -494,6 +497,7 @@ With -profile, the static basic-block structure is listed for each 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")
|
||||||
fuzzN := fs.Int("n", 1000, "number of fuzz iterations per function")
|
fuzzN := fs.Int("n", 1000, "number of fuzz iterations per function")
|
||||||
|
fuzzOne := fs.String("fuzz-one", "", "") // hidden: fuzz a single function (subprocess mode)
|
||||||
fs.Parse(args)
|
fs.Parse(args)
|
||||||
if fs.NArg() != 1 {
|
if fs.NArg() != 1 {
|
||||||
fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] [-abi] [-profile] <file.s>")
|
fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] [-abi] [-profile] <file.s>")
|
||||||
@@ -516,6 +520,37 @@ With -profile, the static basic-block structure is listed for each function.
|
|||||||
fmt.Printf("%s: %d functions JIT-loaded\n", path, len(names))
|
fmt.Printf("%s: %d functions JIT-loaded\n", path, len(names))
|
||||||
rc := 0
|
rc := 0
|
||||||
|
|
||||||
|
// Subprocess mode: fuzz a single function and exit.
|
||||||
|
if *fuzzOne != "" {
|
||||||
|
gt, err := verify.GroundTruth(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
src, err := readSource(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
sigs := verify.ExtractSignatures(src)
|
||||||
|
sig, ok := sigs[*fuzzOne]
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf("%s: no signature\n", *fuzzOne)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
goCode, ok := gt[*fuzzOne]
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf("%s: not in go tool asm\n", *fuzzOne)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
res := k.FuzzFunc(*fuzzOne, sig, goCode, *fuzzN, 42)
|
||||||
|
fmt.Printf("%s\n", res)
|
||||||
|
if !res.OK() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// Ground-truth comparison: assemble with go tool asm and compare bytes.
|
// Ground-truth comparison: assemble with go tool asm and compare bytes.
|
||||||
if *groundTruth {
|
if *groundTruth {
|
||||||
gt, err := verify.GroundTruth(path)
|
gt, err := verify.GroundTruth(path)
|
||||||
@@ -598,11 +633,17 @@ With -profile, the static basic-block structure is listed for each function.
|
|||||||
fmt.Printf(" %s: SKIP (not in go tool asm output)\n", name)
|
fmt.Printf(" %s: SKIP (not in go tool asm output)\n", name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res := k.FuzzFunc(name, sig, goCode, *fuzzN, int64(fuzzed*7+42))
|
// Run in a subprocess: if the function crashes on random
|
||||||
fmt.Printf(" %s\n", res)
|
// input (partial function), we report it and move on.
|
||||||
if !res.OK() {
|
res := fuzzInSubprocess(path, name, *fuzzN)
|
||||||
rc = 1
|
if res != "" {
|
||||||
|
fmt.Printf(" %s\n", res)
|
||||||
|
if strings.Contains(res, "MISMATCH") {
|
||||||
|
rc = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ = sig
|
||||||
|
_ = goCode
|
||||||
fuzzed++
|
fuzzed++
|
||||||
}
|
}
|
||||||
fmt.Printf("fuzz: %d functions tested, %d iterations each\n", fuzzed, *fuzzN)
|
fmt.Printf("fuzz: %d functions tested, %d iterations each\n", fuzzed, *fuzzN)
|
||||||
@@ -651,3 +692,40 @@ With -profile, the static basic-block structure is listed for each function.
|
|||||||
}
|
}
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fuzzInSubprocess runs the fuzz for a single function in a child process.
|
||||||
|
// If the child is killed by a signal (e.g. SIGSEGV from a partial function
|
||||||
|
// faulting on random input), it returns a CRASH report instead of dying.
|
||||||
|
func fuzzInSubprocess(path, funcName string, n int) string {
|
||||||
|
self, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("%s: cannot find self: %v", funcName, err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command(self, "verify", "--fuzz-one="+funcName, "-n", strconv.Itoa(n), path)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
// Check if the child was killed by a signal.
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
ws := exitErr.Sys().(syscall.WaitStatus)
|
||||||
|
if ws.Signaled() {
|
||||||
|
return fmt.Sprintf("%s: CRASH (%v — partial function, use --ground-truth)", funcName, ws.Signal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Non-zero exit without a signal: the fuzz reported mismatches.
|
||||||
|
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||||
|
for _, l := range lines {
|
||||||
|
if strings.Contains(l, funcName) {
|
||||||
|
return strings.TrimSpace(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: FAIL (exit %v)", funcName, err)
|
||||||
|
}
|
||||||
|
// Success: extract the result line.
|
||||||
|
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||||
|
for _, l := range lines {
|
||||||
|
if strings.Contains(l, funcName) {
|
||||||
|
return strings.TrimSpace(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user