Share via


x64 例外狀況處理

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

回溯資料以進行例外狀況處理,偵錯工具支援

例外狀況處理和偵錯支援需要數個資料結構。

struct RUNTIME_FUNCTION

資料表型例外狀況處理需要配置堆疊空間或呼叫另一個函式的所有函式的資料表專案(例如,非分葉函式)。 函式資料表專案的格式如下:

大小
ULONG 函式起始位址
ULONG 函式結束位址
ULONG 回溯資訊位址

RUNTIME_FUNCTION結構必須在記憶體中對齊 DWORD。 所有位址都是相對影像,也就是說,這些位址是從包含函式資料表專案之映射起始位址的 32 位位移。 這些專案會排序,並放入 PE32+ 映射的 .pdata 區段。 對於動態產生的函式 [JIT 編譯程式],支援這些函式的執行時間必須使用 RtlInstallFunctionTableCallback 或 RtlAddFunctionTable 將此資訊提供給作業系統。 若無法這麼做,將會導致進程無法可靠的例外狀況處理和偵錯。

struct UNWIND_INFO

回溯資料資訊結構可用來記錄函式對堆疊指標的影響,以及將非揮發暫存器儲存在堆疊上的位置:

大小
UBYTE: 3 版本
UBYTE: 5 旗標
UBYTE 初構的大小
UBYTE 回溯程式碼計數
UBYTE: 4 框架暫存器
UBYTE: 4 框架暫存器位移 (縮放)
USHORT * n 回溯程式碼陣列
variable 可以是以下形式 (1) 或 (2)

(1) 例外狀況處理常式

大小
ULONG 例外狀況處理常式的位址
variable 特定語言的處理常式資料(選擇性)

(2) 鏈結回溯資訊

