x64 例外狀況處理

結構化例外狀況處理和C++ x64 上的例外狀況處理編碼慣例和行為的概觀。 如需例外狀況處理的一般資訊,請參閱 Microsoft C++ 中的例外狀況處理。

解開資料以進行異常處理與除錯器支援

在處理異常時,為了恢復非揮發性暫存器,非葉函式會以靜態資料標註。 這些資料通常稱為「函式展開資訊」,描述如何在任意指令下正確解開函式。 此數據會儲存為 pdata,或程式數據,接著會 參考 xdata 例外狀況處理數據。

函式還原資訊由數個資料結構組成,以下將逐一說明。

關於支援 Intel APX(進階效能擴充)的 Unwind 資訊,請參閱 Unwind V3 預覽規範。

struct RUNTIME_FUNCTION 结构体

基於表的異常處理需要為所有分配堆疊空間或呼叫其它函式的函式(例如,非葉子函式)設置表格項目。 函式表項目的格式如下:

尺寸 值
ULONG 函式起始位址
ULONG 函式結束位址
ULONG 展開資訊位址

RUNTIME_FUNCTION結構必須在記憶體中按 DWORD 對齊。 所有位址都是相對於影像的,即它們是從包含函式表專案的映像起始位址開始的32位位移。 這些條目會被排序,並放入 .pdata PE32+ 映像的區塊中。 對於動態產生函式(JIT 編譯器),支援這些函式的執行環境必須使用 RtlInstallFunctionTableCallback 或 RtlAddFunctionTable 提供這些資訊給作業系統。 若未做到,會導致異常處理與除錯不可靠。

結構體UNWIND_INFO

解捲資料資訊結構記錄函式對堆疊指標的影響,以及非揮發性暫存器在堆疊中的位置:

尺寸 值
UBYTE: 3 版本
UBYTE: 5 旗標
UBYTE 前言的大小
UBYTE 展開程式碼計數
UBYTE: 4 框架暫存器
UBYTE: 4 框架暫存器偏移量(縮放)
USHORT * n 解除代碼陣列
變數 可以是以下形式 (1) 或 (2)

(1) 例外狀況處理程式

尺寸 值
ULONG 例外狀況處理程序的位址
變數 特定語言的處理程式資料(選擇性)

(2) 鏈結展開資訊

尺寸 值
ULONG 函式起始位址
ULONG 函式結束位址
ULONG 展開資訊位址

UNWIND_INFO結構必須在記憶體中按DWORD對齊。 以下是每個欄位的意義:

  • 版本

    展開數據的版本號碼,目前為 1。

  • 旗標

    目前已定義三個旗標:

    旗標 描述
    UNW_FLAG_EHANDLER 該函式有一個例外處理器,作業系統會呼叫它來檢查異常狀態,並可能進行處理。 像是 C __try 子句這類語言特性會註冊這種處理常式。
    UNW_FLAG_UHANDLER 函式有一個終止處理程序,作業系統在解開堆疊時會呼叫它。 此處理常式可在具例外安全性的程式碼中釋放由函式配置的資源。 語言特性如本地 C++ 物件結構函數與 C __finally 子句會註冊此類終止處理程序。
    UNW_FLAG_CHAININFO 這個回溯資訊結構不是該程序的主要資訊結構。 相反地,鏈結的解展開資訊項目是前一個 RUNTIME_FUNCTION 項目的內容。 如需詳細資訊,請參閱 鏈式 unwind 資訊結構。 若設定此旗標,則必須清除 UNW_FLAG_EHANDLER 和 UNW_FLAG_UHANDLER 旗標。 此外,框架暫存器和固定堆疊分配欄位必須具有與主要回溯資訊相同的值。
  • 序言的大小

    函式前置作業的長度以位元組表示。

  • 回溯碼計數

    取消碼陣列中的插槽數量。 有些回轉碼,例如 UWOP_SAVE_NONVOL,需要陣列中多個插槽。

  • 框架暫存器

    若非零,函式使用框架指標(FP),該欄位為用作框架指標的非揮發性暫存器編號,節點的操作資訊欄位 UNWIND_CODE 使用相同編碼。

  • 框架暫存器偏移量(經縮放)

    此欄位是 RSP 暫存器值與所選取的影格指標(FP)暫存器值之間的縮放偏移量。 所選的 FP 暫存器會設為 RSP + 16 * 此數值,也就是說,你可以使用 0 到 240 的偏移值。 這種偏移讓 FP 暫存器位於動態堆疊幀的本地堆疊配置中間,因此透過較短的指令獲得更好的程式碼密度。 (也就是說,更多指令可以使用8位正負號的位移形式。)

  • 解構程式碼數組

    用來說明前序碼對非揮發性暫存器和 RSP 的影響的項目陣列。 關於個別項目的意義,請參閱 「展開操作程式碼 」章節。 為了維持資料的正確對齊,這個陣列總是包含偶數個項目,最後一個項目可能不被使用。 在此情況下,陣列的長度比展開代碼欄位計數所指出的還要長。

  • 例外狀況處理程序的位址

    如果旗標 UNW_FLAG_CHAININFO 已清除,且旗標 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER 其中之一已設定,則為函式的語言特定例外狀況處理常式或終止處理常式的影像相對指標。

  • 特定語言的處理程序數據

    函式的語言特定例外狀況處理程序數據。 此數據的格式未指定,且完全由使用中的特定例外狀況處理程序決定。

  • 鏈結回溯資訊

    若已設定旗標 UNW_FLAG_CHAININFO,UNWIND_INFO 結構會以三個 UWORD 作結。 這些 UWORDs 代表 RUNTIME_FUNCTION 鏈式解轉函數的資訊。

