1. 只有在 defer 函数中调用 recover() 函数才有效,因为发生 panic 之后只有 defer 函数能够得到执行。
  2. Go 语言在设计上保证所有的 defer 函数都能够得到调用,所以适合用 defer 来释放资源,即使发生 panic 也不会造成资源泄露。

type _panic struct

  1. 该结构体存储着panic的相关信息。
  2. 文件位置:go1.19.3/src/runtime/runtime2.go。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// A _panic holds information about an active panic.
//
// A _panic value must only ever live on the stack.
//
// The argp and link fields are stack pointers, but don't need special
// handling during stack growth: because they are pointer-typed and
// _panic values only live on the stack, regular stack pointer
// adjustment takes care of them.
type _panic struct {
    // argp 设置为当前gopanic()函数栈帧上args to callee区间的起始地址。
    // 主要作用:用于defer()函数执行时判断recover()函数所在范围是否能生效。
    argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
    
    // 则是panic函数自己的参数,也就是【panic(v any)】这里的空接口【v】的参数。
    // 主要作用:结束时用于打印错误信息。
    arg       any            // argument to panic
    
    // 通过link链接到前一个注册的panic形成链表,
    // 解释:新增的panic总是从左边插入
    link      *_panic        // link to earlier panic	g._painc
    
    // pc 来自 defer.pc 拷贝的值
    // 主要作用:用于当前panic被恢复时要执行的下一条指令地址及IP寄存器的值
    pc        uintptr        // where to return to in runtime if this panic is bypassed	
    
    // sp 来自 defer.sp 拷贝的值
    // 主要作用:用于当前panic被恢复时要恢复的栈顶SP寄存器的值
    sp        unsafe.Pointer // where to return to in runtime if this panic is bypassed 

    // 表示当前panic已经被某个defer函数通过recover恢复。【已恢复】
    // 解释:该值在recover()函数中被标记
    recovered bool           // whether this panic is over
    
    // 表示发生了嵌套的panic,旧的panic被新的panic流程标记为aborted。【已中止】
    // 解释:就是发生了嵌套panic了。
    aborted   bool           // the panic was aborted
    
    // 是否执行runtime.Goexit()函数,runtime.Goexit()函数是
    // goroutine执行完后跳转到的清理首尾工作的函数
    // goexit字段在,runtime.Goexit()函数中被标记为true。
    goexit    bool
}

panic

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.
func panic(v any)

gopanic()

  1. gopanic() 也就是 panic() 发生时调用的函数。
  2. gopanic() 函数中关键的 open coded defer 部分:
    1. 在 for 循环开始之前,先通过 addOneOpenDeferFrame() 函数将最近的一个 open coded defer 栈帧添加到 _defer 链表中。
    2. 在调用 defer 函数的时候,如果 openDefer 为 true ,则使用 runOpenDeferFrame() 函数来执行,通过返回值来判断目标栈帧上的 open coded defer 已经完全执行,并且没有 recover,就再次调用 addOneOpenDeferFrame() 函数把下一个 open coded defer 栈帧添加到 _defer 链表。
    3. 根据 runOpenDeferFrame() 函数的返回值来判断,只有完全执行的节点才能从 _defer 链表中移除。事实上只有 openDefer 节点才有可能出现不完全执行的情况,因为一个栈帧上可能有多个 open coded defer 函数,假如其中某一个函数调用了 recover() 函数,后续的就不会再被调用了,所以该节点不能从 _defer 链表中移除,recover 之后的逻辑负责调用这些剩余的 open coded defer。
    4. 检查到当前 panic 的 recover 为 true 后,需要把 _defer 链表中尚未开始执行的 openDefer 节点移除,因为 recover 之后这些 open coded defer 会被正常调用。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// The implementation of the predeclared function panic.
