返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

6.2 恐慌

发布于 2024-10-12 19:16:00 字数 6663 浏览 0 评论 0 收藏 0

编译器将 panic 翻译成 gopanic 调用。和 defer 类似,panic 也会保存到 G 里。

// runtime2.go

type g struct {
	_panic    *_panic
}

// A _panic holds information about an active panic.
type _panic struct {
	argp      unsafe.Pointer // pointer to arguments of deferred call run during panic;
	arg       any            // argument to panic
	link      *_panic        // link to earlier panic
	pc        uintptr        // where to return to in runtime if this panic is bypassed
	sp        unsafe.Pointer // where to return to in runtime if this panic is bypassed
	recovered bool           // whether this panic is over
	aborted   bool           // the panic was aborted
	goexit    bool
}

引发恐慌前,需确保 G 所有已注册(整个调用堆栈,非当前函数)延迟函数得以执行。

仅一个 panic 抛出,因为它导致进程崩溃。

// panic.go

// The implementation of the predeclared function panic.
func gopanic(e any) {
    
	gp := getg()
    
    // 新建,添加到 G._panic 链表头部。
	var p _panic
	p.arg = e
	p.link = gp._panic
	gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

	atomic.Xadd(&runningPanicDefers, 1)

    // 遍历执行 _defer。
	for {
		d := gp._defer
		if d == nil {
			break
		}

		// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
		// take defer off list. An earlier panic will not continue running, but we will make sure below that an
		// earlier Goexit does continue running.
        
        // 延迟函数(d)可再次引发 panic,那么 gopanic 会 “递归嵌套”。
        // 在内层看来,如果当前 d 已被外层 gopanic 或 goexit 启动,跳过,从链表移除。
        // 内层处理掉链表上剩余的 _defer,且不会返回(not return)外层。
        // 结果要么崩溃(fatalpanic),要么 recover 回到 caller 函数。
        
        // 如果当前 _defer 已执行,清理掉。
		if d.started {
			if d._panic != nil {
				d._panic.aborted = true
			}
			d._panic = nil
            
            // 释放当前 defer,继续下一个。
			if !d.openDefer {
				d.fn = nil
				gp._defer = d.link
				freedefer(d)
                
				continue
			}
		}

        // 执行当前 _defer,并绑定 _panic。
		d.started = true
		d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

		done := true
        
		p.argp = unsafe.Pointer(getargp())
		d.fn()
		p.argp = nil
		d._panic = nil

        // 创建 _defer 时保存的 caller 状态。
		pc := d.pc
		sp := unsafe.Pointer(d.sp)
        
        // 丛 G._defer 链表删除。
		if done {
			d.fn = nil
			gp._defer = d.link
			freedefer(d)
		}
        
        // 如果延迟调用内执行了 recover 函数。
        // 那么 gopainc 执行到此结束,由 recover 恢复 caller 调用状态。
        // 剩余 _defer 就交给 caller deferreturn 了。
		if p.recovered {
            
            // 本次 panic 被捕获,从链表移除。
			gp._panic = p.link
            
			atomic.Xadd(&runningPanicDefers, -1)

			d := gp._defer
			var prev *_defer
			if !done {
				// Skip our current frame, if not done. It is
				// needed to complete any remaining defers in
				// deferreturn()
				prev = d
				d = d.link
			}
			for d != nil {
				if d.started { break }
				prev = d
				d = d.link
			}

            // 将放弃的 panic 从链表移除。
			gp._panic = p.link
			for gp._panic != nil && gp._panic.aborted {
				gp._panic = gp._panic.link
			}
            
            // 调用 recovery 恢复执行,当前 gopanic 不再返回。
			gp.sigcode0 = uintptr(sp)
			gp.sigcode1 = pc
			mcall(recovery)
            
			throw("recovery failed") // mcall should not return
		}
	}

    // 输出错误信息,进程崩溃。
	preprintpanics(gp._panic)
	fatalpanic(gp._panic) // should not return
    
	*(*int)(nil) = 0      // not reached
}

