gcc/libgo/go/runtime/traceback_gccgo.go
Ian Lance Taylor 3619ab8bd4 runtime: fix isSystemGoroutine for gccgo
The gc toolchain decides whether a goroutine is a system goroutine by
    comparing startpc to a list of saved special PCs.  In gccgo that
    approach does not work as startpc is often a thunk that invokes the
    real function with arguments, so the thunk address never matches the
    saved special PCs.
    
    This patch fixes gccgo's understanding of system goroutines.  Since
    there are only a limited number of them, we simply change each one to
    mark itself as special.
    
    This fixes stack dumps and functions like runtime.NumGoroutine to
    behave more like gc.  It also fixes the goprint test in the gc
    testsuite.
    
    Reviewed-on: https://go-review.googlesource.com/43156

From-SVN: r247931
2017-05-11 20:55:41 +00:00

235 lines
6.6 KiB
Go

// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Traceback support for gccgo.
// The actual traceback code is written in C.
package runtime
import (
"runtime/internal/sys"
_ "unsafe" // for go:linkname
)
// For gccgo, use go:linkname to rename compiler-called functions to
// themselves, so that the compiler will export them.
// These are temporary for C runtime code to call.
//go:linkname traceback runtime.traceback
//go:linkname printtrace runtime.printtrace
//go:linkname goroutineheader runtime.goroutineheader
//go:linkname printcreatedby runtime.printcreatedby
func printcreatedby(gp *g) {
// Show what created goroutine, except main goroutine (goid 1).
pc := gp.gopc
tracepc := pc // back up to CALL instruction for funcfileline.
entry := funcentry(tracepc)
if entry != 0 && tracepc > entry {
tracepc -= sys.PCQuantum
}
function, file, line := funcfileline(tracepc, -1)
if function != "" && showframe(function, gp) && gp.goid != 1 {
print("created by ", function, "\n")
print("\t", file, ":", line)
if entry != 0 && pc > entry {
print(" +", hex(pc-entry))
}
print("\n")
}
}
// tracebackg is used to collect stack traces from other goroutines.
type tracebackg struct {
gp *g
locbuf [_TracebackMaxFrames]location
c int
}
// location is a location in the program, used for backtraces.
type location struct {
pc uintptr
filename string
function string
lineno int
}
//extern runtime_callers
func c_callers(skip int32, locbuf *location, max int32, keepThunks bool) int32
// callers returns a stack trace of the current goroutine.
// The gc version of callers takes []uintptr, but we take []location.
func callers(skip int, locbuf []location) int {
n := c_callers(int32(skip), &locbuf[0], int32(len(locbuf)), false)
return int(n)
}
// traceback prints a traceback of the current goroutine.
// This differs from the gc version, which is given pc, sp, lr and g and
// can print a traceback of any goroutine.
func traceback(skip int32) {
var locbuf [100]location
c := c_callers(skip+1, &locbuf[0], int32(len(locbuf)), false)
printtrace(locbuf[:c], getg())
}
// printtrace prints a traceback from locbuf.
func printtrace(locbuf []location, gp *g) {
for i := range locbuf {
if showframe(locbuf[i].function, gp) {
print(locbuf[i].function, "\n\t", locbuf[i].filename, ":", locbuf[i].lineno, "\n")
}
}
}
// showframe returns whether to print a frame in a traceback.
// name is the function name.
func showframe(name string, gp *g) bool {
g := getg()
if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
return true
}
// Gccgo can trace back through C functions called via cgo.
// We want to print those in the traceback.
// But unless GOTRACEBACK > 1 (checked below), still skip
// internal C functions and cgo-generated functions.
if !contains(name, ".") && !hasprefix(name, "__go_") && !hasprefix(name, "_cgo_") {
return true
}
level, _, _ := gotraceback()
// Special case: always show runtime.gopanic frame, so that we can
// see where a panic started in the middle of a stack trace.
// See golang.org/issue/5832.
// __go_panic is the current gccgo name.
if name == "runtime.gopanic" || name == "__go_panic" {
return true
}
return level > 1 || contains(name, ".") && (!hasprefix(name, "runtime.") || isExportedRuntime(name))
}
// isExportedRuntime reports whether name is an exported runtime function.
// It is only for runtime functions, so ASCII A-Z is fine.
func isExportedRuntime(name string) bool {
const n = len("runtime.")
return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
}
var gStatusStrings = [...]string{
_Gidle: "idle",
_Grunnable: "runnable",
_Grunning: "running",
_Gsyscall: "syscall",
_Gwaiting: "waiting",
_Gdead: "dead",
_Gcopystack: "copystack",
}
func goroutineheader(gp *g) {
gpstatus := readgstatus(gp)
isScan := gpstatus&_Gscan != 0
gpstatus &^= _Gscan // drop the scan bit
// Basic string status
var status string
if 0 <= gpstatus && gpstatus < uint32(len(gStatusStrings)) {
status = gStatusStrings[gpstatus]
} else {
status = "???"
}
// Override.
if gpstatus == _Gwaiting && gp.waitreason != "" {
status = gp.waitreason
}
// approx time the G is blocked, in minutes
var waitfor int64
if (gpstatus == _Gwaiting || gpstatus == _Gsyscall) && gp.waitsince != 0 {
waitfor = (nanotime() - gp.waitsince) / 60e9
}
print("goroutine ", gp.goid, " [", status)
if isScan {
print(" (scan)")
}
if waitfor >= 1 {
print(", ", waitfor, " minutes")
}
if gp.lockedm != nil {
print(", locked to thread")
}
print("]:\n")
}
// isSystemGoroutine reports whether the goroutine g must be omitted in
// stack dumps and deadlock detector.
func isSystemGoroutine(gp *g) bool {
return gp.isSystemGoroutine
}
func tracebackothers(me *g) {
var tb tracebackg
tb.gp = me
// The getTraceback function will modify me's stack context.
// Preserve it in case we have been called via systemstack.
context := me.context
stackcontext := me.stackcontext
level, _, _ := gotraceback()
// Show the current goroutine first, if we haven't already.
g := getg()
gp := g.m.curg
if gp != nil && gp != me {
print("\n")
goroutineheader(gp)
gp.traceback = &tb
getTraceback(me, gp)
printtrace(tb.locbuf[:tb.c], nil)
printcreatedby(gp)
}
lock(&allglock)
for _, gp := range allgs {
if gp == me || gp == g.m.curg || readgstatus(gp) == _Gdead || isSystemGoroutine(gp) && level < 2 {
continue
}
print("\n")
goroutineheader(gp)
// gccgo's only mechanism for doing a stack trace is
// _Unwind_Backtrace. And that only works for the
// current thread, not for other random goroutines.
// So we need to switch context to the goroutine, get
// the backtrace, and then switch back.
//
// This means that if g is running or in a syscall, we
// can't reliably print a stack trace. FIXME.
// Note: gp.m == g.m occurs when tracebackothers is
// called from a signal handler initiated during a
// systemstack call. The original G is still in the
// running state, and we want to print its stack.
if gp.m != g.m && readgstatus(gp)&^_Gscan == _Grunning {
print("\tgoroutine running on other thread; stack unavailable\n")
printcreatedby(gp)
} else if readgstatus(gp)&^_Gscan == _Gsyscall {
print("\tgoroutine in C code; stack unavailable\n")
printcreatedby(gp)
} else {
gp.traceback = &tb
getTraceback(me, gp)
printtrace(tb.locbuf[:tb.c], nil)
printcreatedby(gp)
}
}
unlock(&allglock)
me.context = context
me.stackcontext = stackcontext
}