func gopanic(e any) {
    // 获取当前正在运行的goroutine
    gp := getg()
    
    // 调用gopanic()应该是在用户栈上发生的
    if gp.m.curg != gp {
        print("panic: ")
        printany(e)
        print("\n")
        // panic 在系统栈上
        throw("panic on system stack")
    }

    // 申请内存期间不允许panic
    if gp.m.mallocing != 0 {
        print("panic: ")
        printany(e)
        print("\n")
        // malloc 期间 panic
        // malloc 是在申请内存期间
        throw("panic during malloc")
    }
    
    // 抢占期间不允许panic
    if gp.m.preemptoff != "" {
        print("panic: ")
        printany(e)
        print("\n")
        print("preempt off reason: ")
        print(gp.m.preemptoff)
        print("\n")
        throw("panic during preemptoff")
    }
    
    // M持有锁期间不允许panic
    if gp.m.locks != 0 {
        print("panic: ")
        printany(e)
        print("\n")
        throw("panic holding locks")
    }

    // 1) 创建一个_panic结构并初始化
    // 可以看出panic和defer结构都是用的用户内存空间
    
    // 初始化一个panic结构体,在栈上分配
    var p _panic		
    p.arg = e // 保存panic的参数,该参数是panic(v any)
    // _panic通过link链接其他panic
    // 记录goroutine上的最后一个panic,也就是形成一个panic的链表
    p.link = gp._panic	
    // g._panic 保存的是最新的panic
    // 特意使用noescape函数来避免p逃逸,应为panic本身就是与栈的状态强相关的
    // 当前panic追加到goroutine的panic链表上,记录最新的一个panic
    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))	

    // 使用原子锁标记panic正在发生,用于runtime.main()函数等待
    // 当runtime.main()即将要结束时,需要循环等待panic完成。
    // 具体代码参看 runtime.main() 函数。
    atomic.Xadd(&runningPanicDefers, 1)

    // 2) open code defers 形式扫描栈,收集信息,已备后面循环gp.defer
    
    // By calculating getcallerpc/getcallersp here, we avoid scanning the
    // gopanic frame (stack scanning is slow...)
    // 
    // 通过这里计算getcallerpc/getcallersp,我们避免扫描gopanic帧(堆栈扫描很慢......)
    // 将最近的一个open coded defer栈帧添加到_defer链表中,可能是一组defer函数组成的一个_defer结构。
    addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))

    // 在这个循环中逐个调用链表中的defer函数,并检测recover的状态。
    // 如果所有的defer函数都执行完后还是没有recover,则循环就会结束,
    // 最后的fatalpanic()函数就会结束当前进程。
    for {
        // 取最新的一个defer
        // 这里可以看看出取出的defer不一定是当前发生panic注册的defer可能是上游调用函数注册的
        d := gp._defer
        if d == nil {
            // defer已经运行完了,结束循环
            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.started为真,表明当前是一个嵌套的panic,
        // 也就是在原有panic或Goexit()函数执行defer函数的时候又触发了panic
        // 因为触发panic的defer函数还没有执行完,所以还没有从链表中移除。
        // 这里会把d相关的旧的_panic设置为aborted,然后把d从链表中移除,并通过freedefer()函数释放defer。
        if d.started { // 首次触发panic这里为 false,多次触发这里为 true
            // d._panic记录的是前一个panic,
            // 在执行d.fn()函数的时候又发生了panic的情况。
            if d._panic != nil {
                // 把前一个panic标记为已终止状态
                d._panic.aborted = true // 已终止
            }
            d._panic = nil
            // 不是open defers形式时,则直接回收当前defer结构体即可,
            // 因为当前gopanic()正是当前defer函数中触发,因此直接结束本次循环即可。
            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).
                //
                // 当前defer已执行完,即将被回收。
                d.fn = nil 
                gp._defer = d.link
                // 因为当前defer中发生了panic,后续代码不会被执行,直接回收defer即可
                freedefer(d)		
                continue
            }
            
            // 否则应该去runOpenDeferFrame回收
        }

        // 后续的3大块逻辑就是:调用defer函数、释放_defer结构和检测recover。
        // 2.1) 调用defer函数
        
        // Mark defer as started, but keep on list, so that traceback
        // can find and update the defer's argument frame if stack growth
        // or a garbage collection happens before executing d.fn.
        // 
        // 如果defer函数又触发了panic,新的panic遍历defer链表时,就能够通过started
        // 的值确定确定该defer函数已经被调用过了,避免重复调用。
        d.started = true    // 标记当前defer被panic触发

        // Record the panic that is running the defer.
        // If there is a new panic during the deferred call, that panic
        // will find d in the list and will mark d._panic (this panic) aborted.
        // 
        // 为d._panic赋值,将d关联到当前panic对象p,
        // 使用noescape函数避免p逃逸,这一步是为了后续嵌套的panic能够通过d._panic找到上一个panic
        d._panic = (*_panic)(noescape(unsafe.Pointer(&p))) // 当前defer的_panic记录触发的panic是那个

        done := true
        if d.openDefer {
            // 当前是open code defers模式时
            // 运行当前defer的fn函数,done=true表示所有的defer已经运行完
            // 通过返回值来判断目标栈帧上的open coded defer已经完全执行,并且没有recover,
            // 就再次调用addOneOpenDeferFrame()函数把下一个open coded defer栈帧添加到_defer链表
            done = runOpenDeferFrame(gp, d) // 这里运行的是一组defer
            // 因为上面运行的d.fn()函数,可能其中存在recover()函数,所以要判断recovered字段
            if done && !d._panic.recovered {	
                // done=true,并且当前这个panic并没有被恢复时
                // 再去寻找后面函数的defer
                // 从调用栈的栈顶开始回溯扫描,直到找到一个带有open coded defer的栈帧,
                // 为该栈帧分配一个_defer结构,为各个字段赋值后添加到defer链表中合适的位置。
                // 不管目标栈帧上有几个open coded defer函数,只分配一个_defer结构,
                // 因为后续通过runOpenDeferFrame()函数来执行的时候,会一并执行栈帧上的所有
                // open coded defer函数。添加到_defer链表中的位置是根据目标栈帧在调用栈中的位置
                // 计算的,而不是添加到头部。
                addOneOpenDeferFrame(gp, 0, nil)	
            }
        } else {	
            // defer是堆或栈分布时
            
            // getargp():设置为当前gopanic()函数栈帧上args to callee区间的起始地址,
            // recover()函数通过这个值来判断自身是否直接被defer函数调用
            p.argp = unsafe.Pointer(getargp()) // 该值在fn执行函数中用于recover函数比较判断
            // 执行defer注册的函数,如果这里又发生了panic时则当前defer并未被清除,又会从新gopanic
            d.fn()	
        }
        // 为什么前面设置了argp值这里又要清除掉?
        // 因此d.fn()执行的defer函数中可能会有recover()函数,需要判断argp的值
        p.argp = nil // 清除

        // 2.2) 释放_defer结构
        
        // Deferred function did not panic. Remove d.
        //
        // 调用完d.fn()函数后,不应该会出现gp._defer不等于d这种情况。
        // 假如在d.fn()函数执行的过程中没有造成新的panic,那么所有新注册的defer都应该在
        // d.fn()函数返回的时候被deferreturn()函数移出链表。
        // 假如d.fn()函数执行过程中造成了新的panic,若没有recover,则不会再回到这里,
        // 若经recover之后再回到这里,则所有在d.fn()函数执行过程中注册的defer也
        // 都应该在d.fn()函数返回之前被移除链表。
        if gp._defer != d {	
            throw("bad defer entry in panic")
        }
        // 当前defer执行完需要把_panic标记为nil,后面会删除这个defer
        d._panic = nil	

        // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
        //GC()

        // 把pc和sp字段保存在局部变量中,供接下来检测执行recover时使用
        // 此处额sp类型必须时指针,因为后续如果栈被移动,只有指针类型会得到更新
        pc := d.pc // 要恢复的PC和SP的值
        sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
        if done {
            // done=true,这一组defer函数已执行完
            // 因此defer函数可以被释放掉
            d.fn = nil			
            gp._defer = d.link  // 从goroutine._defer上移除自己
            freedefer(d)        // 回收defer
        }
        
        // 2.3)检测 recover() 是否标记了panic
        
        // 如果 d.fn() 函数成功地执行了recover(),则当前_panic对象的p的recovered字段就会被设置为true
        // 此处通过检测后就会执行recover逻辑。这里才是recover()函数的具体实现功能,跳转到指定代码处。
        if p.recovered {
            // 2.3.1) recover()生效
            //  1. 移除当前_panic,因为当前_panic已被恢复
            //  2. 向前寻找移除被标记为aborted为真的_panic
            gp._panic = p.link	// 把当前panic从goroutine._panic上移除掉自己
            // gp._panic != nil:后面还有 panic
            // gp._panic.goexit:由runtime.Goexit()函数触发
            // gp._panic.aborted:当前panic已被终止
            if gp._panic != nil && gp._panic.goexit && gp._panic.aborted {	
                // A normal recover would bypass/abort the Goexit.  Instead,
                // we return to the processing loop of the Goexit.
                // 
                // 正常恢复将 bypass/abort到 Goexit。 相反,我们返回到 Goexit 的处理循环。
                gp.sigcode0 = uintptr(gp._panic.sp) // 并没有值
                gp.sigcode1 = uintptr(gp._panic.pc)
                // mcall函数从当前g栈切换到g0栈,在执行recovery函数,恢复后是接到从当前最新panic的处往下执行
                // recovery函数判断栈溢出后,直接把sp和pc值赋值给当前goroutine的sched然后使用gogo函数再次被调度起来接到执行
                mcall(recovery)	 // 这种情况应该报错
                // 从这里跳转到deferreturn函数
                throw("bypassed recovery failed") // mcall should not return 应该永远不会返回到这里
            }
            // 解锁panic标志,runtime.main()可以继续执行
            atomic.Xadd(&runningPanicDefers, -1)	

            // After a recover, remove any remaining non-started,
            // open-coded defer entries, since the corresponding defers
            // will be executed normally (inline). Any such entry will
            // become stale once we run the corresponding defers inline
            // and exit the associated stack frame. We only remove up to
            // the first started (in-progress) open defer entry, not
            // including the current frame, since any higher entries will
            // be from a higher panic in progress, and will still be
            // needed.
            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()
                //
                // 跳过当前帧,如果还没有完成。需要在deferreturn()中完成所有剩余的延迟
                // open coded defer时发生了recover时。
                prev = d
                d = d.link
            }
            for d != nil {
                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
                    //
                    // 这个defer已经启动,但是我们正在它内部进行一个 defer-panic-recovery 
                    // 所以不要删除它或任何进一步的defer条目
                    break
                }
                if d.openDefer {
                    if prev == nil {
                        gp._defer = d.link
                    } else {
                        prev.link = d.link
                    }
                    newd := d.link
                    freedefer(d)
                    d = newd
                } 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.
            //
            // 循环移除链表头部所有已经标记为aborted的_panic
            // 这里可以看出当最后一个panic被恢复前面的所有已标记aborted中止的panic会被移除。
            for gp._panic != nil && gp._panic.aborted {
                gp._panic = gp._panic.link
            }
            // 如果没有发生panic,则此时gp._panic应该为nil,不为nil就表明发生了嵌套的panic
            // 而且只是内层的panic被recover
            if gp._panic == nil { // must be done with signal
                gp.sig = 0
            }
            // Pass information about recovering frame to recovery.
            // 要恢复的PC和SP的值,也就是当前defer注册的后一条指令处,是一条JMP指令
            // 跳转去执行deferreturn()函数。
            gp.sigcode0 = uintptr(sp)   // 需要恢复到的SP地址,也就是注册defer函数的栈顶寄存器
            gp.sigcode1 = pc            // 需要恢复到IP地址,也就是注册defer函数的下一条指令处
            // recovery函数负责用存储在sigcode0和sigcode1中的sp和pc恢复gp的执行状态
            mcall(recovery)             // mcall函数切换到系统g0栈去调用gogo函数执行跳转
            throw("recovery failed")    // mcall should not return
        }
    }
    
    // 所有的defer注册函数都没有recover,最后会到这里去打印错误信息

    // ran out of deferred calls - old-school panic now
    // Because it is unsafe to call arbitrary user code after freezing
    // the world, we call preprintpanics to invoke all necessary Error
    // and String methods to prepare the panic strings before startpanic.
    //
    // gp._panic:保存的最新的panic
    preprintpanics(gp._panic)	// 把panic的参数解析放入arg

    // 打印panic信息,并结束当前进程
    fatalpanic(gp._panic) // should not return
    // 向一个nil的地址写入值,会报错,下面这行代码是防止万一,不应该执行到这里在前一个函数中会结束调进程
    *(*int)(nil) = 0      // not reached
}