进程崩溃前,不会等待其他 G,不会执行其他 G._defer。恐慌代表 “不可修复错误”,如等待其他 G,可能永远无法终止进程,这就违背设计初衷。

func fatalpanic(msgs *_panic) {
	pc := getcallerpc()
	sp := getcallersp()
	gp := getg()
    
	var docrash bool
	systemstack(func() {
		if startpanic_m() && msgs != nil {
			printpanics(msgs)
		}

		docrash = dopanic_m(gp, pc, sp)
	})

	if docrash {
		crash()
	}

	systemstack(func() {
		exit(2)
	})

	*(*int)(nil) = 0 // not reached
}

恢复

在延迟函数内调用 recover,被编译器转换为 gorecover 调用。

仅标记(recovered),由 gopanic 调用 recovery 处理。

参数 argp 由调用 recover 的延迟函数传入,指向该 defer.caller.SP

// panic.go

// The implementation of the predeclared function recover.
func gorecover(argp uintptr) any {
    
	// Must be in a function running as part of a deferred call during the panic.
	// Must be called from the topmost function of the call
	// (the function used in the defer statement).
	// p.argp is the argument pointer of that topmost deferred function call.
	// Compare against argp reported by caller.
	// If they match, the caller is the one who can recover.
    
	gp := getg()
	p := gp._panic
    
    // 通过比对 caller.SP 地址,确保在 topmost 内调用。
	if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
		p.recovered = true
		return p.arg
	}
    
	return nil
}

执行逻辑

  • 首先,gopanic 执行 G._defer 函数。
  • 接着,_defer 调用 gorecover 设置 recovered = true 标记。
  • 最后,recovery 获取 _defer.caller 状态,恢复执行 caller "call deferproc" 下一指令。
// panic.go

// 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.

func recovery(gp *g) {
    
    // sigcode 由 gopanic 设置。
    // call deferproc 创建 _defer 时所保存 caller 状态,
	sp := gp.sigcode0
	pc := gp.sigcode1

	// Make the deferproc for this d return again,
	// this time returning 1. The calling function will
	// jump to the standard return epilogue.
    
    // 恢复到 call deferproc 下一条指令继续执行。
	gp.sched.sp = sp
	gp.sched.pc = pc
	gp.sched.lr = 0
	gp.sched.ret = 1  // !!!
	gogo(&gp.sched)
}

执行函数 gogo 将 ret 值放入 AX 寄存器。编译器会在 "call deferproc" 下插入检查和跳转指令,调用尾部的 deferreturn 执行剩余延迟函数。

// asm_amd64.s

TEXT gogo<>(SB), NOSPLIT, $0
get_tls(CX)
    MOVQ	DX, g(CX)
    MOVQ	DX, R14		        // set the g register
    
    MOVQ	gobuf_sp(BX), SP	// restore SP
    MOVQ	gobuf_ret(BX), AX   // !!!!
    MOVQ	gobuf_ctxt(BX), DX
    MOVQ	gobuf_bp(BX), BP
    
    MOVQ	gobuf_pc(BX), BX
	JMP	    BX
    00037 	CALL	runtime.deferprocStack(SB)
	00042 	TESTL	AX, AX    // if AX == 0 then ZF = 1 else ZF = 0.
	00044 	JNE		72        // if ZF == 0 then jmp.

	00046 	JMP		48
	00048 	CALL	"".test(SB)
	00053 	...

	00072 	CALL	runtime.deferreturn(SB)

而 deferproc 正常返回 0,不会影响 TEST 和跳转指令。

// panic.go

func deferproc(fn func()) {

	// deferproc returns 0 normally.
	// a deferred func that stops a panic
	// makes the deferproc return 1.
	// the code the compiler generates always
	// checks the return value and jumps to the
	// end of the function if deferproc returns != 0.
    
	return0()
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文