// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause //go:build linux && amd64 package main import ( "fmt" "os" "sort" "sourcedock.dev/petrbalvin/gasm-devkit/debug" "sourcedock.dev/petrbalvin/gasm-devkit/verify" ) func cmdDebug(args []string) int { fs := newCommand("debug", "gasm debug --func ", ` Interactive debugger for JIT-assembled amd64 functions. Launches the function in a traced subprocess (ptrace), then provides a REPL for single-stepping, breakpoints, register and memory inspection. REPL commands: break set a breakpoint at a label or absolute address step [n] single-step n instructions (default 1) continue run until next breakpoint or exit regs print general-purpose registers x [addr] [len] hex-dump memory (default: current PC, 64 bytes) labels list function labels and offsets quit kill the debuggee and exit `) target := fs.Bool("target", false, "") // hidden: debuggee subprocess mode funcName := fs.String("func", "", "function to debug") argsFile := fs.String("args", "", "file containing the ABI0 argument block") fs.Parse(args) // --- Debuggee mode (internal, spawned by the debugger) --- if *target { tmpDir := os.Getenv("GASM_DEBUG_TMP") if tmpDir == "" || fs.NArg() < 1 || *funcName == "" || *argsFile == "" { fmt.Fprintln(os.Stderr, "gasm debug --target: internal mode") return 2 } if err := debug.RunTarget(fs.Arg(0), *funcName, *argsFile, tmpDir); err != nil { fmt.Fprintf(os.Stderr, "gasm debug: %v\n", err) return 1 } return 0 } // --- Debugger mode (interactive REPL) --- if fs.NArg() < 1 || *funcName == "" { fmt.Fprintln(os.Stderr, "usage: gasm debug --func ") return 2 } path := fs.Arg(0) // Load the kernel to extract function metadata and labels. k, err := verify.Load(path) if err != nil { fmt.Fprintf(os.Stderr, "gasm debug: %v\n", err) return 1 } defer k.Close() fl, err := k.Func(*funcName) if err != nil { fmt.Fprintf(os.Stderr, "gasm debug: %v\n", err) return 1 } // Build the label list for the REPL. var labels []debug.Label for name, off := range fl.Labels { labels = append(labels, debug.Label{Name: name, Offset: off}) } sort.Slice(labels, func(i, j int) bool { return labels[i].Offset < labels[j].Offset }) // Launch the debuggee with a zeroed argument block. 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() bm := debug.NewBreakpoints(sess) fmt.Printf("gasm debug: %s in %s (pid %d)\n", *funcName, path, sess.Pid()) // Convert the line table for the REPL. var srcLines []debug.SourceLine for _, le := range fl.Lines { srcLines = append(srcLines, debug.SourceLine{Offset: le.Offset, Line: le.Line}) } debug.REPL(sess, bm, sess.CodeBase(), fl.Offset, fl.Size, fl.Args, labels, srcLines) return 0 }