aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/runtime/panic.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/runtime/panic.go')
-rw-r--r--libgo/go/runtime/panic.go167
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
+}