golang panic堆栈日志解读

leidachui · · 4852 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

## 问题由来 以前出现panic问题,总是习惯通过日志中给出的代码行,去“猜测”是哪个变量出了问题, 如果推断不出来,就多加入一些日志,重现panic,再继续定位。 昨天又遇到了panic的问题,看到屏幕上打印了很多堆栈日志,转念一想: 如果现网出现了panic但日志信息不够怎么办,总不能先加日志等下次重现后再定位吧? 然后尝试仔细阅读堆栈日志时,却多出了一些疑惑: ``` 为什么定义是: func Fun1(slice []string, t *Test, i int) 但堆栈日志显示: main.Fun1(0xc000078f48, 0x2, 0x4, 0x0, 0x7) /Users/leidingyu/workspace/go_proj/src/git.code.oa.com/SNG_EDU_GO_SVR/unittest/main.go:33 +0x4a 这里的“0xc000078f48, 0x2, 0x4, 0x0, 0x7”是什么意思呢? 如果是传入参数值,为什么和定义的个数对应不上? ``` ## 日志解析 ### 结论先行 - panic日志中,打印出来的确实是输入参数的值;如果函数有返回值,则返回值也会打印; - 但实际上是以**字长word**来打印的(word:操作系统处理信息的基本单位); - 而每个参数占多少word,又和参数的类型有关(所以我们才会有参数个数对不上的疑惑)。 ### 类型与字长 golang中基础类型如int、char、byte等的字长和C语言一致,不再展开, 下面列举常用的几个: - 指针占一个word; - string占两个word (一个指向不可变字符数组的指针,一个string的长度); - 切片占三个word (一个指向底层数组的指针,一个切片的长度,一个切片的容量) - 接口占两个word(一个指向实际类型的指针,一个指向数据的指针) - 更详细的可参考:https://research.swtch.com/godata ### 堆栈解析 有了上面的知识储备,本文开头提到的panic信息就能解释通了。 ``` func Fun1(slice []string, t *Test, i int) main.Fun1(0xc000078f48, 0x2, 0x4, 0x0, 0x7) /Users/leidingyu/workspace/go_proj/src/git.code.oa.com/SNG_EDU_GO_SVR/unittest/main.go:33 +0x4a ``` 第一个参数slice []string,因为切片类型占3个word,所以: > slice := make([]string, 2, 4) > > // 该切片的实际值 > Pointer: 0xc000078f48 > Length: 0x2 > Capacity: 0x4 > > // 定义 > func Fun1(++slice []string++, t *Test, i int) > > // 堆栈 > main.Fun1(++0x2080c3f50, 0x2, 0x4++, 0x0, 0x7) 第二个参数t *Test,因为指针占1个word,所以: > 实际调用 > Fun1(slice, nil, 7) > > // 定义 > func Fun1(slice []string, ++t *Test++, i int) > > // 堆栈 > main.Fun1(0x2080c3f50, 0x2, 0x4, ++0x0++, 0x7) > 因此从这里也能看出传入的t是nil,这也是panic的所在; 第三个参数i int,因为int占1个word,所以: > //定义 func Fun1(slice []string, t *Test, ++i int++) > > //堆栈 main.Fun1(0x2080c3f50, 0x2, 0x4, 0x0, ++0x7++) ### 函数有返回值 这里增加一个 有两个返回值的函数Fun2: ``` func Fun2(slice []string, t *Test, i int) (int, error) { Fun1(slice, t, i) // 此处调用Fun1,用于有无返回值时的对比 return 0, nil } ``` 再看堆栈信息: ``` 1 main.Fun1(0xc000078f48, 0x2, 0x4, 0x0, 0x7) /Users/leidingyu/workspace/go_proj/src/git.code.oa.com/SNG_EDU_GO_SVR/unittest/main.go:33 +0x4a 2 main.Fun2(0xc000078f48, 0x2, 0x4, 0x0, 0x7, 0x1056c1d, 0xc000078f88, 0x1004c30) /Users/leidingyu/workspace/go_proj/src/git.code.oa.com/SNG_EDU_GO_SVR/unittest/main.go:19 +0x53 3 main.main() /Users/leidingyu/workspace/go_proj/src/git.code.oa.com/SNG_EDU_GO_SVR/unittest/main.go:15 +0x74 ``` 由上述第2行可以清晰看出,Fun2有两个返回值(int, error), 堆栈日志中main.Fun2就增加了0x1056c1d, 0xc000078f88, 0x1004c30三个值,其中: > int占一个word,对应0x1056c1d; > error是interface,占两个word,对应0xc000078f88, 0x1004c30; ### 函数到方法 将上述的Fun2改成如下方式并调用: ``` // 定义 type M struct {} func (m *M) Fun2(slice []string, t *Test, i int) (int, error) { fmt.Printf("m: %p\n", m) Fun1(slice, t, i) return 0, nil } // 调用 m := new(M) m.Fun2(slice, nil, 7) ``` 修改前后堆栈日志对比: ``` // 修改前 main.Fun2(0xc000078f48, 0x2, 0x4, 0x0, 0x7, 0x1056c1d, 0xc000078f88, 0x1004c30) // 修改后 main.(*M).Fun2(0x1183f88, 0xc000078f48, 0x2, 0x4, 0x0, 0x7, 0x1056c1d, 0xc000078f88, 0x1004c30) ``` 由上可知,两者唯一差别是,func (m *M) Fun2 堆栈的第一个是 m的地址,其他的和 func Func2 一致。 ## 引申问题 ### 参数个数限制 进一步发现堆栈中的Fun2最多只能有10个参数,当有更多时候,会用...省略掉: ``` main.(*M).Fun2(0x1183f88, 0xc00007cf48, 0x2, 0x4, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, ...) /Users/leidingyu/workspace/go_proj/src/git.code.oa.com/SNG_EDU_GO_SVR/unittest/main.go:24 +0xb0 ``` ### 参数packing 前面说了,堆栈参数是以word为单位来打印的,那如果参数不足word长度呢,如bool,char等?还是很有趣的,请看下文: ``` func main() { Fun1(true, 40, true, 25) } func Fun1(b1 bool, b2 byte, b3 bool, c byte) { defer func() { err := recover() if err != nil { stackStr := string(debug.Stack()) fmt.Println(stackStr) } }() panic("here") } ``` Fun1的堆栈日志: ``` main.Fun1(0xc019012801) ``` 很明显:b1、b2、b3、c占用了一个word,对应0x19010001,这就是参数packing; 0x19010001每一位都是16进制表示,拆开来看: ``` 19 --> 1*16 + 9*1 = 25 01 --> 1 28 --> 2*16 + 8*1 = 40 01 --> 1 ``` 因此可知:堆栈参数中的高位对应着右边的参数,地位对应着左边的参数。 ## 喜欢的话,关注我的公众号哦 ![image.png](https://upload-images.jianshu.io/upload_images/9737673-085caac4d20ab756.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 本公众号希望从日常工作中的一个小点,深入浅出讲解golang、后台开发的知识点,欢迎一起探讨。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

4852 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传