結構體 UNWIND_CODE

使用展開程式碼陣列來記錄序言中影響非揮發性暫存器和 RSP 的操作順序。 每個程式代碼專案都有下列格式:

尺寸 值
UBYTE prolog 中的位移
UBYTE: 4 展開作業程序代碼
UBYTE: 4 作業資訊

陣列會依初構中的位移遞減順序排序。

prolog 中的位移

從前導部分起始點開始,至執行此操作的指令結尾的位移,再加上 1(即下一個指令開頭的位移)。

展開作業程序代碼

某些操作碼需要使用相對於區域堆疊框架中某個值的無符號位移。 此位移是從開頭開始,也就是固定堆疊配置的最低位址。 若 中的 UNWIND_INFO Frame 暫存器欄位為零,則此偏移來自 RSP。 若 Frame Register 欄位為非零值,則此偏移量是以建立 FP 暫存器時 RSP 所在的位置為基準。 它等於 FP 暫存器減去 FP 暫存器偏移量(16 × UNWIND_INFO 中縮放後的框架暫存器偏移量)。 如果使用 FP 暫存器,則任何取得位移的卸載程式代碼,都必須在前序碼中建立 FP 暫存器之後使用。

對於除了UWOP_SAVE_XMM128和UWOP_SAVE_XMM128_FAR的所有操作碼,位移一律為 8 的倍數,因為相關的堆疊值都儲存在 8 位元組界限上(堆疊本身一律為 16 位元組對齊)。 對於偏移量較短(小於512K)的操作碼,該碼節點的末尾 USHORT 碼將偏移量除以8。 對於使用長偏移的操作碼(512K <= 偏移量 < 4GB),此程式碼最後兩個 USHORT 節點會儲存偏移量(以小端序格式表示)。

對於操作碼 UWOP_SAVE_XMM128 和 UWOP_SAVE_XMM128_FAR,偏移量總是16的倍數,因為所有128位元 XMM 操作都必須在16位元組對齊的記憶體上進行。 因此,會使用 16 的縮放比例, UWOP_SAVE_XMM128允許小於 1M 的位移。