大小
ULONG 函式起始位址
ULONG 函式結束位址
ULONG 回溯資訊位址

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

  • 版本

    回溯資料的版本號碼,目前為 1。

  • 旗標

    目前已定義三個旗標:

    旗標 描述
    UNW_FLAG_EHANDLER 函式具有例外狀況處理常式,在尋找需要檢查例外狀況的函式時,應該呼叫此處理程式。
    UNW_FLAG_UHANDLER 函式具有在回溯例外狀況時應該呼叫的終止處理常式。
    UNW_FLAG_CHAININFO 這個回溯資訊結構不是程式的主要資訊結構。 相反地,鏈結回溯資訊專案是先前RUNTIME_FUNCTION專案的內容。 如需詳細資訊,請參閱 鏈結回溯資訊結構 。 如果設定此旗標,則必須清除UNW_FLAG_EHANDLER和UNW_FLAG_UHANDLER旗標。 此外,框架暫存器和固定堆疊配置欄位必須具有與主要回溯資訊相同的值。
  • 初構的大小

    函式的長度以位元組表示。

  • 回溯程式碼計數

    回溯程式碼陣列中的位置數目。 例如,某些回溯程式碼UWOP_SAVE_NONVOL需要陣列中的多個位置。

  • 框架暫存器

    如果非零,則函式會使用框架指標 (FP),而此欄位是用來作為框架指標的非揮發暫存器數目,使用與UNWIND_CODE節點之作業資訊欄位相同的編碼方式。

  • 框架暫存器位移(縮放)

    如果框架暫存器欄位為非零,此欄位會是建立 FP 暫存器時套用至 FP 暫存器之 RSP 的縮放位移。 實際的 FP 暫存器會設定為 RSP + 16 * 此數位,允許從 0 到 240 的位移。 此位移允許將 FP 暫存器指向動態堆疊框架的本機堆疊配置中間,以透過較短的指示提供更好的程式碼密度。 (也就是說,更多指示可以使用 8 位帶正負號的位移形式。

  • 回溯程式碼陣列

    專案陣列,說明初構對非揮發暫存器和 RSP 的影響。 如需個別專案的意義,請參閱UNWIND_CODE一節。 為了對齊目的,此陣列一律會有偶數個專案,最後一個專案可能未使用。 在此情況下,陣列的長度比回溯代碼欄位計數所指出的還要長。

  • 例外狀況處理常式的位址

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

  • 特定語言的處理常式資料

    函式的語言特定例外狀況處理常式資料。 此資料的格式未指定,且完全由使用中的特定例外狀況處理常式決定。

  • 鏈結回溯資訊

    如果已設定旗標UNW_FLAG_CHAININFO,則UNWIND_INFO結構會以三個 UWORD 結尾。 這些 UWORD 代表鏈結回溯函式的RUNTIME_FUNCTION資訊。

struct UNWIND_CODE

回溯程式碼陣列可用來記錄影響非揮發暫存器和 RSP 之初構中的作業順序。 每個程式碼專案都有下列格式:

大小
UBYTE prolog 中的位移
UBYTE: 4 回溯作業程式碼
UBYTE: 4 作業資訊

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

prolog 中的位移

執行這項作業之指令結尾的位移(即下一個指令開頭的位移)加上 1(也就是下一個指令開頭的位移)。

回溯作業程式碼

注意:某些作業程式碼需要本機堆疊框架中值的不帶正負號位移。 此位移是從開頭開始,也就是固定堆疊配置的最低位址。 如果UNWIND_INFO中的 [框架暫存器] 欄位為零,則此位移來自 RSP。 如果 [框架暫存器] 欄位為非零,則此位移是建立 FP 暫存器時 RSP 所在的位置。 它等於 FP 暫存器減去 FP 暫存器位移 (16 * UNWIND_INFO中縮放框架暫存器位移)。 如果使用 FP 暫存器,則任何取得位移的回溯程式碼,都必須在 prolog 中建立 FP 暫存器之後使用。

對於 和 UWOP_SAVE_XMM128_FAR 以外的 UWOP_SAVE_XMM128 所有 opcode,位移一律為 8 的倍數,因為感興趣的所有堆疊值都會儲存在 8 位元組界限上(堆疊本身一律為 16 位元組對齊)。 對於採用短位移(小於 512K)的作業代碼,此程式碼節點中最後的 USHORT 會保留位移除以 8。 對於需要長位移的作業程式碼(512K < = 位移 < 4GB),此程式碼的最後兩個 USHORT 節點會保留位移(以小結束格式)。

針對 opcode 和 UWOP_SAVE_XMM128UWOP_SAVE_XMM128_FAR ,位移一律為 16 的倍數,因為所有 128 位 XMM 作業都必須發生在 16 位元組對齊的記憶體上。 因此,會使用 16 的縮放比例, UWOP_SAVE_XMM128 允許小於 1M 的位移。

回溯作業程式碼是下列其中一個值:

  • UWOP_PUSH_NONVOL (0) 1 個節點

    推送非volatile 整數暫存器,將 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 * 16 中的 Frame Register 位移(縮放)欄位,允許從 0 到 240 的位移。 使用位移可允許建立指向固定堆疊配置中間的框架指標,藉由允許更多存取者使用簡短的指令表單,協助程式碼密度。 作業資訊欄位是保留的,不應使用。

  • UWOP_SAVE_NONVOL (4) 2 個節點

    使用 MOV 而不是 PUSH,在堆疊上儲存非volatile 整數暫存器。 此程式碼主要用於 壓縮包裝 ,其中非volatile 暫存器會儲存到堆疊中先前配置的位置。 作業資訊是暫存器的數目。 縮放 8 堆疊位移會記錄在下一個回溯作業程式碼位置中,如上述附注所述。

  • UWOP_SAVE_NONVOL_FAR (5) 3 個節點

    使用 MOV 而不是 PUSH,在堆疊上儲存具有長位移的非揮發整數暫存器。 此程式碼主要用於 壓縮包裝 ,其中非volatile 暫存器會儲存到堆疊中先前配置的位置。 作業資訊是暫存器的數目。 未調整的堆疊位移會記錄在下兩個回溯作業程式碼位置中,如上述附注所述。

  • UWOP_SAVE_XMM128 (8) 2 個節點

    儲存堆疊上所有 128 個非揮發性 XMM 暫存器。 作業資訊是暫存器的數目。 縮放比例為 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. 從堆疊頂端到 Temp 的 Pop RIP 傳回位址

    2. 推送 SS

    3. 推送舊的 RSP

    4. 推送 EFLAGS

    5. 推送 CS

    6. 推送 暫存

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

    UWOP_PUSH_MACHFRAME模擬作業會將 RSP 遞減 40 (op info 等於 0) 或 48 (op info 等於 1)。

作業資訊

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

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

鏈結回溯資訊結構

如果已設定UNW_FLAG_CHAININFO旗標,則回溯資訊結構是次要旗標,而共用的 exception-handler/chained-info 位址欄位包含主要回溯資訊。 此範例程式碼會擷取主要回溯資訊,假設 unwindInfo 是已設定UNW_FLAG_CHAININFO旗標的結構。

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

鏈結資訊在兩種情況下很有用。 首先,它可用於非連續的程式碼區段。 藉由使用鏈結資訊,您可以減少所需回溯資訊的大小,因為您不需要從主要回溯資訊複製回溯程式碼陣列。

您也可以使用鏈結資訊來群組動態暫存器儲存。 編譯器可能會延遲儲存一些揮發性暫存器,直到它不在函式專案初構之外為止。 您可以在群組程式碼之前為函式的部分提供主要回溯資訊,然後以非零大小的初構來設定鏈結資訊,其中鏈結資訊中的回溯程式碼會反映非揮發暫存器儲存。 在此情況下,回溯程式碼是UWOP_SAVE_NONVOL的所有實例。 不支援使用 PUSH 或修改 RSP 暫存器來儲存非揮發性暫存器群組,但不支援使用其他固定堆疊配置來修改 RSP 暫存器。

具有UNW_FLAG_CHAININFO集UNWIND_INFO專案可以包含RUNTIME_FUNCTION專案,UNWIND_INFO專案也有UNW_FLAG_CHAININFO集,有時稱為 多個壓縮包裝 。 最後,鏈結的回溯資訊指標會到達已清除UNW_FLAG_CHAININFO UNWIND_INFO專案。 此專案是主要UNWIND_INFO專案,指向實際的程式進入點。

回溯程式

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

  1. 使用內容記錄中儲存的目前 RIP 來搜尋描述目前函式的RUNTIME_FUNCTION資料表專案(或函式部分,用於鏈結UNWIND_INFO專案)。

  2. 如果找不到函式資料表專案,則它位於分葉函式中,而 RSP 會直接定址傳回指標。 [RSP] 的傳回指標會儲存在更新的內容中,模擬 RSP 會遞增 8,並重複步驟 1。

  3. 如果找到函式資料表專案,RIP 可以位於三個區域內:a) 在表結、b) 的初構中,或 c) 的程式碼中可能由例外狀況處理常式所涵蓋。

    • 案例 a) 如果 RIP 位於 epilog 內,則控制項會離開函式,則此函式沒有與此例外狀況相關聯的例外狀況處理常式,而且必須繼續計算呼叫端函式的內容。 若要判斷 RIP 是否在 epilog 內,會檢查從 RIP 開始的程式碼資料流程。 如果該程式碼資料流程可以比對合法表結的尾端部分,則它位於 epilog 中,並模擬了 epilog 的其餘部分,並在處理每個指令時更新內容記錄。 在這個處理之後,會重複步驟 1。

    • 案例 b) 如果 RIP 位於序言中,則控制項尚未輸入函式,則此函式沒有與此例外狀況相關聯的例外狀況處理常式,而且必須復原 prolog 的效果,才能計算呼叫端函式的內容。 如果從函式開始到 RIP 的距離小於或等於回溯資訊中編碼的初構大小,RIP 就會在初構中。 從函式開始,透過回溯程式碼陣列向前掃描第一個專案的回溯程式碼陣列,其位移小於或等於 RIP 的位移,然後復原回溯程式碼陣列中所有剩餘專案的效果,以解除處理。 接著會重複步驟 1。

    • 案例 c) 如果 RIP 不在初構或表結內,且函式具有例外狀況處理常式 (UNW_FLAG_EHANDLER 已設定),則會呼叫語言特定的處理常式。 處理常式會掃描其資料,並視需要呼叫篩選函式。 語言特定的處理常式可以傳回已處理例外狀況,或搜尋要繼續。 它也可以直接起始回溯。

  4. 如果語言特定處理常式傳回已處理的狀態,則會使用原始內容記錄繼續執行。

  5. 如果沒有特定語言的處理常式或處理常式傳回「繼續搜尋」狀態,則內容記錄必須解除至呼叫端的狀態。 其完成方式是處理所有回溯程式碼陣列元素,並復原每個專案的效果。 接著會重複步驟 1。

