feat(verify): add --call and --buf flags for single-function invocation
Assisted-by: GLM 5.2
This commit is contained in:
+140
-2
@@ -823,7 +823,7 @@ func cmdVerifyRISCV(path string, groundTruth, profile bool) int {
|
||||
}
|
||||
|
||||
func cmdVerify(args []string) int {
|
||||
fs := newCommand("verify", "gasm verify [-smoke] [-abi] [-profile] <file.s>", `
|
||||
fs := newCommand("verify", "gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] <file.s>", `
|
||||
Assemble FILE (amd64), map it into executable memory and report the available
|
||||
functions. This confirms the assembled image is self-consistent (no
|
||||
unresolved external symbols) and executable — the prerequisite for dynamic
|
||||
@@ -836,7 +836,18 @@ that tolerate nil pointers and zero lengths in their arguments.
|
||||
With -abi, each function is called with sentinel values in the callee-saved
|
||||
registers (BP, R14) and a red-zone canary below SP; violations are reported.
|
||||
|
||||
With -fuzz, each function with a // func signature is differentially fuzzed
|
||||
against the go-tool-asm version in a subprocess (so a crash on a partial
|
||||
function is reported, not fatal).
|
||||
|
||||
With -ground-truth, the assembled machine code is compared byte-for-byte
|
||||
against go tool asm (relocation sites masked), reporting any encoding drift.
|
||||
|
||||
With -profile, the static basic-block structure is listed for each function.
|
||||
|
||||
With -call, a single function is invoked with user-supplied buffers (-buf)
|
||||
instead of the smoke/abi/fuzz sweeps. Useful for partial functions (e.g.
|
||||
decoders) that crash on random input but should succeed on valid data.
|
||||
`)
|
||||
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)")
|
||||
@@ -846,9 +857,12 @@ With -profile, the static basic-block structure is listed for each function.
|
||||
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")
|
||||
fuzzOne := fs.String("fuzz-one", "", "") // hidden: fuzz a single function (subprocess mode)
|
||||
call := fs.String("call", "", "call a single function with -buf instead of the sweeps")
|
||||
bufSpec := fs.String("buf", "", "buffer spec for -call: name:size:pattern[,name:size:pattern] (zero, ones, seq, or hex)")
|
||||
repeat := fs.Int("repeat", 1, "number of times to repeat a -call invocation")
|
||||
fs.Parse(args)
|
||||
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] [-fuzz] [-ground-truth] [-profile] [-call] <file.s>")
|
||||
return 2
|
||||
}
|
||||
path := fs.Arg(0)
|
||||
@@ -874,6 +888,11 @@ With -profile, the static basic-block structure is listed for each function.
|
||||
fmt.Printf("%s: %d functions JIT-loaded\n", path, len(names))
|
||||
rc := 0
|
||||
|
||||
// Single-function call mode: invoke one function with user-supplied buffers.
|
||||
if *call != "" {
|
||||
return cmdVerifyCall(k, path, *call, *bufSpec, *repeat)
|
||||
}
|
||||
|
||||
// Subprocess mode: fuzz a single function and exit.
|
||||
if *fuzzOne != "" {
|
||||
gt, err := verify.GroundTruth(path)
|
||||
@@ -1095,3 +1114,122 @@ func fuzzInSubprocess(path, funcName string, n int) string {
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
// cmdVerifyCall implements `gasm verify --call <func> [--buf spec] [--repeat n]`.
|
||||
// It invokes a single function with user-supplied buffers and prints the arg
|
||||
// block before and after the call, so the user can inspect return values and
|
||||
// any output written to the buffers.
|
||||
func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec string, repeat int) int {
|
||||
fl, err := k.Func(funcName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if !fl.NoSplit {
|
||||
fmt.Fprintf(os.Stderr, "gasm verify: %s is not NOSPLIT (frame=%d); --call supports NOSPLIT functions only\n", funcName, fl.Frame)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Parse the // func signature to lay out the argument block.
|
||||
src, err := readSource(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
sig, ok := verify.ExtractFuncSig(src, funcName)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "gasm verify: no // func signature found for %s\n", funcName)
|
||||
return 1
|
||||
}
|
||||
layout := verify.ArgLayout(sig)
|
||||
|
||||
// Allocate the requested buffers (if any) and build the arg block.
|
||||
specs, err := verify.ParseBufSpec(bufSpec)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
var pool verify.BufPool
|
||||
if err := pool.Alloc(specs); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
defer pool.Close()
|
||||
args := pool.BuildArgs(layout, fl.Args)
|
||||
|
||||
fmt.Printf("%s: %d bytes, args=%d\n", funcName, fl.Size, fl.Args)
|
||||
fmt.Printf(" signature: func %s(%s) %s\n", sig.Name, formatParams(sig.Params), formatResults(sig.Results))
|
||||
if len(specs) > 0 {
|
||||
fmt.Printf(" buffers:\n")
|
||||
for _, s := range specs {
|
||||
fmt.Printf(" %s: %d bytes, pattern=%s\n", s.Name, s.Size, s.Pattern)
|
||||
}
|
||||
}
|
||||
fmt.Printf(" args before: %s\n", hexDump(args))
|
||||
|
||||
rc := 0
|
||||
for i := 0; i < repeat; i++ {
|
||||
out, err := k.CallFunc(funcName, args)
|
||||
if err != nil {
|
||||
fmt.Printf(" call %d: FAIL — %v\n", i+1, err)
|
||||
rc = 1
|
||||
continue
|
||||
}
|
||||
if repeat == 1 {
|
||||
fmt.Printf(" args after: %s\n", hexDump(out))
|
||||
} else if i == repeat-1 {
|
||||
fmt.Printf(" args after %d calls: %s\n", repeat, hexDump(out))
|
||||
}
|
||||
fmt.Printf(" call %d: OK\n", i+1)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
// formatParams renders a parameter list as "a []byte, b []byte".
|
||||
func formatParams(ps []verify.Param) string {
|
||||
var parts []string
|
||||
for _, p := range ps {
|
||||
if p.Name != "" {
|
||||
parts = append(parts, p.Name+" "+p.Typ)
|
||||
} else {
|
||||
parts = append(parts, p.Typ)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
// formatResults renders a result list as "(n int, code int)" or "int".
|
||||
func formatResults(rs []verify.Param) string {
|
||||
if len(rs) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(rs) == 1 && rs[0].Name == "" {
|
||||
return rs[0].Typ
|
||||
}
|
||||
return "(" + formatParams(rs) + ")"
|
||||
}
|
||||
|
||||
// hexDump returns a one-line hex dump of buf, truncated to 64 bytes.
|
||||
func hexDump(buf []byte) string {
|
||||
const max = 64
|
||||
n := len(buf)
|
||||
if n > max {
|
||||
n = max
|
||||
}
|
||||
var sb strings.Builder
|
||||
for i := 0; i < n; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
fmt.Fprintf(&sb, "%02x", buf[i])
|
||||
}
|
||||
return fmt.Sprintf("%s%s (%d bytes)", sb.String(), truncMark(len(buf), max), len(buf))
|
||||
}
|
||||
|
||||
// truncMark returns "…" when the buffer is longer than max, else "".
|
||||
func truncMark(n, max int) string {
|
||||
if n > max {
|
||||
return "…"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user