解纏作業代碼是下列其中一個值:

  • UWOP_PUSH_NONVOL (0) 1 個節點

    推入一個非揮發性整數暫存器,將 RSP 遞減 8。 作業資訊是緩存器的數目。 由於收尾的條件約束,UWOP_PUSH_NONVOL 展開程式碼必須先出現在前序中,最後一個出現在展開程式碼陣列中。 這個相對順序適用於所有其他回溯程序代碼,除了UWOP_PUSH_MACHFRAME。

  • UWOP_ALLOC_LARGE (1) 2 或 3 個節點

    在堆疊上配置大型區域。 有兩種形式。 如果作業資訊等於 0,則會在下一個位置中記錄配置除以 8 的大小,允許配置最多 512K - 8。 如果作業資訊等於 1,則未調整配置的大小會以小端格式記錄在後兩個儲存槽中,允許配置高達 4GB 減去 8 字節。

  • UWOP_ALLOC_SMALL (2) 1 個節點

    在堆疊上配置小型區域。 配置的大小是作業資訊欄位 * 8 + 8,允許配置從 8 到 128 個字節。

    堆疊配置的展開程式碼應該一律使用最短的可能編碼:

    配置大小 展開代碼
    8 到 128 個字節 UWOP_ALLOC_SMALL
    136 到 512K-8 個字節 UWOP_ALLOC_LARGE、 作業資訊 = 0
    512K 到 4G-8 位元組 UWOP_ALLOC_LARGE、操作資訊 = 1
  • UWOP_SET_FPREG (3) 1 個節點

    透過將該暫存器設為目前的 RSP 的某個偏移值,來建立框架指標暫存器。 偏移量等於 UNWIND_INFO 中的 Frame Register 偏移(縮放)欄位值乘以 16,因此偏移可介於 0 到 240 之間。 使用位移可允許建立指向固定堆疊配置中間的框架指標,藉由允許更多存取者使用簡短的指令窗體,協助程式代碼密度。 作業資訊欄位是保留的,不應使用。

  • UWOP_SAVE_NONVOL (4) 2 個節點

    使用 MOV 而非 PUSH 將非易失性整數暫存器儲存在堆疊上。 此程式碼主要用於 縮減包裹,其中非易失性暫存器被儲存在堆疊中一個先前已分配的位置。 作業資訊是緩存器的數目。 乘以 8 的堆疊位移會記錄在下一個展開作業指令代碼槽中,如上述附註所述。

  • UWOP_SAVE_NONVOL_FAR (5) 3 個節點

    使用MOV而不是PUSH,將具有長位移的非揮發整數暫存器儲存在堆疊中。 此程式碼主要用於 縮減包裹,其中非易失性暫存器被儲存在堆疊中一個先前已分配的位置。 作業資訊是緩存器的數目。 未調整的堆疊位移會記錄在下兩個展開程式碼區塊中,如前述附註所述。

  • UWOP_SAVE_XMM128 (8) 2 個節點

    將非揮發性 XMM 暫存器中所有 128 位元的檔案儲存在堆疊中。 作業資訊是緩存器的數目。 被縮放為16的堆疊偏移量記錄在下一個插槽中。

  • UWOP_SAVE_XMM128_FAR (9) 3 個節點

    將非揮發性 XMM 暫存器的全部 128 位元以長偏移量儲存到堆疊上。 作業資訊是緩存器的數目。 未調整的堆疊位移會記錄在下兩個位置中。

  • UWOP_PUSH_MACHFRAME (10) 1 個節點

    推動機械框架。 此解轉程式碼記錄硬體中斷或例外的影響。 它有兩種形式。 值為 0 表示硬體已在堆疊中推送過這樣的框架:

    地點 值
    RSP+32 SS
    RSP+24 舊版 RSP
    RSP+16 EFLAGS
    RSP+8 CS
    RSP RIP

    值為 1 表示硬體已在堆疊中推送過這樣的框架:

    地點 值
    RSP+40 SS
    RSP+32 舊版 RSP
    RSP+24 EFLAGS
    RSP+16 CS
    RSP+8 RIP
    RSP 錯誤碼

    這個回溯程式代碼一律會出現在虛擬初構中,這永遠不會實際執行,而是出現在插斷例程的實際進入點之前,而且只為了提供一個位置來模擬機器框架的推送。 UWOP_PUSH_MACHFRAME 記錄模擬,這表示機器在概念上已完成此作業:

    1. 將 RIP 回傳位址從堆疊頂端彈出至 Temp

    2. 按下 SS

    3. 推送舊版 RSP

    4. 按下 EFLAGS

    5. 推送 CS

    6. 推送 暫存

    7. 推送錯誤碼 (如果 op info 等於 1)

    模擬的 UWOP_PUSH_MACHFRAME 操作會將 RSP 遞減 40(若操作資訊等於 0)或 48(若操作資訊等於 1)。

