Introduction
在Go語言中有一些調試技巧能幫助咱們快速找到問題,有時候你想盡量多的記錄異常但仍以爲不夠,搞清楚堆棧的意義有助於定位Bug或者記錄更完整的信息。
本文將討論堆棧跟蹤信息以及如何在堆棧中識別函數所傳遞的參數。
Functions
先從這段代碼開始:
Listing 1
01 package main
02
03 func main() {
04 slice := make([]string, 2, 4)
05 Example(slice, "hello", 10)
06 }
07
08 func Example(slice []string, str string, i int) {
09 panic("Want stack trace")
10 }
Example函數定義了3個參數,1個string類型的slice, 1個string和1個integer, 而且拋出了panic,運行這段代碼能夠看到這樣的結果:
Listing 2
Panic: Want stack trace
goroutine 1 [running]:
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:9 +0x64
main.main()
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:5 +0x85
goroutine 2 [runnable]:
runtime.forcegchelper()
/Users/bill/go/src/runtime/proc.go:90
runtime.goexit()
/Users/bill/go/src/runtime/asm_amd64.s:2232 +0x1
goroutine 3 [runnable]:
runtime.bgsweep()
/Users/bill/go/src/runtime/mgc0.go:82
runtime.goexit()
/Users/bill/go/src/runtime/asm_amd64.s:2232 +0x1
堆棧信息中顯示了在panic拋出這個時間全部的goroutines狀態,發生的panic的goroutine會顯示在最上面。
Listing 3
01 goroutine 1 [running]:
02 main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:9 +0x64
03 main.main()
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:5 +0x85
第1行顯示最早發出panic的是goroutine 1, 第二行顯示panic位於main.Example中, 並能定位到該行代碼,在本例中第9行引起了panic。
下面咱們關注參數是如何傳遞的:
Listing 4
// Declaration
main.Example(slice []string, str string, i int)
// Call to Example by main.
slice := make([]string, 2, 4)
Example(slice, "hello", 10)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
這裏展現了在main中帶參數調用Example函數時的堆棧信息,比較就能發現二者的參數數量並不相同,Example定義了3個參數,堆棧中顯示了6個參數。如今的關鍵問題是咱們要弄清楚它們是如何匹配的。
第1個參數是string類型的slice,咱們知道在Go語言中slice是引用類型,即slice變量結構會包含三個部分:指針、長度(Lengthe)、容量(Capacity)
Listing 5
// Slice parameter value
slice := make([]string, 2, 4)
// Slice header values
Pointer: 0x2080c3f50
Length: 0x2
Capacity: 0x4
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
所以,前面3個參數會匹配slice, 以下圖所示:
Figure 1
figure provided by Georgi Knox
咱們如今來看第二個參數,它是string類型,string類型也是引用類型,它包括兩部分:指針、長度。
Listing 6
// 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)
能夠肯定,堆棧信息中第四、5兩個參數對應代碼中的string參數,以下圖所示:
Figure 2
figure provided by Georgi Knox
最後一個參數integer是single word值。
Listing 7
// 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)
如今咱們能夠匹配代碼中的參數到堆棧信息了。
Figure 3
figure provided by Georgi Knox
Methods
若是咱們將Example做爲結構體的方法會怎麼樣呢?
Listing 8
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 }
如上所示修改代碼,將Example定義爲trace的方法,並經過trace的實例t來調用Example。
再次運行程序,會發現堆棧信息有一點不一樣:
Listing 9
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
首先注意第2行的方法調用使用了pointer receiver,在package名字和方法名之間多出了"*trace"字樣。另外,參數列表的第1個參數標明告終構體(t)地址。咱們從堆棧信息中看到了內部實現細節。
Packing
若是有多個參數能夠填充到一個single word, 則這些參數值會合並打包:
Listing 10
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函數爲4個參數:3個bool型和1個八位無符號整型。bool值也是用8個bit表示,因此在32位和64位架構下,4個參數能夠合併爲一個single word。
Listing 11
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
這是本例的堆棧信息,看下圖的具體分析:
Listing 12
// 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)
以上展現了參數值是如何匹配到4個參數的。當咱們看到堆棧信息中包括十六進制值,須要知道這些值是如何傳遞的。
Conclusion
The Go runtime provides a great deal of information to help us debug our programs. In this post we concentrated on stack traces. The ability to decode the values that were passed into each function throughout the call stack is huge. It has helped me more than once to identify my bug very quickly. Now that you know how to read stack traces, hopefully you can leverage this knowledge the next time a stack trace happens to you.