问题由来
以前出现panic问题,我们会习惯通过日志给出的代码行,在对比数据分析问题,
如果推断不出来,我们会多加日志,重新panic,再继续定位。
如果打印了很多堆栈日志,但是日志信息不够怎么办,我们总不能加日志等下次重现后再定位吧?
这样定位bug的工作效率特别低,所以我们可以仔细阅读堆栈日志,结合打印的日志快速定位bug。
在阅读堆栈日志时,可能我们会疑惑:
为什么定义是这样:
func TestStack(slice []string, str string, i int)
但堆栈日志显示:
main.TestStack(0xc00010ff38, 0x2, 0x4, 0xabcac1, 0x5, 0xa)
E:/go-workstation/golangsutdy/stack-tace/main.go:20 +0x65这里的0xc00010ff38, 0x2, 0x4, 0xabcac1, 0x5, 0xa是什么意思?
如果是传入参数值,为什么和定义的个数对不上?
日志分析
分析必备
1. panic日志中,打印出来的确实是输入参数的值,如果函数有返回值,返回值也会打印。
2.但实际上是以字长word去打印的(word:操作系统处理信息的基本单位)
3.而每个参数占多少word,又和参数类型有关。
类型与字长
golang中基本类型如int、char、byte等的字长和C语言一致,不再展开,
下面列举常用的几个:
1.指针占一个word;
2.string占两个word(一个指向不可变字符数组指针,一个string的长度);
3.切片占三个word(一个指向底层数组指针,一个切片长度,一个切片容量)
4,接口占两个word(一个指向实际类型指针,一个指向数据的指针)
堆栈分析
现在我们就解释panic信息
func TestStack(slice []string, str string, i int)
main.TestStack(0xc00010ff38, 0x2, 0x4, 0xabcac1, 0x5, 0xa)
E:/go-workstation/golangsutdy/stack-tace/main.go:20 +0x65
第一个参数是[]string,因为切片类型占3个word,所以
slice := make([]string,2,4)
切片实际值:
Pointer: 0xc00010ff38
Length: 0x2
Capacity: 0x4
第二个参数是str string, 因为string占两个word,所以
Pointer: 0xabcac1
Length: 0x5
第三个参数是i int,因为int占一个work,所以
0xa i=10
函数有返回值
这里增加一个有返回值的函数Fun2:
func TestSt2(slice []string, str string, i int) ( *int, error)
再看TestSt2的日志信息:
main.TestSt2(0xc00010ff38, 0x2, 0x4, 0x66dac1, 0x5, 0xa, 0x1056c1d, 0xc000078f88, 0x1004c30)
E:/go-workstation/golangsutdy/stack-tace/main.go:32 +0x60
对比 TestStack的日志信息发现,TestSt2增加了0x1056c1d, 0xc000078f88, 0x1004c30三个值,其中:
*int是指针占一个word,对应是0x1056c1d,
error是interface,占两个word,对应0xc000078f88, 0x1004c30
函数到方法
上述的TestStack改成如下方式并调用
type trace struct{}
func main() {
slice := make([]string, 2, 4)
var t trace
t.TestStack(slice, "hello", 10)
}
func (t *trace) TestStack(slice []string, str string, i int) {
fmt.Printf("Receiver Address: %p\n", t)
panic("Want stack trace")
}
修改前后堆栈日志对比
//修改前
main.TestStack(0xc00010ff38, 0x2, 0x4, 0xabcac1, 0x5, 0xa)
E:/go-workstation/golangsutdy/stack-tace/main.go:20 +0x65//修改后
main.(*trace).TestStack(0x83cde0, 0xc00010ff38, 0x2, 0x4, 0x75ea41, 0x5, 0xa)
E:/go-workstation/golangsutdy/stack-tace/main.go:15 +0xaf
由上可知,两者唯一差别,func (t *trace) TestStack堆栈的第一个是t的地址,其他和func TestStack一致。
参数packing
前面说了,堆栈参数是以word为单位打印的,那如果参数不够word长度呢,如bool,char等?还是请看下文:
func main() {
Example(true, false, true, 25)
}
func Example(b1, b2, b3 bool, i uint8) {
defer func() {
err := recover()
if err != nil {
stackStr := string(debug.Stack())
fmt.Println(stackStr)
}
}()
panic("Want stack trace")
}
堆栈日志:
main.Example(0xc019010001)
E:/go-workstation/golangsutdy/stack-tace/main.go:19 +0x65
很明显b1,b2,b3,i占了一个word,对应0xc019010001,这个就是参数packing
0xc019010001每一位都是16进制,拆开来看
19 --> 1*16+9*1 = 25
01 --> 1
00 --> 0
01 --> 1