可以在此处找到本文的'代码':https://github.com/peter-mcconnell/petermcconnell.com/blob/main/assets/dummy/godebug/main.go
调试golang-上下文
这是我在程序不表现出我期望的方式时所能达到的工具是错误的。
有什么要求?
选择的调试器(对我来说)是dlv
/ delve
。原因是本文的结尾。
安装delve
:https://github.com/go-delve/delve
git clone https://github.com/go-delve/delve
cd delve
go install github.com/go-delve/delve/cmd/dlv
有以下信息:
- 我想设置断点的代码中的1个或更多点的列表
- 文件名和行号
示例程序
为了本文,我们将创建一个简单的应用程序来调试:
// main.go
package main
import "fmt"
func doubleit(val int) int {
return val * 3 // should be * 2
}
func main() {
fmt.Printf("doubleit 2: %d\n", doubleit(2))
fmt.Printf("doubleit 4: %d\n", doubleit(4))
fmt.Printf("doubleit 8: %d\n", doubleit(8))
}
当我们使用go run main.go
运行时,我们希望它使我们通过的数字加倍,但出于某些原因我们得到了不同的结果。
我们还需要一个go.mod
才能运行,因此也可以从该目录中执行go mod init
。
调试它
从项目目录开始
dlv debug
Type 'help' for list of commands.
(dlv)
现在设置一些断点。对于此示例,我们要说我们要调试我们的doubleit
方法 - 第一行是在:7
:
dlv debug
Type 'help' for list of commands.
(dlv) b main.go:7
Breakpoint 1 set at 0x49c8bb for main.doubleit() ./main.go:7
当我们添加所有需要的断点时,我们可以指示程序以c
:
运行
dlv debug
Type 'help' for list of commands.
(dlv) b main.go:7
Breakpoint 1 set at 0x49c8bb for main.doubleit() ./main.go:7
(dlv) c
> main.doubleit() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x49c8bb)
2:package main
3:
4:import "fmt"
5:
6:func doubleit(val int) int {
=> 7: return val * 3 // should be * 2
8:}
9:
10:func main() {
11: fmt.Printf("doubleit 2: %d\n", doubleit(2))
12: fmt.Printf("doubleit 4: %d\n", doubleit(4))
(dlv)
现在,我们已经使用附带的调试器运行了程序,并且已在我们设置的断点处停止执行。我们可以运行args
,以查看哪些参数传递给方法:
(dlv) c
> main.doubleit() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x49c8bb)
2:package main
3:
4:import "fmt"
5:
6:func doubleit(val int) int {
=> 7: return val * 3 // should be * 2
8:}
9:
10:func main() {
11: fmt.Printf("doubleit 2: %d\n", doubleit(2))
12: fmt.Printf("doubleit 4: %d\n", doubleit(4))
(dlv) args
val = 2
~r0 = 0
因此,在程序中的这一点上,当它被val
值为2
时,我们使用了doubleit
方法。我们可以使用p
打印此和其他变量:
(dlv) p val
2
我们甚至可以使用call
从此来调用方法:
(dlv) call doubleit(6)
> main.doubleit() ./main.go:7 (hits goroutine(6):1 total:2) (PC: 0x49c8bb)
2:package main
3:
4:import "fmt"
5:
6:func doubleit(val int) int {
=> 7: return val * 3 // should be * 2
8:}
9:
10:func main() {
11: fmt.Printf("doubleit 2: %d\n", doubleit(2))
12: fmt.Printf("doubleit 4: %d\n", doubleit(4))
(dlv) args
val = 6
在上面的示例中,我们访问了自己的断点集,允许我们为call
打印args
。
要浏览执行,我们可以按n
进行执行点:
(dlv) n
> main.doubleit() ./main.go:6 (PC: 0x49c8a0)
1:// main.go
2:package main
3:
4:import "fmt"
5:
=> 6:func doubleit(val int) int {
7: return val * 3 // should be * 2
8:}
9:
10:func main() {
11: fmt.Printf("doubleit 2: %d\n", doubleit(2))
(dlv) n
> main.doubleit() ./main.go:7 (hits goroutine(6):2 total:3) (PC: 0x49c8bb)
2:package main
3:
4:import "fmt"
5:
6:func doubleit(val int) int {
=> 7: return val * 3 // should be * 2
8:}
9:
10:func main() {
11: fmt.Printf("doubleit 2: %d\n", doubleit(2))
12: fmt.Printf("doubleit 4: %d\n", doubleit(4))
(dlv) n
并使用bt
查看回溯:
(dlv) bt
0 0x000000000049c8bb in main.doubleit
at ./main.go:7
1 0x000000000046251f in debugCall256
at :0
2 0x0000000000407484 in runtime.debugCallWrap2
at /usr/local/go/src/runtime/debugcall.go:251
3 0x00000000004073b3 in runtime.debugCallWrap1
at /usr/local/go/src/runtime/debugcall.go:203
4 0x0000000000464ca1 in runtime.goexit
at /usr/local/go/src/runtime/asm_amd64.s:1594
要查看当前执行点附近的代码,只需按l
:
(dlv) l
> main.doubleit() ./main.go:7 (hits goroutine(6):3 total:4) (PC: 0x49c8bb)
2:package main
3:
4:import "fmt"
5:
6:func doubleit(val int) int {
=> 7: return val * 3 // should be * 2
8:}
9:
10:func main() {
11: fmt.Printf("doubleit 2: %d\n", doubleit(2))
12: fmt.Printf("doubleit 4: %d\n", doubleit(4))
当然,哪个表明我们很难找到逻辑错误,* 3
而不是* 2
。
注意:您还可以在stdlib函数中设置断点(路径会根据您的设置而变化):
(dlv) b src/net/http/request.go:899
Breakpoint 1 set at 0x794599 for net/http.NewRequestWithContext() /usr/local/go./net/http/request.go:899
概括
上面的示例非常微不足道 - dlv
和它的质量是在复杂的用途上,您甚至可能不知道输入和输出之间的方法,例如调试stdlib。就在本周,我使用dlv
来确定为什么POST
没有尊重307
临时重定向 - 在检查时,使用dlv
,我了解到body
是否被忽略了,如果它是一个不认可的type
https://github.com/golang/go/blob/master/src/net/http/request.go#L899。不得不在不调试器的情况下做到这一点会花费很多代码跳动 - 调试器为我处理了这一点,并允许我像我一样验证参数值。
为什么不gdb?
我知道有些人强烈认为gdb
是用于调试GO代码的工具,但是鉴于Golang Docs本身鼓励您在GDB上使用Delve,我个人远离它:
请注意,在使用标准工具链构建的调试GO程序时,Delve是GDB的更好替代方法。它比GDB更好地了解GO运行时,数据结构和表达式。 Delve当前支持AMD64上的Linux,OSX和Windows。有关受支持平台的最新列表,请参见Delve文档。
例外情况可能是CGO的使用,但我现在将其排除在外。