使用 Delve 代替 Println 来调试 Go 程序
Go 调试

> 
> Delve 是能让调试变成轻而易举的事的万能工具包。
> 
> 
> 


![](/data/attachment/album/202007/09/234357pavw88sbxab0makj.jpg)


你上次尝试去学习一种新的编程语言时什么时候？你有没有持之以恒，你是那些在新事物发布的第一时间就勇敢地去尝试的一员吗？不管怎样，学习一种新的语言也许非常有用，也会有很多乐趣。


你尝试着写简单的 “Hello, world!”，然后写一些示例代码并执行，继续做一些小的修改，之后继续前进。我敢保证我们都有过这个经历，不论我们使用哪种技术。假如你尝试用一段时间一种语言，并且你希望能够精通它，那么有一些事物能在你的进取之路上帮助你。


其中之一就是调试器。有些人喜欢在代码中用简单的 “print” 语句进行调试，这种方式很适合代码量少的简单程序；然而，如果你处理的是有多个开发者和几千行代码的大型项目，你应该使用调试器。


最近我开始学习 Go 编程语言了，在本文中，我们将探讨一种名为 Delve 的调试器。Delve 是专门用来调试 Go 程序的工具，我们会借助一些 Go 示例代码来了解下它的一些功能。不要担心这里展示的 Go 示例代码；即使你之前没有写过 Go 代码也能看懂。Go 的目标之一是简单，因此代码是始终如一的，理解和解释起来都很容易。


### Delve 介绍


Delve 是托管在 [GitHub](https://github.com/go-delve/delve) 上的一个开源项目。


它自己的文档中写道：



> 
> Delve 是 Go 编程语言的调试器。该项目的目标是为 Go 提供一个简单、全功能的调试工具。Delve 应该是易于调用和易于使用的。当你使用调试器时，事情可能不会按你的思路运行。如果你这样想，那么你不适合用 Delve。
> 
> 
> 


让我们来近距离看一下。


我的测试系统是运行着 Fedora Linux 的笔记本电脑，Go 编译器版本如下：



```
$ cat /etc/fedora-release
Fedora release 30 (Thirty)
$
$ go version
go version go1.12.17 linux/amd64
$

```

### Golang 安装


如果你没有安装 Go，你可以运行下面的命令，很轻松地就可以从配置的仓库中获取。



```
$ dnf install golang.x86_64

```

或者，你可以在[安装页面](https://golang.org/doc/install)找到适合你的操作系统的其他安装版本。


在开始之前，请先确认已经设置好了 Go 工具依赖的下列各个路径。如果这些路径没有设置，有些示例可能不能正常运行。你可以在 SHELL 的 RC 文件中轻松设置这些环境变量，我的机器上是在 `$HOME/bashrc` 文件中设置的。



```
$ go env | grep GOPATH
GOPATH="/home/user/go"
$
$ go env | grep GOBIN
GOBIN="/home/user/go/gobin"
$

```

### Delve 安装


你可以像下面那样，通过运行一个简单的 `go get` 命令来安装 Delve。`go get` 是 Golang 从外部源下载和安装需要的包的方式。如果你安装过程中遇到了问题，可以查看 [Delve 安装教程](https://github.com/go-delve/delve/blob/master/Documentation/installation/linux/install.md)。



```
$ go get -u github.com/go-delve/delve/cmd/dlv
$

```

运行上面的命令，就会把 Delve 下载到你的 `$GOPATH` 的位置，如果你没有把 `$GOPATH` 设置成其他值，那么默认情况下 `$GOPATH` 和 `$HOME/go` 是同一个路径。


你可以进入 `go/` 目录，你可以在 `bin/` 目录下看到 `dlv`。



```
$ ls -l $HOME/go
total 8
drwxrwxr-x. 2 user user 4096 May 25 19:11 bin
drwxrwxr-x. 4 user user 4096 May 25 19:21 src
$
$ ls -l ~/go/bin/
total 19596
-rwxrwxr-x. 1 user user 20062654 May 25 19:17 dlv
$

```

因为你把 Delve 安装到了 `$GOPATH`，所以你可以像运行普通的 shell 命令一样运行它，即每次运行时你不必先进入它所在的目录。你可以通过 `version` 选项来验证 `dlv` 是否正确安装。示例中安装的版本是 1.4.1。



```
$ which dlv
~/go/bin/dlv
$
$ dlv version
Delve Debugger
Version: 1.4.1
Build: $Id: bda606147ff48b58bde39e20b9e11378eaa4db46 $
$

```

现在，我们一起在 Go 程序中使用 Delve 来理解下它的功能以及如何使用它们。我们先来写一个 `hello.go`，简单地打印一条 `Hello, world!` 信息。


记着，我把这些示例程序放到了 `$GOBIN` 目录下。



```
$ pwd
/home/user/go/gobin
$
$ cat hello.go
package main

import "fmt"

func main() {
        fmt.Println("Hello, world!")
}
$

```

运行 `build` 命令来编译一个 Go 程序，它的输入是 `.go` 后缀的文件。如果程序没有语法错误，Go 编译器把它编译成一个二进制可执行文件。这个文件可以被直接运行，运行后我们会在屏幕上看到 `Hello, world!` 信息。



```
$ go build hello.go
$
$ ls -l hello
-rwxrwxr-x. 1 user user 1997284 May 26 12:13 hello
$
$ ./hello
Hello, world!
$

```

### 在 Delve 中加载程序


把一个程序加载进 Delve 调试器有两种方式。


#### 在源码编译成二进制文件之前使用 debug 参数


第一种方式是在需要时对源码使用 `debug` 命令。Delve 会为你编译出一个名为 `__debug_bin` 的二进制文件，并把它加载进调试器。


在这个例子中，你可以进入 `hello.go` 所在的目录，然后运行 `dlv debug` 命令。如果目录中有多个源文件且每个文件都有自己的主函数，Delve 则可能抛出错误，它期望的是单个程序或从单个项目构建成单个二进制文件。如果出现了这种错误，那么你就应该用下面展示的第二种方式。



```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun  4 11:48 hello.go
$
$ dlv debug
Type 'help' for list of commands.
(dlv)

```

现在打开另一个终端，列出目录下的文件。你可以看到一个多出来的 `__debug_bin` 二进制文件，这个文件是由源码编译生成的，并会加载进调试器。你现在可以回到 `dlv` 提示框继续使用 Delve。



```
$ ls -l
total 2036
-rwxrwxr-x. 1 user user 2077085 Jun  4 11:48 __debug_bin
-rw-rw-r--. 1 user user      74 Jun  4 11:48 hello.go
$

```

#### 使用 exec 参数


如果你已经有提前编译好的 Go 程序或者已经用 `go build` 命令编译完成了，不想再用 Delve 编译出 `__debug_bin` 二进制文件，那么第二种把程序加载进 Delve 的方法在这些情况下会很有用。在上述情况下，你可以使用 `exec` 命令来把整个目录加载进 Delve 调试器。



```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun  4 11:48 hello.go
$
$ go build hello.go
$
$ ls -l
total 1956
-rwxrwxr-x. 1 user user 1997284 Jun  4 11:54 hello
-rw-rw-r--. 1 user user      74 Jun  4 11:48 hello.go
$
$ dlv exec ./hello
Type 'help' for list of commands.
(dlv)

```

### 查看 delve 帮助信息


在 `dlv` 提示符中，你可以运行 `help` 来查看 Delve 提供的多种帮助选项。命令列表相当长，这里我们只列举一些重要的功能。下面是 Delve 的功能概览。



```
(dlv) help
The following commands are available:

Running the program:

Manipulating breakpoints:

Viewing program variables and memory:

Listing and switching between threads and goroutines:

Viewing the call stack and selecting frames:

Other commands:

Type help followed by a command for full documentation.
(dlv)

```

#### 设置断点


现在我们已经把 hello.go 程序加载进了 Delve 调试器，我们在主函数处设置断点，稍后来确认它。在 Go 中，主程序从 `main.main` 处开始执行，因此你需要给这个名字提供个 `break` 命令。之后，我们可以用 `breakpoints` 命令来检查断点是否正确设置了。


不要忘了你还可以用命令简写，因此你可以用 `b main.main` 来代替 `break main.main`，两者效果相同，`bp` 和 `breakpoints` 同理。你可以通过运行 `help` 命令查看帮助信息来找到你想要的命令简写。



```
(dlv) break main.main
Breakpoint 1 set at 0x4a228f for main.main() ./hello.go:5
(dlv) breakpoints
Breakpoint runtime-fatal-throw at 0x42c410 for runtime.fatalthrow() /usr/lib/golang/src/runtime/panic.go:663 (0)
Breakpoint unrecovered-panic at 0x42c480 for runtime.fatalpanic() /usr/lib/golang/src/runtime/panic.go:690 (0)
        print runtime.curg._panic.arg
Breakpoint 1 at 0x4a228f for main.main() ./hello.go:5 (0)
(dlv)

```

#### 程序继续执行


现在，我们用 `continue` 来继续运行程序。它会运行到断点处中止，在我们的例子中，会运行到主函数的 `main.main` 处中止。从这里开始，我们可以用 `next` 命令来逐行执行程序。请注意，当我们运行到 `fmt.Println("Hello, world!")` 处时，即使我们还在调试器里，我们也能看到打印到屏幕的 `Hello, world!`。



```
(dlv) continue
> main.main() ./hello.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a228f)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func main() {
     6:         fmt.Println("Hello, world!")
     7: }
(dlv) next
> main.main() ./hello.go:6 (PC: 0x4a229d)
     1: package main
     2:
     3: import "fmt"
     4:
     5: func main() {
=>   6:              fmt.Println("Hello, world!")
     7: }
(dlv) next
Hello, world!
> main.main() ./hello.go:7 (PC: 0x4a22ff)
     2:
     3: import "fmt"
     4:
     5: func main() {
     6:         fmt.Println("Hello, world!")
=>   7:      }
(dlv)

```

#### 退出 Delve


你随时可以运行 `quit` 命令来退出调试器，退出之后你会回到 shell 提示符。相当简单，对吗？



```
(dlv) quit
$

```

### Delve 的其他功能


我们用其他的 Go 程序来探索下 Delve 的其他功能。这次，我们从 [golang 教程](https://tour.golang.org/basics/4) 中找了一个程序。如果你要学习 Go 语言，那么 Golang 教程应该是你的第一站。


下面的程序，`functions.go` 中简单展示了 Go 程序中是怎样定义和调用函数的。这里，我们有一个简单的把两数相加并返回和值的 `add()` 函数。你可以像下面那样构建程序并运行它。



```
$ cat functions.go
package main

import "fmt"

func add(x int, y int) int {
        return x + y
}

func main() {
        fmt.Println(add(42, 13))
}
$

```

你可以像下面那样构建和运行程序。



```
$ go build functions.go  && ./functions
55
$

```

#### 进入函数


跟前面展示的一样，我们用前面提到的一个选项来把二进制文件加载进 Delve 调试器，再一次在 `main.main` 处设置断点，继续运行程序直到断点处。然后执行 `next` 直到 `fmt.Println(add(42, 13))` 处；这里我们调用了 `add()` 函数。我们可以像下面展示的那样，用 Delve 的 `step` 命令从 `main` 函数进入 `add()` 函数。



```
$ dlv debug
Type 'help' for list of commands.
(dlv) break main.main
Breakpoint 1 set at 0x4a22b3 for main.main() ./functions.go:9
(dlv) c
> main.main() ./functions.go:9 (hits goroutine(1):1 total:1) (PC: 0x4a22b3)
     4:
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
=>   9:      func main() {
    10:         fmt.Println(add(42, 13))
    11: }
(dlv) next
> main.main() ./functions.go:10 (PC: 0x4a22c1)
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=>  10:              fmt.Println(add(42, 13))
    11: }
(dlv) step
> main.add() ./functions.go:5 (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv)

```

#### 使用`文件名:行号`来设置断点


上面的例子中，我们经过 `main` 函数进入了 `add()` 函数，但是你也可以在你想加断点的地方直接使用“文件名:行号”的组合。下面是在 `add()` 函数开始处加断点的另一种方式。



```
(dlv) break functions.go:5
Breakpoint 1 set at 0x4a2280 for main.add() ./functions.go:5
(dlv) continue
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv)

```

#### 查看当前的栈信息


现在我们运行到了 `add()` 函数，我们可以在 Delve 中用 `stack` 命令查看当前栈的内容。这里在 `0` 位置展示了栈顶的函数 `add()` ，紧接着在 `1` 位置展示了调用 `add()` 函数的 `main.main`。在 `main.main` 下面的函数属于 Go 运行时，是用来处理加载和执行该程序的。



```
(dlv) stack
0  0x00000000004a2280 in main.add
   at ./functions.go:5
1  0x00000000004a22d7 in main.main
   at ./functions.go:10
2  0x000000000042dd1f in runtime.main
   at /usr/lib/golang/src/runtime/proc.go:200
3  0x0000000000458171 in runtime.goexit
   at /usr/lib/golang/src/runtime/asm_amd64.s:1337
(dlv)

```

#### 在帧之间跳转


在 Delve 中我们可以用 `frame` 命令实现帧之间的跳转。在下面的例子中，我们用 `frame` 实现了从 `add()` 帧跳到 `main.main` 帧，以此类推。



```
(dlv) frame 0
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 0: ./functions.go:5 (PC: 4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv) frame 1
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 1: ./functions.go:10 (PC: 4a22d7)
     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=>  10:              fmt.Println(add(42, 13))
    11: }
(dlv)

```

#### 打印函数参数


一个函数通常会接收多个参数。在 `add()` 函数中，它的入参是两个整型。Delve 有个便捷的 `args` 命令，它能打印出命令行传给函数的参数。



```
(dlv) args
x = 42
y = 13
~r2 = 824633786832
(dlv)

```

#### 查看反汇编码


由于我们是调试编译出的二进制文件，因此如果我们能查看编译器生成的汇编语言指令将会非常有用。Delve 提供了一个 `disassemble` 命令来查看这些指令。在下面的例子中，我们用它来查看 `add()` 函数的汇编指令。



```
(dlv) step
> main.add() ./functions.go:5 (PC: 0x4a2280)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5:      func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
    10:         fmt.Println(add(42, 13))
(dlv) disassemble
TEXT main.add(SB) /home/user/go/gobin/functions.go
=>   functions.go:5  0x4a2280   48c744241800000000   mov qword ptr [rsp+0x18], 0x0
        functions.go:6  0x4a2289   488b442408           mov rax, qword ptr [rsp+0x8]
        functions.go:6  0x4a228e   4803442410           add rax, qword ptr [rsp+0x10]
        functions.go:6  0x4a2293   4889442418           mov qword ptr [rsp+0x18], rax
        functions.go:6  0x4a2298   c3                   ret
(dlv)

```

#### 单步退出函数


另一个功能是 `stepout`，这个功能可以让我们跳回到函数被调用的地方。在我们的例子中，如果我们想回到 `main.main` 函数，我们只需要简单地运行 `stepout` 命令，它就会把我们带回去。在我们调试大型代码库时，这个功能会是一个非常便捷的工具。



```
(dlv) stepout
> main.main() ./functions.go:10 (PC: 0x4a22d7)
Values returned:
        ~r2: 55

     5: func add(x int, y int) int {
     6:         return x + y
     7: }
     8:
     9: func main() {
=>  10:              fmt.Println(add(42, 13))
    11: }
(dlv)

```

#### 打印变量信息


我们一起通过 [Go 教程](https://tour.golang.org/basics/9) 的另一个示例程序来看下 Delve 是怎么处理 Go 中的变量的。下面的示例程序定义和初始化了一些不同类型的变量。你可以构建和运行程序。



```
$ cat variables.go
package main

import "fmt"

var i, j int = 1, 2

func main() {
        var c, python, java = true, false, "no!"
        fmt.Println(i, j, c, python, java)
}
$

$ go build variables.go &&; ./variables
1 2 true false no!
$

```

像前面说过的那样，用 `delve debug` 在调试器中加载程序。你可以在 Delve 中用 `print` 命令通过变量名来展示他们当前的值。



```
(dlv) print c
true
(dlv) print java
"no!"
(dlv)

```

或者，你还可以用 `locals` 命令来打印函数内所有的局部变量。



```
(dlv) locals
python = false
c = true
java = "no!"
(dlv)

```

如果你不知道变量的类型，你可以用 `whatis` 命令来通过变量名来打印它的类型。



```
(dlv) whatis python
bool
(dlv) whatis c
bool
(dlv) whatis java
string
(dlv)

```

### 总结


现在我们只是了解了 Delve 所有功能的皮毛。你可以自己去查看帮助内容，尝试下其它的命令。你还可以把 Delve 绑定到运行中的 Go 程序上（守护进程！），如果你安装了 Go 源码库，你甚至可以用 Delve 导出 Golang 库内部的信息。勇敢去探索吧！




---


via: <https://opensource.com/article/20/6/debug-go-delve>


作者：[Gaurav Kamathe](https://opensource.com/users/gkamathe) 选题：[lujun9972](https://github.com/lujun9972) 译者：[lxbwolf](https://github.com/lxbwolf) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
When was the last time you tried to learn a new programming language? Do you stick with your tried and true, or are you one of the brave souls who tries out a new one as soon as it is announced? Either way, learning a new language can be extremely useful, and a lot of fun.

You try out with a simple "Hello, world!" then move onto writing some sample code and executing it, making minor changes along the way and then moving on from there. I am sure we have all gone through this experience no matter which technology we've worked on. Though if you do manage to stay with a language for some time, and you wish to become proficient in it, there are a few things that can help you along the way.

One of those things is a debugger. Some people prefer debugging using simple "print" statements in code, and they are fine for a few simple line programs; however, if you are working on a large project with multiple developers and thousands of lines of code, it makes sense to invest in a debugger.

I've recently started learning the Go programming language, and in this article, we will explore a debugger called Delve. Delve is a special utility made to debug Go programs, and we will cover some of its features using some sample Go code. Don't worry about the Go code examples presented here; they are understandable even if you have never programmed in Go before. One of the goals of Go is simplicity, so the code is consistent, which can be easier to understand and interpret.

## Introduction to Delve


Delve is an open source project hosted at [GitHub](https://github.com/go-delve/delve).

In its own words:

*Delve is a debugger for the Go programming language. The goal of the project is to provide a simple, full-featured debugging tool for Go. Delve should be easy to invoke and easy to use. Chances are if you're using a debugger, things aren't going your way. With that in mind, Delve should stay out of your way as much as possible.*

Let's take a closer look.

My test system is a laptop running Fedora Linux along with the following Go compiler version:

```
$ cat /etc/fedora-release
Fedora release 30 (Thirty)
$
$ go version
go version go1.12.17 linux/amd64
$
```


## Golang installation

If you do not have Go installed, you can get it by simply running the following command to fetch it from your configured repositories.

`$ dnf install golang.x86_64`


Alternatively, you can visit the [install page](https://golang.org/doc/install) for other installation options suitable for your OS distribution.

Before we get started, please ensure that the following required PATHS are set, as required by Go tools. If these paths are not set, some of the examples might not work. These can be easily set as environment variables in your SHELL's RC file, like the `$HOME/bashrc`

file in my case.

```
$ go env | grep GOPATH
GOPATH="/home/user/go"
$
$ go env | grep GOBIN
GOBIN="/home/user/go/gobin"
$
```


## Delve installation

You can install Delve by running a simple `go get`

command, as shown below. `go get`

is the Golang way of downloading and installing required packages from external sources. If you do face any issues with installation, please refer to the install instructions for Delve [here](https://github.com/go-delve/delve/blob/master/Documentation/installation/linux/install.md).

```
$ go get -u github.com/go-delve/delve/cmd/dlv
$
```


Running the above command downloads Delve to your $GOPATH location, which, in the default case, happens to be $HOME/go. It will be different if you have set $GOPATH to something else.

You can move to the go/ directory, within which you will see `dlv`

under bin/ directory.

```
$ ls -l $HOME/go
total 8
drwxrwxr-x. 2 user user 4096 May 25 19:11 bin
drwxrwxr-x. 4 user user 4096 May 25 19:21 src
$
$ ls -l ~/go/bin/
total 19596
-rwxrwxr-x. 1 user user 20062654 May 25 19:17 dlv
$
```


Since you installed Delve under $GOPATH, it is also available as a regular shell command, so you do not have to move to the directory where it is installed every time. You can verify `dlv`

is correctly installed by running it with the `version`

option. The version it installed is 1.4.1.

```
$ which dlv
~/go/bin/dlv
$
$ dlv version
Delve Debugger
Version: 1.4.1
Build: $Id: bda606147ff48b58bde39e20b9e11378eaa4db46 $
$
```


Now, let's use Delve with some Go programs to understand its features and how to use them. As we do with all programs, let's start with a simple "Hello, world!" message, which, in Go, is called `hello.go`

.

Remember, I am placing these sample programs within the $GOBIN directory.

```
$ pwd
/home/user/go/gobin
$
$ cat hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
$
```


To build a Go program, you run the `build`

command and provide it with the source file using the .go extention. If the program is free of any syntax issues, the Go compiler compiles it and puts out a binary or executables file. This file can then be executed directly, and we see the "Hello, world!" message displayed onscreen.

```
$ go build hello.go
$
$ ls -l hello
-rwxrwxr-x. 1 user user 1997284 May 26 12:13 hello
$
$ ./hello
Hello, world!
$
```


## Loading a program in Delve

There are two ways to load a program into the Delve debugger.


**Using the debug argument when source code is not yet compiled to binary.**


The first way is to use the **debug** command when you simply need to source files. Delve compiles a binary for you named **__debug_bin**, and loads it into the debugger.

In this example, move to the directory where **hello.go** is present and run the **dlv debug** command. If there are multiple Go source files within a directory and each has its own main function, then Delve might throw an error, expecting a single program or a single project to build a binary from. Should that occur, you're better off using the second option, presented below.

```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go
$
$ dlv debug
Type 'help' for list of commands.
(dlv)
```



Now open another terminal and list the contents of the same directory. You see an additional **__debug_bin** binary that was compiled from the source code and loaded into the debugger. You can now move to the **dlv** prompt to continue using Delve further.


```
$ ls -l
total 2036
-rwxrwxr-x. 1 user user 2077085 Jun 4 11:48 __debug_bin
-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go
$
```



**Using the exec argument **

The second method to load a program into Delve is useful when you have a pre-compiled Go binary, or one that you have already compiled using the **go build **command, and don't want Delve to compile it to a `__debug_bin` binary. In such cases, use the **exec** argument to load the binary directory into the Delve debugger.

```
$ ls -l
total 4
-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go
$
$ go build hello.go
$
$ ls -l
total 1956
-rwxrwxr-x. 1 user user 1997284 Jun 4 11:54 hello
-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go
$
$ dlv exec ./hello
Type 'help' for list of commands.
(dlv)
```


## Getting help within Delve

At the **dlv** prompt, you can run `help`

to have a look at the various help options available in Delve. The command list is quite extensive, and we will cover some of the important features here. Following is an overview of Delve's functionality.

```
(dlv) help
The following commands are available:
Running the program:
Manipulating breakpoints:
Viewing program variables and memory:
Listing and switching between threads and goroutines:
Viewing the call stack and selecting frames:
Other commands:
Type help followed by a command for full documentation.
(dlv)
```


### Setting breakpoints

Now that we have loaded the hello.go program within the Delve debugger, let's set the breakpoint on our main function and then confirm it. Within Go, the main program starts with `main.main`

, so you need to provide this name to the `break command`

. Next, we will see if the breakpoint was set correctly using the `breakpoints`

command.

Also, remember you can use shorthands for commands, so instead of using `break main.main`

, you can also use `b main.main`

to the same effect, or `bp`

instead of `breakpoints`

. To find the exact shorthand for a command, please refer to the help section by running the `help`

command.

```
(dlv) break main.main
Breakpoint 1 set at 0x4a228f for main.main() ./hello.go:5
(dlv) breakpoints
Breakpoint runtime-fatal-throw at 0x42c410 for runtime.fatalthrow() /usr/lib/golang/src/runtime/panic.go:663 (0)
Breakpoint unrecovered-panic at 0x42c480 for runtime.fatalpanic() /usr/lib/golang/src/runtime/panic.go:690 (0)
print runtime.curg._panic.arg
Breakpoint 1 at 0x4a228f for main.main() ./hello.go:5 (0)
(dlv)
```


### Continue execution of the program

Now, let's continue running the program using "continue." It will run until it hits a breakpoint, which, in our case, is the `main.main`

or the main function. From there, we can use the `next`

command to execute the program line by line. Notice that, once we move ahead of `fmt.Println("Hello, world!")`

, we can see that `Hello, world!`

is printed to the screen while we are still within the debugger session.

```
(dlv) continue
> main.main() ./hello.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a228f)
1: package main
2:
3: import "fmt"
4:
=> 5: func main() {
6: fmt.Println("Hello, world!")
7: }
(dlv) next
> main.main() ./hello.go:6 (PC: 0x4a229d)
1: package main
2:
3: import "fmt"
4:
5: func main() {
=> 6: fmt.Println("Hello, world!")
7: }
(dlv) next
Hello, world!
> main.main() ./hello.go:7 (PC: 0x4a22ff)
2:
3: import "fmt"
4:
5: func main() {
6: fmt.Println("Hello, world!")
=> 7: }
(dlv)
```


### Quitting Delve

If you wish to quit the debugger at any time, you can run the `quit`

command, and you will be returned to the shell prompt. Pretty simple, right?

```
(dlv) quit
$
```


Let's use some other Go programs to explore some other Delve features. This time, we will pick a program from the [Golang tour](https://tour.golang.org/basics/4). If you are learning Go, the Golang tour should be your first stop.

The following program, `functions.go`

, simply displays how functions are defined and called in a Go program. Here, we have a simple `add()`

function that adds two numbers and returns their value. You can build the program and execute it, as shown below.

```
$ cat functions.go
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
$
```


You can build the program and execute it as shown below.

```
$ go build functions.go && ./functions
55
$
```


### Stepping into functions

As shown earlier, let's load the binary into the Delve debugger using one of the options mentioned earlier, again setting a breakpoint at `main.main`

and continuing to run the program while we hit the breakpoint. Then hit `next`

until you reach `fmt.Println(add(42, 13))`

; here we make a call to the `add()`

function. We can use the Delve `step`

command to move from the `main`

function to the `add()`

function, as seen below.

```
$ dlv debug
Type 'help' for list of commands.
(dlv) break main.main
Breakpoint 1 set at 0x4a22b3 for main.main() ./functions.go:9
(dlv) c
> main.main() ./functions.go:9 (hits goroutine(1):1 total:1) (PC: 0x4a22b3)
4:
5: func add(x int, y int) int {
6: return x + y
7: }
8:
=> 9: func main() {
10: fmt.Println(add(42, 13))
11: }
(dlv) next
> main.main() ./functions.go:10 (PC: 0x4a22c1)
5: func add(x int, y int) int {
6: return x + y
7: }
8:
9: func main() {
=> 10: fmt.Println(add(42, 13))
11: }
(dlv) step
> main.add() ./functions.go:5 (PC: 0x4a2280)
1: package main
2:
3: import "fmt"
4:
=> 5: func add(x int, y int) int {
6: return x + y
7: }
8:
9: func main() {
10: fmt.Println(add(42, 13))
(dlv)
```


### Set breakpoint using filename: linenumber

Above, we went through `main`

and then moved to the `add()`

function, but you can also use the `filename:linenumber`

combination to set a breakpoint directly where we want it. Following is another way of setting a breakpoint at the start of the `add()`

function.

```
(dlv) break functions.go:5
Breakpoint 1 set at 0x4a2280 for main.add() ./functions.go:5
(dlv) continue
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
1: package main
2:
3: import "fmt"
4:
=> 5: func add(x int, y int) int {
6: return x + y
7: }
8:
9: func main() {
10: fmt.Println(add(42, 13))
(dlv)
```


### View current stack details

Now that we are at the `add()`

function, we can view the current contents of the stack using the `stack`

command in Delve. This shows the topmost function `add()`

at 0 where we are, followed by `main.main`

at 1 from where the `add()`

function was called. Functions below `main.main`

belong to the Go runtime, which is responsible for loading and executing the program.

```
(dlv) stack
0 0x00000000004a2280 in main.add
at ./functions.go:5
1 0x00000000004a22d7 in main.main
at ./functions.go:10
2 0x000000000042dd1f in runtime.main
at /usr/lib/golang/src/runtime/proc.go:200
3 0x0000000000458171 in runtime.goexit
at /usr/lib/golang/src/runtime/asm_amd64.s:1337
(dlv)
```


### Move between frames

Using the `frame`

command in Delve, we can switch between the above frames at will. In the example below, using `frame 1`

switches us from within the `add()`

frame to the `main.main`

frame, and so on.

```
(dlv) frame 0
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 0: ./functions.go:5 (PC: 4a2280)
1: package main
2:
3: import "fmt"
4:
=> 5: func add(x int, y int) int {
6: return x + y
7: }
8:
9: func main() {
10: fmt.Println(add(42, 13))
(dlv) frame 1
> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)
Frame 1: ./functions.go:10 (PC: 4a22d7)
5: func add(x int, y int) int {
6: return x + y
7: }
8:
9: func main() {
=> 10: fmt.Println(add(42, 13))
11: }
(dlv)
```


**Print function arguments**

A function often accepts multiple arguments to work on. In the case of the `add()`

function, it accepts two integers. Delve has a handy command called `args`

, which displays the command-line arguments passed to the function.

```
(dlv) args
x = 42
y = 13
~r2 = 824633786832
(dlv)
```


### View disassembly

Since we are dealing with compiled binaries, it is extremely helpful to be able to see the assembly language instructions produced by the compiler. Delve provides a `disassemble`

command to view these. In the example below, we use it to view the disassembly instructions for the `add()`

function.

```
(dlv) step
> main.add() ./functions.go:5 (PC: 0x4a2280)
1: package main
2:
3: import "fmt"
4:
=> 5: func add(x int, y int) int {
6: return x + y
7: }
8:
9: func main() {
10: fmt.Println(add(42, 13))
(dlv) disassemble
TEXT main.add(SB) /home/user/go/gobin/functions.go
=> functions.go:5 0x4a2280 48c744241800000000 mov qword ptr [rsp+0x18], 0x0
functions.go:6 0x4a2289 488b442408 mov rax, qword ptr [rsp+0x8]
functions.go:6 0x4a228e 4803442410 add rax, qword ptr [rsp+0x10]
functions.go:6 0x4a2293 4889442418 mov qword ptr [rsp+0x18], rax
functions.go:6 0x4a2298 c3 ret
(dlv)
```


### Stepout of functions

Another feature is `stepout`

, which allows us to return back to where the function was called from. In our example, if we wish to return to the `main.main`

function, we can simply run the `stepout`

command and it will take us back. This can be quite a handy tool to help you move around in a large codebase.

```
(dlv) stepout
> main.main() ./functions.go:10 (PC: 0x4a22d7)
Values returned:
~r2: 55
5: func add(x int, y int) int {
6: return x + y
7: }
8:
9: func main() {
=> 10: fmt.Println(add(42, 13))
11: }
(dlv)
```


Let's use another sample program from the [Go tour](https://tour.golang.org/basics/9) to see how Delve deals with variables in Go. The following sample program defines and initializes some variables of different types. You can build the program and execute it.

```
$ cat variables.go
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}
$
$ go build variables.go && ./variables
1 2 true false no!
$
```


### Print variable information

As stated earlier, use `delve debug`

to load the program in the debugger. You can use the `print`

command from within Delve along with the variable name to show their current values.

```
(dlv) print c
true
(dlv) print java
"no!"
(dlv)
```


Alternatively, you can use the `locals`

command to print all local variables within a function.

```
(dlv) locals
python = false
c = true
java = "no!"
(dlv)
```


If you are not aware of the type of the variable, you can use the `whatis`

command along with the variable name to print the type.

```
(dlv) whatis python
bool
(dlv) whatis c
bool
(dlv) whatis java
string
(dlv)
```


## Conclusion

So far, we have only scratched the surface of the features Delve offers. You can refer to the` help`

section and try out various other commands. Some of the other useful features include attaching Delve to running Go programs (daemons!) or even using Delve to explore some of the internals of Golang libraries, provided you have the Go source code package installed. Keep exploring!

## Comments are closed.