DEV Community

Shrijith Venkatramana
Shrijith Venkatramana

Posted on

Delve The Golang Debugger: A Deep Dive

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
Enter fullscreen mode Exit fullscreen mode

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: ...
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

To debug this, run:

dlv debug main.go
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can also enable logging to see detailed runtime info:

(dlv) config log true
(dlv) continue
[log output with goroutine scheduling, breakpoints, etc.]
Enter fullscreen mode Exit fullscreen mode

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"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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 and dlv 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.