共用方式為


ARM64 ABI 慣例概觀

在 64 位模式的 ARM 處理器上編譯並執行時,Windows 的基本應用程式二進位介面 (ABI)大部分都遵循 ARM 的標準 AArch64 EABI。 本文會重點說明 EABI 中所記載專案的一些重要假設和變更。 如需 32 位 ABI 的相關資訊,請參閱 ARM ABI 慣例概觀 。 如需標準 ARM EABI 的詳細資訊,請參閱 ARM 架構 的應用程式二進位介面 (ABI) (外部連結)。

定義

隨著 64 位支援的引進,ARM 已定義數個詞彙:

  • AArch32 – ARM 所定義的舊版 32 位指令集架構(ISA),包括 Thumb 模式執行。
  • AArch64 – ARM 所定義的新 64 位指令集架構 (ISA)。
  • ARMv7 – 第 7 代 ARM 硬體的規格,其中只包含 AArch32 的支援。 此版本的 ARM 硬體是支援 ARM 的第一個 Windows 版本。
  • ARMv8 – 第 8 代 ARM 硬體的規格,其中包含 AArch32 和 AArch64 的支援。

Windows 也會使用這些詞彙:

  • ARM – 是指 32 位 ARM 架構(AArch32),有時稱為 WoA(ARM 上的 Windows)。
  • ARM32 – 與上述 ARM 相同;為了清楚起見,本檔使用。
  • ARM64 – 是指 64 位 ARM 架構 (AArch64)。 沒有像 WoA64 這樣的事情。

最後,參考資料類型時,會參考 ARM 中的下列定義:

  • Short-Vector – 在 SIMD 中直接表示的資料類型,向量為 8 個位元組或 16 個位元組的元素。 它對齊其大小,可以是 8 個位元組或 16 個位元組,其中每個元素可以是 1、2、4 或 8 個位元組。
  • HFA (同質浮點匯總) – 具有 2 到 4 個相同浮點成員、浮點數或雙精度浮點數的資料類型。
  • HVA (同質短向量匯總) – 具有 2 到 4 個相同 Short-Vector 成員的資料類型。

基本需求

ARM64 版本的 Windows 預先假設它隨時都在 ARMv8 或更新版本的架構上執行。 浮點數和 NEON 支援都假設存在於硬體中。

ARMv8 規格描述 AArch32 和 AArch64 的新選擇性密碼編譯和 CRC 協助程式作業碼。 他們的支援目前是選擇性的,但建議使用。 若要利用這些 opcode,應用程式應該先進行執行時間檢查,以檢查其是否存在。

位元組序

如同 ARM32 版本的 Windows,在 ARM64 Windows 上會以小端模式執行。 若在 AArch64 中支援核心模式,切換結束度很難達成,因此更容易強制執行。

對齊方式

在 ARM64 上執行的 Windows 可讓 CPU 硬體以透明方式處理未對齊的存取。 在 AArch32 的改進中,這項支援現在也適用于所有整數存取(包括多字存取),以及浮點存取。

不過,仍必須一律對齊未快取(裝置)記憶體的存取權。 如果程式碼可能從未快取的記憶體讀取或寫入錯誤資料,則必須確定所有存取都一致。

區域變數的預設版面配置對齊方式:

以位元組為單位的大小 以位元組為單位的對齊方式
1 7
2 2
3, 4 4
> 4 8

全域和靜態的預設版面配置對齊方式:

以位元組為單位的大小 以位元組為單位的對齊方式
1 1
2 - 7 4
8 - 63 8
>= 64 16

整數暫存器

AArch64 架構支援 32 個整數暫存器:

註冊 波動 角色
x0-x8 動態 參數/結果臨時暫存器
x9-x15 動態 臨時暫存器
x16-x17 動態 程式內呼叫臨時暫存器
x18 N/A 保留的平臺暫存器:在核心模式中,指向目前處理器的 KPCR;在使用者模式中,指向 TEB
x19-x28 靜態 臨時暫存器
x29/fp 靜態 框架指標
x30/lr 兩者 連結暫存器:被呼叫端函式必須保留它以傳回它,但呼叫端的值將會遺失。

每個暫存器可以存取為完整的 64 位值(透過 x0-x30)或 32 位值(透過 w0-w30)。 32 位作業會將其結果零延伸至 64 位。

如需參數暫存器使用的詳細資訊,請參閱參數傳遞一節。

