feat(debug): instruction-level coverage and FP register display
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -23,6 +23,14 @@ Unreleased changes on the `development` branch.
|
||||
- **Conditional breakpoints.** `break <label> if <reg> <op> <val>` now also
|
||||
compares two registers (`break loop if RAX > RBX`), not only a register
|
||||
against an immediate.
|
||||
- **Instruction-level coverage.** `gasm debug --cover` now sets a
|
||||
breakpoint on every instruction (walked by disassembly length), counts
|
||||
the hits per instruction and reports the executed instructions with
|
||||
their hit counts, with the label coverage derived from the same run.
|
||||
Expect the run to slow to ptrace speed.
|
||||
- **FP register display on riscv64 and loong64.** The debugger `regs`
|
||||
command shows the 32 FP registers plus fcsr (and fcc on loong64) via
|
||||
`PTRACE_GETREGSET`, where it previously printed nothing.
|
||||
- **JIT execution trampolines.** `verify.Call` now works on all four
|
||||
architectures via hand-written assembly trampolines
|
||||
(`trampoline_{arm64,riscv64,loong64}.s`) that save the Go stack, switch
|
||||
|
||||
+39
-15
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,11 @@ func printRegs(regs *Regs, codeBase, funcOff uint64) {
|
||||
}
|
||||
|
||||
func printVectorRegs(v *VectorRegs) {
|
||||
// No vector registers to display for loong64 yet.
|
||||
fmt.Println("\n FP registers (F0-F31):")
|
||||
for i := 0; i < 32; i += 2 {
|
||||
fmt.Printf(" F%-2d = %#018x F%-2d = %#018x\n", i, v.F[i], i+1, v.F[i+1])
|
||||
}
|
||||
fmt.Printf(" FCC = %#016x FCSR = %#x\n", v.FCC, v.FCSR)
|
||||
}
|
||||
|
||||
// archReturnAddr reads the return address from RA (loong64 convention).
|
||||
|
||||
@@ -28,7 +28,11 @@ func printRegs(regs *Regs, codeBase, funcOff uint64) {
|
||||
}
|
||||
|
||||
func printVectorRegs(v *VectorRegs) {
|
||||
// No vector registers to display for riscv64 yet.
|
||||
fmt.Println("\n FP registers (F0-F31):")
|
||||
for i := 0; i < 32; i += 2 {
|
||||
fmt.Printf(" F%-2d = %#018x F%-2d = %#018x\n", i, v.F[i], i+1, v.F[i+1])
|
||||
}
|
||||
fmt.Printf(" FCSR = %#x\n", v.FCSR)
|
||||
}
|
||||
|
||||
// archReturnAddr reads the return address from RA (riscv64 convention).
|
||||
|
||||
@@ -78,10 +78,20 @@ func (s *Session) GetFPRegs() (FPRegs, error) {
|
||||
return fp, nil
|
||||
}
|
||||
|
||||
// VectorRegs holds the LSX/LASX vector register state (placeholder).
|
||||
type VectorRegs struct{}
|
||||
// VectorRegs holds the FP register state shown by the regs command
|
||||
// (the scalar FP subset: 32 64-bit registers, fcc and fcsr; the LSX/LASX
|
||||
// vector files are not read yet).
|
||||
type VectorRegs struct {
|
||||
F [32]uint64
|
||||
FCC uint64
|
||||
FCSR uint32
|
||||
}
|
||||
|
||||
// GetVectorRegs retrieves the vector registers (not yet implemented for loong64).
|
||||
// GetVectorRegs retrieves the FP registers.
|
||||
func (s *Session) GetVectorRegs() (VectorRegs, error) {
|
||||
return VectorRegs{}, nil
|
||||
fp, err := s.GetFPRegs()
|
||||
if err != nil {
|
||||
return VectorRegs{}, err
|
||||
}
|
||||
return VectorRegs{F: fp.F, FCC: fp.FCC, FCSR: fp.FCSR}, nil
|
||||
}
|
||||
|
||||
@@ -76,10 +76,18 @@ func (s *Session) GetFPRegs() (FPRegs, error) {
|
||||
return fp, nil
|
||||
}
|
||||
|
||||
// VectorRegs holds the vector register state (placeholder).
|
||||
type VectorRegs struct{}
|
||||
// VectorRegs holds the FP register state shown by the regs command
|
||||
// (riscv64 has 32 64-bit FP registers and fcsr).
|
||||
type VectorRegs struct {
|
||||
F [32]uint64
|
||||
FCSR uint32
|
||||
}
|
||||
|
||||
// GetVectorRegs retrieves the vector registers (not yet implemented for riscv64).
|
||||
// GetVectorRegs retrieves the FP registers.
|
||||
func (s *Session) GetVectorRegs() (VectorRegs, error) {
|
||||
return VectorRegs{}, nil
|
||||
fp, err := s.GetFPRegs()
|
||||
if err != nil {
|
||||
return VectorRegs{}, err
|
||||
}
|
||||
return VectorRegs{F: fp.F, FCSR: fp.FCSR}, nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -112,7 +112,7 @@ loong64). Requires a compiled binary on `$PATH` (not `go run`).
|
||||
| `--args <file>` | File containing the ABI0 argument block |
|
||||
| `--script <file>` | Run REPL commands from a file (one per line) and exit; `-` reads stdin |
|
||||
| `--timeout <dur>` | Kill the debuggee after this duration (e.g. `30s`); for headless `--script` runs |
|
||||
| `--cover` | Run to completion with breakpoints on every label and report which blocks executed |
|
||||
| `--cover` | Run to completion with a breakpoint on every instruction; report which executed, how often, and which labels were reached |
|
||||
|
||||
REPL commands:
|
||||
|
||||
@@ -126,7 +126,7 @@ REPL commands:
|
||||
| `finish`, `fin` | Run until the function returns |
|
||||
| `continue`, `c` | Run until breakpoint, watchpoint or exit |
|
||||
| `disas [n]`, `u` | Disassemble n instructions at PC |
|
||||
| `regs` | Print general-purpose + YMM/XMM vector registers |
|
||||
| `regs` | Print general-purpose + vector/FP registers |
|
||||
| `where` | Show source line and nearest label at PC |
|
||||
| `stack` | Show stack near RSP (return address + ABI0 args) |
|
||||
| `bt`, `backtrace` | Backtrace (current frame + return address) |
|
||||
|
||||
Reference in New Issue
Block a user