简介
在调试Go程序方面有一些基本技能可以为程序员节省大量时间来识别问题。我信奉log尽可能多的信息,但有时panic发生,而log的信息并不够。有时理解stack trace中的信息可能意味着立刻发现错误,抑或需要添加更多日志记录并等待它再次发生。
自从我开始写Go以来,我一直在看stack trace。在某些时候,我们都做了一些愚蠢的事情,导致运行时杀死我们的程序并抛出stack trace。我将向您展示stack trace提供的信息,包括如何识别传递到函数的每个参数的值。
Functions
让我们从一小段代码开始,它将产生一个stack trace:
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 }
上述代码展示了一个程序,第05行其main函数调用Example函数。Example函数被声明在第08行,接受三个参数,string slices,字符串和一个整数。Example执行的唯一代码是在第09行调用内置panic函数,它立即生成stack trace:
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
上述stack trace显示了panic时的所有goroutine,每个协程的状态以及相应goroutine下的调用堆栈。导致stack trace的运行中goroutine位于顶部。首先我们把重点放在导致panic的goroutine。
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
代码第01行的stack trace 显示在panic之前goroutine 1正在运行中。第02行,我们看到panic的代码位于main包的Example函数中。缩进的行显示了此函数所在的代码文件和路径,以及正在执行的代码行。当时,第09行的代码正在运行,是一个导致panic的调用。
第03行显示调用Example的函数的名称,main包中的main函数。在函数名称下面,缩进的行显示了对Example进行调用的代码文件,路径和代码行。
stack trace显示直到panic发生时,该goroutine范围内的函数调用链。现在,我们把重点放在传递给Example函数的每个参数的值:
// 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)
上述来自stack trace显示了当main调用时传递给Example函数的值和该函数的声明。将stack trace中的值与函数声明进行比较,似乎不匹配。Example函数的声明接受三个参数,但stack trace显示六个十六进制值。理解值如何与参数匹配的关键在于需要知道每个参数类型的实现。
让我们从第一个参数开始,这是一个string slice。slice在Go中是引用类型。意味着slice的值是header value,带有指向某些基础数据的指针。对于slice,header value是三个word大小的结构,其包含指向底层数组的指针,slice的长度和容量。与slice header相关的值由stack trace中的前三个值表示:
// 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)
上述显示了堆栈跟踪中的前三个值如何与slice参数匹配。第一个值表示指向基础字符串数组的指针。用于初始化切片的长度和容量数与第二个和第三个值匹配。这三个值表示切片标头的每个值,即第一个参数。