zhouhaibing089/blog

Debug your Go programs

Opened this issue · 0 comments

Debugging Go programs is truly a headache for me when I am trying to figure out how to use gdb or lldb, and if you must be frustrated when looking at this blog.

GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo. As a consequence, although GDB can be useful in some situations, it is not a reliable debugger for Go programs, particularly heavily concurrent ones. Moreover, it is not a priority for the Go project to address these issues, which are difficult. In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success.

But luckily, we have another tool which is called dlv.

Install

Install the binary on Mac is quite easy:

$ brew install go-delve/delve/delve
$ mkdir -p $GOPATH/src/github.com/derekparker
$ cd $GOPATH/src/github.com/derekparker
$ git clone https://github.com/derekparker/delve.git
$ cd delve
$ CERT=dlv-cert make install
$ brew uninstall go-delve/delve/delve

brew install go-delve/delve/delve will setup things which are about codesign, that is why we need this, after that, we can clone the latest code, and give it a build.

Basic Usage

There are five cases:

I have the compiled binary

dlv exec path/to/binary -- args/and/flags, this command will allow you to execute and binary, and then attach a debug session on that.

I have the source code

dlv debug package -- args/and/flags, this command will compile the package, then execute the binary, attach the debug session.

I have a running process and want full control

dlv attach pid, it will take over the control of the given process.

I have a running process and only want to trace the application

dlv trace package regexp -p ${pid} -s ${stack trace depth} will let you see the trace which is match with regexp.

I need to debug my test code

dlv test package will only compile the test code, and let you be able to debug code there.

Demo

Let's create a small snippet firstly.

// hello.go
package main

import "fmt"

func f(n int) int {
	arr := []int{0, 1}
	if n == 0 || n == 1 {
		return arr[n]
	}
	for i := 2; i <= n; i++ {
		arr[0], arr[1] = arr[1], arr[0]+arr[1]
	}
	return arr[1]
}

func main() {
	fmt.Printf("fibo(10)=%d\n", f(10))
}

Now dive into the debug session:

$ dlv debug hello.go
Type 'help' for list of commands.
(dlv) break hello.go:11
Breakpoint 1 set at 0x2107 for main.f() ./hello.go:11
(dlv) on 1 print arr[1]
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):1 total:1) (PC: 0x2107)
	arr[1]: 1
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):2 total:2) (PC: 0x2107)
	arr[1]: 1
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):3 total:3) (PC: 0x2107)
	arr[1]: 2
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):4 total:4) (PC: 0x2107)
	arr[1]: 3
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):5 total:5) (PC: 0x2107)
	arr[1]: 5
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):6 total:6) (PC: 0x2107)
	arr[1]: 8
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):7 total:7) (PC: 0x2107)
	arr[1]: 13
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):8 total:8) (PC: 0x2107)
	arr[1]: 21
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
> main.f() ./hello.go:11 (hits goroutine(1):9 total:9) (PC: 0x2107)
	arr[1]: 34
     6:		arr := []int{0, 1}
     7:		if n == 0 || n == 1 {
     8:			return arr[n]
     9:		}
    10:		for i := 2; i <= n; i++ {
=>  11:			arr[0], arr[1] = arr[1], arr[0]+arr[1]
    12:		}
    13:		return arr[1]
    14:	}
    15:
    16:	func main() {
(dlv) c
fibo(10)=55
Process 48681 has exited with status 0

References