diff options
Diffstat (limited to 'src/runtime/panic.go')
| -rw-r--r-- | src/runtime/panic.go | 316 |
1 files changed, 296 insertions, 20 deletions
diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 5f33cd7c0c..291a660b3e 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -10,6 +10,19 @@ import ( "unsafe" ) +// We have two different ways of doing defers. The older way involves creating a +// defer record at the time that a defer statement is executing and adding it to a +// defer chain. This chain is inspected by the deferreturn call at all function +// exits in order to run the appropriate defer calls. A cheaper way (which we call +// open-coded defers) is used for functions in which no defer statements occur in +// loops. In that case, we simply store the defer function/arg information into +// specific stack slots at the point of each defer statement, as well as setting a +// bit in a bitmask. At each function exit, we add inline code to directly make +// the appropriate defer calls based on the bitmask and fn/arg information stored +// on the stack. During panic/Goexit processing, the appropriate defer calls are +// made using extra funcdata info that indicates the exact stack slots that +// contain the bitmask and defer fn/args. + // Check to make sure we can really generate a panic. If the panic // was generated from the runtime, or from inside malloc, then convert // to a throw of msg. @@ -263,19 +276,24 @@ func deferprocStack(d *_defer) { // are initialized here. d.started = false d.heap = false + d.openDefer = false d.sp = getcallersp() d.pc = getcallerpc() + d.framepc = 0 + d.varp = 0 // The lines below implement: // d.panic = nil + // d.fp = nil // d.link = gp._defer // gp._defer = d - // But without write barriers. The first two are writes to + // But without write barriers. The first three are writes to // the stack so they don't need a write barrier, and furthermore // are to uninitialized memory, so they must not use a write barrier. - // The third write does not require a write barrier because we + // The fourth write does not require a write barrier because we // explicitly mark all the defer structures, so we don't need to // keep track of pointers to them with a write barrier. *(*uintptr)(unsafe.Pointer(&d._panic)) = 0 + *(*uintptr)(unsafe.Pointer(&d.fd)) = 0 *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer)) *(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d)) @@ -463,8 +481,12 @@ func freedefer(d *_defer) { // started causing a nosplit stack overflow via typedmemmove. d.siz = 0 d.started = false + d.openDefer = false d.sp = 0 d.pc = 0 + d.framepc = 0 + d.varp = 0 + d.fd = nil // d._panic and d.fn must be nil already. // If not, we would have called freedeferpanic or freedeferfn above, // both of which throw. @@ -493,9 +515,11 @@ func freedeferfn() { // to have been called by the caller of deferreturn at the point // just before deferreturn was called. The effect is that deferreturn // is called again and again until there are no more deferred functions. -// Cannot split the stack because we reuse the caller's frame to -// call the deferred function. - +// +// Declared as nosplit, because the function should not be preempted once we start +// modifying the caller's frame in order to reuse the frame to call the deferred +// function. +// // The single argument isn't actually used - it just has its address // taken so it can be matched against pending defers. //go:nosplit @@ -509,6 +533,15 @@ func deferreturn(arg0 uintptr) { if d.sp != sp { return } + if d.openDefer { + done := runOpenDeferFrame(gp, d) + if !done { + throw("unfinished open-coded defers in deferreturn") + } + gp._defer = d.link + freedefer(d) + return + } // Moving arguments around. // @@ -544,6 +577,8 @@ func Goexit() { // This code is similar to gopanic, see that implementation // for detailed comments. gp := getg() + addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp())) + for { d := gp._defer if d == nil { @@ -554,13 +589,26 @@ func Goexit() { d._panic.aborted = true d._panic = nil } - d.fn = nil - gp._defer = d.link - freedefer(d) - continue + if !d.openDefer { + d.fn = nil + gp._defer = d.link + freedefer(d) + continue + } } d.started = true - reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) + if d.openDefer { + done := runOpenDeferFrame(gp, d) + if !done { + // We should always run all defers in the frame, + // since there is no panic associated with this + // defer that can be recovered. + throw("unfinished open-coded defers in Goexit") + } + addOneOpenDeferFrame(gp, 0, nil) + } else { + reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) + } if gp._defer != d { throw("bad defer entry in Goexit") } @@ -607,6 +655,182 @@ func printpanics(p *_panic) { print("\n") } +// addOneOpenDeferFrame scans the stack for the first frame (if any) with +// open-coded defers and if it finds one, adds a single record to the defer chain +// for that frame. If sp is non-nil, it starts the stack scan from the frame +// specified by sp. If sp is nil, it uses the sp from the current defer record +// (which has just been finished). Hence, it continues the stack scan from the +// frame of the defer that just finished. It skips any frame that already has an +// open-coded _defer record, which would have been been created from a previous +// (unrecovered) panic. +// +// Note: All entries of the defer chain (including this new open-coded entry) have +// their pointers (including sp) adjusted properly if the stack moves while +// running deferred functions. Also, it is safe to pass in the sp arg (which is +// the direct result of calling getcallersp()), because all pointer variables +// (including arguments) are adjusted as needed during stack copies. +func addOneOpenDeferFrame(gp *g, pc uintptr, sp unsafe.Pointer) { + var prevDefer *_defer + if sp == nil { + prevDefer = gp._defer + pc = prevDefer.framepc + sp = unsafe.Pointer(prevDefer.sp) + } + systemstack(func() { + gentraceback(pc, uintptr(sp), 0, gp, 0, nil, 0x7fffffff, + func(frame *stkframe, unused unsafe.Pointer) bool { + if prevDefer != nil && prevDefer.sp == frame.sp { + // Skip the frame for the previous defer that + // we just finished (and was used to set + // where we restarted the stack scan) + return true + } + f := frame.fn + fd := funcdata(f, _FUNCDATA_OpenCodedDeferInfo) + if fd == nil { + return true + } + // Insert the open defer record in the + // chain, in order sorted by sp. + d := gp._defer + var prev *_defer + for d != nil { + dsp := d.sp + if frame.sp < dsp { + break + } + if frame.sp == dsp { + if !d.openDefer { + throw("duplicated defer entry") + } + return true + } + prev = d + d = d.link + } + if frame.fn.deferreturn == 0 { + throw("missing deferreturn") + } + + maxargsize, _ := readvarintUnsafe(fd) + d1 := newdefer(int32(maxargsize)) + d1.openDefer = true + d1._panic = nil + // These are the pc/sp to set after we've + // run a defer in this frame that did a + // recover. We return to a special + // deferreturn that runs any remaining + // defers and then returns from the + // function. + d1.pc = frame.fn.entry + uintptr(frame.fn.deferreturn) + d1.varp = frame.varp + d1.fd = fd + // Save the SP/PC associated with current frame, + // so we can continue stack trace later if needed. + d1.framepc = frame.pc + d1.sp = frame.sp + d1.link = d + if prev == nil { + gp._defer = d1 + } else { + prev.link = d1 + } + // Stop stack scanning after adding one open defer record + return false + }, + nil, 0) + }) +} + +// readvarintUnsafe reads the uint32 in varint format starting at fd, and returns the +// uint32 and a pointer to the byte following the varint. +// +// There is a similar function runtime.readvarint, which takes a slice of bytes, +// rather than an unsafe pointer. These functions are duplicated, because one of +// the two use cases for the functions would get slower if the functions were +// combined. +func readvarintUnsafe(fd unsafe.Pointer) (uint32, unsafe.Pointer) { + var r uint32 + var shift int + for { + b := *(*uint8)((unsafe.Pointer(fd))) + fd = add(fd, unsafe.Sizeof(b)) + if b < 128 { + return r + uint32(b)<<shift, fd + } + r += ((uint32(b) &^ 128) << shift) + shift += 7 + if shift > 28 { + panic("Bad varint") + } + } +} + +// runOpenDeferFrame runs the active open-coded defers in the frame specified by +// d. It normally processes all active defers in the frame, but stops immediately +// if a defer does a successful recover. It returns true if there are no +// remaining defers to run in the frame. +func runOpenDeferFrame(gp *g, d *_defer) bool { + done := true + fd := d.fd + + // Skip the maxargsize + _, fd = readvarintUnsafe(fd) + deferBitsOffset, fd := readvarintUnsafe(fd) + nDefers, fd := readvarintUnsafe(fd) + deferBits := *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) + + for i := int(nDefers) - 1; i >= 0; i-- { + // read the funcdata info for this defer + var argWidth, closureOffset, hasRcvrOffset, rcvrOffset, nArgs uint32 + argWidth, fd = readvarintUnsafe(fd) + closureOffset, fd = readvarintUnsafe(fd) + hasRcvrOffset, fd = readvarintUnsafe(fd) + if hasRcvrOffset > 0 { + rcvrOffset, fd = readvarintUnsafe(fd) + } + nArgs, fd = readvarintUnsafe(fd) + if deferBits&(1<<i) == 0 { + for j := uint32(0); j < nArgs; j++ { + _, fd = readvarintUnsafe(fd) + _, fd = readvarintUnsafe(fd) + _, fd = readvarintUnsafe(fd) + } + continue + } + closure := *(**funcval)(unsafe.Pointer(d.varp - uintptr(closureOffset))) + d.fn = closure + deferArgs := deferArgs(d) + if hasRcvrOffset > 0 { + *(*unsafe.Pointer)(deferArgs) = *(*unsafe.Pointer)((unsafe.Pointer)((d.varp - uintptr(rcvrOffset)))) + } + for j := uint32(0); j < nArgs; j++ { + var argOffset, argLen, argCallOffset uint32 + argOffset, fd = readvarintUnsafe(fd) + argLen, fd = readvarintUnsafe(fd) + argCallOffset, fd = readvarintUnsafe(fd) + memmove(unsafe.Pointer(uintptr(deferArgs)+uintptr(argCallOffset)), + unsafe.Pointer(d.varp-uintptr(argOffset)), + uintptr(argLen)) + } + deferBits = deferBits &^ (1 << i) + *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) = deferBits + if d._panic != nil { + d._panic.argp = unsafe.Pointer(getargp(0)) + } + reflectcall(nil, unsafe.Pointer(closure), deferArgs, argWidth, argWidth) + d.fn = nil + // These args are just a copy, so can be cleared immediately + memclrNoHeapPointers(deferArgs, uintptr(argWidth)) + if d._panic != nil && d._panic.recovered { + done = deferBits == 0 + break + } + } + + return done +} + // The implementation of the predeclared function panic. func gopanic(e interface{}) { gp := getg() @@ -646,6 +870,10 @@ func gopanic(e interface{}) { atomic.Xadd(&runningPanicDefers, 1) + // By calculating getcallerpc/getcallersp here, we avoid scanning the + // gopanic frame (stack scanning is slow...) + addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp())) + for { d := gp._defer if d == nil { @@ -659,10 +887,16 @@ func gopanic(e interface{}) { d._panic.aborted = true } d._panic = nil - d.fn = nil - gp._defer = d.link - freedefer(d) - continue + if !d.openDefer { + // For open-coded defers, we need to process the + // defer again, in case there are any other defers + // to call in the frame (not including the defer + // call that caused the panic). + d.fn = nil + gp._defer = d.link + freedefer(d) + continue + } } // Mark defer as started, but keep on list, so that traceback @@ -675,8 +909,16 @@ func gopanic(e interface{}) { // will find d in the list and will mark d._panic (this panic) aborted. d._panic = (*_panic)(noescape(unsafe.Pointer(&p))) - p.argp = unsafe.Pointer(getargp(0)) - reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) + done := true + if d.openDefer { + done = runOpenDeferFrame(gp, d) + if done && !d._panic.recovered { + addOneOpenDeferFrame(gp, 0, nil) + } + } else { + p.argp = unsafe.Pointer(getargp(0)) + reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) + } p.argp = nil // reflectcall did not panic. Remove d. @@ -684,18 +926,52 @@ func gopanic(e interface{}) { throw("bad defer entry in panic") } d._panic = nil - d.fn = nil - gp._defer = d.link // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic //GC() pc := d.pc sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy - freedefer(d) + if done { + d.fn = nil + gp._defer = d.link + freedefer(d) + } if p.recovered { atomic.Xadd(&runningPanicDefers, -1) + if done { + // Remove any remaining non-started, open-coded defer + // entry after a recover (there's at most one, if we just + // ran a non-open-coded defer), since the entry will + // become out-dated and the defer will be executed + // normally. + d := gp._defer + var prev *_defer + for d != nil { + if d.openDefer { + if d.started { + // This defer is started but we + // are in the middle of a + // defer-panic-recover inside of + // it, so don't remove it or any + // further defer entries + break + } + if prev == nil { + gp._defer = d.link + } else { + prev.link = d.link + } + freedefer(d) + break + } else { + prev = d + d = d.link + } + } + } + gp._panic = p.link // Aborted panics are marked but remain on the g.panic list. // Remove them from the list. @@ -803,7 +1079,7 @@ func recovery(gp *g) { } // Make the deferproc for this d return again, - // this time returning 1. The calling function will + // this time returning 1. The calling function will // jump to the standard return epilogue. gp.sched.sp = sp gp.sched.pc = pc |