涉及鏈結回溯資訊時,仍會遵循這些基本步驟。 唯一的差別在於,當逐步執行回溯程式碼陣列以回溯初構的效果時,一旦到達陣列的結尾,就會連結到父回溯資訊,而找到整個回溯程式碼陣列就會有走動。 此連結會繼續執行,直到到達沒有UNW_CHAINED_INFO旗標的回溯資訊為止,然後完成其回溯程式碼陣列。

最小的回溯資料集是 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__try__finally__try/__except/ 區塊。

ImageBase 是包含此函式之模組的映射基底(載入位址),可新增至函式專案中使用的 32 位位位移,並回溯資訊以記錄相對位址。

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回溯程式碼專案。

請只將它與非volatile 整數暫存器搭配使用。 如需揮發性暫存器推送,請使用 。ALLOCSTACK 8,改為
.SETFRAME 暫存器 位移 使用指定的暫存器和位移,填入框架暫存器欄位,並在回溯資訊中位移。 位移必須是 16 的倍數,且小於或等於 240。 這個指示詞也會使用目前的序言位移,為指定的暫存器產生UWOP_SET_FPREG回溯程式碼專案。
.ALLOCSTACK 大小 產生UWOP_ALLOC_SMALL或具有序言中目前位移指定大小的UWOP_ALLOC_LARGE。

