Golang Panic 的 Stack Traces 信息分析

文中实例参考 Reading stack traces in Go

0x00 前言

调试程序有两大门派:日志派和 Debug 派,没有高下,只要能解决问题。Golang 的 Panic 输出和其语言风格一样,一点多余的内容都不会输出。有时 Panic 后,通过其打印的 Stack Traces 信息可以很快的定位问题,尤其是比较复杂的业务场景中。本文详细分析了 Golang Panic 的 Stack Traces 信息。

0x01 Panic 默认输出格式分析

通过一个例子来触发 panic,然后观察打印信息:

func main() {
    iPanic(3)
}

func iPanic(i int) {
    if i > 0 {
        iPanic(i - 1)
    }
    panic("panic here")
}

信息输出为:

panic: panic here

goroutine 1 [running]:
main.iPanic(0x0)
		/tmp/hello.go:11 +0x56
main.iPanic(0x1)
		/tmp/hello.go:9 +0x3a
main.iPanic(0x2)
		/tmp/hello.go:9 +0x3a
main.iPanic(0x3)
		/tmp/hello.go:9 +0x3a
main.main()
       /tmp/hello.go:4 +0x31
exit status 2

这个输出信息和 GDB 的 Back Trace 很像,只是 Golang 中以 Goroutine 为单位,默认情况下只打印引起 Panic 的 Goroutine 所在的 Stack Traces,全部打印可以使用 runtime.Stack() 修改第二个参数来操作。

每一条记录包含如下信息:

  • 包名.函数名(参数) main.iPanic(0x0)
  • 文件:行数 /tmp/hello.go:11
  • 当前函数在 Stack 中的相对位置 +0x56

Panic 时这个 Goroutine 的 Stack 在内存中的结构如下所示:

Function callRelative position
main.iPanic(0x0)+0x56
main.iPanic(...)+0x3a
main.main()+0x31
Bottom of the stack0x00

0x02 各种数据类型的 Stack Traces 格式

通常使用打印 Stack Traces 信息的目的有两个:

  1. 找到 Panic 的发生位置和 Stack Frame 结构;
  2. 调试函数调用的参数细节;

第一个目的通过整体输出可以容易识别,但是第二个目的相对于 GDB 的使用习惯来说比较不友好,因为 GDB 会结合编译时插入的 .debug 段让 Back Trace 信息输出中的函数调用参数部分非常适合阅读,但是 Golang 的 Stack Trace 信息中的函数调用参数部分却相对比较晦涩,需要根据具体的参数类型进行区分。下文将对 Golang 中每种数据类型作为参数时对应的 Stack Trace 打印信息进行分析。

Case 01 忽略输出

Stack Traces 的参数列表中,如果所有的参数都未被使用或者只是在 fmt.Print() 中未作修改使用,那么参数列表将不会被打印,而是通过 func(...) 的形式打印,例如下面这段:

package main

import "fmt"

func main() {
	iPanic(5)
}

func iPanic(i int) {
	fmt.Println(i)
	panic(i)
}
5
panic: 5

goroutine 1 [running]:
main.iPanic(...)
        /tmp/hello.go:14 +0x114
main.main()
        /tmp/hello.go:6 +0x31
exit status 2

Case 02 合并输出

Golang Stack Traces 不仅不是一个第一字段代表一个参数,而且会一个字段代表多个参数或者多个字段代表一个参数。来看下面的例子:

package main

import "fmt"

func main() {
	iPanic(true, 'b', 'r')
}

func iPanic(bo bool, by byte, ru rune) {
	fmt.Println(by + 1)
	panic("panic here")
}
99
panic: panic here

goroutine 1 [running]:
main.iPanic(0x7200016201)
        /tmp/hello.go:11 +0xa9
main.main()
        /tmp/hello.go:6 +0x37
exit status 2

可以看出,iPanic 函数的三个参数在 Stack Traces 输出中被合并成了一个字段输出。0x72000162010x62='b', 0x72='r', 0x01=true,这种将参数进行 Encode 的操作确实比较晦涩。一般看来,数字类型的参数(包括 bool byte rune) 如果连续出现,会被编码输出,具体编码规则还需要进一步分析。

Case 03 常规输出

下面列举 Golang 非基本类型作为参数时在 Stack Trace 中的形态,与各种类型的底层数据结构基本相同。

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

Case 04 结构体输出

Struct 是字段和嵌入结构和接口的集合,当通过按值引用的方式使用结构体作为参数时,Stack Traces 将按照结构体的内部结构来打印。

package main

import "fmt"

type A struct {
	i int
	s string
}

func main() {
	iPanic(A{i: 50})
}

func iPanic(a A) {
	fmt.Println(a.i + 1)
	panic("panic here")
}

51
panic: panic here

goroutine 1 [running]:
main.iPanic(0x32, 0x0, 0x0)
        /tmp/hello.go:16 +0xab
main.main()
        /tmp/hello.go:11 +0x39
exit status 2

Case 05 方法输出

Golang 中特有的以一个 Struct 作为 Receiver 的方式我们暂且称为方法(Method),Method 的 Stack Trace 输出与 Function 的区别是,先打印 Receiver 再打印 Method 参数。Method 的 Stack Traces 输出根据 Receiver 的类型分为两种情况:

当 Method 的 Receiver 为 Value Receiver 时:

package main

import "fmt"

type A struct {
	i int
	s string
}

func main() {
	A{i: 50}.iPanic(true)
}

func (a A) iPanic(b bool) {
	fmt.Println(a.i + 1)
	panic("panic here")
}

51
panic: panic here

goroutine 1 [running]:
main.A.iPanic(0x32, 0x0, 0x0, 0xc00001a001)
        /tmp/hello.go:16 +0xab
main.main()
        /tmp/hello.go:11 +0x3e
exit status 2

当 Method 的 Receiver 为 Pointer Receiver 时:

package main

import "fmt"

type A struct {
	i int
	s string
}

func main() {
	(&A{i: 50}).iPanic(true)
}

func (a *A) iPanic(b bool) {
	fmt.Println(a.i + 1)
	panic("panic here")
}
51
panic: panic here

goroutine 1 [running]:
main.(*A).iPanic(0xc0000c7f60, 0x1)
        /tmp/hello.go:16 +0xae
main.main()
        /tmp/hello.go:11 +0x56
exit status 2

参考文档

  1. Reading stack traces in Go
  2. Understanding Go panic output
  3. Stack Traces In Go
  4. Go Type System Overview
  5. Go Data Structures
  6. Go Data Structures: Interfaces
  7. https://golang.org/src/runtime/stack.go
  8. The Go Programming Language Specification
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值