golang panic关键词执行原理与代码分析

使用的go版本为 go1.21.2

首先我们写一个简单的panic调度与捕获代码

package main

func main() {
	defer func() {
		recover()
	}()

	panic("panic test")
}

通过go build -gcflags -S main.go获取到对应的汇编代码

可以看到当我们调度panic时,Go的编译器会将这段代码翻译为CALL runtime.gopanic(SB)
我们先来看一下panic构造体的底层源码

panic源码与解读

//代码位置 $GOROOT/src/runtime/runtime2.go L:1035

type _panic struct {
	argp      unsafe.Pointer // 指向在 panic 运行期间执行的延迟调用参数的指针,不可移动 - liblink 工具已知其位置
	arg       any            // 参数
	link      *_panic        // panic链表
	pc        uintptr        // 返回到运行时的位置
	sp        unsafe.Pointer // 返回到运行时的栈指针位置
	recovered bool           // 是否已被恢复
	aborted   bool           // 是否已被中止
	goexit    bool           // 是否执行了 Goexit 函数
}

gopanic源码与解读

//代码位置 $GOROOT/src/runtime/panic.go L:826
// 实现预声明函数 panic
func gopanic(e any) {
	// 处理异常参数为 nil 的情况
	if e == nil {
		// 如果 debug.panicnil 不等于 1,将e设置为PanicNilError类型
		//
		if debug.panicnil.Load() != 1 {
			e = new(PanicNilError)
		} else {
			panicnil.IncNonDefault()
		}
	}

	// 获取当前的G
	gp := getg()

	// 判断当前M上运行的G是不是当前G
	if gp.m.curg != gp {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic on system stack")
	}

	// malloc过程中出现panic
	if gp.m.mallocing != 0 {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic during malloc")
	}

	// 禁止抢占的情况下执行 panic (!="" 保持当前G在这M运行)
	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处于锁的状态
	if gp.m.locks != 0 {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic holding locks")
	}

	// 定义一个panic变量
	var p _panic
	p.arg = e //这个e 就是我们panic("xxxx") 里面写的东西

	//将这个panic加入到G的_panic链表中去
	p.link = gp._panic 
	gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) 

	// 增加运行panic延迟计数
	runningPanicDefers.Add(1)

	// 计算 getcallerpc/getcallersp,以避免扫描 gopanic 帧
	addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))

 
	for {
                //逐步获取当前G中的defer调用
		d := gp._defer
		// 如果获取到的构造体为空,直接返回。
		if d == nil {
			break
		}

		// 如果当前_defer运行,将_defer从G的延迟链表移除,释放对应的_defer构造体资源,防止重复执行
		if d.started {
			if d._panic != nil {
				d._panic.aborted = true
			}
			d._panic = nil
			if !d.openDefer {
				d.fn = nil
				gp._defer = d.link
				freedefer(d)
				continue
			}
		}

		// 标记当前_defer为运行状态
		d.started = true

		// 记录_defer的panic
		d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

		done := true

		if d.openDefer { //如果_defer使用了 open-coded defers(编码的延迟调用)
			// 运行open-coded defer函数
			done = runOpenDeferFrame(d) //如果当前栈下面没有其他延迟函数,则返回true
			if done && !d._panic.recovered { //panic没有recover
				addOneOpenDeferFrame(gp, 0, nil)
			}
		} else {//执行对应方法
			//getargp返回其caller的保存callee参数的地址
			p.argp = unsafe.Pointer(getargp()) 
			d.fn()
		}
		p.argp = nil

		if gp._defer != d {
			throw("bad defer entry in panic")
		}
		d._panic = nil

		pc := d.pc
		sp := unsafe.Pointer(d.sp)
		if done { //将_defer从G的延迟链表移除,释放对应的_defer构造体资源
			d.fn = nil
			gp._defer = d.link
			freedefer(d)
		}
		if p.recovered { //panic已经恢复
			gp._panic = p.link 
			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.
				gp.sigcode0 = uintptr(gp._panic.sp)
				gp.sigcode1 = uintptr(gp._panic.pc)
				mcall(recovery)
				throw("bypassed recovery failed") // mcall should not return
			}
			runningPanicDefers.Add(-1)
			// 从G中获取一个_defer构造体
			d := gp._defer
			var prev *_defer
			if !done { //如果未执行完毕,跳过当前的帧直接执行下一个
				prev = d
				d = d.link
			}
			for d != nil {
				if d.started { //如果启动退出循环
					break
				}
				if d.openDefer { //如果使用了 open-coded defers
					if prev == nil { //将_defer从G的延迟链表移除释放_defer
						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 //上面有对应的赋值,又重新赋了一遍没啥用
			for gp._panic != nil && gp._panic.aborted { //循环G中的_panic链表,去掉已经被标记中止的_panic
				gp._panic = gp._panic.link
			}
			if gp._panic == nil { // 如果当前G没有panic, 重置信号为0
				gp.sig = 0
			}
			// 将恢复帧发送给recovery.
			gp.sigcode0 = uintptr(sp)
			gp.sigcode1 = pc
			mcall(recovery)
			throw("recovery failed") // mcall should not return
		}
	}

	// 没有更多的延迟调用,现在采用传统的 panic 方式
	// 由于在冻结世界之后调用任意用户代码是不安全的,
	// 我们调用 preprintpanics 来调用所有必要的 Error
	// 和 String 方法,以在 startpanic 之前准备好 panic 字符串。
	preprintpanics(gp._panic)
	fatalpanic(gp._panic) //触发致命的 panic
	*(*int)(nil) = 0 //为了消除编译器的错误提示
}

 当我们调度recover时,Go的编译器会将这段代码翻译为CALL runtime.gorecover(SB)

gorecover源码与解读

//代码位置 $GOROOT/src/runtime/panic.go L:1045
func gorecover(argp uintptr) any {
	gp := getg() //获取当前G
	p := gp._panic // 从当前G中获取一个_panic
	// 如果G存在panic,它的状态不为中止,还未进行painc捕获,函数调用参数相同
	if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
		p.recovered = true
		return p.arg
	}

	return nil
}