runOpenDeferFrame()

  1. 循环执行指定栈帧上所有的open coded defer函数。
  2. 返回值表示栈帧上所有的open coded defer函数是否都执行完毕,如果因为某个defer函数执行了recover
  3. 而造成循环中止,则返回值为false
  4. addOneOpenDeferFrame()runOpenDeferFrame()函数都依赖符号表中目标栈帧的OpenCodedDerferInfo
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 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

    deferBitsOffset, fd := readvarintUnsafe(fd)
    nDefers, fd := readvarintUnsafe(fd)
    // 当前的df位,记录着defer执行与否的相关信息
    deferBits := *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset)))

    // 遍历当前函数注册的defer列表
    for i := int(nDefers) - 1; i >= 0; i-- {
        // read the funcdata info for this defer
        var closureOffset uint32
        closureOffset, fd = readvarintUnsafe(fd)
        if deferBits&(1<<i) == 0 {  // 当前位没有注册defer直接跳过
            continue
        }
        closure := *(*func())(unsafe.Pointer(d.varp - uintptr(closureOffset)))
        d.fn = closure                      // defer注册的函数
        deferBits = deferBits &^ (1 << i)   // 清除当前标记位
        *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) = deferBits // 回写给内存
        p := d._panic
        // Call the defer. Note that this can change d.varp if
        // the stack moves.
        // 再次panic时,直接跳转,其他情况继续走下面流程
        deferCallSave(p, d.fn) // 调用fn函数,可能recover也可能panic
        // 这种情况是又panic
        if p != nil && p.aborted {
            break
        }
        d.fn = nil // 帮助GC
        // 该panic是否被恢复
        if d._panic != nil && d._panic.recovered {
            done = deferBits == 0 // deferBits == 0 表示open code defers的defer以运行完
            break
        }
    }

    return done
}

recovery()

  1. 该函数负责用于存储在sigcode0sigcode1中的sppc恢复gp的执行状态。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 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) {
    // Info about defer passed in G struct.
    sp := gp.sigcode0
    pc := gp.sigcode1

    // d's arguments need to be in the stack.
    // 
    // 确保sp不为0,并在在gp的栈中
    if sp != 0 && (sp < gp.stack.lo || gp.stack.hi < sp) {
        print("recover: ", hex(sp), " not in [", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
        throw("bad recovery")
    }

    // Make the deferproc for this d return again,
    // this time returning 1. The calling function will
    // jump to the standard return epilogue.
    //
    // 把sp和pc赋值给gp.sched中对应的字段,
    // 并把返回值设置为1。
    gp.sched.sp = sp    // 为跳转准备 SP
    gp.sched.pc = pc    // 为跳转准备 PC
    gp.sched.lr = 0
    gp.sched.ret = 1    // 设置返回值 1,这里就是AX=1的由来
    // 调用gogo()函数之后,gp的栈指针和指令指针机会恢复到sp和pc的位置,
    // 而这个位置是deferproc()函数通过getcallersp()函数和getcallerpc()函数获得的。
    gogo(&gp.sched)     // 调用gogo函数去跳转
}

preprintpanics()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Call all Error and String methods before freezing the world.
// Used when crashing with panicking.
func preprintpanics(p *_panic) {
    // 防止panic
    defer func() {
        if recover() != nil {
            throw("panic while printing panic value")
        }
    }()
    
    for p != nil {
        // p.arg是any类型,来自panic(v any)
        switch v := p.arg.(type) {
        case error:             // 实现了error接口
            p.arg = v.Error()   // 保存信息 string
        case stringer:	// 实现了stringer接口
            p.arg = v.String()  // 保存信息 string
        }
        p = p.link              // 指向下一个panic
    }
}

printpanics()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Print all currently active panics. Used when crashing.
// Should only be called after preprintpanics.
func printpanics(p *_panic) {
    if p.link != nil {
        // 递归回调从注册第一个panic开始
        printpanics(p.link)
        // 不是goexit,打印符号
        if !p.link.goexit {
            print("\t")
        }
    }
    // 是goexit触发直接返回
    if p.goexit {
        return
    }
    print("panic: ")
    // 打印错误信息
    printany(p.arg)
    // 如果已经recovered,则打印 [recovered]
    if p.recovered {
        print(" [recovered]")
    }
    print("\n")
}

recover()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() any

gorecover()

  1. 该函数必须在defer中作为一部分被使用。
  2. 该函数主要作用是设置 paincrecovered 表示已被恢复。
  3. 被恢复的panic会跳转到恢复的defer注册下一行指令处通过if条件跳转到deferreturn函数处去执行剩余的defer
  4. gopanic()函数的主要逻辑,其中for循环每调用完一个defer函数都会检查p.recovered字段,如果值为true就执行
  5. recover逻辑。也就是说真正的recover逻辑是在gopanic()函数中实现的,defer函数中调用了内置函数recover()
  6. 实际上只会设置_panic的一种状态。内置函数recover()对应runtimegorecover()函数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// The implementation of the predeclared function recover.
// Cannot split the stack because it needs to reliably
// find the stack segment of its caller.
//
// TODO(rsc): Once we commit to CopyStackAlways,
// this doesn't need to be nosplit.
//go:nosplit
func gorecover(argp uintptr) any {
    // argp 参数根据AX寄存器传递
    // 	1. 该参数是来自调用当前defer函数的调用者SP栈
    // 	2. 因此顶层的defer能捕获下层的panic
    
    // 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.
    // 
    // 必须在一个函数中,该函数作为 panic 期间 defer 调用的一部分运行
    // 必须从调用的最顶层函数调用 (在defer语句中使用的函数)
    // p.argp 是最上面那个 defer 函数调用的实参指针
    // 与调用者报告的 argp 进行比较
    // 如果它们匹配,调用方就可以恢复
    gp := getg()    // 获取当前正在执行的g
    p := gp._panic  // 最新注册的panic
    // p != nil:存在注册的panic
    // !p.goexit:不是goexit函数触发的
    // !p.recovered:该panic没有被恢复
    // argp == uintptr(p.argp):必须在defer函数中直接调用recover函数才有用,不可嵌套在其他函数中
    //    1. p.argp:存储的是调用panic的调用者栈SP位置
    if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
        p.recovered = true  // 标识当前这个panic从异常或错误场景中恢复
        return p.arg        // 该参数是panic(v any)传递的空接口参数,直接返回给recover函数调用者即可
    }
    return nil
}
  1. 内置函数recover()是没有参数的,但是gorecover()函数却有一个参数argp,这也是编译器做的手脚。
  2. 编译器会把调用者的args from caller区间的起始地址作为参数传递给gorecover()函数。
