close

【CSDN 編者按】1978年6月8日,Intel發布了新款16位微處理器「8086」,也同時開創了一個新時代:x86架構誕生了。x86指的是特定微處理器執行的一些計算機語言指令集,定義了芯片的基本使用規則,一如今天的x64、IA64等。它不僅成就了Intel如日中天的地位,也成為了一種業界標準,即使是在當今強大的多核心處理器上也能看到x86的身影。

微軟技術專家Raymond Chen參與Windows開發已經25年了,在實際的操作中,他認為X86構架有很多奇怪之處。

原文地址:https://devblogs.microsoft.com/oldnewthing/20220418-00/?p=106489

本文由CSDN翻譯,轉載需註明來源出處。

譯者 | 章雨銘 責編 | 張紅月
出品 | CSDN(ID:CSDNnews)

以下為譯文:

最近,我發現x86架構還有一個和其他的架構不同的地方——Windows結構化異常的管理方式。

在Windows上,所有其他架構都是通過使用unwind代碼和聲明為元數據的其他信息來跟蹤異常處理。如果在其他架構上單步執行一個函數,就不會看到與異常處理相關的任何指令。只有在發生異常時,系統才會在元數據中的異常處理信息中查找指令指針,並使用它來決定要執行的操作:應該運行哪個異常處理程序?哪些對象需要銷毀?和其他諸如此類的問題。

但奇怪的是,在Windows上,x86在運行時跟蹤異常信息。當控制進入一個需要處理異常的函數時(要麼是因為它想要處理異常,要麼只是因為它想在異常被拋出函數時運行析構函數),代碼必須在一個通過堆棧的鏈接列表中創建一個條目,並以.NET中的值為錨。在Microsoft Visual C++的實現中,鏈接列表節點還包含一個整數,代表當前函數的進度,每當需要銷毀的對象列表發生變化時,該整數就會被更新。它在一個對象的構建完成後立即更新,並在對象的銷毀開始前立即更新。fs:[0]

這個特殊的整數是一個非常麻煩的問題,因為優化器視其為廢棄儲存,想把它優化掉。的確,有時它確實是廢棄儲存,但有時它不是。

struct S { S(); ~S(); };void f1();void f2();S g(){ S s1; f1(); S s2; f2(); return S();}

此函數的代碼生成過程如下:

struct ExceptionNode{ ExceptionNode* next; int (__stdcall *handler)(PEXCEPTION_POINTERS); int state;};S g(){ // Create a new node ExceptionNode node; node.next = fs:[0]; node.handler = exception_handler_function; node.state = -1; // nothing needs to be destructed // Make it the new head of the linked list fs:[0] = &node; construct s1; node.state = 0; // s1 needs to be destructed f1(); construct s2; node.state = 1; // s1 and s2 need to be destructed f2(); construct return value; node.state = 2; // s1, s2, and return value need to be destructed node.state = 3; // s1 and return value need to be destructed destruct s2; node.state = 4; // return value needs to be destructed destruct s1;}

每當 "需要銷毀的對象 "的列表發生變化時,就會更新unwind狀態變量。就優化器而言,所有這些更新看起來都是廢棄儲存,因為似乎沒有人讀它們。.state

但確實有人讀它們:.the。問題是,對the的調用是不可見的。當一個異常被或函數拋出時,它被調用,或者被對象的析構器調用。

但是,其中有些真的是廢棄儲存。例如,2的賦值是一個廢棄儲存,因為它後面緊跟着3的存儲,中間沒有任何東西,所以當值是2的時候,不會有異常發生。同樣,3的存儲是廢棄的,因為3的析構器是隱含的。當破壞.node.stateSnoexcepts1時,不可能發生異常。

如果或改為.f1f2noexcept,廢棄儲存就可能被消除。

因此,優化器進退兩難。它想消除廢棄儲存,但識別廢棄儲存的簡單算法在這裡不起作用,因為有可能出現異常。

Coroutine使情況變得更糟:當一個coroutine暫停時,異常處理節點需要從堆棧中複製到coroutine框架中,然後從堆棧框架中刪除。而當協程恢復時,狀態需要從協程框架複製回堆棧,並鏈接到異常處理程序鏈中。

確切地知道何時執行此操作取消鏈接和重新鏈接是很困難的,因為你仍然必須捕獲其中發生的異常,並把它們存儲在promise中。但這很可能不可行,因為在返回之前,coroutine可能已經恢復並運行到完成。

.await_suspendawait_suspendawait_suspend

void await_suspend(coroutine_handle<> handle){ arrange_for_resumption(handle); throw oops; // who catches this?}

拋出的異常被coroutine框架捕獲,該框架調用.NET Framework。但是promise可能已經不存在了!promise.unhandled_exception()

處理所有這些情況使得x86上的異常處理,特別是x86上的coroutine的異常處理,成為一項相當複雜的工作。

END


《新程序員001-004》全面上市,對話世界級大師,報道中國IT行業創新創造


— 推薦閱讀 —
☞《程序員延壽指南》登GitHub熱榜,最多可增壽20年?
☞開源文化依舊熠熠生輝 —— 在openEuler社區,有技術、有idea,你就是主角!
☞霸榜 GitHub 熱門第一多日後,Colossal-AI 正式版發布

—點這裡↓↓↓記得關註標星哦~—


一鍵三連 「分享」「點讚」「在看」

成就一億技術人

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()