Skip to content

Take advantage of suspending breakpoint provided by delve #3713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Lslightly opened this issue Mar 12, 2025 · 4 comments
Open

Take advantage of suspending breakpoint provided by delve #3713

Lslightly opened this issue Mar 12, 2025 · 4 comments

Comments

@Lslightly
Copy link
Contributor

Lslightly commented Mar 12, 2025

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

Version Information

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:

Current interface in delve is:

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: go-delve/delve#3900

@gopherbot gopherbot added this to the Untriaged milestone Mar 12, 2025
@Lslightly Lslightly changed the title Take advantage of ability to suspend breakpoint provided by delve Take advantage of suspending breakpoint provided by delve Mar 12, 2025
@Lslightly
Copy link
Contributor Author

Lslightly commented Mar 12, 2025

After trying to add suspended in the RPC data in legacy delve adapter as follows in delve and goDebug.ts, I found that CreateBreakpoint will return some unwanted breakpoint.

type CreateBreakpointIn struct {
	Breakpoint api.Breakpoint

	LocExpr             string
	SubstitutePathRules [][2]string

	// Suspended decides whether the breakpoint is suspended when its location
	// is not found when creating it
	Suspended bool `json:"suspended"`
}
this.delve?.isApiV1 ? breakpointIn : { Breakpoint: breakpointIn, suspended: true }

unwanted breakpoint:

Image


Reproduce the above screenshot:

git clone -b fixSuspended https://github.com/Lslightly/delve.git
git clone -b fixSuspended https://github.com/Lslightly/delve-dbg-child-proc.git
cd delve-dbg-child-proc
go build -gcflags "-N -l" . # avoid optimization and inlining
cd utils
go build -gcflags "-N -l" . # avoid optimization and inlining
cd ../../
git clone --depth 2 -b fixSuspended https://github.com/Lslightly/vscode-go.git
cd delve
# build delve
make build
# backup old dlv in your GOPATH
cp $(go env GOPATH)/bin/dlv $(go env GOPATH)/bin/dlv_backup
# use new dlv
ln -s $(pwd)/dlv $(go env GOPATH)/bin/dlv
# install vscode-go dependencies
cd ../vscode-go/extension
npm ci
# Then press launch F5 as docs/contribution.md says.
# open delve-dbg-child-proc in [Extension Development Host] vscode instance
# launch debug session in delve
# launch debug session in [Extension Development Host] vscode instance

Notice that in the settings.json in Lslightly/delve-dbg-child-proc, legacy adapter is used.

@Lslightly
Copy link
Contributor Author

Lslightly commented Mar 13, 2025

The cause of unwanted breakpoint: (legacy debug adapter)

Image

Lslightly added a commit to Lslightly/delve that referenced this issue Mar 13, 2025
@firelizzard18
Copy link
Contributor

Just FYI, I don't think the legacy adapter is being actively developed.

@Lslightly
Copy link
Contributor Author

Lslightly commented Mar 14, 2025

Just FYI, I don't think the legacy adapter is being actively developed.

Thanks. I also see the caller of CreateBreakpoint at service/dap/server.go#L1525, i.e. the DAP service of delve. Since suspended is hard coded as false, I choose to change the legacy mode first, which is easier to change the interface.

got, err = s.debugger.CreateBreakpoint(bp, "", nil, false)

Possibly changing false to true, combining with a fake breakpoint service/debugger/debugger.go#L812-L821, is sufficient.

	if suspended {
		// Create a breakpoint with enough information although its location is not found to cheat the frontend
		// Not completed yet.
		// Possibly notifying the client through err is a better solution
		// ErrSuspended can be a part of RPC interface
		createdBp.FunctionName = setbp.FunctionName
		createdBp.File = setbp.File
		createdBp.Line = setbp.Line
		createdBp.ExprString = setbp.ExprString
	}

Besides, @firelizzard18 Thanks for your detailed explanation of what goDebug.ts is. And here is the discussion link in gophers slack for future developers(possibly fewer in legacy adapter 😄 ).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants