共用方式為


ARM ABI 慣例概觀

此針對 Windows on ARM 處理器程式碼編譯的應用程式二進位介面 (ABI) 是以標準 ARM EABI 為基礎。本文章重點說明 Windows on ARM 和標準之間的主要差異。如需標準 ARM EABI 的詳細資訊,請參閱 ARM 架構的應用程式二進位介面 (ABI)

基本需求

ARM 上的 Windows 會假設其始終在 ARMv7 架構上執行。硬體中必須存在 VFPv3-D32 格式或以後版本的浮點數支援。VFP 必須在硬體中支援單精確度和雙精確度浮點。Windows 執行階段不支援模擬浮點,以啟用在非 VFP 硬體上執行。

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

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

位元組序

Windows on ARM 在由小到大的格式模式下執行。Visual C++ 編譯器和 Windows 執行階段始終預期由小到大格式的資料。雖然 ARM 指令集架構 (ISA) 中的 SETEND 指令甚至容許以使用者模式程式碼,來變更目前的位元組序,但不建議這樣做,因為這對應用程式而言太過危險。如果在由大到小格式的模式下產生例外狀況,則行為不可預期,且可能會在使用者模式中導致應用程式錯誤,或在核心模式中導致檢查錯誤。

對齊方式

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

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

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

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

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

指令集

ARM 上 Windows 的指令集嚴格限制為 Thumb-2。在此平台上執行的所有程式碼始終預期在 Thumb 模式下啟動並維持在該模式下。嘗試切換至舊版 ARM 指令集可能會成功,但如果成功的話,發生的任何例外狀況或中斷可能會在使用者模式中導致應用程式錯誤,或在核心模式中導致檢查錯誤。

此需求的副作用是所有程式碼指標必須設定為低位元。如此,當透過 BLX 或 BX 載入及分支到它們時,處理器仍會處於 Thumb 模式,而不會嘗試以 32 位元 ARM 指令來執行目標程式碼。

IT 指令

不容許在 Thumb-2 程式碼中使用 IT 指令,下列特定情況除外:

  • IT 指令只能用於修改一個目標指令。

  • 目標指令必須是 16 位元指令。

  • 目標指令必須是下列其中一種:

    16 位元 Opcode

    類別

    限制

    MOV、MVN

    Move

    Rm != PC、Rd != PC

    LDR、LDR[S]B、LDR[S]H

    從記憶體載入

    但不是 LDR 常值格式

    STR、STRB、STRH

    儲存至記憶體

    ADD、ADC、RSB、SBC、SUB

    加法或減法

    但不是 ADD/SUB SP、SP、imm7 格式

    Rm != PC、Rdn != PC、Rdm != PC

    CMP、CMN

    比較

    Rm != PC、Rn != PC

    MUL

    乘法

    ASR、LSL、LSR、ROR

    位元移位

    AND、BIC、EOR、ORR、TST

    位元算術

    BX

    分支到暫存器

    Rm != PC

雖然目前 ARMv7 CPU 無法報告不容許之指令格式的使用情況,但預期未來版本可以報告。如果偵錯到這些格式,則使用它們的任何程式可能會因未定義的指令例外狀況而終止。

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) 位元欄位:

位元

意義

動態?

角色

31-28

NZCV

動態

狀態旗標

27

QC

動態

累加飽和度

26

AHP

靜態

替代半精確度控制

25

DN

靜態

預設 NaN 模式控制

24

FZ

靜態

清除為零模式控制

23-22

RMode

靜態

捨入模式控制

21-20

分散

靜態

向量分散,必須一律為 0

18-16

長度

靜態

向量長度,必須一律為 0

15, 12-8

IDE、IXE 等

靜態

例外狀況截取啟用位元,必須一律為 0

7, 4-0

IDC、IXC 等

動態

累加例外狀況旗標

浮點例外狀況

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

參數傳遞

對於非 variadic 函式,Windows on ARM ABI 會遵循 ARM 規則進行參數傳遞,這包括 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 中傳回 (適當的話)。

堆疊

堆疊必須始終保持 4 位元組對齊,且在任何函式界限上,必須保持 8 位元組對齊。若要支援對 64 位元堆疊變數頻繁使用聯鎖作業,必須符合上述情況。ARM EABI 說明堆疊在任何公用介面上是 8 位元組對齊的。為了保持一致性,Windows on ARM ABI 會將任何函式界限視為公用介面。

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

在堆疊上配置 4 KB 或更多的函式,必須確保最終頁面之前的每一個頁面都按順序碰觸。這可確保不會有程式碼可以「跳過」Windows 用於擴充堆疊的保護頁面。一般而言,此作業由 __chkstk 協助程式完成,在 r4 中,會對該協助程式傳遞堆疊配置總量除以 4 (以位元組為單位),這會在 r4 中傳回最終的堆疊配置量 (以位元組為單位)。

紅色區域

緊接在目前堆疊指標下的 8 位元組區域,保留供分析和動態修補之用。這允許插入小心產生的程式碼,其在 [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 中不足以進行回溯,因為在 Windows 中必須處理處理器位於函式序言或結尾中間的情況。如需 Windows on ARM 例外狀況資料及回溯的詳細資訊,請參閱 ARM 例外狀況處理

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

週期計數器

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

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

請參閱

參考

Visual C++ ARM 移轉時常見的問題

概念

ARM 例外狀況處理