Hi there! I'm Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a first of its kind tool for helping you automatically index API endpoints across all your repositories. LiveAPI helps you discover, understand and use APIs in large tech infrastructures with ease.
Debugging in Go can feel like a treasure hunt—sometimes you’re digging through logs, other times you’re staring at a crash with no clue where it went wrong. The Go debugger (Delve) is your trusty map. It’s a powerful tool designed specifically for Go, letting you pause, inspect, and step through your code with precision. In this post, we’ll explore Delve’s core features, walk through practical examples, and show you how to make debugging less of a headache. Whether you’re new to Go or a seasoned gopher, this guide will help you wield Delve like a pro.
Why Delve is Go’s Best Debugging Buddy
Delve is a debugger built from the ground up for Go’s unique features, like goroutines and interfaces. Unlike generic debuggers (like GDB), Delve understands Go’s runtime, making it easier to inspect goroutines, channels, and stack traces. It’s open-source, actively maintained, and supports Linux, macOS, and Windows.
Here’s a quick comparison of Delve vs. GDB for Go debugging:
Feature | Delve | GDB |
---|---|---|
Goroutine support | Native, with detailed inspection | Limited, often confusing |
Go runtime awareness | Full support | Partial, error-prone |
Ease of use | Simple commands | Steep learning curve |
Cross-platform | Linux, macOS, Windows | Varies by setup |
Key point: Use Delve for Go-specific debugging to avoid the quirks of generic tools like GDB. Install it with go install github.com/go-delve/delve/cmd/dlv@latest
and you’re ready to roll.
Delve’s GitHub has setup details if you hit snags.
Setting Up Delve: Quick and Painless
Before diving in, you need Delve installed and your environment ready. Run this to install Delve:
go install github.com/go-delve/delve/cmd/dlv@latest
Ensure your Go version is 1.16 or later, as Delve works best with modern Go. If you’re on Windows, you might need a C compiler like MinGW for some features (like call injection). For macOS and Linux, it’s usually smooth sailing.
To verify installation, run:
dlv version
# Output: Delve Debugger
# Version: 1.x.x
# Build: ...
Key point: Always check your Delve version to ensure compatibility with your Go codebase. If you’re debugging a complex project, set up a .dlv
config file in your project root to customize settings like max stack depth.
Your First Debug Session: Stepping Through Code
Let’s start with a simple Go program to see Delve in action. Create a file called main.go
:
package main
import "fmt"
func add(a, b int) int {
sum := a + b
return sum
}
func main() {
x := 5
y := 10
result := add(x, y)
fmt.Println("Sum:", result)
}
To debug this, run:
dlv debug main.go
This compiles the program, starts a debug session, and drops you into Delve’s interactive prompt. Try these commands:
-
break main.main: Sets a breakpoint at the start of the
main
function. - continue: Runs until the breakpoint.
- next: Steps to the next line.
-
print x: Inspects the value of variable
x
.
Example session:
(dlv) break main.main
Breakpoint 1 set at 0x4a7f2b for main.main() ./main.go:10
(dlv) continue
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0x4a7f2b)
5: func add(a, b int) int {
6: sum := a + b
7: return sum
8: }
9:
=> 10: func main() {
11: x := 5
12: y := 10
13: result := add(x, y)
14: fmt.Println("Sum:", result)
15: }
(dlv) next
> main.main() ./main.go:11 (PC: 0x4a7f3c)
6: sum := a + b
7: return sum
8: }
9:
10: func main() {
=> 11: x := 5
12: y := 10
13: result := add(x, y)
14: fmt.Println("Sum:", result)
15: }
(dlv) print x
5
Key point: Delve’s break
, continue
, and next
commands let you control program execution line by line. Use print
to inspect variables at any point.
Debugging Goroutines: Taming Concurrent Code
Go’s concurrency model with goroutines is powerful but tricky to debug. Delve shines here by letting you list and switch between goroutines. Consider this program with two goroutines:
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 3; i++ {
fmt.Println("Number:", i)
time.Sleep(100 * time.Millisecond)
}
}
func printLetters() {
for _, c := range "abc" {
fmt.Println("Letter:", string(c))
time.Sleep(150 * time.Millisecond)
}
}
func main() {
go printNumbers()
go printLetters()
time.Sleep(1 * time.Second)
}
Run dlv debug main.go
, set a breakpoint in printNumbers
, and continue:
(dlv) break printNumbers
Breakpoint 1 set at 0x4a7f5a for main.printNumbers() ./main.go:8
(dlv) continue
> main.printNumbers() ./main.go:8 (hits goroutine(6):1 total:1) (PC: 0x4a7f5a)
(dlv) goroutines
* Goroutine 1 - User: ./main.go:18 main.main (0x4a7f9c)
Goroutine 2 - User: runtime.gopark (0x43a7f0)
Goroutine 6 - User: ./main.go:8 main.printNumbers (0x4a7f5a)
Goroutine 7 - User: ./main.go:13 main.printLetters (0x4a7f7b)
(dlv) goroutine 7
Switched from 6 to 7 (thread 1)
> main.printLetters() ./main.go:13 (PC: 0x4a7f7b)
Key point: Use goroutines
to list all active goroutines and goroutine <id>
to switch to a specific one. This is crucial for debugging concurrent programs.
Inspecting Variables and Expressions
Delve lets you inspect variables, structs, and even evaluate expressions during debugging. Let’s extend our first example with a struct:
package main
import "fmt"
type Point struct {
X, Y int
}
func move(p Point, dx, dy int) Point {
p.X += dx
p.Y += dy
return p
}
func main() {
p := Point{X: 1, Y: 2}
p = move(p, 3, 4)
fmt.Println("Moved point:", p)
}
Set a breakpoint in move
and inspect the p
struct:
(dlv) break move
Breakpoint 1 set at 0x4a7f4a for main.move() ./main.go:9
(dlv) continue
> main.move() ./main.go:9 (hits goroutine(1):1 total:1) (PC: 0x4a7f4a)
(dlv) print p
main.Point {X: 1, Y: 2}
(dlv) print p.X + dx
4
Key point: Use print
to inspect structs or evaluate expressions like p.X + dx
. This helps verify logic without adding temporary fmt.Println
statements.
Conditional Breakpoints: Stopping Smarter
Sometimes you only want to pause execution under specific conditions. Delve supports conditional breakpoints to save time. Using the same printNumbers
program from earlier, let’s break only when i == 2
:
(dlv) break printNumbers
Breakpoint 1 set at 0x4a7f5a for main.printNumbers() ./main.go:8
(dlv) condition 1 i == 2
(dlv) continue
> main.printNumbers() ./main.go:8 (hits goroutine(6):1 total:1) (PC: 0x4a7f5a)
(dlv) print i
2
Key point: Use condition <breakpoint> <expression>
to trigger breakpoints only when a condition is true. This is great for loops or specific edge cases.
Delve’s documentation covers advanced breakpoint options.
Tracing and Logging: Watching Code Flow
Delve’s trace and logging features let you monitor execution without stopping. For example, to trace calls to add
in our first program:
(dlv) trace add
Tracepoint 1 set at 0x4a7f2a for main.add() ./main.go:5
(dlv) continue
Tracing main.add() ./main.go:5 (goroutine(1)): a=5, b=10
You can also enable logging to see detailed runtime info:
(dlv) config log true
(dlv) continue
[log output with goroutine scheduling, breakpoints, etc.]
Key point: Use trace
for lightweight call logging and config log true
for verbose runtime details. These are handy for understanding program flow without stepping through every line.
Debugging in VS Code: A Smoother Workflow
Delve integrates nicely with VS Code via the Go extension. Install the Go extension, then create a launch.json
in your .vscode
folder:
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/main.go"
}
]
}
Open main.go
, set breakpoints by clicking in the gutter, and hit F5 to start debugging. You’ll get a GUI for stepping, inspecting variables, and viewing goroutines.
Key point: VS Code with Delve offers a visual debugging experience, perfect for those who prefer clicking over typing commands.
VS Code Go extension has more setup tips.
Tips for Debugging Like a Gopher
To wrap up, here are practical tips to make Delve your go-to tool:
-
Start simple: Use
dlv debug
for quick sessions anddlv attach
for running processes. -
Leverage goroutine inspection: Always check
goroutines
when debugging concurrent code to avoid missing context. - Use conditional breakpoints: Save time by stopping only when specific conditions are met.
- Integrate with your IDE: Tools like VS Code streamline debugging with visual controls.
-
Check stack traces: Use
stack
to see the call stack when things crash unexpectedly.
Delve’s power lies in its Go-specific features and simplicity. Experiment with these commands on your own projects, and you’ll find debugging less daunting. Keep your Delve version updated, and check the official docs for new features as the tool evolves.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.