feat(debug): add named buffer allocation with pattern filling
Test / vet (push) Successful in 45s
Test / test (push) Successful in 1m58s
Test / build (push) Successful in 42s

This commit is contained in:
2026-08-04 23:55:25 +02:00
parent 5cb7e3e231
commit f58a4fe51d
5 changed files with 390 additions and 27 deletions
+96
View File
@@ -6,9 +6,12 @@
package debug
import (
"encoding/hex"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"syscall"
"unsafe"
@@ -85,6 +88,15 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error {
args = padded
}
// Read buffer specification if present.
bufSpecFile := tmpDir + "/bufspec"
if bufSpec, err := os.ReadFile(bufSpecFile); err == nil && len(bufSpec) > 0 {
args, err = setupBuffers(string(bufSpec), args, fl.Args, tmpDir)
if err != nil {
return fmt.Errorf("debug target: setup buffers: %w", err)
}
}
// Lock this goroutine to the current OS thread so the parent's
// ptrace (attached to this thread) controls the JIT execution.
runtime.LockOSThread()
@@ -100,6 +112,11 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error {
// --- Execution resumes here after the parent continues us ---
// Stop at the function entry point so the debugger can set breakpoints.
// The parent will continue us when ready.
os.WriteFile(tmpDir+"/entry", []byte("ok"), 0o644)
syscall.Kill(syscall.Getpid(), syscall.SIGSTOP)
// Prepare the ABI0 stack and call the function.
fnAddr := codeBase + uintptr(fl.Offset)
stackArgs := make([]byte, fl.Args)
@@ -129,3 +146,82 @@ func mapRWX(code []byte) ([]byte, error) {
copy(mem, code)
return mem, nil
}
// setupBuffers allocates buffers in the debuggee's memory and updates the
// argument block with pointers to them.
// Format: name:size:pattern[,name:size:pattern...]
// Patterns: zero, ones, seq, or hex (e.g. "deadbeef").
func setupBuffers(spec string, args []byte, argSize int, tmpDir string) ([]byte, error) {
// Parse the buffer spec.
type bufSpec struct {
name string
size int
pattern string
}
var specs []bufSpec
for _, part := range strings.Split(spec, ",") {
fields := strings.SplitN(part, ":", 3)
if len(fields) != 3 {
continue
}
size, err := strconv.Atoi(fields[1])
if err != nil || size <= 0 {
continue
}
specs = append(specs, bufSpec{name: fields[0], size: size, pattern: fields[2]})
}
if len(specs) == 0 {
return args, nil
}
// Allocate buffers and write their addresses to a file for the parent.
var bufAddrs []uint64
for _, s := range specs {
buf, err := syscall.Mmap(-1, 0, s.size,
syscall.PROT_READ|syscall.PROT_WRITE,
syscall.MAP_PRIVATE|syscall.MAP_ANON)
if err != nil {
return nil, fmt.Errorf("mmap buffer %s: %w", s.name, err)
}
fillBuffer(buf, s.pattern)
bufAddrs = append(bufAddrs, uint64(uintptr(unsafe.Pointer(&buf[0]))))
}
// Write buffer addresses to a file for the parent to read.
addrFile, err := os.Create(tmpDir + "/bufaddrs")
if err != nil {
return nil, err
}
for _, addr := range bufAddrs {
fmt.Fprintf(addrFile, "%d\n", addr)
}
addrFile.Close()
// For now, return the args unchanged. The parent will read bufaddrs
// and construct the final argument block with the correct pointers.
return args, nil
}
// fillBuffer fills a buffer with the specified pattern.
func fillBuffer(buf []byte, pattern string) {
switch pattern {
case "zero":
// Already zeroed by mmap.
case "ones":
for i := range buf {
buf[i] = 0xFF
}
case "seq":
for i := range buf {
buf[i] = byte(i)
}
default:
// Try to parse as hex.
if data, err := hex.DecodeString(pattern); err == nil && len(data) > 0 {
for i := range buf {
buf[i] = data[i%len(data)]
}
}
}
}