大小 運算元必須是 8 的倍數。
.SAVEREG 暫存器 位移 使用目前的序言位移,為指定的暫存器和位移產生UWOP_SAVE_NONVOL或UWOP_SAVE_NONVOL_FAR回溯程式碼專案。 MASM 選擇最有效率的編碼方式。

offset 必須是正數,且為 8 的倍數。 offset 是相對於程式框架的基底,通常是在 RSP 中,或者,如果使用框架指標,則為未調整框架指標。
. SAVEXMM128暫存器 位移 使用目前的序言位移,為指定的 XMM 暫存器產生UWOP_SAVE_XMM128或UWOP_SAVE_XMM128_FAR回溯程式碼專案。 MASM 選擇最有效率的編碼方式。

offset 必須是正數,且為 16 的倍數。 offset 是相對於程式框架的基底,通常是在 RSP 中,或者,如果使用框架指標,則為未調整框架指標。
.PUSHFRAME [ 程式碼 ] 產生UWOP_PUSH_MACHFRAME回溯程式碼專案。 如果指定了選擇性 程式碼,則回溯程式碼 專案會得到 1 的修飾詞。 否則修飾詞為 0。
.ENDPROLOG 發出序言宣告結尾的訊號。 必須在函式的前 255 個位元組中發生。

以下是使用大部分 Opcode 的範例函式初構:

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 中定義了一組宏,可用來建立典型的程式序言和結尾。

Macro 描述
alloc_stack(n) 配置 n 個位元組的堆疊框架(使用 sub rsp, n ),併發出適當的回溯資訊 (.allocstack n)
save_reg reg loc 在 RSP 位移 loc 儲存堆疊上的非volatile 暫存器 reg ,併發出適當的回溯資訊。 (.savereg reg, loc)
push_reg reg 推送堆疊上的非volatile 暫存器 reg ,併發出適當的回溯資訊。 (.pushreg reg)
rex_push_reg reg 使用 2 位元組推入在堆疊上儲存非揮發暫存器,併發出適當的回溯資訊 (.pushreg reg reg)。 如果推送是函式中的第一個指令,請使用這個宏,以確保函式可熱修補。
save_xmm128 reg loc 在 RSP 位移 loc 儲存堆疊上的非volatile XMM 暫存器 reg ,併發出適當的回溯資訊 (.savexmm128 reg, loc)
set_frame reg offset 將框架暫存器 reg 設定為 RSP + 位移 (使用 movlea ),併發出適當的回溯資訊(.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 軟體慣例