feat(debug): instruction-level coverage and FP register display

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-08-30 21:42:12 +02:00
parent 4171e412b5
commit 41b387e54d
7 changed files with 85 additions and 27 deletions
+39 -15
View File
@@ -52,7 +52,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")
cover := fs.Bool("cover", false, "run to completion with a breakpoint on every instruction and report which executed and how often")
timeout := fs.Duration("timeout", 0, "kill the debuggee after this duration (e.g. 30s); for headless --script runs")
fs.Parse(args)
@@ -195,32 +195,49 @@ 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?".
// Coverage mode: pre-register a breakpoint on every instruction (walked
// by length through the function body while the debuggee is stopped) and
// let the kernel run to completion. Each trap counts a hit for that
// instruction, so the final report shows exactly which instructions
// executed and how often, with the label-level view derived from it.
// Expect the run to slow to ptrace speed: one trap per executed
// instruction.
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 {
base := sess.CodeBase() + uint64(fl.Offset)
type coverInstr struct {
off uint64
text string
}
var instrs []coverInstr
for off := uint64(0); off < uint64(fl.Size); {
text, ln, err := sess.Disassemble(base + off)
if err != nil || ln == 0 {
break
}
instrs = append(instrs, coverInstr{off: off, text: text})
off += uint64(ln)
}
for _, in := range instrs {
if _, err := bm.SetWithCond(base+in.off, fmt.Sprintf("func+%#x", in.off), 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))
hits := map[uint64]int{}
traps := 0
fmt.Printf("gasm debug: coverage run over %d instructions\n", len(instrs))
for {
for _, bp := range bm.All() {
bm.Reinsert(bp.Addr)
}
// Remove the breakpoint the run is parked on, then continue.
// Remove the breakpoint the run is parked on, count the hit,
// 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
traps++
hits[regs.GetPC()-base]++
}
}
if err := sess.Continue(); err != nil {
@@ -233,7 +250,7 @@ REPL commands:
var hit []string
var missed []string
for _, l := range labels {
if covered[l.Name] {
if hits[uint64(l.Offset)] > 0 {
hit = append(hit, l.Name)
} else {
missed = append(missed, l.Name)
@@ -241,6 +258,7 @@ REPL commands:
}
sort.Strings(hit)
sort.Strings(missed)
fmt.Printf("coverage: %d/%d instructions executed (%d traps)\n", len(hits), len(instrs), traps)
fmt.Printf("coverage: %d/%d labels reached\n", len(hit), len(labels))
for _, l := range hit {
fmt.Printf(" covered %s\n", l)
@@ -248,6 +266,12 @@ REPL commands:
for _, l := range missed {
fmt.Printf(" MISSED %s\n", l)
}
fmt.Println("executed instructions:")
for _, in := range instrs {
if n := hits[in.off]; n > 0 {
fmt.Printf(" func+%#04x %4dx %s\n", in.off, n, in.text)
}
}
return 0
}