Debugging Go programs using Delve
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
- 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
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: }
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
3. 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]
Switch to a specific go routine :
# goroutine <num>
(dlv) goroutine 2
Switched from 1 to 2 (thread 5969677)
4. 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
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
5. 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
5. Attach dlv
into 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
6. Display command details
# command: help <cmd>
(dlv) help goroutines
7. Record command output from the current debug session in a file
(dlv) transcript algo_debugug.txt
8. Integration with code editors
In VSCode, dlv
is bundled with the vscode-go extension.