作業資訊

作業資訊位的意義取決於作業程序代碼。 若要編碼一般用途(整數)緩存器,會使用此對應:

位元 註冊
0 RAX
1 RCX
2 RDX
3 RBX
4 RSP
5 RBP
6 RSI
7 RDI
8 到 15 R8 到 R15

鏈結解纏資訊結構

若已設定 UNW_FLAG_CHAININFO 旗標,則 unwind 資訊結構屬於次要結構,而共用的例外處理常式/鏈結資訊位址欄位會包含主要的 unwind 資訊。 此範例程式碼取得主要的解轉資訊,假設 unwindInfo 是具有 UNW_FLAG_CHAININFO 標誌設定的結構。

PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);

鏈結資訊在兩種情況下很有用。 首先,它可用於非連續的程式代碼區段。 透過使用鏈結資訊,你可以縮減所需展開資訊的大小,因為你不必從主要展開資訊中複製展開碼陣列。

您也可以使用鏈結資訊來群組動態緩存器儲存。 編譯器可能會延遲儲存某些揮發性暫存器,直到它已經離開函式項目 prolog。 你可以透過在群組程式碼前的部分設定主要解碼資訊,然後用非零大小的 Prolog 設定鏈狀資訊,讓鏈式資訊中的解帶碼反映非揮發性暫存器的存檔。 在這種情況下,展開程式碼全都是 UWOP_SAVE_NONVOL 的執行個體。 不支援使用 PUSH 儲存非揮發性暫存器,或使用額外的固定堆疊配置空間來修改 RSP 暫存器的分組。

已設定 UNW_FLAG_CHAININFO 的 UNWIND_INFO 項目可包含 RUNTIME_FUNCTION 項目,而其 UNWIND_INFO 項目也已設定 UNW_FLAG_CHAININFO,這種情況有時稱為 多重收縮包裹。 最終,連鎖的解開資訊指標會指向 UNWIND_INFO 一個已 UNW_FLAG_CHAININFO 清空的項目。 此項目為主要 UNWIND_INFO 項目,指向實際程序的入口點。

解繞程序

展開程式碼陣列會排序為遞減順序。 當發生例外時,作業系統會將完整的上下文儲存在上下文記錄中。 接著會叫用例外狀況分派邏輯,並重複執行這些步驟來尋找例外狀況處理程式:

  1. 利用上下文記錄中儲存的當前 RIP 值來搜尋 RUNTIME_FUNCTION 描述當前函式(或串接 UNWIND_INFO 條目為函式部分)的資料表條目。

  2. 如果搜尋找不到函式表的項目,該程式碼會被視為葉函式的一部分,並 RSP 直接指向返回指標。 位於 [RSP] 的回傳指標會儲存在更新後的上下文中,模擬的 RSP 會遞增 8,並重複步驟 1。

  3. 如果搜尋找到函式表項目,RIP 可能位於三個區域之一:a)結語區段中,b)前導區段中,或 c)可能受例外處理常式涵蓋的程式碼中。

    • 情況 a) 如果 RIP 位於尾聲碼中,控制流程將離開該函式。 此函式不能有與此例外相關聯的例外處理常式。 尾聲程式碼的作用必須繼續計算呼叫端函式的上下文。 若要判斷 RIP 是否位於函式尾聲,會檢查自 RIP 起的程式碼流。 如果那個代碼流和正當後文的尾段相符,那它就在後文裡。 尾聲的剩餘部分會進行模擬,並隨著每條指令的處理,上下文記錄會更新。 處理完成後,會重複步驟 1。

      • 情況 b) 如果 RIP 位於函式序言區內,則控制流程尚未進入該函式。 此函式不得有與此例外相關聯的例外處理常式。 必須逆轉前述的效果,才能計算呼叫者函式的上下文。 若從函式起始處到 RIP 的距離小於或等於還原資訊中編碼的序言大小,則 RIP 位於序言中。 還原器會在展開碼陣列中向前掃描,找出第一個位移小於或等於 RIP 相對於函式起始位置之位移的項目,然後撤銷展開碼陣列中其餘所有項目的作用。 接著會重複步驟 1。
    • 情況 c)如果 RIP 不在前導碼或結尾碼內,且該函式有例外處理常式(已設定 UNW_FLAG_EHANDLER),則會呼叫語言特定的處理常式。 處理程式會掃描其數據,並視需要呼叫篩選函式。 語言特定的處理程式可以傳回已處理例外狀況,或搜尋要繼續。 它也可以直接啟動展開。

  4. 若語言專用處理器回傳已處理狀態,執行會依照原始上下文記錄繼續。

  5. 若沒有語言專用的處理器,或處理器回傳「繼續搜尋」狀態,則必須將內容記錄回溯到呼叫端的狀態。 還原器會還原展開程式碼陣列中每個元素所造成的效果。 接著會重複步驟 1。

