feat(debug): add named buffer allocation with pattern filling
This commit is contained in:
@@ -33,9 +33,17 @@ type Session struct {
|
||||
// JIT code, calls PTRACE_TRACEME and raises SIGSTOP; Launch waits for
|
||||
// that initial stop and returns a ready Session.
|
||||
func Launch(gasmBin, asmPath, funcName string, args []byte) (*Session, error) {
|
||||
sess, _, err := LaunchWithBuffers(gasmBin, asmPath, funcName, args, "")
|
||||
return sess, err
|
||||
}
|
||||
|
||||
// LaunchWithBuffers is like Launch but also allocates buffers in the debuggee
|
||||
// based on the buffer specification. Returns the Session and the buffer
|
||||
// addresses (in the order they appear in the spec).
|
||||
func LaunchWithBuffers(gasmBin, asmPath, funcName string, args []byte, bufSpec string) (*Session, []uint64, error) {
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("debug: cannot find gasm binary: %w", err)
|
||||
return nil, nil, fmt.Errorf("debug: cannot find gasm binary: %w", err)
|
||||
}
|
||||
if gasmBin != "" {
|
||||
self = gasmBin
|
||||
@@ -44,12 +52,20 @@ func Launch(gasmBin, asmPath, funcName string, args []byte) (*Session, error) {
|
||||
// Write the arg block to a temp file (the child reads it).
|
||||
tmpDir, err := os.MkdirTemp("", "gasm-debug-*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("debug: tempdir: %w", err)
|
||||
return nil, nil, fmt.Errorf("debug: tempdir: %w", err)
|
||||
}
|
||||
argsFile := filepath.Join(tmpDir, "args.bin")
|
||||
if err := os.WriteFile(argsFile, args, 0o644); err != nil {
|
||||
os.RemoveAll(tmpDir)
|
||||
return nil, fmt.Errorf("debug: write args: %w", err)
|
||||
return nil, nil, fmt.Errorf("debug: write args: %w", err)
|
||||
}
|
||||
|
||||
// Write the buffer spec if present.
|
||||
if bufSpec != "" {
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, "bufspec"), []byte(bufSpec), 0o644); err != nil {
|
||||
os.RemoveAll(tmpDir)
|
||||
return nil, nil, fmt.Errorf("debug: write bufspec: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(self, "debug", "--target", "--func", funcName, "--args", argsFile, asmPath)
|
||||
@@ -60,7 +76,7 @@ func Launch(gasmBin, asmPath, funcName string, args []byte) (*Session, error) {
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
os.RemoveAll(tmpDir)
|
||||
return nil, fmt.Errorf("debug: start debuggee: %w", err)
|
||||
return nil, nil, fmt.Errorf("debug: start debuggee: %w", err)
|
||||
}
|
||||
|
||||
s := &Session{pid: cmd.Process.Pid, cmd: cmd}
|
||||
@@ -79,8 +95,28 @@ func Launch(gasmBin, asmPath, funcName string, args []byte) (*Session, error) {
|
||||
if _, err := syscall.Wait4(s.pid, &ws, syscall.WUNTRACED, nil); err != nil {
|
||||
cmd.Process.Kill()
|
||||
os.RemoveAll(tmpDir)
|
||||
return nil, fmt.Errorf("debug: wait for debuggee: %w", err)
|
||||
return nil, nil, fmt.Errorf("debug: wait for stop: %w", err)
|
||||
}
|
||||
|
||||
// Wait for the debuggee to reach the function entry point.
|
||||
entryFile := filepath.Join(tmpDir, "entry")
|
||||
for i := 0; i < 500; i++ {
|
||||
if _, err := os.Stat(entryFile); err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Continue the debuggee to the entry point.
|
||||
if err := s.Continue(); err != nil {
|
||||
return nil, nil, fmt.Errorf("debug: continue to entry: %w", err)
|
||||
}
|
||||
|
||||
// Wait for the entry stop.
|
||||
if _, err := syscall.Wait4(s.pid, &ws, syscall.WUNTRACED, nil); err != nil {
|
||||
return nil, nil, fmt.Errorf("debug: wait for entry: %w", err)
|
||||
}
|
||||
|
||||
s.stopped = true
|
||||
|
||||
// Read the code base from /proc/pid/maps (find the RWX mapping).
|
||||
@@ -93,7 +129,21 @@ func Launch(gasmBin, asmPath, funcName string, args []byte) (*Session, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
// Read buffer addresses if buffers were allocated.
|
||||
var bufAddrs []uint64
|
||||
if bufSpec != "" {
|
||||
addrFile := filepath.Join(tmpDir, "bufaddrs")
|
||||
if data, err := os.ReadFile(addrFile); err == nil {
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") {
|
||||
var addr uint64
|
||||
if _, err := fmt.Sscanf(line, "%d", &addr); err == nil {
|
||||
bufAddrs = append(bufAddrs, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s, bufAddrs, nil
|
||||
}
|
||||
|
||||
// wait waits for the debuggee to stop and returns the wait status.
|
||||
|
||||
+1
-15
@@ -34,21 +34,7 @@ type SourceLine struct {
|
||||
func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, argsSize int, labels []Label, lines []SourceLine) {
|
||||
entryAddr := codeBase + uint64(funcOffset)
|
||||
|
||||
// Run to the function entry.
|
||||
bp, err := bm.Set(entryAddr, "(entry)")
|
||||
if err != nil {
|
||||
fmt.Printf("warning: cannot set entry breakpoint: %v\n", err)
|
||||
} else {
|
||||
if err := s.Continue(); err != nil {
|
||||
fmt.Printf("warning: continue to entry: %v\n", err)
|
||||
}
|
||||
regs, _ := s.GetRegs()
|
||||
bm.HandleTrap(®s)
|
||||
// Remove the temporary entry breakpoint.
|
||||
bm.Clear(entryAddr)
|
||||
_ = bp
|
||||
}
|
||||
|
||||
// The debuggee is already stopped at the function entry point.
|
||||
fmt.Printf("stopped at function entry: %#x (%d bytes)\n", entryAddr, funcSize)
|
||||
fmt.Println("commands: break <label|addr> | step [n] | continue | disas [n] | regs | where | x <addr> [len] | w <addr> <val...> | labels | quit")
|
||||
|
||||
|
||||
@@ -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)]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user