文中實體參考 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 call | Relative position |
|---|---|
main.iPanic(0x0) | +0x56 |
main.iPanic(...) | +0x3a |
main.main() | +0x31 |
| Bottom of the stack | 0x00 |
0x02 各種資料型別的 Stack Traces 格式
通常使用列印 Stack Traces 資訊的目的有兩個:
- 找到 Panic 的發生位置和 Stack Frame 結構;
- 除錯函式呼叫的引數細節;
第一個目的通過整體輸出可以容易識別,但是第二個目的相對于 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 輸出中被合并成了一個欄位輸出,0x7200016201 中 0x62='b', 0x72='r', 0x01=true,這種將引數進行 Encode 的操作確實比較晦澀,一般看來,數字型別的引數(包括 bool byte rune) 如果連續出現,會被編碼輸出,具體編碼規則還需要進一步分析,
Case 03 常規輸出
下面列舉 Golang 非基本型別作為引數時在 Stack Trace 中的形態,與各種型別的底層資料結構基本相同,
| 型別名稱 | 引數域數量 | 引數域說明 |
|---|---|---|
| string | 2 | 指標 長度 |
| slice | 3 | 指標 長度 容量 |
| map | 1 | 指標 |
| chan | 1 | 指標 |
| interface | 2 | 型別指標 值指標 |
| pointer | 1 | 指標 |
| func | 1 | 指標 |
| nil | 1 | 0x0 |
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
參考檔案
- Reading stack traces in Go
- Understanding Go panic output
- Stack Traces In Go
- Go Type System Overview
- Go Data Structures
- Go Data Structures: Interfaces
- https://golang.org/src/runtime/stack.go
- The Go Programming Language Specification
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/256849.html
標籤:區塊鏈
上一篇:Ubuntu 20.04.2 LTS 編譯 Deepin-editor
下一篇:ubuntu使用wine安裝微信
