feat(debug): add named buffer allocation with pattern filling
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/debug"
|
"sourcedock.dev/petrbalvin/gasm-devkit/debug"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/verify"
|
"sourcedock.dev/petrbalvin/gasm-devkit/verify"
|
||||||
@@ -32,6 +33,7 @@ REPL commands:
|
|||||||
target := fs.Bool("target", false, "") // hidden: debuggee subprocess mode
|
target := fs.Bool("target", false, "") // hidden: debuggee subprocess mode
|
||||||
funcName := fs.String("func", "", "function to debug")
|
funcName := fs.String("func", "", "function to debug")
|
||||||
argsFile := fs.String("args", "", "file containing the ABI0 argument block")
|
argsFile := fs.String("args", "", "file containing the ABI0 argument block")
|
||||||
|
bufSpec := fs.String("buf", "", "buffer specification: name:size:pattern[,name:size:pattern...] where pattern is zero, ones, seq, or hex")
|
||||||
fs.Parse(args)
|
fs.Parse(args)
|
||||||
|
|
||||||
// --- Debuggee mode (internal, spawned by the debugger) ---
|
// --- Debuggee mode (internal, spawned by the debugger) ---
|
||||||
@@ -76,13 +78,80 @@ REPL commands:
|
|||||||
}
|
}
|
||||||
sort.Slice(labels, func(i, j int) bool { return labels[i].Offset < labels[j].Offset })
|
sort.Slice(labels, func(i, j int) bool { return labels[i].Offset < labels[j].Offset })
|
||||||
|
|
||||||
// Launch the debuggee with a zeroed argument block.
|
// Launch the debuggee with the argument block.
|
||||||
argBlock := make([]byte, fl.Args)
|
var argBlock []byte
|
||||||
sess, err := debug.Launch("", path, *funcName, argBlock)
|
var bufAddrs []uint64
|
||||||
|
var sess *debug.Session
|
||||||
|
if *bufSpec != "" {
|
||||||
|
// Parse the function signature to determine argument layout.
|
||||||
|
src, err := readSource(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "gasm debug: %v\n", err)
|
fmt.Fprintf(os.Stderr, "gasm debug: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
sig, ok := verify.ExtractFuncSig(src, *funcName)
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "gasm debug: no // func signature found for %s\n", *funcName)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
layout := verify.ArgLayout(sig)
|
||||||
|
|
||||||
|
// Parse the buffer spec to get buffer names.
|
||||||
|
bufNames := parseBufNames(*bufSpec)
|
||||||
|
|
||||||
|
// Allocate buffers in the debuggee.
|
||||||
|
argBlock = make([]byte, fl.Args)
|
||||||
|
sess, bufAddrs, err = debug.LaunchWithBuffers("", path, *funcName, argBlock, *bufSpec)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gasm debug: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the argument block with buffer pointers at the correct positions.
|
||||||
|
bufIdx := 0
|
||||||
|
for _, arg := range layout {
|
||||||
|
if !arg.IsPtr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Find the buffer that matches this argument.
|
||||||
|
for i, name := range bufNames {
|
||||||
|
if i < len(bufAddrs) && (name == arg.Name || strings.HasPrefix(arg.Name, name)) {
|
||||||
|
addr := bufAddrs[i]
|
||||||
|
off := arg.Offset
|
||||||
|
if off+8 <= len(argBlock) {
|
||||||
|
argBlock[off] = byte(addr)
|
||||||
|
argBlock[off+1] = byte(addr >> 8)
|
||||||
|
argBlock[off+2] = byte(addr >> 16)
|
||||||
|
argBlock[off+3] = byte(addr >> 24)
|
||||||
|
argBlock[off+4] = byte(addr >> 32)
|
||||||
|
argBlock[off+5] = byte(addr >> 40)
|
||||||
|
argBlock[off+6] = byte(addr >> 48)
|
||||||
|
argBlock[off+7] = byte(addr >> 56)
|
||||||
|
}
|
||||||
|
// For slices, also set the length and capacity.
|
||||||
|
if strings.HasPrefix(arg.Typ, "[]") && off+24 <= len(argBlock) {
|
||||||
|
// Find the buffer size from the spec.
|
||||||
|
size := parseBufSize(*bufSpec, name)
|
||||||
|
// Length at offset+8, capacity at offset+16.
|
||||||
|
for j := 0; j < 8; j++ {
|
||||||
|
argBlock[off+8+j] = byte(size >> (j * 8))
|
||||||
|
argBlock[off+16+j] = byte(size >> (j * 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bufIdx++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = bufIdx
|
||||||
|
} else {
|
||||||
|
argBlock = make([]byte, fl.Args)
|
||||||
|
sess, err = debug.Launch("", path, *funcName, argBlock)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "gasm debug: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
defer sess.Kill()
|
defer sess.Kill()
|
||||||
|
|
||||||
bm := debug.NewBreakpoints(sess)
|
bm := debug.NewBreakpoints(sess)
|
||||||
@@ -96,3 +165,29 @@ REPL commands:
|
|||||||
debug.REPL(sess, bm, sess.CodeBase(), fl.Offset, fl.Size, fl.Args, labels, srcLines)
|
debug.REPL(sess, bm, sess.CodeBase(), fl.Offset, fl.Size, fl.Args, labels, srcLines)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseBufNames extracts buffer names from a buffer specification.
|
||||||
|
// Format: name:size:pattern[,name:size:pattern...]
|
||||||
|
func parseBufNames(spec string) []string {
|
||||||
|
var names []string
|
||||||
|
for _, part := range strings.Split(spec, ",") {
|
||||||
|
fields := strings.SplitN(part, ":", 3)
|
||||||
|
if len(fields) >= 1 && fields[0] != "" {
|
||||||
|
names = append(names, fields[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBufSize extracts the size of a named buffer from a buffer specification.
|
||||||
|
func parseBufSize(spec, name string) int {
|
||||||
|
for _, part := range strings.Split(spec, ",") {
|
||||||
|
fields := strings.SplitN(part, ":", 3)
|
||||||
|
if len(fields) >= 2 && fields[0] == name {
|
||||||
|
var size int
|
||||||
|
fmt.Sscanf(fields[1], "%d", &size)
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,9 +33,17 @@ type Session struct {
|
|||||||
// JIT code, calls PTRACE_TRACEME and raises SIGSTOP; Launch waits for
|
// JIT code, calls PTRACE_TRACEME and raises SIGSTOP; Launch waits for
|
||||||
// that initial stop and returns a ready Session.
|
// that initial stop and returns a ready Session.
|
||||||
func Launch(gasmBin, asmPath, funcName string, args []byte) (*Session, error) {
|
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()
|
self, err := os.Executable()
|
||||||
if err != nil {
|
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 != "" {
|
if gasmBin != "" {
|
||||||
self = 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).
|
// Write the arg block to a temp file (the child reads it).
|
||||||
tmpDir, err := os.MkdirTemp("", "gasm-debug-*")
|
tmpDir, err := os.MkdirTemp("", "gasm-debug-*")
|
||||||
if err != nil {
|
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")
|
argsFile := filepath.Join(tmpDir, "args.bin")
|
||||||
if err := os.WriteFile(argsFile, args, 0o644); err != nil {
|
if err := os.WriteFile(argsFile, args, 0o644); err != nil {
|
||||||
os.RemoveAll(tmpDir)
|
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)
|
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 {
|
if err := cmd.Start(); err != nil {
|
||||||
os.RemoveAll(tmpDir)
|
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}
|
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 {
|
if _, err := syscall.Wait4(s.pid, &ws, syscall.WUNTRACED, nil); err != nil {
|
||||||
cmd.Process.Kill()
|
cmd.Process.Kill()
|
||||||
os.RemoveAll(tmpDir)
|
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
|
s.stopped = true
|
||||||
|
|
||||||
// Read the code base from /proc/pid/maps (find the RWX mapping).
|
// 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.
|
// 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) {
|
func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, argsSize int, labels []Label, lines []SourceLine) {
|
||||||
entryAddr := codeBase + uint64(funcOffset)
|
entryAddr := codeBase + uint64(funcOffset)
|
||||||
|
|
||||||
// Run to the function entry.
|
// The debuggee is already stopped at the function entry point.
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("stopped at function entry: %#x (%d bytes)\n", entryAddr, funcSize)
|
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")
|
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
|
package debug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -85,6 +88,15 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error {
|
|||||||
args = padded
|
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
|
// Lock this goroutine to the current OS thread so the parent's
|
||||||
// ptrace (attached to this thread) controls the JIT execution.
|
// ptrace (attached to this thread) controls the JIT execution.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@@ -100,6 +112,11 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error {
|
|||||||
|
|
||||||
// --- Execution resumes here after the parent continues us ---
|
// --- 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.
|
// Prepare the ABI0 stack and call the function.
|
||||||
fnAddr := codeBase + uintptr(fl.Offset)
|
fnAddr := codeBase + uintptr(fl.Offset)
|
||||||
stackArgs := make([]byte, fl.Args)
|
stackArgs := make([]byte, fl.Args)
|
||||||
@@ -129,3 +146,82 @@ func mapRWX(code []byte) ([]byte, error) {
|
|||||||
copy(mem, code)
|
copy(mem, code)
|
||||||
return mem, nil
|
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)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package verify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FuncSig is a parsed // func signature from the assembly source.
|
||||||
|
type FuncSig struct {
|
||||||
|
Name string
|
||||||
|
Params []Param
|
||||||
|
Results []Param
|
||||||
|
}
|
||||||
|
|
||||||
|
// Param is a single function parameter.
|
||||||
|
type Param struct {
|
||||||
|
Name string
|
||||||
|
Typ string // "[]byte", "[]int32", "int", "*[32]uint16", etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPointer reports whether the parameter type is a pointer or slice
|
||||||
|
// (i.e., it needs a buffer address in the argument block).
|
||||||
|
func (p Param) IsPointer() bool {
|
||||||
|
return strings.HasPrefix(p.Typ, "[]") || strings.HasPrefix(p.Typ, "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFuncSig extracts the function signature from a "// func ..." comment.
|
||||||
|
func ParseFuncSig(comment string) (FuncSig, bool) {
|
||||||
|
m := funcSigRe.FindStringSubmatch(strings.TrimSpace(comment))
|
||||||
|
if m == nil {
|
||||||
|
return FuncSig{}, false
|
||||||
|
}
|
||||||
|
sig := FuncSig{Name: m[1]}
|
||||||
|
sig.Params = parseParamsExported(m[2])
|
||||||
|
// Results may be "(a int, b int)" or "int" or "(int, error)".
|
||||||
|
res := strings.TrimSpace(m[3])
|
||||||
|
res = strings.TrimPrefix(res, "(")
|
||||||
|
res = strings.TrimSuffix(res, ")")
|
||||||
|
if res != "" {
|
||||||
|
sig.Results = parseParamsExported(res)
|
||||||
|
}
|
||||||
|
return sig, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseParamsExported splits "a []byte, b []int32" into typed parameters.
|
||||||
|
func parseParamsExported(s string) []Param {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var out []Param
|
||||||
|
for _, field := range strings.Split(s, ",") {
|
||||||
|
field = strings.TrimSpace(field)
|
||||||
|
if field == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Fields(field)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
// Unnamed: "int" or "[]byte".
|
||||||
|
out = append(out, Param{Typ: parts[0]})
|
||||||
|
} else {
|
||||||
|
// Named: "a []byte".
|
||||||
|
out = append(out, Param{Name: parts[0], Typ: parts[1]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractFuncSig extracts the // func signature for the named function
|
||||||
|
// from the assembly source.
|
||||||
|
func ExtractFuncSig(src, funcName string) (FuncSig, bool) {
|
||||||
|
lines := strings.Split(src, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
if sig, ok := ParseFuncSig(line); ok && sig.Name == funcName {
|
||||||
|
// Verify the next non-comment line is the TEXT directive.
|
||||||
|
for j := i + 1; j < len(lines); j++ {
|
||||||
|
trimmed := strings.TrimSpace(lines[j])
|
||||||
|
if trimmed == "" || strings.HasPrefix(trimmed, "//") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(trimmed, "TEXT") {
|
||||||
|
return sig, true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FuncSig{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgLayout computes the ABI0 argument layout for a function signature.
|
||||||
|
// Returns the offset of each parameter in the argument block.
|
||||||
|
func ArgLayout(sig FuncSig) []ArgOffset {
|
||||||
|
var offsets []ArgOffset
|
||||||
|
off := 0
|
||||||
|
for _, p := range sig.Params {
|
||||||
|
size := paramSize(p.Typ)
|
||||||
|
offsets = append(offsets, ArgOffset{Name: p.Name, Typ: p.Typ, Offset: off, Size: size, IsPtr: p.IsPointer()})
|
||||||
|
off += size
|
||||||
|
}
|
||||||
|
return offsets
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgOffset describes one parameter's position in the argument block.
|
||||||
|
type ArgOffset struct {
|
||||||
|
Name string
|
||||||
|
Typ string
|
||||||
|
Offset int
|
||||||
|
Size int
|
||||||
|
IsPtr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// paramSize returns the size in bytes of a parameter type in the ABI0 layout.
|
||||||
|
func paramSize(typ string) int {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(typ, "[]"):
|
||||||
|
// Slice: pointer + length + capacity = 24 bytes.
|
||||||
|
return 24
|
||||||
|
case strings.HasPrefix(typ, "*"):
|
||||||
|
// Pointer: 8 bytes.
|
||||||
|
return 8
|
||||||
|
case typ == "int", typ == "int64", typ == "uint64", typ == "uintptr":
|
||||||
|
return 8
|
||||||
|
case typ == "int32", typ == "uint32", typ == "float32":
|
||||||
|
return 4
|
||||||
|
case typ == "int16", typ == "uint16":
|
||||||
|
return 2
|
||||||
|
case typ == "int8", typ == "uint8", typ == "byte", typ == "bool":
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
// Default to 8 bytes for unknown types.
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user