Go: traceback 中包含的信息

约定

示例使用的 Go 版本: go version go1.12.3 darwin/amd64

因为默认 go build 命令会在编译时进行一些优化导致举简单的示例程序的输出不符合举例的要求,所以除特殊说明外,所有示例程序都使用 go build -gcflags=all="-N -l" <name>.go 的方式进行编译。

基本结构

1
2
3
4
5
6
7
8
 package main

 import "fmt"

 func main() {
     fmt.Println("hello")
     panic("error")
 }

运行结果:

 $ ./hello
 hello
 panic: error

 goroutine 1 [running]:
 main.main()
     /Users/xxx/hello.go:7 +0x9f

从这个最基础的 stack trace 信息中可以看到是 /Users/xxx/hello.go 这个文件的第 7 行出错了,并且这个错误发生在 main package 的 main 这个函数中。

方法

上面是在函数中 panic 了,下面看一下方法中 panic 的 strace 信息。

非指针实例的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 package main

 type User struct{}

 func main() {
     var u User
     u.Name()
 }

 func (u User) Name() int {
     panic("error")
 }

运行结果:

 $ ./method
 panic: error

 goroutine 1 [running]:
 main.User.Name(0x0)
     /Users/xxx/method.go:11 +0x42
 main.main()
     /Users/xxx/method.go:7 +0x22

上方的 main.User.Name(0x0) 中的 0x0 表示的是方法的返回值,这里是 0

指针实例的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 $ cat method.go
 package main

 type User struct{}

 func main() {
     var u User
     u.Name()
 }

 func (u *User) Name() int {
     panic("error")
 }

运行结果:

 $ ./method
 panic: error

 goroutine 1 [running]:
 main.(*User).Name(0xc000044788, 0x0)
     /Users/xxx/method.go:11 +0x42
 main.main()
     /Users/xxx/method.go:7 +0x2b

上方的 main.(*User).Name(0xc000044788, 0x0)

  • 0xc000044788 表示的是 u 的指针地址( &u )。
  • 0x0 表示的是方法的返回值,这里是 0

参数

下面举例说明一下函数或方法的参数在 trace 中是如何展示的。

int/float/bool/array

int/float/bool/array 这些基本数据类型就是按本来的只显示的:

1
2
3
4
5
6
7
8
9
 package main

 func main() {
     test(true, 2, 2.2, [3]int{1, 2, 3})
 }

 func test(b bool, n int, f float32, arr [3]int) {
     panic("test")
 }

结果:

 $ ./base
 panic: test

 goroutine 1 [running]:
 main.test(0xc000044701, 0x2, 0x400ccccd, 0x1, 0x2, 0x3)
     /Users/xxx/base.go:8 +0x39
 main.main()
     /Users/xxx/base.go:4 +0x55

结果解析:

main.test(
    0xc000044701,   // 一个常量,表示 true, false 是 0xc000044700 (至少在我的机器上是两个值)
    0x2,            // 2
    0x400ccccd,     // 2.2 的 16 进制表示
    0x1, 0x2, 0x3   // [3]int{1, 2, 3}
    )

string

1
2
3
4
5
6
7
8
9
 package main

 func main() {
     test("test")
 }

 func test(s string) {
     panic("string")
 }

结果:

 $ ./string
 panic: string

 goroutine 1 [running]:
 main.test(0x1074265, 0x4)
     /Users/mg/tmp/test_go/strace/string.go:8 +0x39
 main.main()
     /Users/mg/tmp/test_go/strace/string.go:4 +0x36

为啥这里 main.test(0x1074265, 0x4) 显示的是两个参数呢,因为字符串是用两个参数来表示的: 第一个参数是指向字符串底层数组的指针地址(0x1074265),第二个参数是字符串的长度(0x4 表示长度为 4 )。

slice

slice 的数据用三个参数来表示:第一个是指向底层数组的指针,第二个是长度,第三个是容量:

 goroutine 1 [running]:
 main.test(0xc000044758, 0x3, 0x3)
     /Users/xxx/slice.go:8 +0x39
 main.main()
     /Users/xxx/slice.go:4 +0x8c

map

map 的话就是一个指针地址表示:

 goroutine 1 [running]:
 main.test(0xc000044648)
     /Users/xxx/map.go:10 +0x39
 main.main()
     /Users/xxx/map.go:4 +0x139

struct

会显示 struct 中所有字段的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 package main

 func main() {
     u := User{
         name: "Tom",
         age:  233,
     }
     test(u)
 }

 type User struct {
     name string
     age  int
 }

 func test(u User) {
     panic("test")
 }

结果:

 $ ./struct
 panic: test

 goroutine 1 [running]:
 main.test(0x1074474, 0x3, 0xe9)
     /Users/xxx/struct.go:17 +0x39
 main.main()
     /Users/xxx/struct.go:8 +0x67

解析:

main.test(
    0x1074474, 0x3,  // name 字段的值 "Tom",0x1074474: 底层数组,0x3: 字符串长度
    0xe9             // age 字段的值 233 的 16 进制表示
)

point

指针类型的话就显示一个指针地址:

 $ ./point
 panic: test

 goroutine 1 [running]:
 main.test(0xc000044770)
     /Users/xxx/point.go:17 +0x39
 main.main()
     /Users/xxx/point.go:8 +0x5a

interface

interface 由两个参数表示,第一个是指向 interface 中存储类型信息的指针,第二个是指向真实数据的指针:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 package main

 type namer interface {
     Name() string
 }

 func main() {
     var u namer = &User{
         name: "Tom",
         age:  233,
     }
     test(u)
 }

 type User struct {
     name string
     age  int
 }

 func (u *User) Name() string {
     return u.name
 }

 func test(n namer) {
     panic("test")
 }

结果:

 $ ./interface
 panic: test

 goroutine 1 [running]:
 main.test(0x107c5c0, 0xc000044770)
     /Users/xxx/interface.go:25 +0x39
 main.main()
     /Users/xxx/interface.go:12 +0x8c

 // 0x107c5c0 包含类型信息的指针地址
 // 0xc000044770 实际数据的指针地址

多个参数用一个值表示

当多个连续的参数可以用一个值表示的时候,trace 中就会用一个值来表示多个参数的值,比如

1
2
3
4
5
6
7
8
9
 package main

 func main() {
     test(1, 2, 3, 20, 30, 60)
 }

 func test(a, b, c, d, e, f uint8) {
     panic("test")
 }

结果:

 $ ./pack
 panic: test

 goroutine 1 [running]:
 main.test(0x3c1e14030201)
     /Users/mg/tmp/test_go/strace/pack.go:8 +0x39
 main.main()
     /Users/mg/tmp/test_go/strace/pack.go:4 +0x30

0x3c1e14030201 是由下面6个值组成的:

Bits     Binary     Hex     Value
00-07    0000 0001  01      1
08-15    0000 0002  02      2
16-23    0000 0003  03      3
24-31    0001 0100  14      20
32-39    0001 1110  1e      30
40-47    0011 1100  3c      60

P.S. 等将来找到对应的源码后再补充更详细的合并规则 :joy

traceback 中显示的值个数

最多只能显示 10 个值 ,超过 10 个时会用 ... 代替:

1
2
3
4
5
6
7
8
9
 package main

 func main() {
     test([13]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13})
 }

 func test(arr [13]int) {
     panic("test")
 }
 $ ./max
 panic: test

 goroutine 1 [running]:
 main.test(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, ...)
     /Users/xxx/max.go:8 +0x39
 main.main()
     /Users/xxx/max.go:4 +0x4c

Comments