现在让我们看看第二个参数,它是一个字符串。字符串也是引用类型,但此它的header value是不可变的。字符串的header value声明为三个word大小的结构,包含指向底层字节数组的指针和字符串的长度:
// String parameter value "hello" // String header values Pointer: 0x425c0 Length: 0x5 // Declaration main.Example(slice []string, `str string`, i int) // Stack trace main.Example(0x2080c3f50, 0x2, `0x4, 0x425c0`, 0x5, 0xa)
上述显示了stack trace中的第四、五个值如何匹配string参数。第四个值表示指向底层字节数组的指针,第五个值表示字符串的长度为5.字符串“hello”需要5个字节。这两个值表示字符串(即,第二个参数)header的每个值。
第三个参数是一个整数,它是一个word的值:
// Integer parameter value 10 // Integer value Base 16: 0xa // Declaration main.Example(slice []string, str string, `i int`) // Stack trace main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, `0xa`)
上述显示了stack trace中的最后一个值如何匹配整数参数。trace中的最后一个值是十六进制数0xa,它是值10.与该参数传递的值相同。该值代表第三个参数。
Methods
我们更改程序,将Example函数改为一个成员函数:
01 package main 02 03 import "fmt" 04 05 type trace struct{} 06 07 func main() { 08 slice := make([]string, 2, 4) 09 10 var t trace 11 t.Example(slice, "hello", 10) 12 } 13 14 func (t *trace) Example(slice []string, str string, i int) { 15 fmt.Printf("Receiver Address: %p\n", t) 16 panic("Want stack trace") 17 }
第5行通过声明一个新类型trace,改变原始程序,转换Example函数为的第14行的成员函数。转换是通过重新声明函数包含一个trace类型的指针接收器来实现。然后在第10行,声明一个trace类型的t变量,并在第11行使用该变量进行方法调用。
由于该方法是使用指针接收器声明的,因此Go将获取t变量的地址以支持接收器类型,即使方法调用使用的是值。这次运行程序时,stack trace略有不同
Receiver Address: `0x1553a8` panic: Want stack trace 01 goroutine 1 [running]: 02 main.(`*trace`).Example(`0x1553a8`, 0x2081b7f50, 0x2, 0x4, 0xdc1d0, 0x5, 0xa) /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/ temp/main.go:16 +0x116 03 main.main() /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/ temp/main.go:11 +0xae
首先,应该注意的是第02行的stack trace清楚表明这是一个使用指针接收器的方法调用。现在,包含(* trace)的成员函数的名称显示在包名称和方法名称之间。其次,要注意的是值列表现在如何第一个显示接收器的值。成员函数调用实际上是第一个参数是接收器值的函数调用。我们从stack trace中看到了这个实现细节。
由于声明或调用Example方法没有其他任何更改,因此所有其他值保持不变。调用Example的行号和发生panic的位置发生了变化,并反映了新代码。
Packing
如果有多个参数适合放入单个word,那么stack trace中参数的值将打包在一起:
01 package main 02 03 func main() { 04 Example(true, false, true, 25) 05 } 06 07 func Example(b1, b2, b3 bool, i uint8) { 08 panic("Want stack trace") 09 }
上述显示了一个新的示例程序,将Example函数更改为接受四个参数。前三个是布尔值,最后一个是八位无符号整数。布尔值也是一个8位值,因此所有四个参数都适合放入32位和64位架构上的单个word。当程序运行时,它会产生一个有趣的stack trace:
01 goroutine 1 [running]: 02 main.Example(`0x19010001`) /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/ temp/main.go:8 +0x64 03 main.main() /Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/ temp/main.go:4 +0x32
对Example的调用,stack trace中没有四个值,取而代之只有一个值。所有四个独立的8位值都拼凑成一个word:
// Parameter values true, false, true, 25 // Word value Bits Binary Hex Value 00-07 0000 0001 `01` true 08-15 0000 0000 `00` false 16-23 0000 0001 `01` true 24-31 0001 1001 `19` 25 // Declaration main.Example(`b1, b2, b3 bool, i uint8`) // Stack trace main.Example(`0x19010001`)
上述显示出了在stack trace的值如何匹配传入的四个参数值。true的8位值对应于1的值,false的值对应0值。二进制25的值是11001,十六进制转换为19。现在,当我们查看堆stack trace中表示的十六进制值时,我们会看到它如何表示传入的值。
结论
Go runtime提供了大量信息来帮助我们调试程序。在这篇文章中,我们专注于stack trace。解码在整个调用堆栈中传递给每个函数的值的能力是巨大的。它不止一次帮助我快速识别我的bug。既然您已经知道如何读取stack trace,那么希望您可以在下次发生stack trace时可以利用这些知识。
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/07-05-2019/live-stack-traces-in-go-cn.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!
- 译|There Are No Reference Types in Go
- Go 语言没有引用类型,指针也与众不同
- 译|What “accept interfaces, return structs” means in Go
- 如何用好 Go interface
- 一个优雅的 LRU 缓存实现