[golang]如何看懂呼叫堆疊

一桶冷水發表於2019-01-06

之前也有文章講過go呼叫堆疊的話題,但並沒有完全講清楚,這裡補充裡面缺漏的幾個點。

阻塞

goroutine 1 [select, 2 minutes]:
複製程式碼

方括號裡的select表示阻塞原因,具體定義見runtime.waitReason 。 後面的時間是阻塞時間。需要注意的是這只是一個大概時間,是在gc的過程中標記的,所以如果這個goroutine不需要gc,那麼永遠也不會有值。

PC偏移

github.com/robfig/cron.(*Cron).run(0xc0000c44b0)
	cron.go:191 +0x28d
複製程式碼

行號後面的16進位制數是什麼?pc偏移。是指函式入口(cron.(*Cron).run)到呼叫處(也就是行號指位置)的指令位置偏差。一般很少用到,除非下面這種特殊情況:

func main() {
	rand.Seed(time.Now().UnixNano())
	_, _, _ = foo(), foo(), foo()
}

func foo() int {
	if rand.NormFloat64() > 0 {
		panic("")
	}
	return 0
}

//main.main()
//	main.go:10 +0x7b 或 +0x80 或 +0x85
複製程式碼

函式引數

函式引數是最複雜的部分,牽涉到go的很多底層實現。

輸入引數,輸出引數

為什麼經常只有一個引數的函式堆疊裡卻跟著兩個數呢?另一個是輸出。

func main() {
	rand.Seed(time.Now().UnixNano())
	r := rand.Int() //2e78e7b163438cc2
	fmt.Printf("%x\n", r)
	foo(r)
}

func foo(i int) (o int) {
	o = rand.Int() //36dd26e720cac1fe
	fmt.Printf("%x\n", o)
	defer panic("want to panic")
	return
}

//main.foo(2e78e7b163438cc2, 36dd26e720cac1fe)
//	main.go:21 +0xdd
複製程式碼
結構體展開

結構體會被展開,然後比較短的欄位會被打包成一個uint。

type S struct {
	a int
	b int
	c int
	d int
	a1 bool
	b1 byte
	c1 bool
	d1 byte
}

func main() {
	foo(S{})
}

func foo(s S) {
	panic("want to panic")
	noInline()
}

func noInline() {
	fmt.Sprint()
}

//main.foo(0x0, 0x0, 0x0, 0x0, 0x0)
//	main.go:67 +0x39
複製程式碼

但是這種打包是依賴欄位順序的。

type S struct {
	a int
	a1 bool
	b int
	b1 bool
	c int
	c1 bool
	d int
	d1 bool
}

func main() {
	foo(S{})
}

func foo(s S) {
	panic("want to panic")
	noInline()
}

func noInline() {
	fmt.Sprint()
}

//main.foo(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
//	main.go:67 +0x39
複製程式碼
函式引數對照表

下面的表涵蓋了大部分情況

函式簽名 輸入 堆疊輸出 說明
foo(float64) 1 (0x3ff0000000000000)
foo(complex64) 1+3i (0x404000003f800000) 兩個32位浮點打包為一個64位數
foo(complex128) 1+3i (0x3ff0000000000000, 0x4008000000000000)
foo(string) "中文" (0x10adc82, 0x6) (指標,位元組數)
foo(interface{}) "" (0x108e520, 0x10bff30) (型別指標,值指標)
foo(interface{}) (*string)(nil) (0x108b8e0, 0x0) 值為nil,型別不為nil
foo(interface{}) nil (0x0, 0x0) 值,型別都為nil
foo([]byte) make([]byte,3,6) (0xc000070f82, 0x3, 0x6) (指標,len,cap)
foo(map[string]string) make(map[string]string) (0xc000070e48)
foo(chan struct{}) make(chan struct{}) (0xc00005e060)
foo([200]int) [200]int{} (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...) 陣列展開
foo(func()) nil (0x0)
foo(int16, uint16) 1,2 (0xc000020001) 兩個16位數打包為32位數,僅低32位有效
foo(bool)bool false (0xc000070f00, 0xc00005c058) 前一個數為輸入,僅低8位有效,後一個數為輸出,未初始化
foo(struct {a,b int}) struct {a,b int}{} (0x0, 0x0) 結構體展開
foo(struct {a int;b bool;c int;d bool;e byte}) struct {a int;b bool;c byte;d bool;e byte}{1,true,2,false,3} (0x1, 0x1, 0x2, 0x300) d,e合併,b不合並
foo(struct {a int;b bool;c string;d bool;e byte}) struct {a int;b bool;c string;d bool;e byte}{1,true,"a",false,3} (0x1, 0x1, 0x10adb18, 0x1, 0x300) d,e合併
foo(struct {a struct{a,b byte};b bool}) struct {a struct{a,b byte};b bool}{struct{a,b byte}{3,4},true} (0xc000010403) 內嵌結構體合併
foo(struct {a struct{a int;b byte};b bool}) struct {a struct{a int;b byte};b bool}{struct {a int;b byte}{5 , 7 },false} (0x5, 0xc000072f07, 0xc00005e000) 內嵌結構體不合並

相關文章