diff options
Diffstat (limited to 'libgo/go/runtime/panic.go')
-rw-r--r-- | libgo/go/runtime/panic.go | 167 |
1 files changed, 139 insertions, 28 deletions
diff --git a/libgo/go/runtime/panic.go b/libgo/go/runtime/panic.go index 6b490b7..752bf71 100644 --- a/libgo/go/runtime/panic.go +++ b/libgo/go/runtime/panic.go @@ -39,7 +39,24 @@ func panicCheckMalloc(err error) { var indexError = error(errorString("index out of range")) +// The panicindex, panicslice, and panicdivide functions are called by +// code generated by the compiler for out of bounds index expressions, +// out of bounds slice expressions, and division by zero. The +// panicdivide (again), panicoverflow, panicfloat, and panicmem +// functions are called by the signal handler when a signal occurs +// indicating the respective problem. +// +// Since panicindex and panicslice are never called directly, and +// since the runtime package should never have an out of bounds slice +// or array reference, if we see those functions called from the +// runtime package we turn the panic into a throw. That will dump the +// entire runtime stack for easier debugging. + func panicindex() { + name, _, _ := funcfileline(getcallerpc(), -1) + if hasprefix(name, "runtime.") { + throw(string(indexError.(errorString))) + } panicCheckMalloc(indexError) panic(indexError) } @@ -47,6 +64,10 @@ func panicindex() { var sliceError = error(errorString("slice bounds out of range")) func panicslice() { + name, _, _ := funcfileline(getcallerpc(), -1) + if hasprefix(name, "runtime.") { + throw(string(sliceError.(errorString))) + } panicCheckMalloc(sliceError) panic(sliceError) } @@ -144,6 +165,12 @@ func newdefer() *_defer { // //go:nosplit func freedefer(d *_defer) { + if d._panic != nil { + freedeferpanic() + } + if d.pfn != 0 { + freedeferfn() + } pp := getg().m.p.ptr() if len(pp.deferpool) == cap(pp.deferpool) { // Transfer half of local cache to the central cache. @@ -176,15 +203,28 @@ func freedefer(d *_defer) { d.link = nil d.frame = nil d.panicStack = nil - d._panic = nil - d.pfn = 0 d.arg = nil d.retaddr = 0 d.makefunccanrecover = false + // d._panic and d.pfn must be nil already. + // If not, we would have called freedeferpanic or freedeferfn above, + // both of which throw. pp.deferpool = append(pp.deferpool, d) } +// Separate function so that it can split stack. +// Windows otherwise runs out of stack space. +func freedeferpanic() { + // _panic must be cleared before d is unlinked from gp. + throw("freedefer with d._panic != nil") +} + +func freedeferfn() { + // fn must be cleared before d is unlinked from gp. + throw("freedefer with d.fn != nil") +} + // deferreturn is called to undefer the stack. // The compiler inserts a call to this function as a finally clause // wrapped around the body of any function that calls defer. @@ -544,15 +584,9 @@ func gopanic(e interface{}) { // the world, we call preprintpanics to invoke all necessary Error // and String methods to prepare the panic strings before startpanic. preprintpanics(gp._panic) - startpanic() - - // startpanic set panicking, which will block main from exiting, - // so now OK to decrement runningPanicDefers. - atomic.Xadd(&runningPanicDefers, -1) - printpanics(gp._panic) - dopanic(0) // should not return - *(*int)(nil) = 0 // not reached + fatalpanic(gp._panic) // should not return + *(*int)(nil) = 0 // not reached } // currentDefer returns the top of the defer stack if it can be recovered. @@ -810,13 +844,16 @@ func sync_throw(s string) { //go:nosplit func throw(s string) { - print("fatal error: ", s, "\n") + // Everything throw does should be recursively nosplit so it + // can be called even when it's unsafe to grow the stack. + systemstack(func() { + print("fatal error: ", s, "\n") + }) gp := getg() if gp.m.throwing == 0 { gp.m.throwing = 1 } - startpanic() - dopanic(0) + fatalthrow() *(*int)(nil) = 0 // not reached } @@ -833,13 +870,76 @@ var panicking uint32 // so that two concurrent panics don't overlap their output. var paniclk mutex +// fatalthrow implements an unrecoverable runtime throw. It freezes the +// system, prints stack traces starting from its caller, and terminates the +// process. +// +//go:nosplit +func fatalthrow() { + pc := getcallerpc() + sp := getcallersp() + gp := getg() + + startpanic_m() + + if dopanic_m(gp, pc, sp) { + crash() + } + + exit(2) + + *(*int)(nil) = 0 // not reached +} + +// fatalpanic implements an unrecoverable panic. It is like fatalthrow, except +// that if msgs != nil, fatalpanic also prints panic messages and decrements +// runningPanicDefers once main is blocked from exiting. +// +//go:nosplit +func fatalpanic(msgs *_panic) { + pc := getcallerpc() + sp := getcallersp() + gp := getg() + var docrash bool + + if startpanic_m() && msgs != nil { + // There were panic messages and startpanic_m + // says it's okay to try to print them. + + // startpanic_m set panicking, which will + // block main from exiting, so now OK to + // decrement runningPanicDefers. + atomic.Xadd(&runningPanicDefers, -1) + + printpanics(msgs) + } + + docrash = dopanic_m(gp, pc, sp) + + if docrash { + // By crashing outside the above systemstack call, debuggers + // will not be confused when generating a backtrace. + // Function crash is marked nosplit to avoid stack growth. + crash() + } + + systemstack(func() { + exit(2) + }) + + *(*int)(nil) = 0 // not reached +} + // startpanic_m prepares for an unrecoverable panic. // +// It returns true if panic messages should be printed, or false if +// the runtime is in bad shape and should just print stacks. +// // It can have write barriers because the write barrier explicitly // ignores writes once dying > 0. // //go:yeswritebarrierrec -func startpanic() { +func startpanic_m() bool { _g_ := getg() if mheap_.cachealloc.size == 0 { // very early print("runtime: panic before malloc heap initialized\n") @@ -850,6 +950,12 @@ func startpanic() { // happen (even if we're not in one of these situations). _g_.m.mallocing++ + // If we're dying because of a bad lock count, set it to a + // good lock count so we don't recursively panic below. + if _g_.m.locks < 0 { + _g_.m.locks = 1 + } + switch _g_.m.dying { case 0: _g_.m.dying = 1 @@ -860,15 +966,13 @@ func startpanic() { schedtrace(true) } freezetheworld() - return + return true case 1: - // Something failed while panicking, probably the print of the - // argument to panic(). Just print a stack trace and exit. + // Something failed while panicking. + // Just print a stack trace and exit. _g_.m.dying = 2 print("panic during panic\n") - dopanic(0) - exit(3) - fallthrough + return false case 2: // This is a genuine bug in the runtime, we couldn't even // print the stack trace successfully. @@ -879,14 +983,14 @@ func startpanic() { default: // Can't even print! Just exit. exit(5) + return false // Need to return something. } } var didothers bool var deadlock mutex -func dopanic(unused int) { - gp := getg() +func dopanic_m(gp *g, pc, sp uintptr) bool { if gp.sig != 0 { signame := signame(gp.sig) if signame != "" { @@ -927,11 +1031,7 @@ func dopanic(unused int) { lock(&deadlock) } - if docrash { - crash() - } - - exit(2) + return docrash } // canpanic returns false if a signal should throw instead of @@ -951,7 +1051,7 @@ func canpanic(gp *g) bool { if gp == nil || gp != _m_.curg { return false } - if _m_.locks-_m_.softfloat != 0 || _m_.mallocing != 0 || _m_.throwing != 0 || _m_.preemptoff != "" || _m_.dying != 0 { + if _m_.locks != 0 || _m_.mallocing != 0 || _m_.throwing != 0 || _m_.preemptoff != "" || _m_.dying != 0 { return false } status := readgstatus(gp) @@ -960,3 +1060,14 @@ func canpanic(gp *g) bool { } return true } + +// isAbortPC returns true if pc is the program counter at which +// runtime.abort raises a signal. +// +// It is nosplit because it's part of the isgoexception +// implementation. +// +//go:nosplit +func isAbortPC(pc uintptr) bool { + return false +} |