涉及串聯解除資訊時,仍會遵循這些基本步驟。 唯一的差別在於:在為了還原函式序言(prolog)的影響而遍歷 unwind code 陣列時,一旦處理程序到達陣列末尾,就會連結到父層的 unwind 資訊,並遍歷其中的整個 unwind code 陣列。 這種鏈結會持續進行,直到到達不含 UNW_CHAINED_INFO 旗標的 unwind 資訊,然後再完成走訪其 unwind code 陣列。

最小的展開資料集是8字節。 這類集合表示某函式僅配置了 128 位元組或更少的堆疊空間,且可能保存了一個非揮發性暫存器的值。 它也是沒有回溯程式碼之零長度序言的連結回溯資訊結構的大小。

語言特定處理程式

當設定了 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER 旗標時,UNWIND_INFO 結構會提供語言特定處理常式的相對位址。 如上一節所述,在搜尋例外處理常式或進行展開時,會呼叫語言特定的處理常式。 操作員使用這個原型:

typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN ULONG64 EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);

ExceptionRecord 提供具有標準 Win64 定義的例外狀況記錄指標。

EstablisherFrame 是此函式之固定堆疊配置基底的位址。

ContextRecord 指向例外狀況引發時例外狀況內容(在例外狀況處理程式案例中)或目前的「回溯」內容(在終止處理程式案例中)。

DispatcherContext 會指向此函式的發送器內容。 它有這個定義:

