summaryrefslogtreecommitdiff
path: root/libgo/go/runtime/mgcmark.go
diff options
context:
space:
mode:
authorIan Lance Taylor <ian@gcc.gnu.org>2018-12-05 23:09:51 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2018-12-05 23:09:51 +0000
commitc43137e800bb9ca2ecda0a6b6189e0eb5c22f0d7 (patch)
treedf5d750d82dff84b98ec03163cc8c2b2552a559a /libgo/go/runtime/mgcmark.go
parente4a9a572770b48375561c7ca424eb94eb45a9fcb (diff)
runtime: add precise stack scan support
This CL adds support of precise stack scan using stack maps to the runtime. The stack maps are generated by the compiler (if supported). Each safepoint is associated with a (real or dummy) landing pad, and its "type info" in the exception table is a pointer to the stack map. When a stack is scanned, the stack map is found by the stack unwinding code by inspecting the exception table (LSDA). For precise stack scan we need to unwind the stack. There are three cases: - If a goroutine is scanning its own stack, it can unwind the stack and scan the frames. - If a goroutine is scanning another, stopped, goroutine, it cannot directly unwind the target stack. We handle this by switching (runtime.gogo) to the target g, letting it unwind and scan the stack, and switch back. - If we are scanning a goroutine that is blocked in a syscall, we send a signal to the target goroutine's thread, and let the signal handler unwind and scan the stack. Extra care is needed as this races with enter/exit syscall. Currently this is only implemented on linux. Reviewed-on: https://go-review.googlesource.com/c/140518 From-SVN: r266832
Diffstat (limited to 'libgo/go/runtime/mgcmark.go')
-rw-r--r--libgo/go/runtime/mgcmark.go121
1 files changed, 115 insertions, 6 deletions
diff --git a/libgo/go/runtime/mgcmark.go b/libgo/go/runtime/mgcmark.go
index 88cae414d40..631c4d7133b 100644
--- a/libgo/go/runtime/mgcmark.go
+++ b/libgo/go/runtime/mgcmark.go
@@ -664,7 +664,10 @@ func gcFlushBgCredit(scanWork int64) {
}
// We use a C function to find the stack.
-func doscanstack(*g, *gcWork)
+// Returns whether we succesfully scanned the stack.
+func doscanstack(*g, *gcWork) bool
+
+func doscanstackswitch(*g, *g)
// scanstack scans gp's stack, greying all pointers found on the stack.
//
@@ -691,7 +694,16 @@ func scanstack(gp *g, gcw *gcWork) {
return
case _Grunning:
// ok for gccgo, though not for gc.
- case _Grunnable, _Gsyscall, _Gwaiting:
+ if usestackmaps {
+ print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
+ throw("scanstack: goroutine not stopped")
+ }
+ case _Gsyscall:
+ if usestackmaps {
+ print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
+ throw("scanstack: goroutine in syscall")
+ }
+ case _Grunnable, _Gwaiting:
// ok
}
@@ -701,15 +713,64 @@ func scanstack(gp *g, gcw *gcWork) {
}
// Scan the stack.
- doscanstack(gp, gcw)
+ if usestackmaps {
+ g := getg()
+ if g == gp {
+ // Scan its own stack.
+ doscanstack(gp, gcw)
+ } else if gp.entry != nil {
+ // This is a newly created g that hasn't run. No stack to scan.
+ } else {
+ // Scanning another g's stack. We need to switch to that g
+ // to unwind its stack. And switch back after scan.
+ scanstackswitch(gp, gcw)
+ }
+ } else {
+ doscanstack(gp, gcw)
- // Conservatively scan the saved register values.
- scanstackblock(uintptr(unsafe.Pointer(&gp.gcregs)), unsafe.Sizeof(gp.gcregs), gcw)
- scanstackblock(uintptr(unsafe.Pointer(&gp.context)), unsafe.Sizeof(gp.context), gcw)
+ // Conservatively scan the saved register values.
+ scanstackblock(uintptr(unsafe.Pointer(&gp.gcregs)), unsafe.Sizeof(gp.gcregs), gcw)
+ scanstackblock(uintptr(unsafe.Pointer(&gp.context)), unsafe.Sizeof(gp.context), gcw)
+ }
gp.gcscanvalid = true
}
+// scanstackswitch scans gp's stack by switching (gogo) to gp and
+// letting it scan its own stack, and switching back upon finish.
+//
+//go:nowritebarrier
+func scanstackswitch(gp *g, gcw *gcWork) {
+ g := getg()
+
+ // We are on the system stack which prevents preemption. But
+ // we are going to switch to g stack. Lock m to block preemption.
+ mp := acquirem()
+
+ // The doscanstackswitch function will modify the current g's
+ // context. Preserve it.
+ // The stack scan code may call systemstack, which will modify
+ // gp's context. Preserve it as well so we can resume gp.
+ context := g.context
+ stackcontext := g.stackcontext
+ context2 := gp.context
+ stackcontext2 := gp.stackcontext
+
+ gp.scangcw = uintptr(unsafe.Pointer(gcw))
+ gp.scang = uintptr(unsafe.Pointer(g))
+ doscanstackswitch(g, gp)
+
+ // Restore the contexts.
+ g.context = context
+ g.stackcontext = stackcontext
+ gp.context = context2
+ gp.stackcontext = stackcontext2
+ gp.scangcw = 0
+ // gp.scang is already cleared in C code.
+
+ releasem(mp)
+}
+
type gcDrainFlags int
const (
@@ -1064,6 +1125,10 @@ func scanobject(b uintptr, gcw *gcWork) {
// scanblock, but we scan the stack conservatively, so there is no
// bitmask of pointers.
func scanstackblock(b, n uintptr, gcw *gcWork) {
+ if usestackmaps {
+ throw("scanstackblock: conservative scan but stack map is used")
+ }
+
for i := uintptr(0); i < n; i += sys.PtrSize {
// Same work as in scanobject; see comments there.
obj := *(*uintptr)(unsafe.Pointer(b + i))
@@ -1073,6 +1138,50 @@ func scanstackblock(b, n uintptr, gcw *gcWork) {
}
}
+// scanstackblockwithmap is like scanstackblock, but with an explicit
+// pointer bitmap. This is used only when precise stack scan is enabled.
+//go:linkname scanstackblockwithmap runtime.scanstackblockwithmap
+//go:nowritebarrier
+func scanstackblockwithmap(pc, b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) {
+ // Use local copies of original parameters, so that a stack trace
+ // due to one of the throws below shows the original block
+ // base and extent.
+ b := b0
+ n := n0
+
+ for i := uintptr(0); i < n; {
+ // Find bits for the next word.
+ bits := uint32(*addb(ptrmask, i/(sys.PtrSize*8)))
+ if bits == 0 {
+ i += sys.PtrSize * 8
+ continue
+ }
+ for j := 0; j < 8 && i < n; j++ {
+ if bits&1 != 0 {
+ // Same work as in scanobject; see comments there.
+ obj := *(*uintptr)(unsafe.Pointer(b + i))
+ if obj != 0 {
+ o, span, objIndex := findObject(obj, b, i, false)
+ if obj < minPhysPageSize ||
+ span != nil && span.state != _MSpanManual &&
+ (obj < span.base() || obj >= span.limit || span.state != mSpanInUse) {
+ print("runtime: found in object at *(", hex(b), "+", hex(i), ") = ", hex(obj), ", pc=", hex(pc), "\n")
+ name, file, line := funcfileline(pc, -1)
+ print(name, "\n", file, ":", line, "\n")
+ //gcDumpObject("object", b, i)
+ throw("found bad pointer in Go stack (incorrect use of unsafe or cgo?)")
+ }
+ if o != 0 {
+ greyobject(o, b, i, span, gcw, objIndex, false)
+ }
+ }
+ }
+ bits >>= 1
+ i += sys.PtrSize
+ }
+ }
+}
+
// Shade the object if it isn't already.
// The object is not nil and known to be in the heap.
// Preemption must be disabled.