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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With