共用方式為


ARM32 ABI 慣例概觀

此針對 Windows on ARM 處理器程式碼編譯的應用程式二進位介面 (ABI) 是以標準 ARM EABI 為基礎。 本文章重點說明 Windows on ARM 和標準之間的主要差異。 本文件涵蓋ARM32 ABI。 如需 ARM64 ABI 的相關信息,請參閱 ARM64 ABI 慣例概觀。 如需標準 ARM EABI 的詳細資訊,請參閱 ARM 架構 的應用程式二進位介面 (ABI) (外部連結)。

基本需求

ARM 上的 Windows 一律假設它在 ARMv7 架構上執行。 硬體中必須存在 VFPv3-D32 格式或以後版本的浮點數支援。 VFP 必須在硬體中支援單精確度和雙精確度浮點。 Windows 運行時間不支持模擬浮點,以在非 VFP 硬體上執行。

進階 SIMD 延伸模組 (NEON) 支援,包括整數和浮點運算,也必須存在於硬體中。 不提供對模擬的執行階段支援。

建議使用整數除法支援 (UDIV/SDIV),但並非必要。 不支援整數除法的平台可能會發生效能損失,因為必須對這些運算進行截取,也可能需要對其進行修補。

位元組序

Windows on ARM 在由小到大的格式模式下執行。 MSVC 編譯程式和 Windows 運行時間一律需要小端數據。 ARM 指令集架構中的 SETEND 指令可讓即使是使用者模式程式代碼變更目前的結束度。 不過,不建議這麼做,因為應用程式很危險。 如果例外狀況是在大端模式中產生,則行為是無法預測的。 這可能會導致使用者模式中的應用程式錯誤,或核心模式中的錯誤檢查。

對齊方式

雖然 Windows 可讓 ARM 硬體無障礙地控制未對齊的整數存取,但在某些情況下,仍可能產生對齊錯誤。 請遵循下列規則,來進行對齊:

  • 您不需要對齊半字大小 (16 位) 和文字大小 (32 位) 整數載入和儲存。 硬體會高效且無障礙地處理它們。

  • 浮點載入和儲存應該對齊。 核心會無障礙地處理未對齊的載入和儲存,但額外負荷會很大。

  • 載入或儲存加倍 (LDRD/STRD) 和多重 (LDM/STM) 運算應該對齊。 核心會無障礙地處理大部分的運算,但額外負荷會很大。

  • 所有未快取的記憶體存取必須對齊,整數存取亦如此。 未對齊的存取會導致對齊錯誤。

指令集

ARM 上 Windows 的指令集嚴格限制為 Thumb-2。 在此平台上執行的所有程式代碼都預期會啟動,且一律維持在 Thumb 模式中。 嘗試切換到舊版 ARM 指令集可能會成功。 不過,如果這樣做,則發生的任何例外狀況或中斷都可能導致使用者模式中的應用程式錯誤,或核心模式中的錯誤檢查。

此需求的副作用是所有程式碼指標必須設定為低位元。 然後,當它們透過BLX或BX載入並分支至時,處理器會維持在Thumb模式中。 它不會嘗試以 32 位 ARM 指令的形式執行目標程式代碼。

SDIV/UDIV 指示

完全支援使用整數除法指令 SDIV 和 UDIV,即使在沒有原生硬體處理它們的平台上也支援。 Cortex-A9 處理器上每個 SDIV 或 UDIV 除法的額外額外負荷大約是 80 個週期。 這會根據輸入,新增至 20-250 循環的整體除法時間。

整數快取器

ARM 處理器支援 16 個整數暫存器:

註冊 動態? 角色
r0 動態 參數、結果、臨時暫存器 1
r1 動態 參數、結果、臨時暫存器 2
r2 動態 參數、臨時暫存器 3
r3 動態 參數、臨時暫存器 4
r4 靜態
r5 靜態
r6 靜態
r7 靜態
r8 靜態
r9 靜態
r10 靜態
r11 靜態 框架指標
r12 動態 程序內呼叫臨時暫存器
r13 (SP) 靜態 堆疊指標
r14 (LR) 靜態 連結暫存器
r15 (PC) 靜態 程式計數器

如需如何使用參數並傳回值暫存器的詳細資料,請參閱本文章中的<參數傳遞>一節。

Windows 使用 r11 進行堆疊框架的快速查核行程。 如需詳細資訊,請參閱<堆疊查核行程>一節。 由於這項需求,r11 必須一律指向鏈結中最上層的連結。 請勿將 r11 用於一般用途,因為您的程式代碼不會在分析期間產生正確的堆疊逐步解說。

VFP 快取器

