x64 初構和終解
配置堆棧空間、呼叫其他函式、儲存非揮發性緩存器,或使用例外狀況處理的每個函式都必須有一個 prolog,其位址限制會在與個別函式數據表專案相關聯的回溯數據中描述。 如需詳細資訊,請參閱 x64 例外狀況處理。 Prolog 會視需要將自變數緩存器儲存在其主位址中、在堆棧上推送非揮發緩存器、為局部變數和暫時配置堆疊的固定部分,並選擇性地建立框架指標。 相關聯的回溯數據必須描述初構的動作,而且必須提供復原初構程序代碼效果所需的資訊。
如果堆疊中的固定配置是一個以上的頁面(也就是大於 4096 個字節),則堆疊配置可能會跨越一個以上的虛擬記憶體頁面,因此,配置必須在配置之前加以檢查。 從初構呼叫且不會終結任何自變數緩存器的特殊例程會針對此目的提供。
儲存非揮發性緩存器慣用的方法,是在固定堆疊配置之前將它們移至堆疊。 如果在儲存非揮發性緩存器之前執行固定堆疊配置,則最可能需要 32 位位移才能處理儲存的緩存器區域。 (據報導,儘管推播之間隱含相依性,但註冊的推送速度與移動速度一樣快,在可預見的未來應該保持如此。非volatile 緩存器可以依任何順序儲存。 不過,第一次在初構中使用非volatiatile 緩存器必須是儲存它。
Prolog 程式代碼
一般初構的程式代碼可能是:
mov [RSP + 8], RCX
push R15
push R14
push R13
sub RSP, fixed-allocation-size
lea R13, 128[RSP]
...
此初構會將自變數緩存器 RCX 儲存在其主位置、儲存非揮發緩存器 R13-R15、配置堆疊框架的固定部分,並建立框架指標,指向固定配置區域 128 個字節。 使用位移可讓更多固定配置區域使用一位元組位移來處理。
如果固定配置大小大於或等於一頁記憶體,則必須在修改 RSP 之前呼叫協助程式函式。 此協助程式 __chkstk
會探查要配置的堆疊範圍,以確保堆疊已正確擴充。 在此情況下,先前的初構範例會改為:
mov [RSP + 8], RCX
push R15
push R14
push R13
mov RAX, fixed-allocation-size
call __chkstk
sub RSP, RAX
lea R13, 128[RSP]
...
協助 __chkstk
程式不會修改 R10、R11 和條件代碼以外的任何緩存器。 特別是,它會傳回RAX不變,並保留所有非揮發緩存器和自變數傳遞緩存器未修改。
Epilog 程式代碼
在函式的每個結束處都有 Epilog 程式代碼。 雖然通常只有一個初構,但有許多表結。 Epilog 程式代碼會將堆疊修剪為其固定配置大小(如有必要),解除分配固定堆棧配置、藉由從堆棧擷取其儲存的值來還原非揮發緩存器,並傳回 。
epilog 程式代碼必須遵循一組嚴格的規則,讓回溯程式代碼可靠地透過例外狀況和中斷來回溯。 這些規則可減少所需的回溯數據量,因為不需要額外的數據來描述每個表結。 相反地,回溯程式代碼可以藉由透過程式代碼數據流向前掃描以識別 epilog 來判斷要執行的 epilog。
如果函式中未使用框架指標,則 epilog 必須先解除分配堆疊的固定部分、非揮發緩存器會彈出,並將控件傳回給呼叫的函式。 例如,
add RSP, fixed-allocation-size
pop R13
pop R14
pop R15
ret
如果函式中使用框架指標,則堆疊必須修剪成其固定配置,再執行 epilog。 此動作在技術上不是表結的一部分。 例如,下列 epilog 可用來復原先前使用的初構:
lea RSP, -128[R13]
; epilogue proper starts here
add RSP, fixed-allocation-size
pop R13
pop R14
pop R15
ret
在實務上,使用框架指標時,沒有充分的理由在兩個步驟中調整 RSP,因此會改用下列結尾:
lea RSP, fixed-allocation-size - 128[R13]
pop R13
pop R14
pop R15
ret
這些形式是表結的唯一法律形式。 它必須包含 add RSP,constant
或 lea RSP,constant[FPReg]
,後面接著一連串零或多個8位元組緩存器快顯和 return
或 jmp
。 (在表結中,只能允許語句的 jmp
子集。子集是具有ModRM記憶體參考的 jmp
語句類別,其中ModRM mod域值為00。 jmp
禁止在 epilog 中使用具有 ModRM mod 域值 01 或 10 的語句。如需允許的ModRM參考詳細資訊,請參閱 AMD x86-64 架構程式設計人員手冊第 3 卷:一般用途和系統指示中的表格 A-15。沒有其他程式代碼可以出現。 特別是,在表結內無法排程任何專案,包括載入傳回值。
未使用框架指標時,epilog 必須使用 add RSP,constant
來解除分配堆疊的固定部分。 它可能不會改用 lea RSP,constant[RSP]
。 此限制存在,因此回溯程式代碼在搜尋表結時可辨識的模式較少。
遵循這些規則可讓回溯程式代碼判斷目前正在執行 epilog,並模擬執行其餘的 epilog,以允許重新建立呼叫函式的內容。