前言
go 代码在运行时,可能因为代码整体不够健壮,因此可能会产生panic,如果panic未被recover,会导致整个进程退出,我们经常需要通过panic抛出的函数堆栈对代码进行分析,找到panic所在
本文会涉及同一份go代码在go1.16和go1.17的表现,之所以使用两个版本,是因为go在1.17时候引入了一个重大更新,即函数的调用不再只是用内存传参,而是先使用寄存器,然后再使用内存
代码
package main
func main() {
getPanic([]string{"get", "panic"}, 0x1234)
}
//go:noinline
func getPanic(p1 []string, p2 int) string {
panic("paniced")
return "panic"
}
NOTE: //gonoinline 加这一行是为了防止简单的函数被内联优化,内联以后运行时其实就不存在这个函数了
首先看下在go1.16的表现
panic: paniced
goroutine 1 [running]:
main.getPanic(0xc000046758, 0x2, 0x2, 0x1234, 0xc00006a058, 0x1013201)
/Users/holen/main.go:9 +0x39
main.main()
/Users/holen/main.go:4 +0x7d
exit status 2
首先我们知道的是每个堆栈的栈底都是每个goroutine开始的地方,在这个例子中,只有一个goroutine,也就是main routine
然后从上往下依次看下
从 /Users/holen/main.go:9 可以确定代码发生panic的位置,那 0xc000046758, 0x2, 0x2, 0x1234, 0xc00006a058, 0x1013201 分别是什么意思呢
其实就是 getPanic 函数的入参和返回值,或许有人奇怪 参数 + 返回值 不是一共3个参数么,为什么这会有6个参数,获取我们分组看就明白了
0xc000046758, 0x2, 0x2 => []string{"get","panic"}
0x1234 => 0x1234
0xc00006a058, 0x1013201 => 返回值
golang 中切片是由三个成员组成,切片地址,长度,容量,字符串是由字符串地址和长度两个元素组成的
然后我们再看下go1.17的表现
panic: paniced
goroutine 1 [running]:
main.getPanic({0xc000048770, 0x1004319, 0x60}, 0x0)
/Users/holen/main.go:9 +0x27
main.main()
/Users/holen/main.go:4 +0x65
exit status 2
Shell 已返回1
和go1.16做比较 我们不难发现
- 有三个值被
{}包裹了 - 少了点参数
- 显示的值似乎和参数对不上
解答下上面的三个问题
确实,go 在1.17在堆栈的信息显示上做了优化,不再是采用全部铺开的方式,而是会用{}做分组,表示这是1个参数的三个元素。其次是返回值参数少了,这个和问题3其实是同一个问题,都是因为 go 在1.17函数调用的传参约定改变导致,因此这些参数其实都在寄存器上,所以显示的也是不对的 具体的可以看这个issue