Skip to main content

Command Palette

Search for a command to run...

Debugging Go programs using Delve

Published
3 min read
Debugging Go programs using Delve
N

Software Engineer based in Kathmandu, Nepal.

Enjoys mix of backend development, little bit of frontend, SRE, and DevOps. Currently building cloud native solutions in AWS using my favorite languages Ruby and GoLang.

Hobbies: Tech, Music and BIKES!

Delve is a very powerful tool to debug Go programs. For example, we can:

  • Set breakpoints and execute line-by-line.

  • Trace execution flow e.g.main -> fn1 -> fn2 .

  • View function parameters, and local/package variables and modify their values.

  • List, filter, and examine Go routines, and switch to a specific Go routine or thread.

  • List and analyze stack frames (can be useful to debug recursive functions)

  • Examine memory used by a certain address.

  • Debug a remote Go program e.g. inside a container.

Installation

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

Examples

  1. Run Go code in debug mode with breakpoint

     dlv debug algo.go
     Type 'help' for list of commands.
     # adding breakpoint in line num 15
     (dlv) break algo.go:15
     Breakpoint 1 set at 0x1021828c0 for main.fibRecur() ./algo.go:15
     # resume execution using 'continue' command
     (dlv) continue
     > main.fibRecur() ./algo.go:15 (hits goroutine(1):1 total:1) (PC: 0x1041a68c0)
     14: func fibRecur(n int) int {
     => 15: if n <= 2 {
     16: return 1
    
  2. Print variables and function arguments

     (dlv) args # prints function arguments and their values
     n = 30
     ~r0 = 0
     (dlv) locals # prints local variables at this execution
     (no locals)
     continue
     next
    
  3. Show code around the current breakpoint

     (dlv) list
     > main.fibRecur() ./algo.go:15 (hits goroutine(1):4 total:4) (PC: 0x102ab28c0)
     14: func fibRecur(n int) int {
     => 15: if n <= 2 {
     16: return 1
     17: }
     18: return fibRecur(n-1) + fibRecur(n-2)
     19: }
    
  4. Clear breakpoint and continue the code execution

     (dlv) clearall # deletes all breakpoints
     Breakpoint 1 cleared at 0x1046c28c0 for main.fibRecur() ./algo.go:15
     (dlv) continue # continues code execution
     fibRecur res: 832040
     finished in 51.890292084s
     fibDynamic res: 832040
     finished in 69µs
     Process 64689 has exited with status 0
    
  5. Display Goroutines
    Lists current goroutines, including the main function and Garbage Collector.

     (dlv) goroutines
     * Goroutine 1 - User: ./algo.go:15 main.fibRecur (0x1021828c0) (thread 5969677)
     Goroutine 2 - User: /Users/nirdosh/.gvm/gos/go1.21.1/src/runtime/proc.go:399 runtime.gopark (0x102116ba0) [force gc (idle)]
     Goroutine 3 - User: /Users/nirdosh/.gvm/gos/go1.21.1/src/runtime/proc.go:399 runtime.gopark (0x102116ba0) [GC sweep wait]
     Goroutine 4 - User: /Users/nirdosh/.gvm/gos/go1.21.1/src/runtime/proc.go:399 runtime.gopark (0x102116ba0) [GC scavenge wait]
     Goroutine 5 - User: /Users/nirdosh/.gvm/gos/go1.21.1/src/runtime/proc.go:399 runtime.gopark (0x102116ba0) [finalizer wait]
    
  6. Switch to a specific goroutine

     # goroutine 
     (dlv) goroutine 2
     Switched from 1 to 2 (thread 5969677)
    
  7. View Call Stack

     (dlv) stack
     0 0x0000000102ab28c0 in main.fibRecur # recursive function called 2nd time
     at ./algo.go:15
     1 0x0000000102ab28e8 in main.fibRecur # recursive function called 1st time
     at ./algo.go:18
     2 0x0000000102ab2ac8 in main.main
     at ./algo.go:40
     3 0x0000000102a46744 in runtime.main
     at /Users/nirdosh/.gvm/gos/go1.21.1/src/runtime/proc.go:267
     4 0x0000000102a71094 in runtime.goexit
     at /Users/nirdosh/.gvm/gos/go1.21.1/src/runtime/asm_arm64.s:1197
    
  8. See arguments in a specific call stack

     (dlv) stack 0 -full
     0 0x0000000102ab28c0 in main.fibRecur
     at ./algo.go:15
     n = 27 # variable passed to the function 'fibRecur'
     ~r0 = 0
    
  9. Tracing

     (dlv) trace main.main # enabling tracing for 'main' function
     Tracepoint 1 set at 0x104afaaa0 for main.main() ./algo.go:38
     (dlv) continue
     > goroutine(1): main.main()
     fibRecur res: 5 # fibRecur fn was called and it returned 5
     finished in 41.042µs
     fibDynamic res: 5. # fibDynamic fn was called and it returned 5
     finished in 2.708µs
     >> goroutine(1): => ()
     Process 66401 has exited with status 0
    
  10. Attach dlv to a running process
    Note that the code execution can be at any point(line number) when you attach the debugger. So, factor this while adding breakpoints.

    # ------- build and run go binary ----------
    go build -o testbinary main.go
    ./testbinary
    
    # ------- attach debugger -----------
    # from another terminal session, get process id
    pgrep testbinary
    88848
    
    # use pid from above
    dlv attach 88848
    (dlv) break main.go:28
    (dlv) continue
    
  11. Display command details

    # command: help 
    (dlv) help goroutines
    
  12. Record command output from the current debug session in a file

    (dlv) transcript algo_debugug.txt
    
  13. Integration with code editors
    In VSCode, dlv is bundled with the vscode-go extension.