vscode-go icon indicating copy to clipboard operation
vscode-go copied to clipboard

Take advantage of suspending breakpoint provided by delve

Open Lslightly opened this issue 8 months ago • 10 comments

What version of Go, VS Code & VS Code Go extension are you using?

Version Information
  • Run go version to get version of Go from the VS Code integrated terminal.
    • go version go1.24.0 linux/amd64
  • Run gopls -v version to get version of Gopls from the VS Code integrated terminal.
    • golang.org/x/tools/gopls v0.18.1
  • Run code -v or code-insiders -v to get version of VS Code or VS Code Insiders.
    • 1.98.0 6609ac3d66f4eade5cf376d1cb76f13985724bcb x64
  • Check your installed extensions to get the version of the VS Code Go extension
    • 0.46.1
  • Run Ctrl+Shift+P (Cmd+Shift+P on Mac OS) > Go: Locate Configured Go Tools command. - the dlv is the version not released yet in https://github.com/Lslightly/delve/tree/followExecByDefault which enable target follow-exec -on by default and fixed some other bugs(see service/debugger: fix FindLocation with child processes by aarzilli · Pull Request #3937 · go-delve/delve).

Environment

GOBIN: undefined toolsGopath: gopath: /home/lqw/go GOROOT: /home/lqw/go1.24.0 PATH: /home/lqw/.vscode-server/bin/6609ac3d66f4eade5cf376d1cb76f13985724bcb/bin/remote-cli:/home/lqw/.rvm/gems/ruby-3.0.0/bin:/home/lqw/.rvm/gems/ruby-3.0.0@global/bin:/home/lqw/.rvm/rubies/ruby-3.0.0/bin:/home/lqw/mygit/delve:/home/lqw/go/bin:/home/lqw/go1.24.0/bin:/home/lqw/scripts:/home/lqw/cmake/bin:/home/lqw/.local/bin:/home/lqw/miniconda3/bin:/home/lqw/miniconda3/condabin:/home/lqw/.opam/default/bin:/home/lqw/.elan/bin:/home/lqw/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/home/lqw/.rvm/bin

Tools

go:	/home/lqw/go1.24.0/bin/go: go version go1.24.0 linux/amd64

gopls:	/home/lqw/go/bin/gopls	(version: v0.18.1 built with go: go1.24.1)
gotests:	not installed
gomodifytags:	not installed
impl:	not installed
goplay:	not installed
dlv:	/home/lqw/go/bin/dlv	(version: v0.0.0-20250311114909-570f5725d595+dirty built with go: go1.24.0)
staticcheck:	/home/lqw/go/bin/staticcheck	(version: v0.6.0 built with go: go1.24.0)

Go env

Workspace Folder (delve): /home/lqw/mygit/delve

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE='on'
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/lqw/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/lqw/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build4247711127=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/lqw/mygit/delve/go.mod'
GOMODCACHE='/home/lqw/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/lqw/go'
GOPRIVATE=''
GOPROXY='https://goproxy.cn,direct'
GOROOT='/home/lqw/go1.24.0'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/lqw/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/lqw/go1.24.0/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.0'
GOWORK=''
PKG_CONFIG='pkg-config'

Share the Go related settings you have added/edited

Run Preferences: Open Settings (JSON) command to open your settings.json file. Share all the settings with the go. or ["go"] or gopls prefixes.

"go.alternateTools": {
    "dlv": "<modified dlv mentioned above>"
}

Describe the bug

A clear and concise description of what the bug. A clear and concise description of what you expected to happen.

Bug: The suspended argument is always false when CreateBreakpoint is invoked in delve server through RPC call by vscode-go debug adapter. The consequence is that when the location of a breakpoint is not found in the delve debugger, the breakpoint will be deleted in service/debugger/debugger.go#L792-L797 as shown in the following code, which makes vscode frontend fail to set the breakpoint. Current interface not fully utilizes the ability of suspending breakpoints in delve, which can limit vscode-go debug adapter's ability in cases like multiple process debugging even if FollowExec is enabled.

if suspended {
	logflags.DebuggerLogger().Debugf("could not enable new breakpoint: %v (breakpoint will be suspended)", err)
} else {
	delete(d.target.LogicalBreakpoints, lbp.LogicalID)
	return nil, err
}

What I expected to see: suspended is always sent true so that when the location of a breakpoint is not found, the breakpoint can still be suspended. In the future, the breakpoint will be valid.

