fix(debug): hardware watchpoints, signal stops and breakpoint restore
Assisted-by: GLM 5.3
This commit is contained in:
+38
-188
@@ -6,7 +6,6 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -48,7 +47,7 @@ func TestConditionEval(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tt.cond.Eval(regs)
|
||||
got := tt.cond.Eval(regs, nil)
|
||||
if got != tt.want {
|
||||
t.Errorf("Condition{%q %q %d}.Eval() = %v, want %v",
|
||||
tt.cond.Reg, tt.cond.Op, tt.cond.Value, got, tt.want)
|
||||
@@ -56,65 +55,33 @@ func TestConditionEval(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLineAt(t *testing.T) {
|
||||
lines := []SourceLine{
|
||||
{Offset: 0, Line: 5},
|
||||
{Offset: 5, Line: 6},
|
||||
{Offset: 10, Line: 7},
|
||||
{Offset: 15, Line: 8},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
offset int
|
||||
want int
|
||||
}{
|
||||
{0, 5},
|
||||
{1, 5},
|
||||
{4, 5},
|
||||
{5, 6},
|
||||
{7, 6},
|
||||
{10, 7},
|
||||
{12, 7},
|
||||
{15, 8},
|
||||
{20, 8},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := lineAt(lines, tt.offset)
|
||||
if got != tt.want {
|
||||
t.Errorf("lineAt(lines, %d) = %d, want %d", tt.offset, got, tt.want)
|
||||
// TestConditionEvalMem covers the register-memory form: the value is read
|
||||
// through the supplied reader, and a missing or failing reader must not
|
||||
// block the breakpoint.
|
||||
func TestConditionEvalMem(t *testing.T) {
|
||||
regs := &Regs{RAX: 7}
|
||||
mem := func(addr uint64) (uint64, bool) {
|
||||
if addr == 0x5000 {
|
||||
return 7, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Empty table.
|
||||
if lineAt(nil, 5) != 0 {
|
||||
t.Error("lineAt(nil, 5) should return 0")
|
||||
eq := Condition{Reg: "rax", Op: "==", MemAddr: 0x5000}
|
||||
if !eq.Eval(regs, mem) {
|
||||
t.Error("register-memory comparison with matching word should hold")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOffsetForLine(t *testing.T) {
|
||||
lines := []SourceLine{
|
||||
{Offset: 0, Line: 5},
|
||||
{Offset: 5, Line: 6},
|
||||
{Offset: 10, Line: 7},
|
||||
ne := Condition{Reg: "rax", Op: "!=", MemAddr: 0x5000}
|
||||
if ne.Eval(regs, mem) {
|
||||
t.Error("register-memory comparison with mismatching word should not hold")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
line int
|
||||
want int
|
||||
}{
|
||||
{5, 0},
|
||||
{6, 5},
|
||||
{7, 10},
|
||||
{99, -1}, // not found
|
||||
{0, -1}, // not found
|
||||
bad := Condition{Reg: "rax", Op: "==", MemAddr: 0x6000}
|
||||
if !bad.Eval(regs, mem) {
|
||||
t.Error("unreadable memory must not block the breakpoint")
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := offsetForLine(lines, tt.line)
|
||||
if got != tt.want {
|
||||
t.Errorf("offsetForLine(lines, %d) = %d, want %d", tt.line, got, tt.want)
|
||||
}
|
||||
noReader := Condition{Reg: "rax", Op: "==", MemAddr: 0x5000}
|
||||
if !noReader.Eval(regs, nil) {
|
||||
t.Error("missing memory reader must not block the breakpoint")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,139 +108,6 @@ func TestDecodeRflags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNearestLabel(t *testing.T) {
|
||||
labels := []Label{
|
||||
{Name: "start", Offset: 0},
|
||||
{Name: "loop", Offset: 10},
|
||||
{Name: "done", Offset: 20},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
offset int
|
||||
want string
|
||||
}{
|
||||
{0, "start"},
|
||||
{5, "start"},
|
||||
{10, "loop"},
|
||||
{15, "loop"},
|
||||
{20, "done"},
|
||||
{25, "done"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := nearestLabel(labels, tt.offset)
|
||||
if got != tt.want {
|
||||
t.Errorf("nearestLabel(labels, %d) = %q, want %q", tt.offset, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreakpointsSetAndClear(t *testing.T) {
|
||||
tr := newMockTracer()
|
||||
bm := NewBreakpoints(tr)
|
||||
|
||||
// Set a breakpoint at address 0x1000.
|
||||
bp, err := bm.Set(0x1000, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Set: %v", err)
|
||||
}
|
||||
if !bp.Enabled {
|
||||
t.Error("breakpoint not enabled")
|
||||
}
|
||||
if bp.Label != "test" {
|
||||
t.Errorf("label = %q, want test", bp.Label)
|
||||
}
|
||||
|
||||
// Verify Peek was called.
|
||||
if len(tr.peeks) != 1 || tr.peeks[0] != 0x1000 {
|
||||
t.Errorf("peeks = %v, want [0x1000]", tr.peeks)
|
||||
}
|
||||
|
||||
// Verify Poke wrote INT3.
|
||||
if len(tr.pokes) != 1 || tr.pokes[0].addr != 0x1000 {
|
||||
t.Errorf("pokes = %v", tr.pokes)
|
||||
}
|
||||
|
||||
// At should find it.
|
||||
if bm.At(0x1000) == nil {
|
||||
t.Error("At(0x1000) returned nil")
|
||||
}
|
||||
|
||||
// All should return it.
|
||||
all := bm.All()
|
||||
if len(all) != 1 {
|
||||
t.Errorf("All() = %d breakpoints, want 1", len(all))
|
||||
}
|
||||
|
||||
// Clear it.
|
||||
if err := bm.Clear(0x1000); err != nil {
|
||||
t.Fatalf("Clear: %v", err)
|
||||
}
|
||||
if bm.At(0x1000) != nil {
|
||||
t.Error("At(0x1000) after Clear should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreakpointsSetWithCond(t *testing.T) {
|
||||
tr := newMockTracer()
|
||||
bm := NewBreakpoints(tr)
|
||||
|
||||
cond := &Condition{Reg: "rax", Op: "==", Value: 42}
|
||||
bp, err := bm.SetWithCond(0x2000, "cond_test", cond)
|
||||
if err != nil {
|
||||
t.Fatalf("SetWithCond: %v", err)
|
||||
}
|
||||
if bp.Cond == nil || bp.Cond.Value != 42 {
|
||||
t.Error("condition not set")
|
||||
}
|
||||
|
||||
// Re-setting the same address should update the condition.
|
||||
cond2 := &Condition{Reg: "rbx", Op: "<", Value: 100}
|
||||
bp2, err := bm.SetWithCond(0x2000, "cond_test2", cond2)
|
||||
if err != nil {
|
||||
t.Fatalf("SetWithCond (update): %v", err)
|
||||
}
|
||||
if bp2.Cond.Value != 100 {
|
||||
t.Error("condition not updated")
|
||||
}
|
||||
// Should have only 1 Peek (first Set), second is update (no Peek needed).
|
||||
if len(tr.peeks) != 1 {
|
||||
t.Errorf("expected 1 Peek, got %d", len(tr.peeks))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreakpointsClearAll(t *testing.T) {
|
||||
tr := newMockTracer()
|
||||
bm := NewBreakpoints(tr)
|
||||
|
||||
bm.Set(0x1000, "a")
|
||||
bm.Set(0x2000, "b")
|
||||
bm.Set(0x3000, "c")
|
||||
|
||||
if len(bm.All()) != 3 {
|
||||
t.Fatalf("expected 3 breakpoints, got %d", len(bm.All()))
|
||||
}
|
||||
|
||||
bm.ClearAll()
|
||||
if len(bm.All()) != 0 {
|
||||
t.Errorf("ClearAll: expected 0 breakpoints, got %d", len(bm.All()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreakpointInfo(t *testing.T) {
|
||||
tr := newMockTracer()
|
||||
bm := NewBreakpoints(tr)
|
||||
bm.Set(0x4000, "info_test")
|
||||
|
||||
info := bm.Info()
|
||||
if info == "" {
|
||||
t.Error("Info returned empty string")
|
||||
}
|
||||
if !strings.Contains(info, "info_test") {
|
||||
t.Errorf("Info %q does not contain label", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchpointSlotTracking(t *testing.T) {
|
||||
s := &Session{} // per-session slots start free
|
||||
|
||||
@@ -323,3 +157,19 @@ func TestWatchpointSlotTracking(t *testing.T) {
|
||||
t.Errorf("FindFreeWatchpointSlot() with all slots used = %d, want -1", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnwatchSlotBound checks the bound the REPL parses against: it must
|
||||
// cover the architecture's whole slot range, not a hardcoded 0-3.
|
||||
func TestUnwatchSlotBound(t *testing.T) {
|
||||
max := maxWatchpoints()
|
||||
if max < 4 {
|
||||
t.Fatalf("maxWatchpoints() = %d, want at least 4", max)
|
||||
}
|
||||
s := &Session{}
|
||||
if s.IsWatchpointSlotUsed(max - 1) {
|
||||
t.Errorf("slot %d should be free initially", max-1)
|
||||
}
|
||||
if s.IsWatchpointSlotUsed(max) {
|
||||
t.Errorf("slot %d must be out of range", max)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user