1
2
3
4
5
6
func fn() {
    defer func(a int) {
        recover()
        println(a)
    }(0)
}
  1. 经编译转换后的等价代码如下:
1
2
3
4
5
6
func fn() {
    defer func(a int) {
        gorecover(uintptr(unsafe.Pointer(&a)))
        println(&a)
    }(0)
}
  1. 为什么要传递这个argp参数呢?
    • 从代码逻辑来看,gorecover()函数会把它跟当前_panic对象pargp字段比较,只有相等时才会把p.recovered设置为true。
  2. 从参数逻辑上看argp == uintptr(p.argp)是不相等的,编译器会在调用fn()函数中插入以下代码逻辑来修正argp的值。
  3. 如果gp._panic不为nilgp._panic.argp的值等于当前函数栈帧args from caller区间的起始地址,就把它的值改成当前
  4. 函数栈帧args to callee区间的起始地址。与编译器插入的这些指令等价的Go代码如下:
1
2
3
4
5
6
7
gp := getg()
if gp._panic != nil {
    // 这一条限制只是限制了recover()必须在defer函数中【直接调用】才起作用。
    if gp._panic.argp == uintptr(unsafe.Pointer(&argtype)) {
        gp._panic.argp = getargp(0)
    }
}
  1. Go语言对recover强加的一条限制:必须在defer函数中直接调用recover()函数才有用,不可嵌套在其他函数中
  2. recover()函数调用有效的示例代码:
