Skip to content

Delve

Language: Go · Transport: TCP

The catalog entry:

[adapters.dlv]
id = "dlv"
command = "dlv"
args = ["dap", "--check-go-version=false"]
tcp_argument = "--listen={host}:{port}"
transport = { tcp = { host = "127.0.0.1" } }

[adapters.dlv.build]
command = "go"
args = ["build", "-gcflags=all=-N -l", "-o", "{out}", "{program}"]

--check-go-version=false keeps Delve from refusing to run when its build and your Go toolchain differ in version — a mismatch that is usually harmless and otherwise blocks debugging entirely.

Install

go install github.com/go-delve/delve/cmd/dlv@latest
command -v dlv
dlv version

Bugsaur builds, Delve debugs

This is the one adapter for which the catalog carries a build entry. Bugsaur compiles the program itself, in a building phase, and hands Delve a finished binary at a stable path — <root>/.bugsaur/build/<program name>.

The reason is measured, not theoretical: dlv debug builds into __debug_bin<random> and deletes it afterwards, and Go only reuses a linked executable at the same path. With a random name, every run paid for a full link. On a service with a 74 MB binary: 14 s every run, versus ~2 s for an unchanged rebuild.

The build flags matter

-gcflags=all=-N -l disables optimisation and inlining. Without it the binary has neither variables nor un-inlined frames — precisely what you started the debugger for. all= extends the rule to dependencies, not just main.

Note that -gcflags=all=-N -l is one argument containing a space, which is why command and args are separate lists rather than a single string.

Overriding the build

[profiles.worker.build]
command = "go"
args = ["build", "-tags=dev", "-gcflags=all=-N -l", "-o", "{out}", "./cmd/worker"]

{out} is the binary being built; {program} is what program names.

When Bugsaur does not build

  • mode = "attach" — the process is already running;
  • the profile names mode or program to Delve itself in launch_arguments — you are driving the launch by hand.

The second is how package-test debugging works: mode = "test" makes Delve build the test binary itself, and our build would hand it the wrong file.

Delve's own modes

Two different mode keys

mode at the profile level is DAP's launch / attach. mode inside launch_arguments is Delve's own — debug, test, exec — and it overrides the generated debug.

[profiles.api.launch_arguments]
mode = "exec"    # debug a binary that is already built
[profiles.orders-test.launch_arguments]
mode = "test"    # build and debug the package's tests

Goroutines

Delve reports goroutines as threads, so the Threads panel can list thousands of rows. That is the intended behaviour and the main tool for investigating a deadlock. See Threads.

Diagnostics

Symptom Cause Fix
Session never starts dlv not on PATH go install, check command -v
Session fails with compiler output the build failed fix the code; the text is in the Logs panel
launch takes minutes Delve is building inside the request mode = "exec" with a pre-built binary
Breakpoints land on odd lines optimisation was not disabled keep -gcflags=all=-N -l in a custom build
testdata/ not found in a test cwd defaults to the project root set cwd to the package directory