Hey,
I was trying to get a better understanding of how Go deals with Goroutine’s stacks, and I came across a blog post1 that despite being great about describing how the whole thing works, it didn’t give great hints about how to extract the stack growing & shrink information from the Go runtime.
tl;dr: build a patched version of Go from source, then use that to build your binary
It turns out that under the runtime
package, more specifically, in the
runtime/stack.go
file, there’s a variable once set to a non-zero value, brings
the information we’re searching for:
const (
// stackDebug == 0: no logging
// == 1: logging of per-stack operations
// == 2: logging of per-frame operations
// == 3: logging of per-word updates
// == 4: logging of per-word reads
stackDebug = 0
)
Now, the problem was - how could I turn that value on? i.e., how could I set
stackDebug
to 1
so that I could get per-stack operations being logged?
My first reaction was to go with ldflags
, changing it at build time, but it
turns out that that doesn’t work - being a const
, it’ll not be seen during
the link phase, thus, having no effect.
E.g., in the following example:
package main
import "fmt"
const foo = "bar"
var zaz = "caz"
func main() {
fmt.Println(foo, zaz)
}
we can try changing the value of those two, but we can see that only the
variable (zaz
) gets its value changed:
$ go build -a -ldflags='-X main.foo=aaaaaaaa -X main.zaz=bbbbbbbb'
$ ./sample
bar bbbbbbbb
Well, the solution then? Compile Go from scratch, and then build our binary with that version of Go.
First, clone go
(either https://github.com/golang/go or
https://go.googlesource.com/go), then apply the patch:
diff --git a/src/runtime/stack.go b/src/runtime/stack.go
index ebbe3e013d..439c1d00d1 100644
--- a/src/runtime/stack.go
+++ b/src/runtime/stack.go
@@ -109,7 +109,7 @@ const (
// == 2: logging of per-frame operations
// == 3: logging of per-word updates
// == 4: logging of per-word reads
- stackDebug = 0
+ stackDebug = 1
stackFromSystem = 0 // allocate stacks from system memory instead of the heap
stackFaultOnFree = 0 // old stacks are mapped noaccess to detect use after free
stackPoisonCopy = 0 // fill stack that should not be accessed with garbage, to detect bad dereferences during copy
And then, already having Go installed, use it to build the version of Go under your modified repository:
cd ./src
./make.bash
Once it finishes, it’ll have the binaries available at ./bin
:
bin/
├── go
└── gofmt
Which can now give you stack grow & shrink info once you compile a program with it:
stackfree 0xc00708c000 4096
runtime: newstack sp=0xc004770350 stack=[0xc004770000, 0xc004770800]
morebuf={pc:0x4941f9 sp:0xc004770360 lr:0x0}
sched={pc:0x5e8379 sp:0xc004770358 lr:0x0 ctxt:0x0}
stackalloc 4096
stackcacherefill order=1
allocated 0xc000c6e000
copystack gp=0xc007102f00 [0xc004770000 0xc004770358
0xc004770800] -> [0xc000c6e000 0xc000c6eb58 0xc000c6f000]/4096
stackfree 0xc004770000 2048
If you’re interesting to learn more, make sure you check out the Go contributing guide! https://golang.org/doc/contribute.html