日韩欧美国产精品免费一二-日韩欧美国产精品亚洲二区-日韩欧美国产精品专区-日韩欧美国产另-日韩欧美国产免费看-日韩欧美国产免费看清风阁

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

.NET Core 異常(Exception)底層原理淺談

freeflydom
2024年12月19日 10:40 本文熱度 886

中斷與異常模型圖

  1. 內(nèi)中斷
    內(nèi)中斷是由 CPU 內(nèi)部事件引起的中斷,通常是在程序執(zhí)行過程中由于 CPU 自身檢測到某些異常情況而產(chǎn)生的。例如,當執(zhí)行除法運算時除數(shù)為零,或者訪問了不存在的內(nèi)存地址,CPU 就會產(chǎn)生內(nèi)中斷。

    1. 故障Fault
      故障是在指令執(zhí)行過程中檢測到的錯誤情況導(dǎo)致的內(nèi)中斷,比如空指針,除0異常,缺頁中斷等

    2. 自陷Trap
      這是一種有意的內(nèi)中斷,是由軟件預(yù)先設(shè)定的特殊指令或操作引起的。比如syscall,int 3這種故意設(shè)定的陷阱

    3. 終止abort
      終止是一種比較嚴重的內(nèi)中斷,通常是由于不可恢復(fù)的硬件錯誤或者軟件嚴重錯誤導(dǎo)致的,比如內(nèi)存硬件損壞、Cache 錯誤等

    4. 硬件異常
      CPU內(nèi)部產(chǎn)生的異常事件

    5. 用戶異常
      軟件模擬出的異常,比如操作系統(tǒng)的SEH,.NET的OutOfMemoryException

  2. 外中斷
    外中斷是由 CPU 外部的設(shè)備或事件引起的中斷。比如鍵盤,鼠標,主板定時器。這些外部設(shè)備通過向 CPU 發(fā)送中斷請求信號來通知 CPU 需要處理某個事件。外中斷是計算機系統(tǒng)與外部設(shè)備進行交互的重要方式,使得 CPU 能夠及時響應(yīng)外部設(shè)備的請求,提高系統(tǒng)的整體性能和響應(yīng)能力。

    1. NMI(Non - Maskable Interrupt,非屏蔽中斷)
      NMI 是一種特殊類型的中斷,它不能被 CPU 屏蔽。與普通中斷(可以通過設(shè)置中斷屏蔽位來阻止 CPU 響應(yīng))不同,NMI 一旦被觸發(fā),CPU 必須立即響應(yīng)并處理。這種特性使得 NMI 通常用于處理非常緊急且至關(guān)重要的事件,這些事件的優(yōu)先級高于任何其他可屏蔽中斷。

    2. INTR(Interrupt Request,中斷請求)
      INTR 是 CPU 用于接收外部中斷請求的引腳(在硬件層面)或者信號機制(在軟件層面)。外部設(shè)備(如磁盤驅(qū)動器、鍵盤、鼠標等)通過向 CPU 的 INTR 引腳發(fā)送信號來請求 CPU 中斷當前任務(wù),為其提供服務(wù)。這是計算機系統(tǒng)實現(xiàn)設(shè)備交互和多任務(wù)處理的關(guān)鍵機制之一。

用戶異常

