Skip to content

Commit

Permalink
pkg/proc: add support for interface watchpoints (#3922)
Browse files Browse the repository at this point in the history
Adds support for setting a watchpoint on an interface by setting
the watchpoint on the data pointer in the eface/iface runtime
structs representing the interface.
  • Loading branch information
derekparker authored Feb 27, 2025
1 parent 6badfa1 commit 2880422
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 14 deletions.
24 changes: 12 additions & 12 deletions Documentation/backend_test_health.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Tests skipped by each supported backend:

* 386 skipped = 9
* 386 skipped = 10
* 1 broken
* 3 broken - cgo stacktraces
* 4 not implemented
* 5 not implemented
* 1 not working due to optimizations
* arm64 skipped = 1
* 1 broken - global variable symbolication
Expand All @@ -14,10 +14,10 @@ Tests skipped by each supported backend:
* 1 broken - cgo stacktraces
* darwin/lldb skipped = 1
* 1 upstream issue
* freebsd skipped = 11
* freebsd skipped = 12
* 2 flaky
* 2 follow exec not implemented on freebsd
* 5 not implemented
* 6 not implemented
* 2 not working on freebsd
* linux/386 skipped = 2
* 2 not working on linux/386
Expand All @@ -36,24 +36,24 @@ Tests skipped by each supported backend:
* linux/riscv64 skipped = 2
* 1 broken - cgo stacktraces
* 1 not working on linux/riscv64
* loong64 skipped = 7
* loong64 skipped = 8
* 2 broken
* 1 broken - global variable symbolication
* 4 not implemented
* 5 not implemented
* pie skipped = 2
* 2 upstream issue - https://github.com/golang/go/issues/29322
* ppc64le skipped = 12
* ppc64le skipped = 13
* 6 broken
* 1 broken - global variable symbolication
* 5 not implemented
* riscv64 skipped = 6
* 6 not implemented
* riscv64 skipped = 7
* 2 broken
* 1 broken - global variable symbolication
* 3 not implemented
* windows skipped = 7
* 4 not implemented
* windows skipped = 8
* 1 broken
* 2 not working on windows
* 4 see https://github.com/go-delve/delve/issues/2768
* 5 see https://github.com/go-delve/delve/issues/2768
* windows/arm64 skipped = 5
* 3 broken
* 1 broken - cgo stacktraces
Expand Down
15 changes: 15 additions & 0 deletions _fixtures/watchpointInterface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"errors"
"fmt"
"runtime"
)

func main() {
var err error = errors.New("test error")
fmt.Println("Error created:", err)
err = errors.New("modified error")
fmt.Println("Error modified:", err)
runtime.Breakpoint()
}
24 changes: 22 additions & 2 deletions pkg/proc/breakpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,11 +644,31 @@ func (t *Target) SetWatchpoint(logicalID int, scope *EvalScope, expr string, wty
if xv.Kind == reflect.UnsafePointer || xv.Kind == reflect.Invalid {
return nil, fmt.Errorf("can not watch variable of type %s", xv.Kind.String())
}

// Special handling for interface types
if xv.Kind == reflect.Interface {
// For interfaces, we want to watch the data they point to
// Read the interface to get the data pointer
_, data, isnil := xv.readInterface()
if xv.Unreadable != nil {
return nil, fmt.Errorf("error reading interface %q: %v", expr, xv.Unreadable)
}
if isnil {
return nil, fmt.Errorf("can not watch nil interface %q", expr)
}
if data == nil {
return nil, fmt.Errorf("invalid interface %q", expr)
}

// Use the data field as our watch target
xv = data
expr = expr + " (interface data)"
}

sz := xv.DwarfType.Size()
if sz <= 0 || sz > int64(t.BinInfo().Arch.PtrSize()) {
//TODO(aarzilli): it is reasonable to expect to be able to watch string
//and interface variables and we could support it by watching certain
//member fields here.
//variables and we could support it by watching certain member fields here.
return nil, fmt.Errorf("can not watch variable of type %s", xv.DwarfType.String())
}

Expand Down
34 changes: 34 additions & 0 deletions pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5541,6 +5541,40 @@ func TestStepIntoGoroutine(t *testing.T) {
})
}

func TestWatchpointInterface(t *testing.T) {
skipOn(t, "not implemented", "freebsd")
skipOn(t, "not implemented", "386")
skipOn(t, "not implemented", "ppc64le")
skipOn(t, "not implemented", "riscv64")
skipOn(t, "not implemented", "loong64")
skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows")
protest.AllowRecording(t)

withTestProcess("watchpointInterface", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
// Set breakpoint at line after error is created and printed
setFileBreakpoint(p, t, fixture.Source, 11)
assertNoError(grp.Continue(), t, "Continue()")
assertLineNumber(p, t, 11, "continued to wrong line")

// Get scope and set watchpoint on interface type
scope, err := proc.GoroutineScope(p, p.CurrentThread())
assertNoError(err, t, "GoroutineScope")

// Set watchpoint on the error interface
_, err = p.SetWatchpoint(0, scope, "err", proc.WatchWrite, nil)
assertNoError(err, t, "SetWatchpoint on interface type")

// Continue to hit the watchpoint when the error is modified
assertNoError(grp.Continue(), t, "Continue to watchpoint")

// Verify we stopped at the correct line where err is modified
assertLineNumberIn(p, t, []int{12, 13}, "stopped at wrong line after interface watchpoint")

// Continue to the end
assertNoError(grp.Continue(), t, "Final continue")
})
}

func TestStackwatchClearBug(t *testing.T) {
skipOn(t, "not implemented", "freebsd")
skipOn(t, "not implemented", "386")
Expand Down

0 comments on commit 2880422

Please sign in to comment.