总结

从上面的源码我们可以了解到panic的大致逻辑,当使用panic关键词时,将painc加入到G的_panic链表中去. 调度时 defer func() {recover()}(),会改写_painc中的recovered字段,可恢复的panic必须要recover的配合。 而且这个recover必须位于同一goroutine的直接调用链上,否则无法对 panic 进行恢复,未写完有些细节点还是没读懂,后续查阅资料补充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/186804.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

利用Nginx与php处理方式不同绕过Nginx_host实现SQL注入

目录 首先需要搭建环境 nginxphpmysql环境: 搭建网站 FILTER_VALIDATE_EMAIL 绕过 方法1:冒号号分割host字段 方法2:冒号号分割host字段 方法3:SNI扩展绕过 首先需要搭建环境 nginxphpmysql环境: php安装包&a…

具有150KHz固定频率的PWM控制降压型稳压电路芯片D2504,可兼容型号XL4001

D2504是一块具有150KHz固定频率的PWM控制降压型稳压电路,具有高转换效率、2A负 载能力和优异的负载调整率和电压线性度。 主要特点: ● 输入电压范围: 4.5~40V ● 可调输出电压: 1.235~37V ● 最小Drop电压1 5V2A ● 150K 固…

迈巴赫S480升级主动式氛围灯 浪漫婉转的气氛

主动式氛围灯有263个可多色渐变的LED光源,营造出全情沉浸的动态光影氛围。结合智能驾驶辅助系统,可在转向或检测到危险时,予以红色环境光提示,令光影艺术彰显智能魅力。配件有6个氛围灯,1个电脑模块。 1、气候&#xf…

超分辨率重建

意义 客观世界的场景含有丰富多彩的信息,但是由于受到硬件设备的成像条件和成像方式的限制,难以获得原始场景中的所有信息。而且,硬件设备分辨率的限制会不可避免地使图像丢失某些高频细节信息。在当今信息迅猛发展的时代,在卫星…

数据结构与算法编程题21

判别两棵树是否相等。 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std;typedef char ElemType; #define ERROR 0 #define OK 1typedef struct BiNode {ElemType data;BiNode* lchild, * rchild; }BiNode, * BiTree;bool Create_tree(BiTree&a…

python之pyqt专栏3-QT Designer

从前面两篇文章python之pyqt专栏1-环境搭建与python之pyqt专栏2-项目文件解析&#xff0c;我们对QT Designer有基础的认识。 QT Designer用来创建UI界面&#xff0c;保存的文件是"xxx.ui"文件&#xff0c;"xxx.ui"可以被pyuic转换为"xxx.py",而&…

html table样式的设计 表格边框修饰

