99 lines
3.0 KiB
Go
99 lines
3.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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 <file.s> --func <name>", `
|
|
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 <label|addr> 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 <file.s> --func <name>")
|
|
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
|
|
}
|