This is a creation in Article, where the information may have evolved or changed.
Chinese translation
Introduction
Have some basic skills in debugging Go programs can save no programmer a good amount of time trying to identify problem S. I believe in logging as much information as can, but sometimes a panic occurs and what are you logged are not enough. Understanding the information in a stack trace can sometimes mean the difference between finding the bug now or needing to Add logging and waiting for it to happen again.
I have been seeing stack traces since I started writing Go. At some point we are something silly that causes the runtime to kill our program and throw a stack trace. I am going to show you the information the stack trace provides, including how to identify the value for each parameter th At is passed into each function.
Functions
Let's start with a small piece of code that would produce a stack trace:
Listing 1
The package main
02
The. Func main () {
Slice: = Make ([]string, 2, 4)
Example (Slice, "hello", 10)
06}
07
Example func (Slice []string, str string, I int) {
Panic ("Want stack Trace")
10}
Listing 1 shows a program where the Mainfunction calls the Examplefunction on line 05. The Examplefunction is declared on line and accepts three parameters, a slice of strings, a string and an integer. The only code ExampleExecutes is a call to the built-in function PanicOn line, which immediately produces a stack trace:
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
The stack trace in Listing 2 shows all the goroutines that existed at the time of the panic, the status of each routine an d The call stack under that respective goroutine. The goroutines that were running and the one of that caused the stack trace would be at the top. Let's focus on the goroutine that panicked.
Listing 3
Goroutine 1 [Running]:
The 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
The stack trace on line listing 3 is showing this Goroutine 1 was running prior to the panic. On line and we see that the code that panicked is in the Examplefunction in Package Main. The line indented shows the code file and the path to this function are located in, plus the line of code, which was executing. In this case, the ' Code on line ' running which is Panic.
Line shows the name of the function that called Example. The Mainfunction in the MainPackage. Underneath the function name once again, the line that's indented shows the code file, path and line of code where the CA ll to ExampleWas made.
The stack trace is showing the chain of function calls in the scope of this goroutine up to the time of the panic occurred. Now, let's focus on the values of all parameter that is passed into the Examplefunction
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)
Listing 4 shows the values from the stack trace, the were passed into the Examplefunction when the call is made by Mainand the declaration of the function. When you compare the values from the stack trace with the function declaration, it doesn ' t seem to match up. The Declaration of the Examplefunction accepts three parameters but the stack trace is showing six hexadecimal values. The key to understanding how the values does match up with the parameters requires knowing the implementation for each param Eter type.
Let's start with the first parameter which is a slice of strings. A slice is a reference type in Go. This means the value of a slice is a header value with a pointer to some underlying data. In the case of a slice, the header value is a three word structure that contains a pointer to an underlying array, the Len Gth of the slice and the capacity. The values associated with the slice header is represented by the first three values in the stack trace:
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)
Listing 5 shows how the first three values in the stack trace does match up with the slice parameter. The first value represents the pointer to the underlying array of strings. The length and capacity numbers used to initialize the slice match with the second and third values. Those three values represent each value of the slice header, the first parameter.
Figure
1
Figure provided by Georgi Knox
Now let's look at the second parameter which is a string. A string is also a reference type and this header value is immutable. The header value for a string is declared as a, word structure, contains a pointer to an underlying byte array and The length of the 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)
Listing 6 shows how the fourth and fifth values in the stack trace does match up with the string parameter. The fourth value represents the pointer to the underlying array of bytes and the fifth value is the length of the string w Hich was 5. The string "Hello"Requires 5 bytes. Those represent each value of the string header, the second parameter.
Figure
2
Figure provided by Georgi Knox
The third parameter is an integer which are a single word value:
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)
Listing 7 shows how the last value in the stack trace matches up with the integer parameter. The very last value in the trace is hexadecimal number 0xa, which is the value of 10. The same value is passed in for that parameter. That value represents the third parameter.
Figure
3
Figure provided by Georgi Knox
Methods
Let's change the program so that the Example function is now a method:
Listing 8
The package main
02
Import "FMT"
04
Type Trace struct{}
06
The func main () {
Slice: = Make ([]string, 2, 4)
09
Ten Var t trace
T.example (Slice, "hello", 10)
12}
13
+ func (t *trace) Example (slice []string, str string, I int) {
FMT. Printf ("Receiver Address:%p\n", T)
Panic ("Want stack Trace")
17}
Listing 8 Changes the original program by declaring a new type named TraceOn line and converting the Examplefunction into a method in line 14. The conversion is accomplished by re-declaring the function with a pointer receiver of type Trace. Then on line ten, a variable named Tis declared of type TraceAnd the method call are made with the variable in line 11.
Since the method is declared with a pointer receiver, Go'll take the address of the TVariable to support the receiver type, even though the method of call was made with a value. This time if the program was run, the stack trace is a little different:
Listing 9
Receiver Address:
0x1553a8
Panic:want Stack Trace
Goroutine 1 [Running]:
The 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
Main.main ()
/users/bill/spaces/go/projects/src/github.com/goinaction/code/
Temp/main.go:11 +0xae
The first thing you should notice in Listing 9 are the stack trace on line, is making it clear this was a method cal L using a pointer receiver. The name of the function is now displayed with (*trace)Between the package name and the method name. The second thing to notice are how the value list now shows the value of the receiver first. Method calls is really function calls with the first parameter being the receiver value. We is seeing this implementation detail on action from the stack trace.
Since nothing else changed with the declaration or call to the Examplemethod, all of the other values remain the same. The line numbers where the Exampleis made and where the panic occurred changed and reflects the new code.
Packing
When you had multiple parameters that fit inside of a single word and then the values for the parameters in the stack trace Would be packed together:
Listing Ten
The package main
02
The. Func main () {
Example (True, False, true, 25)
05}
06
Example func (B1, B2, b3 bool, I uint8) {
Panic ("Want stack Trace")
09}
Listing shows a new sample program that changes the Examplefunction to accept the four parameters. The first three was booleans and the last one was an eight bit unsigned integer. A Boolean value is also an eight bit value, so all four parameters fit inside of a single word on both and the bit Archi Tectures. When the program runs, it produces a interesting stack trace:
Listing
Goroutine 1 [Running]:
The main. Example (
0x19010001)
/users/bill/spaces/go/projects/src/github.com/goinaction/code/
Temp/main.go:8 +0x64
Main.main ()
/users/bill/spaces/go/projects/src/github.com/goinaction/code/
Temp/main.go:4 +0x32
Instead of there being four values in the stack trace for the Example, there is a single value. All four individual 8 bit values were packed together to a single word:
Listing
Parameter values
True, False, True, 25
Word value
Bits Binary Hex Value
00-07 0000 0001
onTrue
08-15 0000 0000
xxFalse
16-23 0000 0001
onTrue
24-31 0001 1001
+25
Declaration
Main. Example (
B1, B2, b3 bool, I uint8)
Stack Trace
Main. Example (
0x19010001)
Listing shows how the value of the stack trace matches up and all four parameter values, were passed in. The value of trueis a 8 bit value that's represented with the value of 1 and the value of falseis represented with the value of 0. The value of in binary is 11001 which converts to hexadecimal. Now if we look at the hexadecimal value represented in the stack trace, we see how it does represent the values that wer E passed in.
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, the 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 so you know how to read stack traces, hopefully you can leverage this knowledge the next time a stack trace happens To you.