不同于 AArch32,程式計數器 (PC) 和堆疊指標 (SP) 不會編制索引暫存器。 其存取方式有限。 另請注意,沒有 x31 暫存器。 該編碼用於特殊用途。

框架指標 (x29) 與 ETW 和其他服務所使用的快速堆疊步行相容。 它必須指向堆疊上先前的 {x29, x30} 組。

浮點/SIMD 暫存器

AArch64 架構也支援 32 個浮點/SIMD 暫存器,摘要如下:

註冊 波動 角色
v0-v7 動態 參數/結果臨時暫存器
v8-v15 兩者 低 64 位為非揮發性。 高 64 位是 Volatile。
v16-v31 動態 臨時暫存器

每個暫存器都可以以完整的 128 位值存取(透過 v0-v31 或 q0-q31)。 它可存取為 64 位值(透過 d0-d31)、32 位值(透過 s0-s31)、16 位值(透過 h0-h31)或 8 位值(透過 b0-b31)。 存取小於 128 位的存取只會存取完整 128 位暫存器中的較低位。 除非另有指定,否則它們會讓其餘位維持不變。 (AArch64 與 AArch32 不同,其中較小的暫存器被包裝在較大的暫存器之上。

浮點控制項暫存器 (FPCR) 在其內的各種位欄位上具有特定需求:

Bits 意義 波動 角色
26 AHP 非揮發性 替代半精確度控制項。
25 DN 非揮發性 預設 NaN 模式控制項。
24 FZ 靜態 排清到零模式控制項。
23-22 RMode 靜態 四捨五入模式控制項。
15,12-8 IDE/IXE/etc 非揮發性 例外狀況陷阱啟用位,必須一律為 0。

系統暫存器

如同 AArch32,AArch64 規格提供三個系統控制的「執行緒識別碼」暫存器:

註冊 角色
TPIDR_EL0 保留。
TPIDRRO_EL0 包含目前處理器的 CPU 號碼。
TPIDR_EL1 指向目前處理器的 KPCR 結構。

浮點例外狀況

AArch64 系統上支援 IEEE 浮點例外狀況是選擇性的。 對於具有硬體浮點例外狀況的處理器變體,Windows 核心會以無訊息方式攔截例外狀況,並在 FPCR 暫存器中隱含停用這些例外狀況。 這個陷阱可確保跨處理器變體的正規化行為。 否則,在沒有例外狀況支援的情況下,在平臺上開發的程式碼可能會在支援的平臺上執行時,發現自己會採取非預期的例外狀況。

參數傳遞

針對非變異函式,Windows ABI 會遵循 ARM 為參數傳遞所指定的規則。 這些規則會直接從 AArch64 架構的程式調用標準中摘錄:

階段 A – 初始化

這個階段剛好完成一次,再開始處理引數。

  1. 下一個一般用途暫存器號碼 (NGRN) 設定為零。

  2. 下一個 SIMD 和浮點暫存器號碼 (NSRN) 設定為零。

  3. 下一個堆疊引數位址 (NSAA) 會設定為目前的堆疊指標值 (SP)。

階段 B – 引數的預先填補和延伸

針對清單中的每個引數,會套用下列清單中的第一個比對規則。 如果沒有符合規則,則會使用未修改引數。

  1. 如果引數類型是複合類型,其大小不能由呼叫端和被呼叫者靜態決定,引數會複製到記憶體,而引數會由複本的指標取代。 (C/C++ 中沒有這類類型,但它們存在於其他語言或語言延伸模組中。

  2. 如果引數類型是 HFA 或 HVA,則會使用未修改的引數。

  3. 如果引數類型是大於 16 個位元組的複合類型,則會將引數複製到呼叫端配置的記憶體,而引數會由複本的指標取代。

  4. 如果引數類型是複合類型,則引數的大小會四捨五入為最接近的 8 個位元組倍數。

階段 C – 將引數指派給暫存器和堆疊

針對清單中的每個引數,會接著套用下列規則,直到已配置引數為止。 將引數指派給暫存器時,暫存器中的任何未使用位都有未指定的值。 如果引數指派給堆疊位置,則任何未使用的填補位元組都有未指定的值。

  1. 如果引數是半精確度、單精確度、雙精度浮點數或短向量類型,且 NSRN 小於 8,則會將引數配置給暫存器 v[NSRN] 最小有效位。 NSRN 會遞增一個。 引數現在已配置。

  2. 如果引數是 HFA 或 HVA,而且有足夠的未配置 SIMD 和浮點暫存器(NSRN + 成員數目 ≤ 8),則引數會配置給 SIMD 和浮點暫存器,每一個 HFA 或 HVA 成員一個暫存器。 NSRN 會以所使用的暫存器數目遞增。 引數現在已配置。

  3. 如果引數是 HFA 或 HVA,則 NSRN 會設定為 8,而引數的大小會四捨五入為最接近的 8 個位元組倍數。

  4. 如果引數是 HFA、HVA、四精確度浮點數或短向量類型,則 NSAA 會四捨五入為 8 的較大值或引數類型的自然對齊。

  5. 如果引數是半精確度或單精確度浮點類型,則引數的大小會設定為 8 個位元組。 效果就像引數已複製到 64 位暫存器中最小有效位,而其餘位則填入未指定值。

  6. 如果引數是 HFA、HVA、半精確度、單精確度、雙精度浮點數或短向量類型,則會將引數複製到調整後的 NSAA 記憶體中。 NSAA 會增加引數的大小。 引數現在已配置。

  7. 如果引數是整數或指標類型,引數的大小會小於或等於 8 個位元組,而 NGRN 小於 8,引數會複製到 x[NGRN] 中最小有效位。 NGRN 會遞增一個。 引數現在已配置。

  8. 如果引數的對齊方式為 16,則 NGRN 會四捨五入為下一個偶數。

  9. 如果引數是整數類型,則引數的大小等於 16,而 NGRN 小於 7,引數會複製到 x[NGRN] 和 x[NGRN+1]。 x[NGRN] 應包含引數記憶體標記法的較低定址雙字。 NGRN 會遞增兩個。 引數現在已配置。

  10. 如果引數是複合類型,且引數的雙字大小不超過 8 減 NGRN,則引數會複製到連續的一般用途暫存器,從 x[NGRN 開始]。 引數會傳遞,就像它已從雙字對齊位址載入暫存器一樣,並具有從記憶體載入連續暫存器的適當 LDR 指令序列。 此標準未指定暫存器中任何未使用部分的內容。 NGRN 會以所使用的暫存器數目遞增。 引數現在已配置。

  11. NGRN 設定為 8。

  12. NSAA 會四捨五入為 8 或引數類型的自然對齊。

  13. 如果引數是複合類型,則會將引數複製到調整後的 NSAA 記憶體。 NSAA 會增加引數的大小。 引數現在已配置。

  14. 如果引數的大小小於 8 個位元組,則引數的大小會設定為 8 個位元組。 效果就好像引數已複製到 64 位暫存器中最小有效位,而其餘位則填入未指定的值。

  15. 引數會複製到調整後的 NSAA 記憶體。 NSAA 會增加引數的大小。 引數現在已配置。

Addendum:Variadic 函式

採用可變引數數目的函式會以不同于上述方式處理,如下所示:

  1. 所有複合都會一樣處理;不特別處理 HFA 或 HVA。

  2. 不會使用 SIMD 和浮點暫存器。

實際上,將引數配置至虛構堆疊的規則 C.12–C.15 與下列規則相同,其中前 64 個位元組的堆疊會載入 x0-x7,而任何剩餘的堆疊引數通常會放置。

傳回值

整數值會在 x0 中傳回。

浮點值會視情況在 s0、d0 或 v0 中傳回。

如果下列所有保留專案,類型會被視為 HFA 或 HVA:

  • 它不是空的,
  • 它沒有任何非簡單的預設或複製建構函式、解構函式或指派運算子,
  • 其所有成員具有相同的 HFA 或 HVA 類型,或是符合其他成員 HFA 或 HVA 類型的 float、double 或 neon 類型。

在 s0-s3、d0-d3 或 v0-v3 中傳回具有四個或更少專案的 HVA 值。

傳回的型別會根據是否有特定屬性,以及函式是否為非靜態成員函式,以不同的方式處理。 具有所有這些屬性的類型,

  • 它們是由 C++14 標準定義所匯總 ,也就是說,他們沒有使用者提供的建構函式、沒有私人或受保護的非靜態資料成員、沒有基類,也沒有虛擬函式,以及
  • 他們有一個簡單的複製指派運算子,以及
  • 他們有一個微不足道的解構函式,

和 是由非成員函式或靜態成員函式傳回,請使用下列傳回樣式:

  • 在 s0-s3、d0-d3 或 v0-v3 中會傳回具有四個或更少元素的 HFA 類型。
  • 小於或等於 8 個位元組的類型會在 x0 中傳回。
  • 小於或等於 16 個位元組的類型會在 x0 和 x1 中傳回,其中 x0 包含低階 8 個位元組。
  • 對於其他匯總類型,呼叫端應保留足夠大小和對齊方式的記憶體區塊,以保存結果。 記憶體區塊的位址應該以 x8 中函式的額外引數的形式傳遞。 被呼叫者可以在副程式執行期間的任何時間點修改結果記憶體區塊。 呼叫端不需要保留儲存在 x8 中的值。

所有其他類型都會使用此慣例:

  • 呼叫端應保留足夠大小和對齊方式的記憶體區塊,以保存結果。 記憶體區塊的位址應當做 x0 中函式的額外引數傳遞,如果 x0 中傳遞$this則為 x1。 被呼叫者可以在副程式執行期間的任何時間點修改結果記憶體區塊。 被呼叫者會傳回 x0 中記憶體區塊的位址。

Stack

在 ARM 提出的 ABI 之後,堆疊必須隨時保持 16 位元組對齊。 AArch64 包含硬體功能,可在 SP 未對齊 16 位元組且完成 SP 相對負載或存放區時產生堆疊對齊錯誤。 Windows 會隨時啟用此功能執行。

配置 4k 或更多堆疊值的函式,必須確保每個頁面在最後一頁之前依序觸控。 此動作可確保 Windows 用來展開堆疊的防護頁面無法「跳躍」任何程式碼。 一般而言,觸控是由 __chkstk 協助程式所完成,其具有自訂呼叫慣例,其會通過 x15 中除以 16 的總堆疊配置。

紅色區域

目前堆疊指標正下方的 16 位元組區域會保留供分析和動態修補案例使用。 此區域允許插入仔細產生的程式碼,以將兩個暫存器儲存在 [sp, #-16] ,並暫時將其用於任意用途。 Windows 核心保證在使用者和核心模式中都採用例外狀況或中斷時,不會覆寫這 16 個位元組。

核心堆疊

Windows 中的預設核心模式堆疊是六頁(24k)。 請特別注意核心模式中具有大型堆疊緩衝區的函式。 一個不及時的中斷可能會有很少的頭部,並建立堆疊恐慌錯誤檢查。

堆疊行走

Windows 中的程式碼會使用啟用框架指標來編譯 , 以啟用快速堆疊行走。 一般而言,x29 (fp) 會指向鏈結中的下一個連結,也就是 {fp, lr} 組,表示堆疊上上一個框架的指標和傳回位址。 鼓勵協力廠商程式碼啟用框架指標,以允許改善的分析與追蹤。

例外狀況回溯

例外狀況處理期間的回溯會透過使用回溯程式碼來協助。 回溯程式碼是儲存在可執行檔之 .xdata 區段中的位元組序列。 它們以抽象的方式描述序言和結尾的作業,以便復原函式序言的效果,以準備備份至呼叫端的堆疊框架。 如需回溯程式碼的詳細資訊,請參閱 ARM64 例外狀況處理

ARM EABI 也會指定使用回溯程式碼的例外狀況回溯模型。 不過,所呈現的規格不足以在 Windows 中回溯,這必須處理電腦位於函式序言或結尾的案例。

動態產生的程式碼應該透過 RtlAddFunctionTable 和 相關聯的函式來描述動態函式資料表,讓產生的程式碼可以參與例外狀況處理。

迴圈計數器

所有 ARMv8 CPU 都必須支援迴圈計數器暫存器,這是 Windows 設定在任何例外狀況層級可讀取的 64 位暫存器,包括使用者模式。 它可透過特殊PMCCNTR_EL0暫存器、在元件程式碼中使用 MSR opcode 或 _ReadStatusReg C/C++ 程式碼中的內建來存取。

這裡的迴圈計數器是真正的迴圈計數器,而不是時鐘。 計數頻率會隨著處理器頻率而有所不同。 如果您覺得必須知道迴圈計數器的頻率,就不應該使用迴圈計數器。 相反地,您想要測量時鐘時間,您應該使用 QueryPerformanceCounter

另請參閱

Visual C++ ARM 移轉時常見的問題
ARM64 例外狀況處理