ARM64EC ABI 慣例概觀
ARM64EC 是一種應用程式二進位介面 (ABI),可讓 ARM64 二進位檔以原生方式執行並與 x64 程式碼互通運作。 具體來說,ARM64EC ABI 遵循 x64 軟體慣例,包括呼叫慣例、堆疊使用方式和資料對齊,讓 ARM64EC 和 x64 程式碼可以互通運作。 作業系統會模擬二進位檔的 x64 部分。 (ARM64EC 中的 EC 代表模擬相容。)
如需 x64 和 ARM64 ABI 的詳細資訊,請參閱 x64 ABI 慣例概觀和 ARM64 ABI 慣例概觀。
ARM64EC 無法解決 x64 與 ARM 型結構之間的記憶體模型差異。 如需詳細資訊,請參閱常見的 Visual C++ ARM 移轉問題。
定義
- ARM64 - 包含傳統 ARM64 程式碼的 ARM64 程式碼串流處理。
- ARM64EC - 程式碼串流使用 ARM64 登錄組子集來提供 x64 程式碼的互通性。
登錄對應
x64 處理可能有執行 ARM64EC 程式碼的執行緒。 因此,一律可以擷取 x64 登錄內容,ARM64EC 使用 ARM64 核心登錄子集,採用 1:1 對應來模擬 x64 登錄。 重要的是,ARM64EC 絕不會使用此子集以外的登錄,但不包括從 x18
讀取執行緒環境區塊 (TEB) 位址。
當部分或許多函式重新編譯為 ARM64EC 時,原生 ARM64 處理不應發生效能退化。 為了維持效能,ABI 遵循下列原則:
ARM64EC 登錄子集包含屬於 ARM64 函式呼叫慣例的所有登錄。
ARM64EC 呼叫慣例會直接對應至 ARM64 呼叫慣例。
特殊協助程式 (例如 __chkstk_arm64ec
) 使用自訂呼叫慣例和登錄。 這些登錄也包含在登錄的 ARM64EC 子集中。
整數登錄的登錄對應
ARM64EC 登錄 | x64 登錄 | ARM64EC 呼叫慣例 | ARM64 呼叫慣例 | x64 呼叫慣例 |
---|---|---|---|---|
x0 |
rcx |
volatile | volatile | volatile |
x1 |
rdx |
volatile | volatile | volatile |
x2 |
r8 |
volatile | volatile | volatile |
x3 |
r9 |
volatile | volatile | volatile |
x4 |
r10 |
volatile | volatile | volatile |
x5 |
r11 |
volatile | volatile | volatile |
x6 |
mm1 (x87 R1 登錄的低 64 位元) |
volatile | volatile | volatile |
x7 |
mm2 (x87 R2 登錄的低 64 位元) |
volatile | volatile | volatile |
x8 |
rax |
volatile | volatile | volatile |
x9 |
mm3 (x87 R3 登錄的低 64 位元) |
volatile | volatile | volatile |
x10 |
mm4 (x87 R4 登錄的低 64 位元) |
volatile | volatile | volatile |
x11 |
mm5 (x87 R5 登錄的低 64 位元) |
volatile | volatile | volatile |
x12 |
mm6 (x87 R6 登錄的低 64 位元) |
volatile | volatile | volatile |
x13 |
N/A | 不允許 | volatile | N/A |
x14 |
N/A | 不允許 | volatile | N/A |
x15 |
mm7 (x87 R7 登錄的低 64 位元) |
volatile | volatile | volatile |
x16 |
每個 x87 R0 -R3 登錄的高 16 位元 |
變動(xip0 ) |
變動(xip0 ) |
volatile |
x17 |
每個 x87 R4 -R7 登錄的高 16 位元 |
變動(xip1 ) |
變動(xip1 ) |
volatile |
x18 |
GS.base | 固定(TEB) | 固定(TEB) | 固定(TEB) |
x19 |
r12 |
非變動 | 非變動 | 非變動 |
x20 |
r13 |
非變動 | 非變動 | 非變動 |
x21 |
r14 |
非變動 | 非變動 | 非變動 |
x22 |
r15 |
非變動 | 非變動 | 非變動 |
x23 |
N/A | 不允許 | 非變動 | N/A |
x24 |
N/A | 不允許 | 非變動 | N/A |
x25 |
rsi |
非變動 | 非變動 | 非變動 |
x26 |
rdi |
非變動 | 非變動 | 非變動 |
x27 |
rbx |
非變動 | 非變動 | 非變動 |
x28 |
N/A | 不允許 | 不允許 | N/A |
fp |
rbp |
非變動 | 非變動 | 非變動 |
lr |
mm0 (x87 R0 登錄的低 64 位元) |
兩者 | 兩者 | 兩者 |
sp |
rsp |
非變動 | 非變動 | 非變動 |
pc |
rip |
指令指標 | 指令指標 | 指令指標 |
PSTATE 子集:N /Z /C /V /SS 1、2 |
RFLAGS 子集:SF /ZF /CF /OF /TF |
volatile | volatile | volatile |
N/A | RFLAGS 子集:PF /AF |
N/A | N/A | volatile |
N/A | RFLAGS 子集:DF |
N/A | N/A | 非變動 |
1 避免在 PSTATE
和 RFLAGS
之間直接讀取、寫入或計算對應。 日後可能會使用這些位元,且可能會變更。
2 ARM64EC 進位旗標 C
是減法作業中 x64 進位旗標 CF
的反轉。 沒有特殊處理,因為旗標是揮發性,因此在 (ARM64EC 和 x64) 函式之間轉換時會被清除。
向量快取器註冊對應
ARM64EC 登錄 | x64 登錄 | ARM64EC 呼叫慣例 | ARM64 呼叫慣例 | x64 呼叫慣例 |
---|---|---|---|---|
v0 -v5 |
xmm0 -xmm5 |
volatile | volatile | volatile |
v6 -v7 |
xmm6 -xmm7 |
volatile | volatile | 非變動 |
v8 -v15 |
xmm8 -xmm15 |
變動 1 | 變動 1 | 非變動 |
v16 -v31 |
xmm16 -xmm31 |
不允許 | volatile | 不允許 (x64 模擬器不支援 AVX-512) |
FPCR 2 |
MXCSR[15:6] |
非變動 | 非變動 | 非變動 |
FPSR 2 |
MXCSR[5:0] |
volatile | volatile | volatile |
1 這些 ARM64 登錄很特別,低 64 位元是非變動,但高 64 位元是變動性。 從 x64 呼叫者的觀點來看,它們實際上是變動性的,因為被呼叫者會清除資料。
2 避免直接讀取、寫入或計算 FPCR
和 FPSR
的對應。 日後可能會使用這些位元,且可能會變更。
結構封裝
ARM64EC 遵循 x64 所使用的相同結構封裝規則,以確保 ARM64EC 程式碼和 x64 程式碼之間的互通性。 如需 x64 結構封裝的詳細資訊和範例,請參閱 x64 ABI 慣例概觀。
模擬協助程式 ABI 常式
ARM64EC 程式碼和 Thunks 使用模擬協助程式常式在 x64 和 ARM64EC 函式之間轉換。
下表描述每個特殊的 ABI 常式,以及 ABI 所使用的登錄。 常式不會修改 ABI 資料行底下列出的保留登錄。 不應對未列出的登錄進行任何假設。 在磁碟上,ABI 常式指標為 Null。 載入器會在載入時更新指標,以指向 x64 模擬器常式。
名稱 | 描述 | ABI |
---|---|---|
__os_arm64x_dispatch_call_no_redirect |
由結束 Thunk 呼叫以呼叫 x64 目標 (x64 函式或 x64 向前快轉序列)。 常式會推送 ARM64EC 傳回位址 (在 LR 登錄中),後面接著指令位址,再接著叫用 x64 模擬器的 blr x16 指令。 然後執行 blr x16 指令 |
在 x8 (rax ) 中傳回值 |
__os_arm64x_dispatch_ret |
由輸入 Thunk 呼叫,以傳回其 x64 呼叫者。 從堆疊快顯 x64 傳回位址,並叫用 x64 模擬器以跳至該位址 | N/A |
__os_arm64x_check_call |
由 ARM64EC 程式碼呼叫,其中包含結束 Thunk 的指標,以及要執行的間接 ARM64EC 目標位址。 ARM64EC 目標視為可修補,而且執行一律會以呼叫使用的相同資料或修改過的資料傳回至呼叫者 | 引數:x9 :目標位址x10 :結束 thunk 位址x11 :向前快轉序列位址輸出: x9 :如果目標函式被繞道,會包含向前快轉序列的位址x10 :結束 thunk 位址x11 :如果函式被繞道,會包含結束 Thunk 位址。 否則,會跳至目標位址保留的登錄: x0 -x8 、x15 (chkstk ) 和 q0 -q7 |
__os_arm64x_check_icall |
由 ARM64EC 程式碼呼叫,使用指向結束 Thunk 的指標,以處理跳至 x64 或 ARM64EC 的目標位址。 如果目標為 x64 且 x64 程式碼尚未修補,則常式會設定目標位址登錄。 它會指向函式的 ARM64EC 版本 (如果有的話)。 否則,它會將登錄設定為指向轉換至 x64 目標的結束 Thunk。 然後,它會返回呼叫 ARM64EC 程式碼,接著跳至登錄中的位址。 此常式是非最佳化的 __os_arm64x_check_call 版本,其中目標位址在編譯時並不知道用於間接呼叫的呼叫位置 |
引數:x9 :目標位址x10 :結束 thunk 位址x11 :向前快轉序列位址輸出: x9 :如果目標函式被繞道,會包含向前快轉序列的位址x10 :結束 thunk 位址x11 :如果函式被繞道,會包含結束 Thunk 位址。 否則,會跳至目標位址保留的登錄: x0 -x8 、x15 (chkstk ) 和 q0 -q7 |
__os_arm64x_check_icall_cfg |
與 __os_arm64x_check_icall 相同,但也會檢查指定的位址是否為有效的控制流程圖間接呼叫目標 |
引數:x10 :結束 Thunk 的位址x11 :目標函式的位址輸出: x9 :如果目標為 x64,則為函式的位址。 否則為未定義x10 :結束 Thunk 的位址x11 :如果目標為 x64,則包含結束 Thunk 的位址。 否則為函式的位址保留的登錄: x0 -x8 、x15 (chkstk ) 和 q0 -q7 |
__os_arm64x_get_x64_information |
取得即時 x64 登錄內容的要求部分 | _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo) |
__os_arm64x_set_x64_information |
設定即時 x64 登錄內容的要求部分 | _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo) |
__os_arm64x_x64_jump |
用於無簽章調整器和其他 thunk,將呼叫直接轉送 (jmp ) 至另一個可以使用任何簽章的函式,將右 Thunk 的潛在應用延後至實際目標 |
引數:x9 :要跳至的目標保留的所有參數登錄 (轉送) |
Thunk
Thunk 是支援 ARM64EC 和 x64 函式彼此呼叫的低階機制。 有兩種類型:輸入 thunk 用於輸入 ARM64EC 函式,以及結束 thunk 用於呼叫 x64 函式。
輸入 thunk 和內部輸入 thunk:x64 對 ARM64EC 函式呼叫
若要在 C/C++ 函式編譯為 ARM64EC 時支援 x64 呼叫者,工具鏈會產生由 ARM64EC 電腦程式碼組成的單一輸入 Thunk。 內部有專用的輸入 thunk。 所有其他函式都會與具有相符呼叫慣例、參數和傳回類型的所有函式共享輸入 Thunk。 Thunk 的內容取決於 C/C++ 函式的呼叫慣例。
除了處理參數和傳回位址之外,Thunk 還橋接 ARM64EC 與 x64 向量登錄之間因 ARM64EC 向量登錄對應所造成的變動性差異:
ARM64EC 登錄 | x64 登錄 | ARM64EC 呼叫慣例 | ARM64 呼叫慣例 | x64 呼叫慣例 |
---|---|---|---|---|
v6 -v15 |
xmm6 -xmm15 |
變動性,但儲存/還原在輸入 thunk 中 (x64 至 ARM64EC) | 變動或部分變動高 64 位元 | 非變動 |
輸入 Thunk 會執行下列動作:
參數號碼 | 堆疊使用方式 |
---|---|
0-4 | 將 ARM64EC v6 和 v7 儲存到呼叫者配置的主空間由於被呼叫者是 ARM64EC,沒有主空間的概念,因此預存的值不會受到阻礙。 在堆疊上配置額外的 128 個位元組,並透過 v15 儲存 ARM64EC v8 。 |
5-8 | x4 = 來自堆疊的第 5 個參數x5 = 來自堆疊的第 6 個參數x6 = 來自堆疊的第 7 個參數x7 = 來自堆疊的第 8 個參數如果參數是 SIMD,則會改為使用 v4 -v7 登錄 |
+9 | 在堆疊上配置 AlignUp(NumParams - 8 , 2) * 8 位元組。 *將第 9 個和剩餘的參數複製到此區域 |
* 將值對齊偶數可保證堆疊保持對齊 16 個位元組
如果函式接受 32 位元整數參數,則允許 thunk 只推送 32 位元,而不是父登錄的完整 64 位元。
接下來,thunk 會使用 ARM64 bl
指令來呼叫 ARM64EC 函式。 函式傳回之後,thunk:
- 復原任何堆疊配置
- 呼叫
__os_arm64x_dispatch_ret
模擬器協助程式以快顯 x64 傳回位址並繼續 x64 模擬。
結束 thunk:ARM64EC 至 x64 函式呼叫
對於 ARM64EC C/C++ 函式對潛在 x64 程式碼所發出的每個呼叫,MSVC 工具鏈都會產生一個結束 Thunk。 Thunk 的內容取決於 x64 被呼叫者的參數,以及被呼叫者是否使用標準呼叫慣例或 __vectorcall
。 編譯器會從被呼叫者的函式宣告中取得這項資訊。
首先,Thunk 會推送 ARM64EC lr
登錄中的傳回位址和虛擬 8 位元組值,以保證堆疊對齊 16 位元組。 其次,Thunk 會處理參數:
參數號碼 | 堆疊使用方式 |
---|---|
0-4 | 在堆疊上配置 32 位元組的主空間 |
5-8 | 在堆疊上配置更高的 AlignUp(NumParams - 4, 2) * 8 以上位元組。 * 將第 5 個和任何後續參數從 ARM64EC 的 x4 -x7 複製到這個額外空間 |
+9 | 將第 9 個和剩餘的參數複製到額外空間 |
* 將值對齊偶數以保證堆疊保持對齊 16 位元組。
第三,Thunk 會呼叫 __os_arm64x_dispatch_call_no_redirect
模擬器協助程式來叫用 x64 模擬器以執行 x64 函式。 呼叫必須是 blr x16
指令 (為了方便起見,x16
是變動登錄)。 因為 x64 模擬器會將此指令剖析為提示,因此需要 blr x16
指令。
x64 函式通常會使用 x64 ret
指令,嘗試傳回模擬器協助程式。 此時,x64 模擬器會偵測到它位於 ARM64EC 程式碼中。 然後,它會讀取先前的 4 位元組提示,該提示恰好是 ARM64 blr x16
指令。 由於此提示指出傳回位址是在此協助程式中,因此模擬器會直接跳至此位址。
允許 x64 函式使用任何分支指令返回模擬器協助程式,包括 x64 jmp
和 call
。 模擬器也會處理這些案例。
當協助程式接著返回 Thunk 時,thunk:
- 復原任何堆疊配置
- 快顯 ARM64EC
lr
登錄 - 執行 ARM64
ret lr
指令。
ARM64EC 函式名稱裝飾
ARM64EC 函式名稱會在任何語言特定裝飾之後套用次要裝飾。 對於含有 C 連結的函式 (無論是編譯為 C 還是使用 extern "C"
),都會在名稱前面加上 #
。 對於 C++ 裝飾函式,會在名稱中插入 $$h
標記。
foo => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ
__vectorcall
ARM64EC 工具鏈目前不支援 __vectorcall
。 當編譯器使用 ARM64EC 偵測到 __vectorcall
使用時,就會發出錯誤。