typedef struct _DISPATCHER_CONTEXT {
    ULONG64 ControlPc;
    ULONG64 ImageBase;
    PRUNTIME_FUNCTION FunctionEntry;
    ULONG64 EstablisherFrame;
    ULONG64 TargetIp;
    PCONTEXT ContextRecord;
    PEXCEPTION_ROUTINE LanguageHandler;
    PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;

ControlPc 是這個函式中的值 RIP 。 這個值是例外狀況位址或控件離開建立函式的位址。 RIP 用於判斷在此函式內,控制流程是否位於某個受保護結構中,例如用於 __try/__except 或 __try/__finally 的 __try 區塊。

ImageBase 是包含此函式的模組的映像基底(載入位址)。 函式項目資料與還原資訊中使用的 32 位元偏移量應加到 ImageBase 上,以取得最終位址。

FunctionEntry 提供一個指向 RUNTIME_FUNCTION 函式項目的指標,該項目包含此函式的函式與還原資訊的影像基底相對位址。

EstablisherFrame 是此函式之固定堆疊配置基底的位址。

TargetIp 提供一個選用的指令位址,用來指定回溯展開的接續位址。 如果未 指定 EstablisherFrame ,則會忽略此位址。

ContextRecord 指向例外狀況內容,以供系統例外狀況分派/回溯程序代碼使用。

LanguageHandler 會指向所呼叫的語言特定語言處理程式例程。

HandlerData 指向此函式的語言特定處理程序數據。

MASM 的回溯協助程式

要撰寫正確的組合語言程序,請在實際的組合語言指令旁使用一組偽運算。 這些偽運算會產生適當的 .pdata 和 .xdata。 此外,使用一組巨集來簡化這些偽操作在最常見用法中的使用。

原始伪操作

虛擬作業 描述
PROC FRAME [:ehandler] 使 MASM 在 .pdata 中產生函式表項目,並在 .xdata 中產生函式結構化例外處理還原行為所需的還原資訊。 如果 ehandler 存在,此程式會在 .xdata 中輸入為語言特定的處理程式。

當你使用 FRAME 屬性時,請在後面加上 。ENDPROLOG 指令。 若函式為葉函式(如 函式類型定義),則無需 FRAME 屬性,其他偽操作亦然。
.PUSHREG 暫存器 使用函式前序碼中目前的位移,為指定的暫存器編號產生一個 UWOP_PUSH_NONVOL unwind 程式碼項目。

請只將它與非volatile 整數緩存器搭配使用。 對於揮發性暫存器的推送作業,請改用 `.ALLOCSTACK 8`。
.SETFRAME 暫存器, 位移 使用指定的暫存器和偏移量,填入框架暫存器欄位,並填寫回溯資訊中的偏移量。 位移必須是 16 的倍數,且小於或等於 240。 這個指示也會使用目前的前序碼位移,為指定的暫存器產生一個 UWOP_SET_FPREG 展開程式碼項目。
.ALLOCSTACK 大小 為序言中目前的偏移量產生指定大小的 UWOP_ALLOC_SMALL 或 UWOP_ALLOC_LARGE。

大小操作數必須是8的倍數。
.SAVEREG 暫存器, 位移 使用目前的 prologue 位移,為指定的暫存器和位移產生 UWOP_SAVE_NONVOL 或 UWOP_SAVE_NONVOL_FAR 其中之一的 unwind 程式碼項目。 MASM 選擇最有效率的編碼方式。

offset 必須是正數,且為8的倍數。 偏移量是相對於程序框架的基底,該基底通常位於 RSP 中;若使用框架指標,則是相對於未縮放的框架指標。
. SAVEXMM128暫存器, 偏移 使用目前的前序碼位移,為指定的XMM暫存器和位移產生UWOP_SAVE_XMM128或UWOP_SAVE_XMM128_FAR還原程式碼項目。 MASM 選擇最有效率的編碼方式。

offset 必須是正數,且為16的倍數。 偏移是相對於程序的框架基底位址,通常是在 RSP 中;或者,如果使用幀指標,則是相對於未經縮放的幀指標。
.PUSHFRAME [程式代碼] 產生一個 UWOP_PUSH_MACHFRAME 展開程式碼項目。 如果你指定可選 程式碼,解捲程式碼的條目會被修正為 1。 否則修飾符為 0。
.ENDPROLOG 發出序言宣告結尾的訊號。 必須在函式的前 255 個字節中發生。

以下是適當使用大部分操作碼的範例函式序言:

sample PROC FRAME
    db      048h; emit a REX prefix, to enable hot-patching
    push rbp
    .pushreg rbp
    sub rsp, 040h
    .allocstack 040h
    lea rbp, [rsp+020h]
    .setframe rbp, 020h
    movdqa [rbp], xmm7
    .savexmm128 xmm7, 020h ;the offset is from the base of the frame
                           ;not the scaled offset of the frame
    mov [rbp+018h], rsi
    .savereg rsi, 038h
    mov [rsp+010h], rdi
    .savereg rdi, 010h ; you can still use RSP as the base of the frame
                       ; or any other register you choose
    .endprolog

; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer

    sub rsp, 060h

; we can unwind from the next AV because of the frame pointer

    mov rax, 0
    mov rax, [rax] ; AV!

; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5

    movdqa xmm7, [rbp]
    mov rsi, [rbp+018h]
    mov rdi, [rbp-010h]

; Here's the official epilog

    lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
    pop rbp
    ret
sample ENDP

如需 epilog 範例的詳細資訊,請參閱 x64 prolog 和 epilog 中的 Epilog 程式代碼。

MASM 宏指令

為了簡化 Raw 偽運算的使用,請使用 定義在 ksamd64.inc中的巨集集合。 這些巨集可協助你建立典型的程序前置碼與後置碼。

巨集 描述
alloc_stack(n) 分配一個由 n 位元組組成的堆疊框架(使用 sub rsp, n),並發出適當的解開資訊(.allocstack n)
save_reg reg, loc 將非揮發性暫存器 reg 儲存在堆疊上位於 RSP 位移 loc 的位置,並產生適當的展開資訊 (.savereg reg, loc)
push_reg reg 將非揮發性暫存器 reg 推入堆疊,並產生適當的展開資訊 (.pushreg reg)
rex_push_reg reg 使用 2 位元組的 push 指令,將非揮發性暫存器儲存到堆疊中,並產生適當的回溯還原資訊(.pushreg reg)。 如果推送是函式中的第一個指令,請使用這個巨集,以確保函式可熱修補。
save_xmm128 reg, loc 將非揮發性 XMM暫存器 reg 儲存在堆疊中位於 RSP 偏移 loc 的位置,並產生適當的還原資訊 (.savexmm128 reg, loc)
set_frame reg、offset 將影格暫存器 reg 設定為 RSP + 位移(使用 mov 或 lea),並輸出適當的 unwind 資訊(.set_frame reg, offset)
push_eflags 透過指令 pushfq 推送 eflag,並發出適當的解開資訊(.alloc_stack 8)

以下是一個函式前導碼範例,示範如何正確使用這些巨集:

sampleFrame struct
    Fill     dq ?; fill to 8 mod 16
    SavedRdi dq ?; Saved Register RDI
    SavedRsi dq ?; Saved Register RSI
sampleFrame ends

sample2 PROC FRAME
    alloc_stack(sizeof sampleFrame)
    save_reg rdi, sampleFrame.SavedRdi
    save_reg rsi, sampleFrame.SavedRsi
    .end_prolog

; function body

    mov rsi, sampleFrame.SavedRsi[rsp]
    mov rdi, sampleFrame.SavedRdi[rsp]

; Here's the official epilog

    add rsp, (sizeof sampleFrame)
    ret
sample2 ENDP

在 C 中展開數據定義

以下是展開數據的 C 說明:

typedef enum _UNWIND_OP_CODES {
    UWOP_PUSH_NONVOL = 0, /* info == register number */
    UWOP_ALLOC_LARGE,     /* no info, alloc size in next 2 slots */
    UWOP_ALLOC_SMALL,     /* info == size of allocation / 8 - 1 */
    UWOP_SET_FPREG,       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
    UWOP_SAVE_NONVOL,     /* info == register number, offset in next slot */
    UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
    UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
    UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
    UWOP_PUSH_MACHFRAME   /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;

typedef unsigned char UBYTE;

typedef union _UNWIND_CODE {
    struct {
        UBYTE CodeOffset;
        UBYTE UnwindOp : 4;
        UBYTE OpInfo   : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

#define UNW_FLAG_EHANDLER  0x01
#define UNW_FLAG_UHANDLER  0x02
#define UNW_FLAG_CHAININFO 0x04

typedef struct _UNWIND_INFO {
    UBYTE Version       : 3;
    UBYTE Flags         : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister : 4;
    UBYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
*   union {
*       OPTIONAL ULONG ExceptionHandler;
*       OPTIONAL ULONG FunctionEntry;
*   };
*   OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;

typedef struct _RUNTIME_FUNCTION {
    ULONG BeginAddress;
    ULONG EndAddress;
    ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

#define GetUnwindCodeEntry(info, index) \
    ((info)->UnwindCode[index])

#define GetLanguageSpecificDataPtr(info) \
    ((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))

#define GetExceptionHandler(base, info) \
    ((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetChainedFunctionEntry(base, info) \
    ((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetExceptionDataPtr(info) \
    ((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))

另請參閱

x64 軟體慣例