1
2
3
4
5
func fn() {
    defer func() {
        recover() 
    }()
}
  1. recover()函数调用无效的示例代码:
1
2
3
4
5
6
7
8
9
func fn() {
    defer func() {
        r()
    }()
}

func r() {
    recover()
}
  1. Go语言的recover与其他语言的trycatch有明显的不同,即不像catch语句那样能够限定异常的类型。
  2. 如果没有对recover的这种限制,就会使代码行为变得不可控,panic可能经常会被某个深度嵌套的recover恢复,然而这并不是开发者想要的。

panic()汇编

1
2
3
4
5
package main

func main() {
    panic("111111")
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
TEXT main.main(SB) /mnt/hgfs/g/hello1/hello.go
    hello.go:3  0x45b4a0    493b6610        cmp rsp, qword ptr [r14+0x10]
    hello.go:3  0x45b4a4    7622            jbe 0x45b4c8
    hello.go:3  0x45b4a6    4883ec18        sub rsp, 0x18
    hello.go:3  0x45b4aa    48896c2410      mov qword ptr [rsp+0x10], rbp
    hello.go:3  0x45b4af    488d6c2410      lea rbp, ptr [rsp+0x10]
    # 准备空接口参数 AX=rip+0x4c65  etype.type *type类型
    hello.go:4  0x45b4b4    488d05654c0000  lea rax, ptr [rip+0x4c65]
    # 准备空接口参数 BX=rip+0x1463e etype.data uintptr类型
    hello.go:4  0x45b4bb    488d1d3e460100  lea rbx, ptr [rip+0x1463e]
    # gopanic标识panic开始,AX和BX寄存器存储的是要传递的数据
    hello.go:4  0x45b4c2    e8d946fdff      call $runtime.gopanic
    hello.go:4  0x45b4c7    90              nop
    hello.go:3  0x45b4c8    e893cdffff      call $runtime.morestack_noctxt
    .:0         0x45b4cd    ebd1            jmp $main.main