ARM非对齐访问和Alignment Fault
1、指令對齊
A64指令必須word對齊。嘗試在非對齊地址取值會觸發(fā)PC alignment fault。
1.1、PC alignment checking
PC(Program Counter)寄存器用來存放下一條執(zhí)行指令地址,對于AArch64架構(gòu),如果PC寄存器低2位不為0,則觸發(fā)PC alignment fault。
類似于Instruction Aborts異常,將非對齊地址加載到PC寄存器并不會直接觸發(fā)PC alignment fault,只有當CPU嘗試從該地址取指令時才會觸發(fā)異常。
當取指異常發(fā)生在EL0時,CPU切換到EL1。當取指異常發(fā)生時,HCR_EL2.TGE位為1且EL2使能時,CPU切換到EL2。當取指異常發(fā)生時,CPU處于其他模式,CPU運行模式不切換。
當發(fā)生PC alignment fault時,ESR(Exception Syndrome Register)的EC域置0x22,表明異常級別。
偽代碼AArch64.CheckPCAlignment()執(zhí)行PC alignment check,偽代碼AArch64.PCAlignmentFault()則主動觸發(fā)異常。
2、數(shù)據(jù)訪問對齊
2.1、數(shù)據(jù)非對齊訪問
如果被訪問的內(nèi)存地址不按照被訪問的數(shù)據(jù)類型的位寬對齊,稱為非對齊訪問。比如int型占4個字節(jié),則訪問int型數(shù)據(jù)的內(nèi)存地址需要按照4字節(jié)對齊。
2.2、硬件非對齊訪問支持
MIPS架構(gòu)不支持非對齊訪問。
X86架構(gòu)支持非對齊訪問,其實現(xiàn)機制是將非對齊訪問指令拆分成多條指令執(zhí)行,結(jié)合拼接(或者拆分)指令獲取數(shù)據(jù)。缺點是犧牲性能。
ARMv5架構(gòu)不支持非對齊訪問。
ARMv6架構(gòu)開始參考X86架構(gòu)實現(xiàn)方式支持非對齊訪問,但是是部分內(nèi)存訪問指令支持。
ARMv7-M架構(gòu)中CCR.UNALIGN_TRP位控制是否使能對齊檢查(Alignment Check),ARMv7-A、ARMv7-R、ARMv8架構(gòu)中SCTLR.A位控制是否使能對齊檢查,默認情況下不使能對齊檢查。
如果使能對齊檢查,則任何指令的非對齊訪問均觸發(fā)非對齊異常。
對于A32/T32代碼,如果不使能對齊檢查,則大部分指令的非對齊訪問由CPU處理,如LDR, LDRH, STR, STRH,LDRSH, LDRT, STRT,LDRSHT,LDRHT,STRHT,TBH。其他的數(shù)據(jù)訪問指令的非對齊訪問都會觸發(fā)非對齊異常,如STRD,LDRD。
對于A64代碼,如果不使能對齊檢查,則所有的load和store指令的非對齊訪問均由CPU處理,但是exclusive load/store, load acquire和store release指令的非對齊訪問則會觸發(fā)非對齊異常,包括LDAXR, LDAXRB, LDAXRH, LDAXP, STLXR, STLXRB, STLXRH, STLXP。
對于SIMD指令,一般都是強制64字節(jié)或者128字節(jié)對齊,如果發(fā)生非對齊訪問,則觸發(fā)非對齊訪問異常。
對于任何內(nèi)存訪問,如果使用SP指針作為基地址,就必須quadword對齊,否則觸發(fā)棧對齊異常。
2.3、軟件非對齊訪問支持
部分MIPS架構(gòu),通過在VxWorks內(nèi)核中對非對齊訪問異常進行處理,通過多次訪存操作和拼接操作來實現(xiàn)非對齊訪問,代價是犧牲性能。ARM架構(gòu)內(nèi)核中也有類似的處理方式,可以通過相關(guān)的配置來控制其處理方式,詳細內(nèi)容查看第四節(jié)。
2.4、編譯器非對齊訪問支持
2.4.1、GCC編譯器
使能非對齊訪問:-munaligned-access
禁止非對齊訪問:-mno-unaligned-access
默認情況下,ARM都是aligned-access的,如果代碼中使用__attribute__((packed))定義的結(jié)構(gòu)體,會出現(xiàn)結(jié)構(gòu)體成員是非對齊的,此時如果沒有使能非對齊訪問會導致觸發(fā)abort異常。
2.4.2、編譯器優(yōu)化
編譯器一般支持對非對齊訪問代碼的優(yōu)化,即在編譯階段通過多次內(nèi)存訪問操作拆分和拼接從而規(guī)避非對齊訪問。
GCC編譯選項-Ox用來指定代碼優(yōu)化級別,-O0表示不優(yōu)化,其他優(yōu)化級別下會對非對齊訪問代碼進行優(yōu)化,比如將LDRD指令的非對齊訪問拆分成多條LDR指令。
3、棧對齊
3.1、SP alignment checking
當SP(Stack Pointer)寄存器的低4位不為0時,如果當前指令使用SP作為基地址,則觸發(fā)棧非對齊異常。
偽代碼AArch64.CheckSPAlignment()執(zhí)行stack pointer check,AArch64.SPAlignmentFault()則觸發(fā)棧對齊異常。
類似于Data Aborts異常,將非對齊值加載到SP寄存器并不會直接觸發(fā)異常,只有當嘗試從非對齊地址取數(shù)據(jù)時才會觸發(fā)異常。
4、ARM Linux內(nèi)核非對齊訪問
4.1、/proc/cpu/alignment
Linux內(nèi)核只針對arm架構(gòu)實現(xiàn)了非對齊訪問處理機制,主要是針對LDR, STR, LDRD, LDRD, STRD, LDM, STM, LDRH, STRH指令的非對齊訪問進行處理。對于arm64架構(gòu),因為ARMv8架構(gòu)CPU可以處理所有LDR/STR類內(nèi)存訪問指令的非對齊訪問,因此沒有實現(xiàn)該機制。這樣就會導致如果在arm64架構(gòu)上運行64位Linux內(nèi)核,而用戶態(tài)為32位應用程序時,如果發(fā)生非對齊訪問,則會觸發(fā)異常。
|
值 |
宏定義 |
說明 |
|
0 |
UM_WARM |
內(nèi)核打印Alignment Trap警告,打印進程名,pid,pc,指令,地址,和異常錯誤碼等 |
|
1 |
UM_FIXUP |
內(nèi)核嘗試修復用戶代碼非對齊訪問 |
|
2 |
UM_SIGNAL |
發(fā)生非對齊訪問時,內(nèi)核發(fā)送SIGBUS信號量給對應進程 |
ARMv5架構(gòu),該節(jié)點默認值為0,即忽略非對齊訪問。
ARMv6及以上架構(gòu),CPU本身部分支持非對齊訪問,因此基本不關(guān)心該節(jié)點值,但是對于LDM, STM, LDRD和STRD等復合指令進行非對齊訪問時,仍然需要軟件處理,此時,可以將該節(jié)點值設(shè)置為1,即fix up模式。
上述三種值是位映射,也可以是組合值。
4.2、非對齊訪問異常處理流程
4.2.1、ARM架構(gòu)
Linux內(nèi)核初始化時,通過fs_initcall(alignment_init)加載非對齊訪問處理模塊。alignment_init()函數(shù)中首先創(chuàng)建/proc/cpu/alignment節(jié)點,然后通過hook_fault_code(FAULT_CODE_ALIGNMENT, do_alignment, SIGBUS, BUS_ADRALN, “alignment exception”)掛do_alignment鉤子函數(shù)。當發(fā)生非對齊訪問異常時,進入do_alignment中處理異常,根據(jù)獲取的/proc/cpu/alignment節(jié)點值,分別進入不同的處理分支。
該部分代碼位于arch/arm/mm/alignment.c中。
4.2.2、ARM64架構(gòu)
Linux內(nèi)核在fault_info[]中注冊鉤子函數(shù),當發(fā)生非對齊訪問(BUS_ADRALN)時,進入do_bad()函數(shù),給對應進程發(fā)送SIGBUS信號量。
該部分代碼位于arch/arm64/mm/fault.c中。
5、什么情況下容易發(fā)生非對齊訪問?
出現(xiàn)alignment fault問題,通常是用戶編寫的代碼導致。估計很多程序猿在編寫代碼(特別是c/c++代碼)時,從未考慮過這樣的問題,那是因為多數(shù)可能都在X86架構(gòu)下的進行代碼開發(fā),而且沒有考慮過代碼的移植性,如前面所說X86硬件會自動處理非對齊問題,用戶感知不到,但這種情況下,由此帶來的性能損耗,用戶可能也關(guān)注不到了。另一方面,部分情況下,編譯器也會自動做padding處理(如對結(jié)構(gòu)體的自動填充對齊),這也進一步讓程序猿們減少了對alignment fault的關(guān)注。
最常見的可能導致alignment fault的代碼編寫方式如:
1) 指針轉(zhuǎn)換
將低位寬類型的指針轉(zhuǎn)換為高位寬類型的指針,如:將char * 轉(zhuǎn)為int *,或?qū)oid *轉(zhuǎn)為結(jié)構(gòu)體指針。這類操作是導致alignment fault的最主要的來源,在分析定位問題時,需要特別關(guān)注。對于出現(xiàn)異常卻又必須這樣使用的場景,對這類轉(zhuǎn)換后的指針進行訪問時,如果不能確認其對應的地址是對齊的,則應該使用memcpy訪問(memcpy方式不存在對齊問題)。另外,建議轉(zhuǎn)換后立即使用,不要將其傳遞到其他函數(shù)和模塊,防止擴展,帶來潛在的問題。
2) 使用packed屬性或者編譯選項
這樣的操作會關(guān)閉編譯器的自動填充功能,從而使結(jié)構(gòu)體中各個字段緊湊排列,如果排列時未處理好對齊,則可能導致alignment fault。一些場景下(內(nèi)核中也較常見)確實需要用戶自行緊湊排列結(jié)構(gòu)體,可節(jié)省空間(在內(nèi)存資源稀缺的場景下,很有用),此時需要特別關(guān)注對齊問題,建議通過填充的方法盡量對齊,如此可能會導致空間浪費,但是會提升訪問性能,典型的“以空間換時間”的思路。如果對空間有強烈要求,而可以接受性能損失,也可以不考慮對齊,不做padding,但在訪問這些結(jié)構(gòu)體的數(shù)據(jù)時,需要全部使用memcpy的方式。
6、測試代碼
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigbus_handler(int sno)
{
printf("signal %d captured
", sno);
exit(1);
}
int main(int argc, char *argv[])
{
char intarray[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
signal(SIGBUS, sigbus_handler);
printf("int1 = 0x%08x, int2 = 0x%08x, int3 = 0x%08x, int4 = 0x%08x
",
*((int *)(intarray + 1)),
*((int *)(intarray + 2)),
*((int *)(intarray + 3)),
*((int *)(intarray + 4)));
return 0;
}
總結(jié)
以上是生活随笔為你收集整理的ARM非对齐访问和Alignment Fault的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《20170911-构建之法:现代软件工
- 下一篇: 移动端常见的一些兼容性问题