Golang 调试

Golang 调试

简介

Golang程序的调试工具包括gdb调试、go pprof性能调试工具及go gc分析工具。熟练掌握这些工具的基本用法对golang的程序开发及调试分析拥有很大的帮助。

GDB:单步调试工具

gdb可以用来作为golang的调试工具。

Gdb用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#编译时,打开相关编译变量, -gcflags是给go编译器的参数,gc是go compile的意思。-N是不要优化代码,-l 是禁止内联代码。
$ go build -gcflags "-N -l" test.go

# 运行gdb
$ gdb test
(gdb) info files   #查看文件
(gdb) l main.main  # list
(gdb) b 10         # breakpoint 10,第10行设置断点
(gdb) r            # run
(gdb) s            # step, 单
(gdb) p *b         # print *b
(gdb) n            # next

gdb对golang的调试功能支持不完善,delve

PProf

pprof是go tool自带的性能调试工具,看用于对pprof采样数据进行分析。

获取采样数据

要使用pprof,需要先生成采样数据,有两种使用方式可以产生pprof数据:

  • 通过引入runtime/pprof包,并手动调用rutime.StartCPUProfile, runtimeStopCPUProfile等API来获取采样数据;

  • 通过引入import _ "net/http/prprof"方式在线使用;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import (
    // 引入net/http/pprof包,该包自动注册 handler到 http server
    _ "net/http/pprof" // 
)

func main() {

    runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载
    runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪
    runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪

    go func() {
        // 启动一个 http server,以提供pprof http服务端口,服务默认在/debug/pprof下
        if err := http.ListenAndServe(":6060", nil); err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    }()
}

pprof用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 浏览器
$ curl 'http://127.0.0.1:6060/debug/pprof/goroutine' > /tmp/goroutine.dbg
$ go tool pprof -http=":8081" /tmp/goroutine.dbg

# 函数调用cpu耗时
$ go tool pprof http://localhost:6060/debug/pprof/profile 
# 内存
$ go tool pprof -sample_index=alloc_space "http://localhost:6060/debug/pprof/heap?gc=1&seconds=60"

# 已分配的堆内存
$ go tool pprof http://localhost:6060/debug/pprof/allocs
# goroutine
$ go tool pprof http://localhost:6060/debug/pprof/goroutine

$ curl 'http://localhost:6060/debug/goroutine?debug=1' > ~/tmp/gopprof.txt

# 阻塞
$ go tool pprof http://localhost:6060/debug/pprof/block 
# 锁 
$ go tool pprof http://localhost:6060/debug/pprof/mutex
(pprof) top      # 查看top 前的指标
(pprof) list <>  # 查看指标对象所在源码,需设置源码目录为编译时目录
(pprof) web --nodefraction=0.1 [metanode.NewInode] # 生成svg,在浏览其中图形化展示指标
(pprof) traces   #

go tool trace

1
2
3
$ curl 'http://localhost:6060/debug/pprof/trace?seconds=5' > ~/tmp/gotrace.out
$ go tool trace -http=":8081" ~/tmp/gotrace.out
$ go tool trace ~/tmp/gotrace.out

GODEBUG:GC调试

GODEBUG 开启 debug 模式后,可做内存 trace 和调度器的 trace

GODEBUG 还支持设置以下变量:

  • GOGC: 改变堆增长方式 —— 设置初始的 GC 目标百分比。当新分配内存,与上一次采集后剩余的实时数据的比例达到这个百分比时,才会触发一次 GC。默认值是 GOGC=100。设置 GOGC=off 则完全禁用垃圾收集器。
  • schedtrace:设置 schedtrace=X ,每 X 毫秒打印一次调度器状态 —— 包括调度器、处理器、线程和 goroutine

用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 输出gc
$ GODEBUG=gctrace=1 go run example.go
$ GODEBUG=gctrace=1 ./go-pprof-practice | grep gc

# 手动触发gc
$ curl -X GET "http://localhost:6060/debug/pprof/heap?gc=1"

# 查看调度
$ GODEBUG=schedtrace=1000 ./awesomeProject

# 查看调度详情
$ GODEBUG=scheddetail=1,schedtrace=1000 ./awesomeProject
  • gctrace格式
1
gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P
  • gc#:GC 执行次数的编号,每次叠加。
  • @#s:自程序启动后到当前的具体秒数。
  • #%:自程序启动以来在GC中花费的时间百分比。
  • #+...+#:GC 的标记工作共使用的 CPU 时间占总 CPU 时间的百分比。
  • #->#-># MB:分别表示 GC 启动时, GC 结束时, GC 活动时的堆大小.
  • #MB goal:下一次触发 GC 的内存占用阈值。
  • #P:当前使用的处理器 P 的数量。

golang调度器追踪

1
2
3
4
5
6
7
$ GOMAXPROCS=2 GODEBUG=schedtrace=1000 ./example

SCHED 0ms: gomaxprocs=2 idleprocs=1 threads=2 spinningthreads=0 idlethreads=0 runqueue=0 [0 0]
SCHED 1002ms: gomaxprocs=2 idleprocs=0 threads=4 spinningthreads=1 idlethreads=1 runqueue=0 [0 4]
SCHED 2002ms: gomaxprocs=2 idleprocs=0 threads=4 spinningthreads=0 idlethreads=1 runqueue=0 [4 4]

