Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unknown field in panic stack trace

Tags:

go

In trying to learn how to debug stack traces from panics, I came across something confusing.

package main

func F(a int) {
    panic(nil)
}

func main() {
    F(1)
}

outputs the following when I run it on the attached play link:

panic: nil

goroutine 1 [running]:
main.F(0x1, 0x10436000)
    /tmp/sandbox090887108/main.go:4 +0x20
main.main()
    /tmp/sandbox090887108/main.go:8 +0x20

I can't decipher what the second number means (the 0x10436000 in main.F(0x1, 0x10436000)). It doesn't appear if there's a second int argument, or if anything else if passed in as the first argument (can be seen in the second play link).

One arg: https://play.golang.org/p/3iV48xlNFR

Two args: https://play.golang.org/p/4jA7ueI86K

like image 929
szabado Avatar asked Dec 05 '17 19:12

szabado


1 Answers

The data printed in the stack trace is the arguments, but the values don't correspond directly to the arguments passed in, it's the raw data printed in pointer-sized values (though usually this is the same as the native word size). The playground is slightly unique, in that it's a 64bit word architecture with 32bit pointers(GOARCH=amd64p32).

In traceback.go you can see that the values are printed by stepping through the arguments based on pointer size;

for i := uintptr(0); i < frame.arglen/sys.PtrSize; i++ {

So because the word size is twice as big as the pointer size in the playground, you will always have an even number of values printed in the frame arguments.

Another example of how the data is presented can be seen in the playground by using smaller types in the function arguments: https://play.golang.org/p/vHZDEHQZLh

func F(a uint8) {
    panic(nil)
}

// F(1)
// main.F(0x97301, 0x10436000)

Only the first 8 bits of the 64bit word are used, which is 0x97301 & 0x0f, or simply 1. The extra 0x97300 from the first value and the entire 0x10436000 are just the remainder of that first 64bit word which is unused by the function.

You can see the same behavior on amd64 systems by using more arguments. This signature for example;

func F(a, b, c uint32)

when called via F(1, 1, 1), it will print a stack trace like:

main.F(0x100000001, 0xc400000001)

because the 3 32bit values take 2 words

The final set of values to note are return values, which are also allocated on the stack. The following function signature:

func F(a int64) (int, int)

on amd64, would show the stack frame arguments like:

main.F(0xa, 0x1054d60, 0xc420078058)

With one word for a, and two more for the (int, int) return values. The return values are not initialized however, so there's not much to me gained here other than to understand why these values are there.

like image 154
JimB Avatar answered Dec 03 '22 05:12

JimB