The interfaces in both sides are:

  • vscode-go extension/src/debugAdapter/goDebug.ts: https://github.com/golang/vscode-go/blob/1a21ede94c3ffd440d24ccc128ef9e4d7786dd08/extension/src/debugAdapter/goDebug.ts#L2189
  • delve:
    • argv = reflect.New(mtype.ArgType) at https://github.com/go-delve/delve/blob/570f5725d595dc698d257f91326e677b9eda89df/service/rpccommon/server.go#L238
    • function.Call([]reflect.Value{argv, reflect.ValueOf(ctl)}) at https://github.com/go-delve/delve/blob/570f5725d595dc698d257f91326e677b9eda89df/service/rpccommon/server.go#L295
    • s.debugger.CreateBreakpoint(&arg.Breakpoint, arg.LocExpr, arg.SubstitutePathRules, arg.Suspended) at https://github.com/go-delve/delve/blob/570f5725d595dc698d257f91326e677b9eda89df/service/rpc2/server.go#L263

Current interface in delve is:

  • https://github.com/go-delve/delve/blob/570f5725d595dc698d257f91326e677b9eda89df/service/api/types.go#L74-L141
  • https://github.com/go-delve/delve/blob/570f5725d595dc698d257f91326e677b9eda89df/service/rpc2/server.go#L243-L249
Code of delve
// Breakpoint addresses a set of locations at which process execution may be
// suspended.
type Breakpoint struct {
	// ID is a unique identifier for the breakpoint.
	ID int `json:"id"`
	// User defined name of the breakpoint.
	Name string `json:"name"`
	// Addr is deprecated, use Addrs.
	Addr uint64 `json:"addr"`
	// Addrs is the list of addresses for this breakpoint.
	Addrs []uint64 `json:"addrs"`
	// AddrPid[i] is the PID associated with by Addrs[i], when debugging a
	// single target process this is optional, otherwise it is mandatory.
	AddrPid []int `json:"addrpid"`
	// File is the source file for the breakpoint.
	File string `json:"file"`
	// Line is a line in File for the breakpoint.
	Line int `json:"line"`
	// FunctionName is the name of the function at the current breakpoint, and
	// may not always be available.
	FunctionName string `json:"functionName,omitempty"`
	// ExprString is the string that will be used to set a suspended breakpoint.
	ExprString string

	// Breakpoint condition
	Cond string
	// Breakpoint hit count condition.
	// Supported hit count conditions are "NUMBER" and "OP NUMBER".
	HitCond string
	// HitCondPerG use per goroutine hitcount as HitCond operand, instead of total hitcount
	HitCondPerG bool

	// Tracepoint flag, signifying this is a tracepoint.
	Tracepoint bool `json:"continue"`
	// TraceReturn flag signifying this is a breakpoint set at a return
	// statement in a traced function.
	TraceReturn bool `json:"traceReturn"`
	// retrieve goroutine information
	Goroutine bool `json:"goroutine"`
	// number of stack frames to retrieve
	Stacktrace int `json:"stacktrace"`
	// expressions to evaluate
	Variables []string `json:"variables,omitempty"`
	// LoadArgs requests loading function arguments when the breakpoint is hit
	LoadArgs *LoadConfig
	// LoadLocals requests loading function locals when the breakpoint is hit
	LoadLocals *LoadConfig

	// WatchExpr is the expression used to create this watchpoint
	WatchExpr string
	WatchType WatchType

	VerboseDescr []string `json:"VerboseDescr,omitempty"`

	// number of times a breakpoint has been reached in a certain goroutine
	HitCount map[string]uint64 `json:"hitCount"`
	// number of times a breakpoint has been reached
	TotalHitCount uint64 `json:"totalHitCount"`
	// Disabled flag, signifying the state of the breakpoint
	Disabled bool `json:"disabled"`

	UserData interface{} `json:"-"`

	// RootFuncName is the Root function from where tracing needs to be done
	RootFuncName string
	// TraceFollowCalls indicates the Depth of tracing
	TraceFollowCalls int
}

type CreateBreakpointIn struct {
	Breakpoint api.Breakpoint

	LocExpr             string
	SubstitutePathRules [][2]string
	Suspended           bool
}

Describe the solution you'd like A clear and concise description of what you want to happen.

add suspended in RPC pack. vscode-go always send suspended=true to delve.

Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.

nothing yet.

Additional context Add any other context or screenshots about the feature request here.

This issue is blocking #3712. #3712 is a promising enhancement.

Related PR: https://github.com/go-delve/delve/pull/3900

Lslightly avatar Mar 12 '25 14:03 Lslightly