diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index 2f48e4d..89aa4c1 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -1067,6 +1067,7 @@ decoders) that crash on random input but should succeed on valid data. sweepOne := fs.String("sweep-one", "", "") // hidden: smoke/abi 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)") + scalarSpec := fs.String("args", "", "scalar args for -call: name=value[,name=value] (decimal or 0x hex)") repeat := fs.Int("repeat", 1, "number of times to repeat a -call invocation") fs.Parse(args) if fs.NArg() != 1 { @@ -1105,7 +1106,7 @@ decoders) that crash on random input but should succeed on valid data. // Single-function call mode: invoke one function with user-supplied buffers. if *call != "" { - return cmdVerifyCall(k, path, *call, *bufSpec, *repeat) + return cmdVerifyCall(k, path, *call, *bufSpec, *scalarSpec, *repeat) } // Subprocess mode: fuzz a single function and exit. @@ -1451,11 +1452,11 @@ func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smok return msgs, failed } -// cmdVerifyCall implements `gasm verify --call [--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 { +// cmdVerifyCall implements `gasm verify --call [--buf spec] [--args spec] [--repeat n]`. +// It invokes a single function with user-supplied buffers and scalar +// arguments 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, scalarSpec string, repeat int) int { fl, err := k.Func(funcName) if err != nil { fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err) @@ -1493,6 +1494,16 @@ func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec string, repeat int) defer pool.Close() args := pool.BuildArgs(layout, fl.Args) + scalars, err := verify.ParseScalarArgs(scalarSpec) + if err != nil { + fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err) + return 1 + } + if err := verify.ApplyScalarArgs(args, layout, scalars); err != nil { + fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err) + return 1 + } + 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 { @@ -1501,6 +1512,17 @@ func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec string, repeat int) fmt.Printf(" %s: %d bytes, pattern=%s\n", s.Name, s.Size, s.Pattern) } } + if len(scalars) > 0 { + names := make([]string, 0, len(scalars)) + for n := range scalars { + names = append(names, n) + } + sort.Strings(names) + fmt.Printf(" scalars:\n") + for _, n := range names { + fmt.Printf(" %s = %d\n", n, scalars[n]) + } + } fmt.Printf(" args before: %s\n", hexDump(args)) rc := 0 diff --git a/verify/buffers.go b/verify/buffers.go index b355d19..1489087 100644 --- a/verify/buffers.go +++ b/verify/buffers.go @@ -148,3 +148,73 @@ func fillBuffer(buf []byte, pattern string) { } } } + +// ApplyScalarArgs writes user-supplied scalar argument values into an ABI0 +// argument block after BuildArgs. Each name=value pair addresses the layout +// entry of that parameter name; only word-sized scalar parameters (int, +// int64, uint64, and their named spellings) accept values, so a typo'd name +// or a slice parameter fails loudly instead of silently corrupting the call. +func ApplyScalarArgs(args []byte, layout []ArgOffset, scalars map[string]uint64) error { + if len(scalars) == 0 { + return nil + } + byName := make(map[string]ArgOffset, len(layout)) + for _, l := range layout { + byName[l.Name] = l + } + for name, val := range scalars { + l, ok := byName[name] + if !ok { + return fmt.Errorf("scalar arg %q: no such parameter", name) + } + if l.IsPtr || l.Size != 8 { + return fmt.Errorf("scalar arg %q: parameter has type %s; only word-sized scalars accept values", name, l.Typ) + } + if l.Offset+8 > len(args) { + return fmt.Errorf("scalar arg %q: offset %d outside the %d-byte arg block", name, l.Offset, len(args)) + } + binary.LittleEndian.PutUint64(args[l.Offset:], val) + } + return nil +} + +// ParseScalarArgs parses a "name=value[,name=value...]" spec into a map. +// Values are decimal or 0x-prefixed hex. +func ParseScalarArgs(spec string) (map[string]uint64, error) { + out := map[string]uint64{} + if strings.TrimSpace(spec) == "" { + return out, nil + } + for _, part := range strings.Split(spec, ",") { + part = strings.TrimSpace(part) + if part == "" { + continue + } + name, val, err := splitScalar(part) + if err != nil { + return nil, err + } + out[name] = val + } + return out, nil +} + +func splitScalar(part string) (string, uint64, error) { + eq := strings.Index(part, "=") + if eq <= 0 { + return "", 0, fmt.Errorf("scalar arg spec %q: want name=value", part) + } + name := strings.TrimSpace(part[:eq]) + valStr := strings.TrimSpace(part[eq+1:]) + var val uint64 + var err error + if strings.HasPrefix(valStr, "0x") || strings.HasPrefix(valStr, "0X") { + _, err = fmt.Sscanf(valStr, "0x%x", &val) + } else { + _, err = fmt.Sscanf(valStr, "%d", &val) + } + if err != nil || name == "" { + return "", 0, fmt.Errorf("scalar arg spec %q: bad value", part) + } + return name, val, nil +} diff --git a/verify/buffers_test.go b/verify/buffers_test.go index 5b0fbf9..862c51b 100644 --- a/verify/buffers_test.go +++ b/verify/buffers_test.go @@ -156,3 +156,41 @@ func TestBufPoolBuildArgs(t *testing.T) { t.Errorf("src.len = %d, want 128", args[32]) } } + +func TestParseScalarArgs(t *testing.T) { + m, err := ParseScalarArgs("n=57,base=0x1f,x= 12 ") + if err != nil { + t.Fatal(err) + } + if m["n"] != 57 || m["base"] != 0x1f || m["x"] != 12 { + t.Fatalf("parsed %+v", m) + } + if _, err := ParseScalarArgs("n"); err == nil { + t.Error("name-only spec must fail") + } + if _, err := ParseScalarArgs("n=zz"); err == nil { + t.Error("bad value must fail") + } +} + +func TestApplyScalarArgs(t *testing.T) { + layout := []ArgOffset{ + {Name: "text", Typ: "*byte", Offset: 0, Size: 8, IsPtr: true}, + {Name: "n", Typ: "int", Offset: 8, Size: 8, IsPtr: false}, + } + args := make([]byte, 16) + if err := ApplyScalarArgs(args, layout, map[string]uint64{"n": 0x1f}); err != nil { + t.Fatal(err) + } + if got := args[8]; got != 0x1f { + t.Fatalf("args[8] = %d, want 31", got) + } + // Pointer parameters reject scalar writes. + if err := ApplyScalarArgs(args, layout, map[string]uint64{"text": 5}); err == nil { + t.Fatal("pointer param must reject a scalar write") + } + // Unknown names reject. + if err := ApplyScalarArgs(args, layout, map[string]uint64{"nope": 1}); err == nil { + t.Fatal("unknown param must reject") + } +}