diff options
author | Keith Randall <khr@golang.org> | 2014-09-05 10:04:16 -0400 |
---|---|---|
committer | Keith Randall <khr@golang.org> | 2014-09-05 10:04:16 -0400 |
commit | 3f94b79d4827411c92a95d8e3c76049a3784e3e0 (patch) | |
tree | eac953b858b2d8f49e96e9853740e879663975b4 /src/pkg | |
parent | 1dba806d6a948976a2ecdb08a7c4839ff0a42766 (diff) | |
download | go-3f94b79d4827411c92a95d8e3c76049a3784e3e0.tar.gz |
runtime: convert panic/recover to Go
created panic1.go just so diffs were available.
After this CL is in, I'd like to move panic.go -> defer.go
and panic1.go -> panic.go.
LGTM=rsc
R=rsc, khr
CC=golang-codereviews
https://codereview.appspot.com/133530045
Committer: Russ Cox <rsc@golang.org>
Diffstat (limited to 'src/pkg')
-rw-r--r-- | src/pkg/runtime/cgo/callbacks.c | 2 | ||||
-rw-r--r-- | src/pkg/runtime/panic.c | 213 | ||||
-rw-r--r-- | src/pkg/runtime/panic1.go | 183 | ||||
-rw-r--r-- | src/pkg/runtime/runtime.h | 5 | ||||
-rw-r--r-- | src/pkg/runtime/stack.c | 24 | ||||
-rw-r--r-- | src/pkg/runtime/stack_test.go | 15 | ||||
-rw-r--r-- | src/pkg/runtime/stubs.go | 7 |
7 files changed, 242 insertions, 207 deletions
diff --git a/src/pkg/runtime/cgo/callbacks.c b/src/pkg/runtime/cgo/callbacks.c index 954a1cdcc..eb69255db 100644 --- a/src/pkg/runtime/cgo/callbacks.c +++ b/src/pkg/runtime/cgo/callbacks.c @@ -73,7 +73,7 @@ _cgo_panic_internal(byte *p) s = runtime·gostring(p); ·cgoStringToEface(s, &err); - runtime·panic(err); + runtime·gopanic(err); } #pragma cgo_export_static _cgo_panic diff --git a/src/pkg/runtime/panic.c b/src/pkg/runtime/panic.c index 152c9a3d5..e38ce740b 100644 --- a/src/pkg/runtime/panic.c +++ b/src/pkg/runtime/panic.c @@ -38,109 +38,11 @@ runtime·deferproc_m(void) { runtime·memmove(d->args, (void*)argp, siz); } -// Print all currently active panics. Used when crashing. -static void -printpanics(Panic *p) -{ - if(p->link) { - printpanics(p->link); - runtime·printf("\t"); - } - runtime·printf("panic: "); - runtime·printany(p->arg); - if(p->recovered) - runtime·printf(" [recovered]"); - runtime·printf("\n"); -} - -static void recovery(G*); -static void abortpanic(Panic*); -static FuncVal abortpanicV = { (void(*)(void))abortpanic }; - -// The implementation of the predeclared function panic. -void -runtime·panic(Eface e) -{ - Defer *d, dabort; - Panic p; - uintptr pc, argp; - void (*fn)(G*); - - runtime·memclr((byte*)&p, sizeof p); - p.arg = e; - p.link = g->panic; - p.stackbase = g->stackbase; - g->panic = &p; - - dabort.fn = &abortpanicV; - dabort.siz = sizeof(&p); - dabort.args[0] = &p; - dabort.argp = NoArgs; - dabort.special = true; - - for(;;) { - d = g->defer; - if(d == nil) - break; - // take defer off list in case of recursive panic - g->defer = d->link; - g->ispanic = true; // rock for runtime·newstack, where runtime·newstackcall ends up - argp = d->argp; - pc = d->pc; - - // The deferred function may cause another panic, - // so newstackcall may not return. Set up a defer - // to mark this panic aborted if that happens. - dabort.link = g->defer; - g->defer = &dabort; - p.defer = d; - - runtime·newstackcall(d->fn, (byte*)d->args, d->siz); - - // Newstackcall did not panic. Remove dabort. - if(g->defer != &dabort) - runtime·throw("bad defer entry in panic"); - g->defer = dabort.link; - - runtime·freedefer(d); - if(p.recovered) { - g->panic = p.link; - // Aborted panics are marked but remain on the g->panic list. - // Recovery will unwind the stack frames containing their Panic structs. - // Remove them from the list and free the associated defers. - while(g->panic && g->panic->aborted) { - runtime·freedefer(g->panic->defer); - g->panic = g->panic->link; - } - if(g->panic == nil) // must be done with signal - g->sig = 0; - // Pass information about recovering frame to recovery. - g->sigcode0 = (uintptr)argp; - g->sigcode1 = (uintptr)pc; - fn = recovery; - runtime·mcall(&fn); - runtime·throw("recovery failed"); // mcall should not return - } - } - - // ran out of deferred calls - old-school panic now - runtime·startpanic(); - printpanics(g->panic); - runtime·dopanic(0); // should not return - runtime·exit(1); // not reached -} - -static void -abortpanic(Panic *p) -{ - p->aborted = true; -} - // Unwind the stack after a deferred function calls recover // after a panic. Then arrange to continue running as though // the caller of the deferred function returned normally. -static void -recovery(G *gp) +void +runtime·recovery_m(G *gp) { void *argp; uintptr pc; @@ -199,40 +101,8 @@ runtime·unwindstack(G *gp, byte *sp) } } -// The implementation of the predeclared function recover. -// Cannot split the stack because it needs to reliably -// find the stack segment of its caller. -#pragma textflag NOSPLIT -void -runtime·recover(byte *argp, GoOutput retbase, ...) -{ - Panic *p; - Stktop *top; - Eface *ret; - - // Must be an unrecovered panic in progress. - // Must be on a stack segment created for a deferred call during a panic. - // Must be at the top of that segment, meaning the deferred call itself - // and not something it called. The top frame in the segment will have - // argument pointer argp == top - top->argsize. - // The subtraction of g->panicwrap allows wrapper functions that - // do not count as official calls to adjust what we consider the top frame - // while they are active on the stack. The linker emits adjustments of - // g->panicwrap in the prologue and epilogue of functions marked as wrappers. - ret = (Eface*)&retbase; - top = (Stktop*)g->stackbase; - p = g->panic; - if(p != nil && !p->recovered && top->panic && argp == (byte*)top - top->argsize - g->panicwrap) { - p->recovered = 1; - *ret = p->arg; - } else { - ret->type = nil; - ret->data = nil; - } -} - void -runtime·startpanic(void) +runtime·startpanic_m(void) { if(runtime·mheap.cachealloc.size == 0) { // very early runtime·printf("runtime: panic before malloc heap initialized\n"); @@ -273,28 +143,34 @@ runtime·startpanic(void) } void -runtime·dopanic(int32 unused) +runtime·dopanic_m(void) { + G *gp; + uintptr sp, pc; static bool didothers; bool crash; int32 t; - if(g->sig != 0) + gp = g->m->ptrarg[0]; + g->m->ptrarg[0] = nil; + pc = g->m->scalararg[0]; + sp = g->m->scalararg[1]; + if(gp->sig != 0) runtime·printf("[signal %x code=%p addr=%p pc=%p]\n", - g->sig, g->sigcode0, g->sigcode1, g->sigpc); + gp->sig, gp->sigcode0, gp->sigcode1, gp->sigpc); if((t = runtime·gotraceback(&crash)) > 0){ - if(g != g->m->g0) { + if(gp != gp->m->g0) { runtime·printf("\n"); - runtime·goroutineheader(g); - runtime·traceback((uintptr)runtime·getcallerpc(&unused), (uintptr)runtime·getcallersp(&unused), 0, g); + runtime·goroutineheader(gp); + runtime·traceback(pc, sp, 0, gp); } else if(t >= 2 || g->m->throwing > 0) { runtime·printf("\nruntime stack:\n"); - runtime·traceback((uintptr)runtime·getcallerpc(&unused), (uintptr)runtime·getcallersp(&unused), 0, g); + runtime·traceback(pc, sp, 0, gp); } if(!didothers) { didothers = true; - runtime·tracebackothers(g); + runtime·tracebackothers(gp); } } runtime·unlock(&paniclk); @@ -341,58 +217,3 @@ runtime·canpanic(G *gp) #endif return true; } - -void -runtime·throw(int8 *s) -{ - if(g->m->throwing == 0) - g->m->throwing = 1; - runtime·startpanic(); - runtime·printf("fatal error: %s\n", s); - runtime·dopanic(0); - *(int32*)0 = 0; // not reached - runtime·exit(1); // even more not reached -} - -void -runtime·gothrow(String s) -{ - if(g->m->throwing == 0) - g->m->throwing = 1; - runtime·startpanic(); - runtime·printf("fatal error: %S\n", s); - runtime·dopanic(0); - *(int32*)0 = 0; // not reached - runtime·exit(1); // even more not reached -} - -void -runtime·panicstring(int8 *s) -{ - Eface err; - - // 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. - if(g->m->softfloat) { - g->m->locks--; - g->m->softfloat = 0; - } - - if(g->m->mallocing) { - runtime·printf("panic: %s\n", s); - runtime·throw("panic during malloc"); - } - if(g->m->gcing) { - runtime·printf("panic: %s\n", s); - runtime·throw("panic during gc"); - } - if(g->m->locks) { - runtime·printf("panic: %s\n", s); - runtime·throw("panic holding locks"); - } - runtime·newErrorCString(s, &err); - runtime·panic(err); -} diff --git a/src/pkg/runtime/panic1.go b/src/pkg/runtime/panic1.go new file mode 100644 index 000000000..6d939703d --- /dev/null +++ b/src/pkg/runtime/panic1.go @@ -0,0 +1,183 @@ +// Copyright 2012 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. + +package runtime + +import "unsafe" + +// Print all currently active panics. Used when crashing. +func printpanics(p *_panic) { + if p.link != nil { + printpanics(p.link) + print("\t") + } + print("panic: ") + printany(p.arg) + if p.recovered { + print(" [recovered]") + } + print("\n") +} + +// The implementation of the predeclared function panic. +func gopanic(e interface{}) { + gp := getg() + if gp.m.curg != gp { + gothrow("panic on m stack") + } + var p _panic + var dabort _defer + p.arg = e + p.link = gp._panic + gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) + + fn := abortpanic + dabort.fn = *(**funcval)(unsafe.Pointer(&fn)) + dabort.siz = ptrSize + dabort.args[0] = noescape((unsafe.Pointer)(&p)) // TODO(khr): why do I need noescape here? + dabort.argp = _NoArgs + dabort.special = true + + for { + d := gp._defer + if d == nil { + break + } + // take defer off list in case of recursive panic + gp._defer = d.link + gp.ispanic = true // rock for runtime·newstack, where runtime·newstackcall ends up + argp := unsafe.Pointer(d.argp) // must be pointer so it gets adjusted during stack copy + pc := d.pc + + // The deferred function may cause another panic, + // so newstackcall may not return. Set up a defer + // to mark this panic aborted if that happens. + dabort.link = gp._defer + gp._defer = (*_defer)(noescape(unsafe.Pointer(&dabort))) + p._defer = d + + newstackcall(d.fn, unsafe.Pointer(&d.args), uint32(d.siz)) + + // Newstackcall did not panic. Remove dabort. + if gp._defer != &dabort { + gothrow("bad defer entry in panic") + } + gp._defer = dabort.link + + // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic + //GC() + + freedefer(d) + if p.recovered { + gp._panic = p.link + // Aborted panics are marked but remain on the g.panic list. + // Remove them from the list and free the associated defers. + for gp._panic != nil && gp._panic.aborted { + freedefer(gp._panic._defer) + gp._panic = gp._panic.link + } + if gp._panic == nil { // must be done with signal + gp.sig = 0 + } + // Pass information about recovering frame to recovery. + gp.sigcode0 = uintptr(argp) + gp.sigcode1 = pc + mcall(recovery_m) + gothrow("recovery failed") // mcall should not return + } + } + + // ran out of deferred calls - old-school panic now + startpanic() + printpanics(gp._panic) + dopanic(0) // should not return + *(*int)(nil) = 0 // not reached +} + +func abortpanic(p *_panic) { + p.aborted = true +} + +// The implementation of the predeclared function recover. +// Cannot split the stack because it needs to reliably +// find the stack segment of its caller. +//go:nosplit +func gorecover(argp uintptr) interface{} { + // Must be an unrecovered panic in progress. + // Must be on a stack segment created for a deferred call during a panic. + // Must be at the top of that segment, meaning the deferred call itself + // and not something it called. The top frame in the segment will have + // argument pointer argp == top - top.argsize. + // The subtraction of g.panicwrap allows wrapper functions that + // do not count as official calls to adjust what we consider the top frame + // while they are active on the stack. The linker emits adjustments of + // g.panicwrap in the prologue and epilogue of functions marked as wrappers. + gp := getg() + top := (*stktop)(unsafe.Pointer(gp.stackbase)) + p := gp._panic + if p != nil && !p.recovered && top._panic && argp == gp.stackbase-uintptr(top.argsize+gp.panicwrap) { + p.recovered = true + return p.arg + } + return nil +} + +func startpanic() { + onM(startpanic_m) +} + +func dopanic(unused int) { + gp := getg() + mp := acquirem() + mp.ptrarg[0] = unsafe.Pointer(gp) + mp.scalararg[0] = getcallerpc((unsafe.Pointer)(&unused)) + mp.scalararg[1] = getcallersp((unsafe.Pointer)(&unused)) + onM(dopanic_m) // should never return + *(*int)(nil) = 0 +} + +func throw(s *byte) { + gothrow(gostringnocopy(s)) +} + +func gothrow(s string) { + gp := getg() + if gp.m.throwing == 0 { + gp.m.throwing = 1 + } + startpanic() + print("fatal error: ", s, "\n") + 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/pkg/runtime/runtime.h b/src/pkg/runtime/runtime.h index 7007957b6..b3d1a9422 100644 --- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -656,15 +656,12 @@ struct Defer struct Panic { Eface arg; // argument to panic - uintptr stackbase; // g->stackbase in panic Panic* link; // link to earlier panic Defer* defer; // current executing defer bool recovered; // whether this panic is over bool aborted; // the panic was aborted }; -typedef struct XXX XXX; - /* * stack traces */ @@ -1020,7 +1017,7 @@ void runtime·printcomplex(Complex128); */ void runtime·newstackcall(FuncVal*, byte*, uint32); void reflect·call(FuncVal*, byte*, uint32, uint32); -void runtime·panic(Eface); +void runtime·gopanic(Eface); void runtime·panicindex(void); void runtime·panicslice(void); void runtime·panicdivide(void); diff --git a/src/pkg/runtime/stack.c b/src/pkg/runtime/stack.c index 3993a372e..f0861e408 100644 --- a/src/pkg/runtime/stack.c +++ b/src/pkg/runtime/stack.c @@ -714,8 +714,8 @@ adjustdefers(G *gp, AdjustInfo *adjinfo) if(adjinfo->oldstk <= (byte*)d && (byte*)d < adjinfo->oldbase) { // The Defer record is on the stack. Its fields will // get adjusted appropriately. - // This only happens for runtime.main now, but a compiler - // optimization could do more of this. + // This only happens for runtime.main and runtime.gopanic now, + // but a compiler optimization could do more of this. *dp = (Defer*)((byte*)d + adjinfo->delta); continue; } @@ -753,6 +753,25 @@ adjustdefers(G *gp, AdjustInfo *adjinfo) } static void +adjustpanics(G *gp, AdjustInfo *adjinfo) +{ + Panic *p; + + // only the topmost panic is on the current stack + p = gp->panic; + if(p == nil) + return; + if(p->link != nil) { + // only the topmost panic can be on the current stack + // (because panic runs defers on a new stack) + if(adjinfo->oldstk <= (byte*)p->link && (byte*)p->link < adjinfo->oldbase) + runtime·throw("two panics on one stack"); + } + if(adjinfo->oldstk <= (byte*)p && (byte*)p < adjinfo->oldbase) + gp->panic = (Panic*)((byte*)p + adjinfo->delta); +} + +static void adjustsudogs(G *gp, AdjustInfo *adjinfo) { SudoG *s; @@ -811,6 +830,7 @@ copystack(G *gp, uintptr nframes, uintptr newsize) // adjust other miscellaneous things that have pointers into stacks. adjustctxt(gp, &adjinfo); adjustdefers(gp, &adjinfo); + adjustpanics(gp, &adjinfo); adjustsudogs(gp, &adjinfo); // copy the stack (including Stktop) to the new location diff --git a/src/pkg/runtime/stack_test.go b/src/pkg/runtime/stack_test.go index a822d73db..7b9412af4 100644 --- a/src/pkg/runtime/stack_test.go +++ b/src/pkg/runtime/stack_test.go @@ -350,3 +350,18 @@ func TestStackAllOutput(t *testing.T) { t.Errorf("Stack output should begin with \"goroutine \"") } } + +func TestStackPanic(t *testing.T) { + // Test that stack copying copies panics correctly. This is difficult + // to test because it is very unlikely that the stack will be copied + // in the middle of gopanic. But it can happen. + // To make this test effective, edit panic.go:gopanic and uncomment + // the GC() call just before freedefer(d). + defer func() { + if x := recover(); x == nil { + t.Errorf("recover failed") + } + }() + useStack(32) + panic("test panic") +} diff --git a/src/pkg/runtime/stubs.go b/src/pkg/runtime/stubs.go index 8e67114f3..7ed4aaa55 100644 --- a/src/pkg/runtime/stubs.go +++ b/src/pkg/runtime/stubs.go @@ -83,6 +83,7 @@ func badonm() { // Call using mcall. func gosched_m(*g) func park_m(*g) +func recovery_m(*g) // More C functions that run on the M stack. // Call using onM. @@ -100,6 +101,8 @@ func setmaxthreads_m() func ready_m() func deferproc_m() func goexit_m() +func startpanic_m() +func dopanic_m() // memclr clears n bytes starting at ptr. // in memclr_*.s @@ -133,10 +136,6 @@ func memeq(a, b unsafe.Pointer, size uintptr) bool var nohashcode uintptr var noequalcode uintptr -// Go version of runtime.throw. -// in panic.c -func gothrow(s string) - // noescape hides a pointer from escape analysis. noescape is // the identity function but escape analysis doesn't think the // output depends on the input. noescape is inlined and currently |