<!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /> <title>今日小说排行榜</title> <style> table {border-collapse: collapse;border: 4px double red; /*…

VC++彻底理解链接器:四,重定位

重定位 程序的运行过程就是CPU不断的从内存中取出指令然后执行执行的过程&#xff0c;对于函数调用来说比如我们在C/C语言中调用简单的加法函数add&#xff0c;其对应的汇编指令可能是这样的: call 0x4004fd 其中0x4004fd即为函数add在内存中的地址&#xff0c;当CPU执行这条…

2023大模型安全解决方案白皮书

今天分享的是大模型系列深度研究报告&#xff1a;《2023大模型安全解决方案白皮书》。 &#xff08;报告出品方&#xff1a;百度安全&#xff09; 报告共计&#xff1a;60页 前言 在当今迅速发展的数字化时代&#xff0c;人工智能技术正引领着科技创新的浪潮而其中的大模型…

Linux(7):Vim 程序编辑器

vi 基本上 vi 共分为三种模式&#xff0c;分别是【一般指令模式】、【编辑模式】与【指令列命令模式】。 这三种模式的作用分别是&#xff1a; 一般指令模式(command mode) 以 vi 打开一个文件就直接进入一般指令模式了(这是默认的模式&#xff0c;也简称为一般模式)。在这个模…

使用 HTML、CSS 和 JavaScript 创建图像滑块

使用 HTML、CSS 和 JavaScript 创建轮播图 在本文中&#xff0c;我们将讨论如何使用 HTML、CSS 和 JavaScript 构建轮播图。我们将演示两种不同的创建滑块的方法&#xff0c;一种是基于opacity的滑块&#xff0c;另一种是基于transform的。 创建 HTML 我们首先从 HTML 代码开…

WPF实战项目十七(客户端):数据等待加载弹框动画

1、在Common文件夹下新建文件夹Events&#xff0c;新建扩展类UpdateLoadingEvent public class UpdateModel {public bool IsOpen { get; set; }}internal class UpdateLoadingEvent : PubSubEvent<UpdateModel>{} 2、新建一个静态扩展类DialogExtensions来编写注册和推…

JSP EL 通过 三元运算符 控制界面 标签 标签属性内容

然后 我们来说说 EL配合三元运算符的妙用 我们先这样写 <% page contentType"text/html; charsetUTF-8" pageEncoding"UTF-8" %> <%request.setCharacterEncoding("UTF-8");%> <!DOCTYPE html> <html> <head>&l…

分布式篇---第六篇

系列文章目录 文章目录 系列文章目录前言一、说说什么是漏桶算法二、说说什么是令牌桶算法三、数据库如何处理海量数据?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码…

css三角,鼠标样式,溢出文字

目录 css三角 鼠标样式 例子&#xff1a;页码模块 溢出文字表示方式 margin负值运用 css三角强化 css三角 css三角中&#xff1a;line-height&#xff1a;0和font-size&#xff1a;0是防止兼容性的问题 jd {position: relative;width: 120px;height: 249px;background-…

【matlab程序】matlab利用工具包nctool读取grib2、nc、opendaf、hdf5、hdf4等格式数据

【matlab程序】matlab利用工具包nctool读取grib2、nc、opendaf、hdf5、hdf4等格式数据 引用&#xff1a; B. Schlining, R. Signell, A. Crosby, nctoolbox (2009), Github repository, https://github.com/nctoolbox/nctoolbox Brief summary: nctoolbox is a Matlab toolbox…

「Verilog学习笔记」数据串转并电路

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 关于什么是Valid/Ready握手机制&#xff1a; 深入 AXI4 总线&#xff08;一&#xff09;握手机制 - 知乎 时序图含有的信息较多&#xff0c;观察时序图需要注意&#xff1a…

YOLOv8改进 | 2023 | MPDIoU、InnerMPDIoU助力细节涨点

论文地址&#xff1a;官方论文地址点击即可跳转 代码地址&#xff1a;官方并没有开源的该损失的函数的代码&#xff0c;我根据文章内容进行了复现代码块在第三章 一、本文介绍 本文为读者详细介绍了YOLOv8模型的最新改进&#xff0c;带来的改进机制是最新的损失函数MPDIoU和融…

Django必备知识点(图文详解)

目录 day02 django必备知识点 1.回顾 2.今日概要 3.路由系统 3.1 传统的路由 3.2 正则表达式路由 3.3 路由分发 小结 3.4 name 3.5 namespace 3.4 最后的 / 如何解决&#xff1f; 3.5 当前匹配对象 小结 4.视图 4.1 文件or文件夹 4.2 相对和绝对导入urls​编辑…

脏页刷新机制总结

1、Buffer Cache和Page Cache 一句话解释&#xff1a;Page Cache用于缓存文件的页数据&#xff0c;Buffer Cache用于缓存块设备&#xff08;磁盘&#xff09;的块数据。但由于磁盘都是由文件系统管理的&#xff0c;所以会导致数据会被缓存两次&#xff0c;因此现在Linux已经不再…