From 7a30e902fcaa7e879d241517e9a5fcd289092a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Sat, 29 Aug 2026 14:17:26 +0200 Subject: [PATCH] feat(debug): label coverage report for headless runs --- cmd/gasm/debug_linux.go | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/cmd/gasm/debug_linux.go b/cmd/gasm/debug_linux.go index a1b68b5..b73c4f6 100644 --- a/cmd/gasm/debug_linux.go +++ b/cmd/gasm/debug_linux.go @@ -37,6 +37,7 @@ REPL commands: 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") script := fs.String("script", "", "run REPL commands from a file (one per line) and exit; '-' reads stdin") + cover := fs.Bool("cover", false, "run to completion with breakpoints on every label and report which blocks executed") timeout := fs.Duration("timeout", 0, "kill the debuggee after this duration (e.g. 30s); for headless --script runs") fs.Parse(args) @@ -179,6 +180,62 @@ REPL commands: srcLines = append(srcLines, debug.SourceLine{Offset: le.Offset, Line: le.Line}) } + // Coverage mode: pre-register a breakpoint on every label (the basic + // blocks of a hand-written kernel) and let the kernel run to completion. + // Each hit removes its breakpoint, so the final report lists exactly the + // labels execution reached — the fast answer to "did my test ever enter + // the tail loop / the negative branch?". + if *cover { + covered := map[string]bool{} + for _, l := range labels { + addr := sess.CodeBase() + uint64(fl.Offset) + uint64(l.Offset) + name := l.Name + if _, err := bm.SetWithCond(addr, name, nil); err != nil { + fmt.Fprintf(os.Stderr, "gasm debug: cover: %v\n", err) + return 1 + } + } + fmt.Printf("gasm debug: coverage run over %d labels\n", len(labels)) + for { + for _, bp := range bm.All() { + bm.Reinsert(bp.Addr) + } + // Remove the breakpoint the run is parked on, then continue. + regs, rerr := sess.GetRegs() + if rerr == nil { + if bp := bm.At(regs.GetPC()); bp != nil { + bm.Clear(bp.Addr) + covered[bp.Label] = true + } + } + if err := sess.Continue(); err != nil { + break // debuggee finished or died + } + if sess.Exited() { + break + } + } + var hit []string + var missed []string + for _, l := range labels { + if covered[l.Name] { + hit = append(hit, l.Name) + } else { + missed = append(missed, l.Name) + } + } + sort.Strings(hit) + sort.Strings(missed) + fmt.Printf("coverage: %d/%d labels reached\n", len(hit), len(labels)) + for _, l := range hit { + fmt.Printf(" covered %s\n", l) + } + for _, l := range missed { + fmt.Printf(" MISSED %s\n", l) + } + return 0 + } + // Headless mode: run the script through the normal command loop and // exit. The watchdog armed above covers launch, continue and step. var in io.Reader = os.Stdin