From da8cf5438aa676a99e8bb55c94011b2581743e1a Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 16 Sep 2014 17:26:16 -0700 Subject: runtime: always run semacquire on the G stack semacquire might need to park the currently running G. It can only park if called from the G stack (because it has no way of saving the M stack state). So all calls to semacquire must come from the G stack. The three violators are GOMAXPROCS, ReadMemStats, and WriteHeapDump. This change moves the semacquire call earlier, out of their C code and into their Go code. This seldom caused bugs because semacquire seldom actually had to park the caller. But it did happen intermittently. Fixes #8749 LGTM=dvyukov R=golang-codereviews, dvyukov, bradfitz CC=golang-codereviews https://golang.org/cl/144940043 --- src/runtime/runtime.h | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'src/runtime/runtime.h') diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index adc74cf417..c034f3aa97 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -60,6 +60,7 @@ typedef struct SudoG SudoG; typedef struct Mutex Mutex; typedef struct M M; typedef struct P P; +typedef struct SchedType SchedType; typedef struct Note Note; typedef struct Slice Slice; typedef struct String String; @@ -433,6 +434,42 @@ enum { MaxGomaxprocs = 1<<8, }; +struct SchedType +{ + Mutex lock; + + uint64 goidgen; + + M* midle; // idle m's waiting for work + int32 nmidle; // number of idle m's waiting for work + int32 nmidlelocked; // number of locked m's waiting for work + int32 mcount; // number of m's that have been created + int32 maxmcount; // maximum number of m's allowed (or die) + + P* pidle; // idle P's + uint32 npidle; + uint32 nmspinning; + + // Global runnable queue. + G* runqhead; + G* runqtail; + int32 runqsize; + + // Global cache of dead G's. + Mutex gflock; + G* gfree; + int32 ngfree; + + uint32 gcwaiting; // gc is waiting to run + int32 stopwait; + Note stopnote; + uint32 sysmonwait; + Note sysmonnote; + uint64 lastpoll; + + int32 profilehz; // cpu profiling rate +}; + // The m->locked word holds two pieces of state counting active calls to LockOSThread/lockOSThread. // The low bit (LockExternal) is a boolean reporting whether any LockOSThread call is active. // External locks are not recursive; a second lock is silently ignored. @@ -716,6 +753,8 @@ extern DebugVars runtime·debug; extern uintptr runtime·maxstacksize; extern Note runtime·signote; extern ForceGCState runtime·forcegc; +extern SchedType runtime·sched; +extern int32 runtime·newprocs; /* * common functions and data -- cgit v1.3 From c3b5db895b11ba28bc1546f37178efcb057ab3f0 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 18 Sep 2014 14:49:24 -0400 Subject: runtime: delete panicstring; move its checks into gopanic In Go 1.3 the runtime called panicstring to report errors like divide by zero or memory faults. Now we call panic (gopanic) with pre-allocated error values. That new path is missing the checking that panicstring did, so add it there. The only call to panicstring left is in cnew, which is problematic because if it fails, probably the heap is corrupt. In that case, calling panicstring creates a new errorCString (no allocation there), but then panic tries to print it, invoking errorCString.Error, which does a string concatenation (allocating), which then dies. Replace that one panicstring with a throw: cnew is for allocating runtime data structures and should never ask for an inappropriate amount of memory. With panicstring gone, delete newErrorCString, errorCString. While we're here, delete newErrorString, not called by anyone. (It can't be: that would be C code calling Go code that might block or grow the stack.) Found while debugging a malloc corruption. This resulted in 'panic during panic' instead of a more useful message. LGTM=khr R=khr CC=golang-codereviews https://golang.org/cl/138290045 --- src/runtime/error.go | 22 ------------------- src/runtime/malloc.c | 2 +- src/runtime/panic.go | 59 +++++++++++++++++++++++++-------------------------- src/runtime/proc.c | 7 ------ src/runtime/runtime.h | 3 --- 5 files changed, 30 insertions(+), 63 deletions(-) (limited to 'src/runtime/runtime.h') diff --git a/src/runtime/error.go b/src/runtime/error.go index 3ea93680ce..0b40c702b0 100644 --- a/src/runtime/error.go +++ b/src/runtime/error.go @@ -71,28 +71,6 @@ func (e errorString) Error() string { return "runtime error: " + string(e) } -// For calling from C. -func newErrorString(s string, ret *interface{}) { - *ret = errorString(s) -} - -// An errorCString represents a runtime error described by a single C string. -// Not "type errorCString unsafe.Pointer" because of http://golang.org/issue/7084. -// Not uintptr because we want to avoid an allocation if interfaces can't hold -// uintptrs directly (and cstr _is_ a pointer). -type errorCString struct{ cstr unsafe.Pointer } - -func (e errorCString) RuntimeError() {} - -func (e errorCString) Error() string { - return "runtime error: " + gostringnocopy((*byte)(e.cstr)) -} - -// For calling from C. -func newErrorCString(s unsafe.Pointer, ret *interface{}) { - *ret = errorCString{s} -} - type stringer interface { String() string } diff --git a/src/runtime/malloc.c b/src/runtime/malloc.c index d5f2b9ab80..60d20a992d 100644 --- a/src/runtime/malloc.c +++ b/src/runtime/malloc.c @@ -335,7 +335,7 @@ static void* cnew(Type *typ, intgo n) { if(n < 0 || (typ->size > 0 && n > MaxMem/typ->size)) - runtime·panicstring("runtime: allocation size out of range"); + runtime·throw("runtime: allocation size out of range"); return runtime·mallocgc(typ->size*n, typ, typ->kind&KindNoPointers ? FlagNoScan : 0); } diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 927b6db44b..3cc31053e8 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -281,6 +281,35 @@ func gopanic(e interface{}) { if gp.m.curg != gp { gothrow("panic on m stack") } + + // m.softfloat is set during software floating point. + // It increments m.locks to avoid preemption. + // We moved the memory loads out, so there shouldn't be + // any reason for it to panic anymore. + if gp.m.softfloat != 0 { + gp.m.locks-- + gp.m.softfloat = 0 + gothrow("panic during softfloat") + } + if gp.m.mallocing != 0 { + print("panic: ") + printany(e) + print("\n") + gothrow("panic during malloc") + } + if gp.m.gcing != 0 { + print("panic: ") + printany(e) + print("\n") + gothrow("panic during gc") + } + if gp.m.locks != 0 { + print("panic: ") + printany(e) + print("\n") + gothrow("panic holding locks") + } + var p _panic p.arg = e p.link = gp._panic @@ -431,33 +460,3 @@ func gothrow(s string) { dopanic(0) *(*int)(nil) = 0 // not reached } - -func panicstring(s *int8) { - // m.softfloat is set during software floating point, - // which might cause a fault during a memory load. - // It increments m.locks to avoid preemption. - // If we're panicking, the software floating point frames - // will be unwound, so decrement m.locks as they would. - gp := getg() - if gp.m.softfloat != 0 { - gp.m.locks-- - gp.m.softfloat = 0 - } - - if gp.m.mallocing != 0 { - print("panic: ", s, "\n") - gothrow("panic during malloc") - } - if gp.m.gcing != 0 { - print("panic: ", s, "\n") - gothrow("panic during gc") - } - if gp.m.locks != 0 { - print("panic: ", s, "\n") - gothrow("panic holding locks") - } - - var err interface{} - newErrorCString(unsafe.Pointer(s), &err) - gopanic(err) -} diff --git a/src/runtime/proc.c b/src/runtime/proc.c index 4282a145e1..860701ee58 100644 --- a/src/runtime/proc.c +++ b/src/runtime/proc.c @@ -123,7 +123,6 @@ runtime·schedinit(void) { int32 n, procs; byte *p; - Eface i; // raceinit must be the first call to race detector. // In particular, it must be done before mallocinit below calls racemapshadow. @@ -137,12 +136,6 @@ runtime·schedinit(void) runtime·mallocinit(); mcommoninit(g->m); - // Initialize the itable value for newErrorCString, - // so that the next time it gets called, possibly - // in a fault during a garbage collection, it will not - // need to allocated memory. - runtime·newErrorCString(0, &i); - runtime·goargs(); runtime·goenvs(); runtime·parsedebugvars(); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index c034f3aa97..386b09b96b 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -804,7 +804,6 @@ void runtime·goenvs(void); void runtime·goenvs_unix(void); void* runtime·getu(void); void runtime·throw(int8*); -void runtime·panicstring(int8*); bool runtime·canpanic(G*); void runtime·prints(int8*); void runtime·printf(int8*, ...); @@ -1063,8 +1062,6 @@ void runtime·panicdivide(void); */ void runtime·printany(Eface); void runtime·newTypeAssertionError(String*, String*, String*, String*, Eface*); -void runtime·newErrorString(String, Eface*); -void runtime·newErrorCString(int8*, Eface*); void runtime·fadd64c(uint64, uint64, uint64*); void runtime·fsub64c(uint64, uint64, uint64*); void runtime·fmul64c(uint64, uint64, uint64*); -- cgit v1.3 From c7f6bd795acf002d60f712f0f4e2701051e74e4a Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 18 Sep 2014 23:51:22 -0400 Subject: runtime: rename SchedType to SchedT CL 144940043 renamed it from Sched to SchedType to avoid a lowercasing conflict in the Go code with the variable named sched. We've been using just T resolve those conflicts, not Type. The FooType pattern is already taken for the kind-specific variants of the runtime Type structure: ChanType, MapType, and so on. SchedType isn't a Type. LGTM=bradfitz, khr R=khr, bradfitz CC=golang-codereviews https://golang.org/cl/145180043 --- src/runtime/proc.c | 2 +- src/runtime/runtime.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/runtime/runtime.h') diff --git a/src/runtime/proc.c b/src/runtime/proc.c index 860701ee58..3f4179d473 100644 --- a/src/runtime/proc.c +++ b/src/runtime/proc.c @@ -31,7 +31,7 @@ enum GoidCacheBatch = 16, }; -SchedType runtime·sched; +SchedT runtime·sched; int32 runtime·gomaxprocs; uint32 runtime·needextram; bool runtime·iscgo; diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 386b09b96b..7fefbc2997 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -60,7 +60,7 @@ typedef struct SudoG SudoG; typedef struct Mutex Mutex; typedef struct M M; typedef struct P P; -typedef struct SchedType SchedType; +typedef struct SchedT SchedT; typedef struct Note Note; typedef struct Slice Slice; typedef struct String String; @@ -434,7 +434,7 @@ enum { MaxGomaxprocs = 1<<8, }; -struct SchedType +struct SchedT { Mutex lock; @@ -753,7 +753,7 @@ extern DebugVars runtime·debug; extern uintptr runtime·maxstacksize; extern Note runtime·signote; extern ForceGCState runtime·forcegc; -extern SchedType runtime·sched; +extern SchedT runtime·sched; extern int32 runtime·newprocs; /* -- cgit v1.3 From 7283e08cbf06bcd32a391183e26080cff301e7f9 Mon Sep 17 00:00:00 2001 From: Hector Martin Cantero Date: Wed, 24 Sep 2014 13:20:25 -0400 Subject: runtime: keep g->syscallsp consistent after cgo->Go callbacks Normally, the caller to runtime.entersyscall() must not return before calling runtime.exitsyscall(), lest g->syscallsp become a dangling pointer. runtime.cgocallbackg() violates this constraint. To work around this, save g->syscallsp and g->syscallpc around cgo->Go callbacks, then restore them after calling runtime.entersyscall(), which restores the syscall stack frame pointer saved by cgocall. This allows the GC to correctly trace a goroutine that is currently returning from a Go->cgo->Go chain. This also adds a check to proc.c that panics if g->syscallsp is clearly invalid. It is not 100% foolproof, as it will not catch a case where the stack was popped then pushed back beyond g->syscallsp, but it does catch the present cgo issue and makes existing tests fail without the bugfix. Fixes #7978. LGTM=dvyukov, rsc R=golang-codereviews, dvyukov, minux, bradfitz, iant, gobot, rsc CC=golang-codereviews, rsc https://golang.org/cl/131910043 --- misc/cgo/test/cgo_test.go | 1 + misc/cgo/test/issue7978.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++ src/run.bash | 2 + src/run.bat | 7 ++++ src/runtime/cgocall.go | 12 +++++- src/runtime/proc.c | 39 ++++++++++++------ src/runtime/runtime.h | 1 + src/runtime/stubs.go | 1 + 8 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 misc/cgo/test/issue7978.go (limited to 'src/runtime/runtime.h') diff --git a/misc/cgo/test/cgo_test.go b/misc/cgo/test/cgo_test.go index 3783af061c..1899d46053 100644 --- a/misc/cgo/test/cgo_test.go +++ b/misc/cgo/test/cgo_test.go @@ -56,5 +56,6 @@ func TestNaming(t *testing.T) { testNaming(t) } func Test7560(t *testing.T) { test7560(t) } func Test5242(t *testing.T) { test5242(t) } func Test8092(t *testing.T) { test8092(t) } +func Test7978(t *testing.T) { test7978(t) } func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) } diff --git a/misc/cgo/test/issue7978.go b/misc/cgo/test/issue7978.go new file mode 100644 index 0000000000..39864476ce --- /dev/null +++ b/misc/cgo/test/issue7978.go @@ -0,0 +1,99 @@ +// Copyright 2014 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. + +// Issue 7978. Stack tracing didn't work during cgo code after calling a Go +// callback. Make sure GC works and the stack trace is correct. + +package cgotest + +/* +#include + +void issue7978cb(void); + +// use ugly atomic variable sync since that doesn't require calling back into +// Go code or OS dependencies +static void issue7978c(uint32_t *sync) { + while(__sync_fetch_and_add(sync, 0) != 0) + ; + __sync_fetch_and_add(sync, 1); + while(__sync_fetch_and_add(sync, 0) != 2) + ; + issue7978cb(); + __sync_fetch_and_add(sync, 1); + while(__sync_fetch_and_add(sync, 0) != 6) + ; +} +*/ +import "C" + +import ( + "runtime" + "strings" + "sync/atomic" + "testing" +) + +var issue7978sync uint32 + +func issue7978check(t *testing.T, wantFunc string, badFunc string, depth int) { + runtime.GC() + buf := make([]byte, 65536) + trace := string(buf[:runtime.Stack(buf, true)]) + for _, goroutine := range strings.Split(trace, "\n\n") { + if strings.Contains(goroutine, "test.issue7978go") { + trace := strings.Split(goroutine, "\n") + // look for the expected function in the stack + for i := 0; i < depth; i++ { + if badFunc != "" && strings.Contains(trace[1+2*i], badFunc) { + t.Errorf("bad stack: found %s in the stack:\n%s", badFunc, goroutine) + return + } + if strings.Contains(trace[1+2*i], wantFunc) { + return + } + } + t.Errorf("bad stack: didn't find %s in the stack:\n%s", wantFunc, goroutine) + return + } + } + t.Errorf("bad stack: goroutine not found. Full stack dump:\n%s", trace) +} + +func issue7978wait(store uint32, wait uint32) { + if store != 0 { + atomic.StoreUint32(&issue7978sync, store) + } + for atomic.LoadUint32(&issue7978sync) != wait { + runtime.Gosched() + } +} + +//export issue7978cb +func issue7978cb() { + issue7978wait(3, 4) +} + +func issue7978go() { + C.issue7978c((*C.uint32_t)(&issue7978sync)) + issue7978wait(7, 8) +} + +func test7978(t *testing.T) { + issue7978sync = 0 + go issue7978go() + // test in c code, before callback + issue7978wait(0, 1) + issue7978check(t, "runtime.cgocall_errno(", "", 1) + // test in go code, during callback + issue7978wait(2, 3) + issue7978check(t, "test.issue7978cb(", "test.issue7978go", 3) + // test in c code, after callback + issue7978wait(4, 5) + issue7978check(t, "runtime.cgocall_errno(", "runtime.cgocallback", 1) + // test in go code, after return from cgo + issue7978wait(6, 7) + issue7978check(t, "test.issue7978go(", "", 3) + atomic.StoreUint32(&issue7978sync, 8) +} diff --git a/src/run.bash b/src/run.bash index b5f061d885..d6e53304d8 100755 --- a/src/run.bash +++ b/src/run.bash @@ -119,6 +119,8 @@ go run $GOROOT/test/run.go - . || exit 1 [ "$CGO_ENABLED" != 1 ] || (xcd ../misc/cgo/test +# cgo tests inspect the traceback for runtime functions +export GOTRACEBACK=2 go test -ldflags '-linkmode=auto' || exit 1 # linkmode=internal fails on dragonfly since errno is a TLS relocation. [ "$GOHOSTOS" == dragonfly ] || go test -ldflags '-linkmode=internal' || exit 1 diff --git a/src/run.bat b/src/run.bat index 62692acaf2..309e06d507 100644 --- a/src/run.bat +++ b/src/run.bat @@ -90,11 +90,18 @@ go run "%GOROOT%\test\run.go" - ..\misc\cgo\stdio if errorlevel 1 goto fail echo. +# cgo tests inspect the traceback for runtime functions +set OLDGOTRACEBACK=%GOTRACEBACK% +set GOTRACEBACK=2 + echo # ..\misc\cgo\test go test ..\misc\cgo\test if errorlevel 1 goto fail echo. +set GOTRACEBACK=%OLDGOTRACEBACK% +set OLDGOTRACEBACK= + echo # ..\misc\cgo\testso cd ..\misc\cgo\testso set FAIL=0 diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go index a21474b01f..7fd91469eb 100644 --- a/src/runtime/cgocall.go +++ b/src/runtime/cgocall.go @@ -177,14 +177,22 @@ func cfree(p unsafe.Pointer) { // Call from C back to Go. //go:nosplit func cgocallbackg() { - if gp := getg(); gp != gp.m.curg { + gp := getg() + if gp != gp.m.curg { println("runtime: bad g in cgocallback") exit(2) } + // entersyscall saves the caller's SP to allow the GC to trace the Go + // stack. However, since we're returning to an earlier stack frame and + // need to pair with the entersyscall() call made by cgocall, we must + // save syscall* and let reentersyscall restore them. + savedsp := unsafe.Pointer(gp.syscallsp) + savedpc := gp.syscallpc exitsyscall() // coming out of cgo call cgocallbackg1() - entersyscall() // going back to cgo call + // going back to cgo call + reentersyscall(savedpc, savedsp) } func cgocallbackg1() { diff --git a/src/runtime/proc.c b/src/runtime/proc.c index 3f4179d473..564798be7b 100644 --- a/src/runtime/proc.c +++ b/src/runtime/proc.c @@ -1700,9 +1700,9 @@ goexit0(G *gp) #pragma textflag NOSPLIT static void -save(void *pc, uintptr sp) +save(uintptr pc, uintptr sp) { - g->sched.pc = (uintptr)pc; + g->sched.pc = pc; g->sched.sp = sp; g->sched.lr = 0; g->sched.ret = 0; @@ -1730,9 +1730,15 @@ static void entersyscall_gcwait(void); // In practice, this means that we make the fast path run through // entersyscall doing no-split things, and the slow path has to use onM // to run bigger things on the m stack. +// +// reentersyscall is the entry point used by cgo callbacks, where explicitly +// saved SP and PC are restored. This is needed when exitsyscall will be called +// from a function further up in the call stack than the parent, as g->syscallsp +// must always point to a valid stack frame. entersyscall below is the normal +// entry point for syscalls, which obtains the SP and PC from the caller. #pragma textflag NOSPLIT void -·entersyscall(int32 dummy) +runtime·reentersyscall(uintptr pc, uintptr sp) { void (*fn)(void); @@ -1748,9 +1754,9 @@ void g->throwsplit = 1; // Leave SP around for GC and traceback. - save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); - g->syscallsp = g->sched.sp; - g->syscallpc = g->sched.pc; + save(pc, sp); + g->syscallsp = sp; + g->syscallpc = pc; runtime·casgstatus(g, Grunning, Gsyscall); if(g->syscallsp < g->stack.lo || g->stack.hi < g->syscallsp) { fn = entersyscall_bad; @@ -1760,7 +1766,7 @@ void if(runtime·atomicload(&runtime·sched.sysmonwait)) { // TODO: fast atomic fn = entersyscall_sysmon; runtime·onM(&fn); - save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); + save(pc, sp); } g->m->mcache = nil; @@ -1769,7 +1775,7 @@ void if(runtime·sched.gcwaiting) { fn = entersyscall_gcwait; runtime·onM(&fn); - save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); + save(pc, sp); } // Goroutines must not split stacks in Gsyscall status (it would corrupt g->sched). @@ -1779,6 +1785,14 @@ void g->m->locks--; } +// Standard syscall entry used by the go syscall library and normal cgo calls. +#pragma textflag NOSPLIT +void +·entersyscall(int32 dummy) +{ + runtime·reentersyscall((uintptr)runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); +} + static void entersyscall_bad(void) { @@ -1826,7 +1840,7 @@ void g->stackguard0 = StackPreempt; // see comment in entersyscall // Leave SP around for GC and traceback. - save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); + save((uintptr)runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); g->syscallsp = g->sched.sp; g->syscallpc = g->sched.pc; runtime·casgstatus(g, Grunning, Gsyscall); @@ -1839,7 +1853,7 @@ void runtime·onM(&fn); // Resave for traceback during blocked call. - save(runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); + save((uintptr)runtime·getcallerpc(&dummy), runtime·getcallersp(&dummy)); g->m->locks--; } @@ -1856,12 +1870,15 @@ entersyscallblock_handoff(void) // from the low-level system calls used by the runtime. #pragma textflag NOSPLIT void -runtime·exitsyscall(void) +·exitsyscall(int32 dummy) { void (*fn)(G*); g->m->locks++; // see comment in entersyscall + if(runtime·getcallersp(&dummy) > g->syscallsp) + runtime·throw("exitsyscall: syscall frame is no longer valid"); + g->waitsince = 0; if(exitsyscallfast()) { // There's a cpu for us, so we can run. diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 7fefbc2997..3a6d3e3262 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -901,6 +901,7 @@ void runtime·goexit(void); void runtime·asmcgocall(void (*fn)(void*), void*); int32 runtime·asmcgocall_errno(void (*fn)(void*), void*); void runtime·entersyscall(void); +void runtime·reentersyscall(uintptr, uintptr); void runtime·entersyscallblock(void); void runtime·exitsyscall(void); G* runtime·newproc1(FuncVal*, byte*, int32, int32, void*); diff --git a/src/runtime/stubs.go b/src/runtime/stubs.go index 2e6aadca7a..1381c7efdb 100644 --- a/src/runtime/stubs.go +++ b/src/runtime/stubs.go @@ -164,6 +164,7 @@ func noescape(p unsafe.Pointer) unsafe.Pointer { } func entersyscall() +func reentersyscall(pc uintptr, sp unsafe.Pointer) func entersyscallblock() func exitsyscall() -- cgit v1.3 From 70b2da98ca097598326d5d01406b287bcd5eb6ee Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 29 Sep 2014 21:21:36 -0700 Subject: runtime: initialize traceback variables earlier Our traceback code needs to know the PC of several special functions, including goexit, mcall, etc. Make sure that these PCs are initialized before any traceback occurs. Fixes #8766 LGTM=rsc R=golang-codereviews, rsc, khr, bradfitz CC=golang-codereviews https://golang.org/cl/145570043 --- src/runtime/proc.c | 1 + src/runtime/runtime.h | 1 + src/runtime/traceback.go | 39 +++++++++++++++++++++++++++++---------- 3 files changed, 31 insertions(+), 10 deletions(-) (limited to 'src/runtime/runtime.h') diff --git a/src/runtime/proc.c b/src/runtime/proc.c index 1f0a79098b..e84dc1d048 100644 --- a/src/runtime/proc.c +++ b/src/runtime/proc.c @@ -131,6 +131,7 @@ runtime·schedinit(void) runtime·sched.maxmcount = 10000; + runtime·tracebackinit(); runtime·symtabinit(); runtime·stackinit(); runtime·mallocinit(); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 3a6d3e3262..aa300d7bb8 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -841,6 +841,7 @@ void runtime·mpreinit(M*); void runtime·minit(void); void runtime·unminit(void); void runtime·signalstack(byte*, int32); +void runtime·tracebackinit(void); void runtime·symtabinit(void); Func* runtime·findfunc(uintptr); int32 runtime·funcline(Func*, uintptr, String*); diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index a93c42186b..24dc3eea95 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -31,20 +31,36 @@ import "unsafe" const usesLR = GOARCH != "amd64" && GOARCH != "amd64p32" && GOARCH != "386" var ( - deferprocPC = funcPC(deferproc) - goexitPC = funcPC(goexit) - jmpdeferPC = funcPC(jmpdefer) - mcallPC = funcPC(mcall) - morestackPC = funcPC(morestack) - mstartPC = funcPC(mstart) - newprocPC = funcPC(newproc) - newstackPC = funcPC(newstack) - rt0_goPC = funcPC(rt0_go) - sigpanicPC = funcPC(sigpanic) + // initialized in tracebackinit + deferprocPC uintptr + goexitPC uintptr + jmpdeferPC uintptr + mcallPC uintptr + morestackPC uintptr + mstartPC uintptr + newprocPC uintptr + rt0_goPC uintptr + sigpanicPC uintptr externalthreadhandlerp uintptr // initialized elsewhere ) +func tracebackinit() { + // Go variable initialization happens late during runtime startup. + // Instead of initializing the variables above in the declarations, + // schedinit calls this function so that the variables are + // initialized and available earlier in the startup sequence. + deferprocPC = funcPC(deferproc) + goexitPC = funcPC(goexit) + jmpdeferPC = funcPC(jmpdefer) + mcallPC = funcPC(mcall) + morestackPC = funcPC(morestack) + mstartPC = funcPC(mstart) + newprocPC = funcPC(newproc) + rt0_goPC = funcPC(rt0_go) + sigpanicPC = funcPC(sigpanic) +} + // Traceback over the deferred function calls. // Report them like calls that have been invoked but not started executing yet. func tracebackdefers(gp *g, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer) { @@ -81,6 +97,9 @@ func tracebackdefers(gp *g, callback func(*stkframe, unsafe.Pointer) bool, v uns // collector (callback != nil). A little clunky to merge these, but avoids // duplicating the code and all its subtlety. func gentraceback(pc0 uintptr, sp0 uintptr, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, printall bool) int { + if goexitPC == 0 { + gothrow("gentraceback before goexitPC initialization") + } g := getg() gotraceback := gotraceback(nil) if pc0 == ^uintptr(0) && sp0 == ^uintptr(0) { // Signal to fetch saved values from gp. -- cgit v1.3