Windows 僅支援具有 VFPv3-D32 副處理器支援的 ARM 變異。 這表示浮點緩存器一律存在,而且可以依賴參數傳遞。 而且,有 32 個緩存器的完整集合可供使用。 VFP 暫存器及其使用方式在下表中彙總:

單核心 雙核心 四核心 動態? 角色
s0-s3 d0-d1 q0 動態 參數、結果、臨時暫存器
s4-s7 d2-d3 q1 動態 參數、臨時暫存器
s8-s11 d4-d5 q2 動態 參數、臨時暫存器
s12-s15 d6-d7 q3 動態 參數、臨時暫存器
s16-s19 d8-d9 q4 靜態
s20-s23 d10-d11 q5 靜態
s24-s27 d12-d13 q6 靜態
s28-s31 d14-d15 q7 靜態
d16-d31 q8-q15 動態

下一個表格說明浮點狀態和控制暫存器 (FPSCR) 位元欄位:

Bits 意義 動態? 角色
31-28 NZCV 動態 狀態旗標
27 QC 動態 累加飽和度
26 AHP 靜態 替代半精確度控制
25 DN 靜態 預設 NaN 模式控制
24 FZ 靜態 清除為零模式控制
23-22 RMode 靜態 捨入模式控制
21-20 分散 靜態 向量分散,必須一律為 0
18-16 Len 靜態 向量長度,必須一律為 0
15, 12-8 IDE、IXE 等等 靜態 例外狀況截取啟用位元,必須一律為 0
7, 4-0 IDC、IXC 等等 動態 累加例外狀況旗標

浮點例外狀況

大部分 ARM 硬體都不支援 IEEE 浮點例外狀況。 在具有硬體浮點例外狀況的處理器變異上,Windows 核心會以無訊息模式攔截例外狀況,並隱含地在 FPSCR 暫存器中停用它們。 此動作可確保跨處理器變體的標準化行為。 否則,在沒有例外狀況支援的平臺上開發的程式代碼可能會在具有例外狀況支援的平台上執行時收到非預期的例外狀況。

參數傳遞

ARM ABI 上的 Windows 遵循針對非變異函式傳遞參數的 ARM 規則。 ABI 規則包括 VFP 和進階 SIMD 擴充功能。 這些規則遵循 ARM 架構的程式調用標準,並結合 VFP 擴充功能。 根據預設,前四個整數自變數和最多八個浮點或向量自變數會在緩存器中傳遞。 在堆疊上傳遞任何進一步的自變數。 使用下列程序,將引數指派給暫存器或堆疊:

階段 A:初始化

初始化在開始處理引數之前,僅執行一次:

  1. 下一個核心暫存器號碼 (NCRN) 設定為 r0。

  2. VFP 暫存器標記為未配置。

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

  4. 如果呼叫在記憶體中傳回結果的函式,則結果的位址會置於 r0,且 NCRN 會設定為 r1。

階段 B:自變數的預先填補和延伸

對於清單中的每一個引數,下列清單的第一個相符規則適用:

  1. 如果引數是一種複合類型,其大小無法由呼叫者和被呼叫者以靜態方式判定,則該引數會複製到記憶體,並由副本的指標取代。

  2. 如果引數是位元組或 16 位元半字組,則該引數會以零擴充的方式或帶正負號的方式擴充到 32 位元完整字組,並被視為 4 位元組引數。

  3. 如果該引數是複合類型,則其大小會四捨五入至最近的 4 個倍數。

階段 C:將自變數指派給緩存器和堆疊

對於清單中的每一個引數,下列規則輪流適用,直到已配置該引數為止:

  1. 如果引數是 VFP 類型,且有足夠的適當類型連續未配置的 VFP 暫存器,則該引數會配置到此類暫存器編號最低的序列。

  2. 如果引數是 VFP 類型,則所有剩餘未配置的暫存器會標記為無法使用。 NSAA 會向上進行調整,直到其針對引數類型正確對齊,且該引數會複製到已調整的 NSAA 堆疊中。 NSAA 隨後會增加引數的大小。

  3. 如果引數需要 8 位元組對齊,則 NCRN 會四捨五入至下一個偶數暫存器號碼。

  4. 如果 32 位元字組中的引數大小不超過 r4 減 NCRN,則該引數會複製到核心暫存器,從 NCRN 開始,且以最低有效位元佔據較低編號的暫存器。 NCRN 會增加所使用的暫存器數目。

  5. 如果 NCRN 小於 r4,且 NSAA 等於 SP,則該引數會在核心暫存器和堆疊之間分割。 引數的第一部分會複製到核心暫存器,從 NCRN 開始,直到 r3 (含)。 自變數的其餘部分會複製到堆疊上,從NSAA開始。 NCRN 會設定為 r4,且 NSAA 會增加引數的大小減去在暫存器中傳遞的量。

  6. 如果引數需要 8 位元組對齊,則 NSAA 會四捨五入至下一個 8 位元組對齊位址。

  7. 該引數會複製到記憶體中,從 NSAA開始。 NSAA 會增加引數的大小。

