Delve 是 Go 语言专用的调试器,提供了强大的调试功能。
# 安装 Delve
go install github.com/go-delve/delve/cmd/dlv@latest
# 验证安装
dlv version
# 调试主程序
dlv debug
# 调试特定文件
dlv debug main.go
# 调试测试
dlv test
# 附加到运行中的进程
dlv attach <pid>
| 命令 | 缩写 | 说明 |
|---|---|---|
| break | b | 设置断点 |
| continue | c | 继续执行 |
| next | n | 单步执行(不进入函数) |
| step | s | 单步执行(进入函数) |
| stepout | so | 跳出当前函数 |
| p | 打印变量 | |
| locals | 显示局部变量 | |
| args | 显示函数参数 | |
| goroutines | grs | 显示所有 goroutine |
| goroutine | gr | 切换 goroutine |
| stack | bt | 显示调用栈 |
| list | l | 显示源代码 |
| quit | q | 退出调试 |
# 在 main 函数设置断点
(dlv) break main.main
# 在特定文件行号设置断点
(dlv) break main.go:20
# 在特定函数设置断点
(dlv) break main.calculate
# 查看所有断点
(dlv) breakpoints
# 清除断点
(dlv) clear 1
(dlv) clearall
# 设置条件断点
(dlv) break main.go:20
(dlv) condition 1 i > 10
# 当变量 i 大于 10 时触发断点
package main
import "fmt"
func main() {
numbers := []int{1, 2, 3, 4, 5}
sum := 0
for i, num := range numbers {
result := calculate(i, num)
sum += result
fmt.Printf("i=%d, num=%d, result=%d, sum=%d\n", i, num, result, sum)
}
fmt.Println("Final sum:", sum)
}
func calculate(i, num int) int {
return i * num
}
$ dlv debug main.go
# 设置断点
(dlv) break main.main
(dlv) break main.go:12
(dlv) break calculate
# 运行到第一个断点 c
# 单步执行
# 查看变量
(dlv) print numbers
(dlv) print i
(dlv) print sum
# 进入函数
# 查看调用栈
# 查看局部变量
# 继续执行 c
# 打印简单变量
(dlv) print i
(dlv) print sum
# 打印切片
(dlv) print numbers
(dlv) print numbers[0]
(dlv) print len(numbers)
# 打印结构体
(dlv) print user
(dlv) print user.Name
# 打印指针
(dlv) print *ptr
# 显示当前函数的局部变量
# 显示当前函数的参数
# 显示所有变量
(dlv) vars
###修改变量
# 修改变量值
(dlv) set i = 10
(dlv) set sum = 100
# 显示所有 goroutine
(dlv) goroutines
# 切换到特定 goroutine
(dlv) goroutine 5
# 查看 goroutine 的调用栈
(dlv) goroutine 5 bt
# 查看 goroutine 的局部变量
(dlv) goroutine 5 locals
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Printf("Worker %d: %d\n", id, i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("Done")
}
调试:
$ dlv debug
# 在 worker 函数设置断点
(dlv) break worker
# 运行 c
# 查看所有 goroutine
(dlv) goroutines
# 切换 goroutine 并查看状态
(dlv) goroutine 2
(dlv) bt
(dlv) locals
# 显示完整调用栈
# 显示指定深度的调用栈
(dlv) stack 10
# 切换栈帧
(dlv) frame 2
# 在特定栈帧查看变量
(dlv) frame 2 locals
(dlv) frame 2 args
package main
func main() {
result := level1()
println(result)
}
func level1() int {
return level2()
}
func level2() int {
return level3()
}
func level3() int {
return 42
}
调试:
$ dlv debug
# 在 level3 设置断点
(dlv) break level3
# 运行 c
# 查看调用栈
# 切换到 level2 的栈帧
(dlv) frame 1
# 切换到 level1 的栈帧
(dlv) frame 2
package main
import (
"log"
"os"
)
func main() {
// 设置日志输出
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("程序开始")
result := calculate(10, 20)
log.Printf("计算结果: %d\n", result)
log.Println("程序结束")
}
func calculate(a, b int) int {
log.Printf("calculate(%d, %d)\n", a, b)
return a + b
}
package main
import (
"log"
"os"
)
var debug = os.Getenv("DEBUG") == "true"
func debugLog(format string, args ...interface{}) {
if debug {
log.Printf("[DEBUG] "+format, args...)
}
}
func main() {
debugLog("程序开始")
result := calculate(10, 20)
debugLog("计算结果: %d", result)
}
func calculate(a, b int) int {
debugLog("calculate(%d, %d)", a, b)
return a + b
}
package main
import "fmt"
func main() {
data := map[string]int{"a": 1, "b": 2}
// 打印值
fmt.Printf("data: %v\n", data)
// 打印类型和值
fmt.Printf("data: %#v\n", data)
// 打印类型
fmt.Printf("type: %T\n", data)
}
| 动词 | 说明 |
|---|---|
| %v | 默认格式 |
| %+v | 包含字段名 |
| %#v | Go 语法格式 |
| %T | 类型 |
| %d | 整数 |
| %s | 字符串 |
| %x | 十六进制 |
| %p | 指针 |
package main
func main() {
data := []int{1, 2, 3}
// 故意制造 panic 来定位问题
if len(data) > 5 {
panic("unexpected data length")
}
// 访问越界会自动 panic
_ = data[10]
}
package main
import (
"fmt"
"runtime"
)
func safeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
// 获取调用栈
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
err = fmt.Errorf("panic: %v\n%s", r, buf[:n])
}
}()
// 可能 panic 的操作
riskyOperation()
return nil
}
func riskyOperation() {
panic("something went wrong")
}
func main() {
if err := safeOperation(); err != nil {
fmt.Println("捕获到错误:")
fmt.Println(err)
}
}
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"env": {
"DEBUG": "true"
},
"args": []
},
{
"name": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": 0
}
]
}
| 操作 | VS Code | GoLand |
|---|---|---|
| 开始调试 | F5 | Shift+F9 |
| 单步跳过 | F10 | F8 |
| 单步进入 | F11 | F7 |
| 单步跳出 | Shift+F11 | Shift+F8 |
| 继续 | F5 | F9 |
| 停止 | Shift+F5 | Ctrl+F2 |
本章详细介绍了 Go 语言的调试技巧:
调试是开发过程中必不可少的技能,掌握调试技巧能让你更快地定位和解决问题。在下一章中,我们将开始实战项目的学习。