$ GOMAXPROCS=2 GODEBUG=schedtrace=1000,scheddetail=1 ./example

第2秒:

1
2
3
4
5
6
7
2002ms        : This is the trace for the 2 second mark.
gomaxprocs=2  : 2 processors are configured for this program.
threads=4     : 4 threads exist. 2 for processors and 2 for the runtime.
idlethreads=1 : 1 idle thread (3 threads running).
idleprocs=0   : 0 processors are idle (2 processors busy).
runqueue=0    : All runnable goroutines have been moved to a local run queue.
[4 4]         : 4 goroutines are waiting inside each local run queue.
输出项意义
1009ms自从程序开始的毫秒数
gomaxprocs=1配置的处理器数(逻辑的processor,也就是Go模型中的P,会通过操作系统的线程绑定到一个物理处理器上)
threads=3运行期管理的线程数,目前三个线程
idlethreads=1空闲的线程数,当前一个线程空闲,两个忙
idleprocs=0空闲的处理器数,当前0个空闲
runqueue=0在全局的run队列中的goroutine数,目前所有的goroutine都被移动到本地run队列
[9]本地run队列中的goroutine数,目前9个goroutine在本地run队列中等待

堆栈track

golang程序panic后,会打印出panic时的内存堆栈信息以便于问题 分析,输出如下:

1
2
3
4
5
6
7
8
9
1    panic: runtime error: invalid memory address or nil pointer dereference
2    [signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0x751ba4]
3    goroutine 58 [running]:
4    github.com/joeshaw/example.UpdateResponse(0xad3c60, 0xc420257300, 0xc4201f4200, 0x16, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
5        /go/src/github.com/joeshaw/example/resp.go:108 +0x144
6    github.com/joeshaw/example.PrefetchLoop(0xacfd60, 0xc420395480, 0x13a52453c000, 0xad3c60, 0xc420257300)
7        /go/src/github.com/joeshaw/example/resp.go:82 +0xc00
8     created by main.runServer
9        /go/src/github.com/joeshaw/example/cmd/server/server.go:100 +0x7e0
  • 第1行 :panic错误提示消息,

  • 第2行:引发panic的UNIX信号 ,

    • code: UNIX siginfo.si_code, 0x1SEGV_MAPERR(“address not mapped to object”) in Linux’s siginfo.h file.

    • addr: siginfo.si_addr,030: invalid memory address, 无效内存地址;

    • pc: 程序计数器, 代表panic时,程序当前运行的地址;

  • 第3行:panic时,goroutine 58 的状态

  • 第4-9行: gorutine panic时的stack frame

    • 第4行: UpdateResponse函数调用参数

    • 第5行:所在文件行数;

stack track函数参数遵守如下规则:

  • 每个参数 按函数原型参数列表从左到右按内存布局按word逐一展开,不是和原型参数个数一一对应;

  • 如果是method,receiver为最左边开始 展开;

  • 返回值在参数展开后展开,多返回值也按左到右顺序逐一展开;

  • 内建类型(int, rune,byte)按word逐个输出,不足一个word的 ,将合并成一个word;

  • 指针类型:输出指针地址;

  • string类型:输出两个:指针地址,string长度;

  • slice:输出三个 :地址, 长度,容量;

  • struct:按stuct字段顺序逐个展开;

  • interface: 2个: 类型,数据指针;

  • 参数都未被使用或者只是在 fmt.Print() 中未作修改使用,用func(...)代替,内联的函数也只显示... ;

golang stack track 中函数调用各种类型参数的对应的数量:

类型名称参数域数量参数域说明
string2指针 长度
slice3指针 长度 容量
map1指针
chan1指针
interface2类型指针 值指针
pointer1指针
func1指针
nil10x0

strace

1
$ sudo strace -Tfp <PID>

参考

  1. https://guidao.github.io/go_debug.html

  2. https://cizixs.com/2017/09/11/profiling-golang-program/

  3. golang pprof 实战 | Wolfogre's Blog

  4. https://segmentfault.com/a/1190000020255157

  5. https://mp.weixin.qq.com/s/Brby6D7d1szUIBjcD_8kfg

  6. GitHub - go-delve/delve: Delve is a debugger for the Go programming language.

  7. Debugging Go Code with GDB - The Go Programming Language

  8. Go函数调用链跟踪的一种实现思路 | Tony Bai

  9. 记一次go panic问题的解决过程 | Tony Bai

  10. https://www.orztu.com/post/golang-trace/

  11. Go 语言的 Stack Trace - Go语言中文网 - Golang中文社区

  12. https://segmentfault.com/a/1190000040612732

  13. Go 调度器跟踪

  14. https://segmentfault.com/a/1190000019736288

  15. pprof/README.md at master · google/pprof · GitHub

  16. golang中定时器cpu使用率高的现象详析_Golang_脚本之家

  17. https://swsmile.info/post/golang-trace/

  18. Golang 大杀器之跟踪剖析 trace - 掘金

  19. https://zhuanlan.zhihu.com/p/95056679

  20. Golang 性能测试 (3) 跟踪刨析 golang trace - 搬砖程序员带你飞 - 博客园

  21. 6. strace 跟踪进程中的系统调用 — Linux Tools Quick Tutorial

  22. [译]strace的10个命令

updatedupdated2024-05-102024-05-10