C#的異常,在Windows平臺下是完全圍繞SEH處理框架來展開。在Linux上則是圍繞signal模擬成SEH結(jié)構(gòu),因為都會進入內(nèi)核態(tài),所以其開銷并不低,內(nèi)部走了很多流程。

        static void Main(string[] args)
        {
            try
            {
                var num = Convert.ToInt32("a");
            }
            catch (Exception ex)
            {
                Debugger.Break();
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }

眼見為實:用戶Execption的調(diào)用棧

硬件異常

硬件異常指CPU執(zhí)行機器碼出現(xiàn)異常后,由CPU通知操作系統(tǒng),操作系統(tǒng)再通知進程觸發(fā)的異常。
比如:

  1. 內(nèi)核模式切換:syscall

  2. 訪問違例:AccessViolationException

  3. visual studio中F9中斷:int 3

        static void Main(string[] args)
        {
            try
            {
                string str = null;
                var len = str.Length;
                Console.WriteLine(len);
            }
            catch (Exception ex)
            {
                Debugger.Break();
                Console.WriteLine(ex.ToString());
            }
            Console.ReadLine();
        }

與用戶異常不同的是,異常的發(fā)起點在CPU上,并且CLR為了統(tǒng)一處理。會先將硬件異常轉(zhuǎn)換成用戶異常。以此來復(fù)用后續(xù)邏輯。所以相比用戶異常,硬件異常的開銷更大

眼見為實:硬件Execption的調(diào)用棧

硬件異常如何與用戶異常綁定?

上面說到,CLR會先將硬件異常轉(zhuǎn)換成用戶異常。那么在拋出異常的時候,如何正確拋出一個托管堆認識的異常呢?
以空指針異常為例

核心邏輯在ProcessCLRException中,它會判斷 Thread 是否掛了異常?沒有的話就會通過MapWin32FaultToCOMPlusException來轉(zhuǎn)換,然后通過 pThread.SafeSetThrowables 塞入到線程里。從而實現(xiàn)了硬件異常在托管堆上的映射。

眼見為實

上源碼
https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/excep.cpp

.NET 異常處理流程

對.NET Runtime來說,主要實現(xiàn)以下四個操作

  1. 捕獲異常并拋出異常的位置

  2. 通過線程棧空間獲取異常調(diào)用棧
    線程的棧空間維護了整個調(diào)用棧,掃描整個棧空間即可獲取。

windbg的k系列命令就是參考此原理。

  1. 獲取元數(shù)據(jù)的異常處理表
    一旦方法中有try-catch語句塊時,JIT會將try-catch的適用范圍記錄下來,并整理成異常處理表(Execption Handling Table , EH Table)

C# 代碼
    public class ExceptionEmample
    {
        public static void Example()
        {
			try
			{
                Console.WriteLine("Try outer");
				try
				{
                    Console.WriteLine("Try inner");
                }
				catch (Exception)
				{ 
                    Console.WriteLine("Catch Expception inner");
                }
            }
			catch (ArgumentException)
			{
                Console.WriteLine("Catch ArgumentException outer");
            }
            catch (Exception)
            {
                Console.WriteLine("Catch Exception outer");
            }
            finally
            {
                Console.WriteLine("Finally outer");
            }
        }
    }
?IL代碼
.method public hidebysig static void  Example() cil managed
{
  // Code size       96 (0x60)
  .maxstack  1
  IL_0000:  nop
  IL_0001:  nop
  IL_0002:  ldstr      "Try outer"
  IL_0007:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000c:  nop
  IL_000d:  nop
  IL_000e:  ldstr      "Try inner"
  IL_0013:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0018:  nop
  IL_0019:  nop
  IL_001a:  leave.s    IL_002c
  IL_001c:  pop
  IL_001d:  nop
  IL_001e:  ldstr      "Catch Expception inner"
  IL_0023:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0028:  nop
  IL_0029:  nop
  IL_002a:  leave.s    IL_002c
  IL_002c:  nop
  IL_002d:  leave.s    IL_004f
  IL_002f:  pop
  IL_0030:  nop
  IL_0031:  ldstr      "Catch ArgumentException outer"
  IL_0036:  call       void [System.Console]System.Console::WriteLine(string)
  IL_003b:  nop
  IL_003c:  nop
  IL_003d:  leave.s    IL_004f
  IL_003f:  pop
  IL_0040:  nop
  IL_0041:  ldstr      "Catch Exception outer"
  IL_0046:  call       void [System.Console]System.Console::WriteLine(string)
  IL_004b:  nop
  IL_004c:  nop
  IL_004d:  leave.s    IL_004f
  IL_004f:  leave.s    IL_005f
  IL_0051:  nop
  IL_0052:  ldstr      "Finally outer"
  IL_0057:  call       void [System.Console]System.Console::WriteLine(string)
  IL_005c:  nop
  IL_005d:  nop
  IL_005e:  endfinally
  IL_005f:  ret
  IL_0060:  
  // Exception count 4
  .try IL_000d to IL_001c catch [System.Runtime]System.Exception handler IL_001c to IL_002c
  .try IL_0001 to IL_002f catch [System.Runtime]System.ArgumentException handler IL_002f to IL_003f
  .try IL_0001 to IL_002f catch [System.Runtime]System.Exception handler IL_003f to IL_004f
  .try IL_0001 to IL_0051 finally handler IL_0051 to IL_005f
} // end of method ExceptionEmample::Example

IL代碼中最后4行就代表了方法的異常處理表。

1. IL_000d to IL_001c 之間代碼發(fā)生的Exception異常由IL_001c to IL_002c 之間的代碼處理
2. IL_0001 to IL_002f 之間發(fā)生的ArgumentException異常由IL_002f to IL_003f之間的代碼處理
3. IL_0001 to IL_002f 之間發(fā)生的Exception異常由IL_003f to IL_004f之間的代碼處理
4. IL_0001 to IL_0051 之間無論發(fā)生什么,結(jié)束后都要執(zhí)行IL_0051 to IL_005f之間的代碼
  1. 枚舉異常處理表,調(diào)用對應(yīng)的catch塊與finally塊
    當異常發(fā)生時,Runtime會枚舉EH Table,找出并調(diào)用對應(yīng)的catch塊與finally塊。
    核心方法為ProcessManagedCallFrame:


https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/exceptionhandling.cpp

需要注意的是,一旦CLR找到catch塊,就會先執(zhí)行內(nèi)層所有finally塊中的代碼,再等到當前catch塊中的代碼執(zhí)行完畢f(xié)inally才會執(zhí)行

  1. 重新拋出異常
    在執(zhí)行catch,finally的過程中,如果又拋出了異常。程序會再次進入ProcessCLRException中走重復(fù)流程。
    但是調(diào)用鏈會消失,如果想要防止調(diào)用鏈丟失,需要特殊處理。

        static void Main(string[] args)
        {
            try
            {
                Test();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }		private static void Test()
		{
            try
            {
                throw new Exception("test");
            }
            catch (Exception ex)
            {
                //throw ex; //會丟失調(diào)用鏈,找不到真正的異常所在
                //throw; //調(diào)用鏈完整
                //ExceptionDispatchInfo.Capture(ex).Throw();//調(diào)用鏈更完整,顯示了重新拋出異常所在的位置。
            }
        }

我在這里踩過大坑,使用throw ex重新拋出異常,結(jié)果丟失了異常真正的觸發(fā)點,日志跟沒記一樣。

finally一定會執(zhí)行嗎?

常規(guī)情況下,finally是保證會執(zhí)行的代碼,但如果直接用win32函數(shù)TerminateThread殺死線程,或使用System.Environment的Failfast殺死進程,finally塊不會執(zhí)行。

先執(zhí)行return還是先執(zhí)行finally?

C#代碼
~~~
        public static int Example2()
        {
            try
            {
                return 100+100;
            }
            finally
            {
                Console.WriteLine("finally");
            }
        }
~~~
IL代碼
.method public hidebysig static int32  Example2() cil managed
{
  // Code size       22 (0x16)
  .maxstack  1
  .locals init (int32 V_0)
  IL_0000:  nop
  IL_0001:  nop
  IL_0002:  ldc.i4.1  //將100+100的值,壓入Evaluation Stack
  IL_0003:  stloc.0   //從Evaluation Stack出棧,保存到序號為0的本地變量
  IL_0004:  leave.s   IL_0014 //退出代碼保護區(qū)域,并跳轉(zhuǎn)到指定內(nèi)存區(qū)域IL_0014, 指令 leave.s 清空計算堆棧并確保執(zhí)行相應(yīng)的周圍 finally 塊。
  IL_0006:  nop
  IL_0007:  ldstr      "finally"
  IL_000c:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0011:  nop
  IL_0012:  nop
  IL_0013:  endfinally
  IL_0014:  ldloc.0 //讀取序號0的本地變量并存入Evaluation Stack
  IL_0015:  ret  //從方法返回,返回值從Evaluation Stack中獲取
  IL_0016: 
  // Exception count 1
  .try IL_0001 to IL_0006 finally handler IL_0006 to IL_0014
} // end of method ExceptionEmample::Example2

從IL中可以看到,當try中包含return語句時,編譯器會生成一個臨時變量將返回值保存起來。然后再執(zhí)行finally塊。最后再return 臨時變量。這個過程稱為局部展開(local unwind)

再舉一個例子

C#代碼
        public static int Test()
        {
			int result = 1;
			try
			{
				return result;
			}
			finally
			{
				result = 3;
			}
        }
IL代碼
.method public hidebysig static int32  Test() cil managed
{
  // 代碼大小       15 (0xf)
  .maxstack  1
  .locals init (int32 V_0,
           int32 V_1)
  IL_0000:  nop
  IL_0001:  ldc.i4.1  //將常量1壓棧
  IL_0002:  stloc.0   //將序號0出棧,賦值給result
  IL_0003:  nop
  IL_0004:  ldloc.0  //將當前方法序號0的變量,也就是result,壓入棧中。
  IL_0005:  stloc.1  //將序號1的值出棧,保存到一個臨時變量中。也就是return的值
  IL_0006:  leave.s    IL_000d   //跳轉(zhuǎn)到對應(yīng)行, 指令 leave.s 清空計算堆棧并確保執(zhí)行相應(yīng)的周圍 finally 塊。
  IL_0008:  nop
  IL_0009:  ldc.i4.3   
  IL_000a:  stloc.0
  IL_000b:  nop
  IL_000c:  endfinally
  IL_000d:  ldloc.1  //將return的值 入棧
  IL_000e:  ret  //執(zhí)行return
  IL_000f:  
  // Exception count 1
  .try IL_0003 to IL_0008 finally handler IL_0008 to IL_000d
} // end of method Class1::Test
雖然在finally塊中修改了result的值,但是return語句已經(jīng)確定了要返回的值,finally塊中的修改不會改變這個返回值。不過,如果返回的是引用類型),在finally塊中修改引用類型對象的內(nèi)容是會生效的

異常對性能的影響

引用別人的數(shù)據(jù),自己就不班門弄斧了

  1. 大佬的研究
    https://www.cnblogs.com/huangxincheng/p/12866824.html

  2. <.NET Core底層入門>

總體來說,只要進入內(nèi)核態(tài)。就沒有開銷低的。

CLS與非CLS異常(歷史包袱)

在CLR的2.0版本之前,CLR只能捕捉CLS相容的異常。如果一個C#方法調(diào)用了其他編程語言寫的方法,且拋出一個非CLS相容的異常。那么C#無法捕獲到該異常。
在后續(xù)版本中,CLR引入了RuntimeWrappedException類。當非CLS相容的異常被拋出時,CLR會自動構(gòu)造RuntimeWrappedException實例。使之與與CLS兼容

        public static void Example2()
        {
            try
            {
            }
            catch(Exception)
            {
                //c# 2.0之前這個塊只能捕捉CLS相容的異常
            }
            catch
            {
                //這個塊可以捕獲所有異常
            }
        }

.NET 9 的改進

.NET 9 重寫了異常處理機制,新實現(xiàn)基于 NativeAOT Runtime的異常處理模型。
https://learn.microsoft.com/zh-cn/dotnet/core/whats-new/dotnet-9/runtime#faster-exceptions

轉(zhuǎn)自https://www.cnblogs.com/lmy5215006/p/18604440


該文章在 2024/12/19 10:40:06 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产精品不卡免费视频 | 欧美日韩另类视频在线观看 | 老女人擦 | 欧美日韩视频在线播放 | 国产精品毛多多水多 | 亚洲欧美日韩中文字幕二区 | 成·人午夜在线观看 | 国产在线视频一区二区三区 | 欧美又大粗又爽又黄大片视频 | 国产精品成人aaaa网站女吊丝 | 92国产福利午夜 | 亚洲六十熟女系 | 亚洲欧美日韩不卡在线观看 | 国产亚洲欧美日韩国产片 | 中文字幕日韩欧美一区二区三区 | 欧美牛逼aa | 精品亚洲成a人app | 国产理论片在线观看 | 99视频免费 | 欧洲精品免费高清在线视频 | 99ri国产在线观看 | 99国产清国产精品国产 | 热播电影在线观看 | 国产精品猎奇系列在线观看 | 99视频精品免费在线观看 | 91大片淫黄大片.在线天堂 | 免费99精品国产 | 美国产日产一区∨ | 欧美日韩精品一区二区免费看 | 国产精品每日更新在线观看 | 国产熟女乱伦一区二区 | 国语自产偷拍精品视频偷拍 | 中日韩精品视频在线观看 | 日本护士毛茸茸 | 久精品视在线观看视频 | 欧美高清免费精品国产自 | 日韩美女黄大片在线观看 | 国产乱子伦精品免费视频 | 亚洲s色大片在线观看 | 片免费观看 | 91精品视频在线 |