VFP 快取器不會用於 variadic 函式,而且會忽略階段 C 規則 1 和 2。 這表示 variadic 函式可以從選擇性推送 {r0-r3} 開始,將緩存器自變數前面加上呼叫端傳遞的任何其他自變數,然後直接從堆棧存取整個自變數清單。

整數類型值在 r0 中傳回,對於 64 位元傳回值,可選擇性地擴充到 r1。 VFP/NEON 浮點或 SIMD 類型值在 s0、d0 或 q0 中傳回 (適當的話)。

Stack

堆疊必須一律保持 4 位元組對齊,而且必須在任何函式界限上對齊 8 位元組。 需要支援在64位堆疊變數上頻繁使用相互鎖定的作業。 ARM EABI 說明堆疊在任何公用介面上是 8 位元組對齊的。 為了保持一致性,Windows on ARM ABI 會將任何函式界限視為公用介面。

必須使用框架指標的函式 (例如呼叫 alloca 或動態變更堆疊指標的函式),必須在函式序言中,設定 r11 中的框架指標,且保持其不變,直到結尾。 不需要框架指標的函式必須執行序言中的所有堆疊更新,並讓堆棧指標保持不變,直到結尾為止。

在堆疊上配置 4 KB 或更多的函式,必須確保最終頁面之前的每一個頁面都按順序碰觸。 此順序可確保 Windows 用來展開堆疊的防護頁面無法「跳躍」任何程序代碼。 一般而言,擴充是由 __chkstk 協助程式所完成,其會傳遞以位元組為單位的總堆疊配置除以 r4 中的 4,並以位元組為單位傳回 r4 的最終堆疊配置數量。

紅色區域

緊接在目前堆疊指標下的 8 位元組區域,保留供分析和動態修補之用。 它允許插入仔細產生的程式代碼,其會將 2 個緩存器儲存在 , [sp, #-8] 並暫時將其用於任意用途。 Windows 核心保證,如果使用者模式和核心模式發生例外狀況或中斷,則不會覆寫這 8 個字節。

核心堆疊

Windows 中預設核心模式堆疊是三個頁面 (12 KB)。 請注意,在核心模式中,不要建立具有大型堆疊緩衝區的函式。 中斷可能帶來非常小的堆疊空餘空間,會導致堆疊緊急檢查錯誤。

C/C++細節

列舉型別是 32 位元整數類型,除非列舉型別中至少有一個值需要 64 位元雙字組儲存區。 在該情況下,列舉型別會提升至 64 位元整數類型。

wchar_t 定義為等同於 unsigned short,以保留與其他平台的相容性。

堆疊行走

Windows 程式代碼會使用啟用框架指標 (/Oy (框架指標遺漏)進行編譯,以啟用快速堆棧行走。 一般而言,r11 暫存器會指向鏈結中的下一個連結,即 {r11, lr} 配對,其會指定堆疊上前一個框架的指標,並傳回位址。 我們建議您的程式碼也啟用框架指標,以改進分析和追蹤。

例外狀況回溯

例外狀況處理期間的堆疊回溯透過使用回溯程式碼啟用。 回溯程式碼是儲存在可執行映像檔 .xdata 區段的位元組序列。 他們會以抽象的方式描述函式序言和結尾程式代碼的作業,以便復原函式序言的效果,以準備回溯呼叫端的堆疊框架。

ARM EABI 會指定使用回溯程式碼的例外狀況回溯模型。 不過,此規格不足以在 Windows 中回溯,它必須處理處理器位於函式序言或結尾的案例。 如需有關 ARM 例外狀況數據和回溯之 Windows 的詳細資訊,請參閱 ARM 例外狀況處理

我們建議動態產生的程式碼使用 RtlAddFunctionTable 及相關聯函式呼叫中指定的動態函式表格描述,以便所產生的程式碼可以參與例外狀況處理。

迴圈計數器

若要支援週期計數器,需要執行 Windows 的 ARM 處理器,但直接使用計數器可能會引起問題。 為避免這些問題,Windows on ARM 使用未定義的 opcode,來要求標準化的 64 位元週期計數器值。 從 C 或 C++ 中,使用 __rdpmccntr64 內建功能,來發出適當的 opcode;從組件中,使用 __rdpmccntr64 指令。 讀取週期計數器在 Cortex-A9 上大約要 60 個週期。

計數器是真正的週期計數器,不是時鐘;因此,計數頻率隨處理器頻率而變化。 如果您要測量經歷的時鐘時間,請使用 QueryPerformanceCounter

另請參閱

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