1.發(fā)生中斷時處理器的行為
不考慮其他細節(jié)八孝,M3內(nèi)核在發(fā)生中斷時首先自動將如下8個寄存器壓棧。因此在中斷處理函數(shù)中,發(fā)生中斷時正常執(zhí)行時的寄存器數(shù)值已經(jīng)被壓入了堆棧中丧叽。在中斷處理函數(shù)開始執(zhí)行時,除了PC公你,LR踊淳,SP等控制寄存器,從r0-r12等這些通用寄存器的數(shù)據(jù)是沒有變化的陕靠。下圖描述了M3內(nèi)核將寄存器壓棧的順序:
地址 | 寄存器 | 被保存的順序 |
---|---|---|
舊SP(N-0) | 原先已壓入的內(nèi)容 | - |
(N-4) | xPSR | 2 |
(N-8) | PC | 1 |
(N-12) | LR | 8 |
(N-16) | R12 | 7 |
(N-20) | R3 | 6 |
(N-24) | R2 | 5 |
(N-28) | R1 | 4 |
新SP(N-32) | R0 | 3 |
2.編譯器通過棧來實現(xiàn)函數(shù)調(diào)用
C編譯器通過棧來實現(xiàn)函數(shù)的調(diào)用迂尝,即在棧中記錄程序執(zhí)行的軌跡并輔助寄存器進行參數(shù)傳遞。具體如何實現(xiàn)C函數(shù)的調(diào)用剪芥,歷史上有很多的規(guī)范垄开,這些規(guī)范叫做調(diào)用慣例。
對于ARM處理器來說税肪,有一個官方的規(guī)范AAPCS(Procedure Call Standard for the ARM? Architecture)詳細描述了進行函數(shù)調(diào)用時如何進行參數(shù)的傳遞和調(diào)用路徑的記錄等溉躲。
如下僅對使用棧記錄調(diào)用路徑的行為進行簡單描述:查看編譯器生成的匯編代碼可以得知,大多數(shù)的函數(shù)調(diào)用通過BL語句實現(xiàn)益兄,BL語句將當前程序下一條指令的地址存入LR寄存器锻梳,并跳轉到指定的地方(子函數(shù)開始的地方)開始執(zhí)行。子函數(shù)中如果還需要調(diào)用孫子函數(shù)净捅,就會在函數(shù)的入口處將LR的值壓棧疑枯,以便函數(shù)執(zhí)行結束后能夠返回父函數(shù)。因此依次找到棧中LR的數(shù)值蛔六,就能找到調(diào)用路徑中各個函數(shù)的地址荆永。最后根據(jù)map文件翻譯出各函數(shù)的名稱,就可以得到函數(shù)的調(diào)用路徑了国章。
如下是一個簡單函數(shù)匯編代碼的例子屁魏,函數(shù)OnPowerOff調(diào)用了函數(shù)FS_Deinit,函數(shù)FS_Deinit調(diào)用了SPIFFS_unmount捉腥∶テ矗可以看出OnPowerOff函數(shù)的入口如將LR壓入棧中(此時LR中保存的是函數(shù)OnPowerOff的返回地址,也就是調(diào)用OnPowerOff的父函數(shù)中的某條指令的地址),然后調(diào)用了FS_Deinit桃漾。同樣FS_Deinit也在入口處將LR壓入棧中(此時LR中保存的是OnPowerOff函數(shù)中POP指令的地址)坏匪,然后再調(diào)用SPIFFS_unmount。返回的過程撬统,依次將棧中保存的返回地址直接出棧到PC寄存器适滓,完成函數(shù)的返回。這樣恋追,如果某個函數(shù)將棧中的返回地址寫壞凭迹,則函數(shù)在返回時就會跳轉到某個隨機的地方,這就是常說的“程序跑飛了”苦囱。
OnPowerOff PROC
;;;202 void OnPowerOff(void)
0001b2 b510 PUSH {r4,lr}
;;;203 {
;;;204 FS_Deinit();
0001b4 f7fffffe BL FS_Deinit
;;;205 }
0001b8 bd10 POP {r4,pc}
;;;206
ENDP
FS_Deinit PROC
;;;166 void FS_Deinit(void)
000158 b510 PUSH {r4,lr}
;;;167 {
;;;168 SPIFFS_unmount(&g_fs);
00015a 4810 LDR r0,|L1.412|
00015c f7fffffe BL SPIFFS_unmount
;;;169 return;
;;;170 }
000160 bd10 POP {r4,pc}
;;;171
ENDP
3.對信息的繼續(xù)挖掘
- 通用寄存器
通用寄存器中可供挖掘的信息并不多嗅绸,通常情況下r0-r3寄存器保存著函數(shù)的前四個參數(shù)(其余的參數(shù)在棧中保存),需要注意的是:這四個寄存器的數(shù)值僅在函數(shù)開始執(zhí)行的時候是可靠的撕彤,在函數(shù)執(zhí)行的過程中可能被改變鱼鸠。在函數(shù)返回時,寄存器r0和r1用于保存返回值(根據(jù)返回數(shù)據(jù)的大小羹铅,決定僅使用r0還是同時使用r0和r1)蚀狰。同樣這兩個寄存器僅在子函數(shù)剛返回時數(shù)值才是可靠的。 - 特殊功能寄存器
特殊功能寄存器就是PC职员、LR和SP了麻蹋。
SP指向當前的棧頂,在知曉棧的結構時焊切,可以根據(jù)SP訪問棧中的數(shù)據(jù)扮授。
在中斷處理函數(shù)中LR有特殊用法,其中保存了返回被中斷地點的方法蛛蒙,而不是通常情況下的返回地址糙箍。因此在Hardfault處理函數(shù)中寄存器LR和PC的值沒有太多參考意義渤愁,被處理器自動壓棧的LR和PC最有用牵祟,PC記錄了被中斷打斷前正在執(zhí)行的指令地址(也是正在執(zhí)行的函數(shù)地址),LR記錄了被中斷打斷前抖格,正在執(zhí)行的函數(shù)的父函數(shù)的地址诺苹。根據(jù)這兩個地址,可以找到引發(fā)Hardfault異常的函數(shù)和語句雹拄,以及其父函數(shù)(如果輔以匯編代碼繼續(xù)對棧的內(nèi)容進行分析收奔,則可以回溯整個調(diào)用路徑)。
而具體引發(fā)Hardfault異常的原因滓玖,可以根據(jù)下面章節(jié)介紹的SCB寄存器來查看坪哄。 - SCB寄存器
在M3/M4處理器標準外設中,有一個叫做SCB(System Control Block)的部分,其中有6個寄存器記錄了發(fā)生Hardfault異常的原因翩肌。
CMSIS規(guī)范中對SCB寄存器的定義:
typedef struct
{
__I uint32_t CPUID;
__IO uint32_t ICSR;
__IO uint32_t VTOR;
__IO uint32_t AIRCR;
__IO uint32_t SCR;
__IO uint32_t CCR;
__IO uint8_t SHP[12];
__IO uint32_t SHCSR;
__IO uint32_t CFSR; //主要關注
__IO uint32_t HFSR; //主要關注
__IO uit32_t DFSR;
__IO uint32_t MMFAR; //主要關注
__IO uint32_t BFAR; //主要關注
__IO uint32_t AFSR;
__I uint32_t PFR[2];
__I uint32_t DFR;
__I uint32_t ADR;
__I uint32_t MMFR[4];
__I uint32_t ISAR[5];
uint32_t RESERVED0[5];
__IO uint32_t CPACR;
} SCB_Type;
CFSR模暗、HFSR、MMFAR念祭、BFAR幾個寄存器是我們需要關注的兑宇,AFSR是平臺相關的暫時忽略。上述寄存器中CFSR又可以分為三個寄存器分別是:UFSR粱坤,BFSR隶糕,MFSR。上述寄存器的內(nèi)存分布如下表所示:
地址 | 寄存器 | 全名 | 尺寸 |
---|---|---|---|
0xE000 ED28 | MFSR | MemManage fault 狀態(tài)寄存器 | 字節(jié) |
0XE000 ED29 | BFSR | 總線 fault 狀態(tài)寄存器 | 字節(jié) |
0XE000 ED2A | UFSR | 用法 fault 狀態(tài)寄存器 | 半字 |
0XE000 ED2C | HFSR | 硬 fault 狀態(tài)寄存器 | 字 |
0XE000 ED30 | DFSR | 調(diào)試 fault 狀態(tài)寄存器 | 字 |
0XE000 ED3C | AFSR | 輔助 fault 狀態(tài)寄存器 | 字 |
各寄存器數(shù)據(jù)的描述如下:
MFSR 中可能出現(xiàn)的錯誤及原因:
位 | 可能的原因 |
---|---|
MSTKERR | 入棧時發(fā)生錯誤(異常響應序列開始時) 1.堆棧指針的值被破壞 2.堆棧容易過大站玄,已經(jīng)超出MPU允許的region范圍 |
MUNSTKERR | 出棧時發(fā)生錯誤(異常響應序列終止時)枚驻,入棧時沒有發(fā)生錯誤,出棧時卻出錯蜒什,總令人有些匪夷所思测秸,可能的原因是 1.異常服務例程破壞的堆棧指針 2. MPU配置被異常服務例程更改 |
DACCVIOL | 內(nèi)存訪問保護違例。這是MPU發(fā)揮作用的體現(xiàn)灾常。常常是用戶應用程序企圖訪問特權級region所致 |
IACCVIOL | 1.內(nèi)存訪問保護違例霎冯。常常是用戶應用程序企圖訪問特權級region。入棧的PC給出的地址钞瀑,就是產(chǎn)生問題代碼之所在 2.跳轉到不可執(zhí)行指令的regions 3.異常返回時沈撞,使用了無效的EXC_RETURN值 4.向量表中有無效的向量。例如雕什,異常在向量建立之前就發(fā)生了缠俺,或者加載的是用于傳統(tǒng)ARM內(nèi)核的可執(zhí)行映像 |
BFSR 中可能出現(xiàn)的錯誤及原因:
位 | 可能的原因 |
---|---|
STKERR | (自動)入棧期間出錯 1.堆棧指針的值被破壞 2.堆棧容易太大,到達了未定義存儲器的區(qū)域 3.PSP未經(jīng)初始化就使用 |
UNSTKERR | (自動)出棧器件出錯贷岸。如果沒有發(fā)生過STKERR壹士,則最可能的就是異常處理器件把SP的值破壞了 |
IMPRECISERR | 與設備傳送數(shù)據(jù)的過程中發(fā)生總線錯誤〕ゾ可能因為設備未經(jīng)初始化而引起:在用戶級訪問了特權級的設備躏救,或者傳送的數(shù)據(jù)單位尺寸不能為設備所接受。此時螟蒸,有可能是LDM/STM指令造成了非精確總線fault盒使。 |
PRECISERR | 在數(shù)據(jù)訪問期間的總線錯誤。通過BFAR可以獲取具體的地址七嫌。發(fā)生fault的原因同上少办。 |
IBUSERR | 同MFSR中的IACCVIOL |
UFSR 中可能出現(xiàn)的錯誤及原因:
位 | 可能的原因 |
---|---|
DIVBYZERO | 當DIV_0_TRP置位時發(fā)生除數(shù)為零。導致此fault的指令可以從入棧的PC讀取 |
UNALIGNED | 當UNALIGN_TRP置位時發(fā)生未對齊訪問诵原。導致此fault的指令可以從入棧的PC讀取 |
NOCP | 企圖執(zhí)行一個協(xié)處理器指令英妓。導致此fault的指令可以從入棧的PC讀取 |
INVPC | 1.異常返回時使用了無效的 EXC_RETURN挽放,例如: 1)當 EXC_RETURN = 0xFFFF FFF1 時卻要返回線程模式 2)當 EXC_RETURN = 0xFFFF FFF9 時卻要返回 handler 模式 2.無效的異常活動狀態(tài)蔓纠,例如: 1)當前異常的活動狀態(tài)已經(jīng)清除了骂维,卻在此時執(zhí)行異常返回。往往是因為濫用 VECTCLRACTIVE 或清除了 SHCSR 中活動狀態(tài)所致 2)在還有其他異常的活動位置位時贺纲,卻要返回線程模式 3.由于堆棧指針錯誤導致了 IPSR 的值不正確航闺。對于 INVPC fault ,入棧的 PC 指出了該 fault 服務例程在何處搶占了其他的代碼猴誊。這個問題往往是比較隱晦的程序錯誤造成的潦刃,欲詳細調(diào)查該問題的原因,最好使用ITM的跟蹤功能懈叹。 4.ICI/IT 位對當前指令無效乖杠。當LDM/STM 指令被異常打斷后,在異常服務例程中又更改了入棧的 PC澄成。結果在中斷返回時胧洒,非零的 ICI 位段作用到了不是用 ICI 位段的指令上。如果是其他原因破壞了 PSR 的值墨状,也可能導致此 fault卫漫。 |
INVSTATE | 1.加載到 PC 中的跳轉地址值是偶數(shù)(LSB=0)。通過檢查入棧 PC 的值肾砂,一下子就可以查出該問題列赎。 2.向量地址的 LSB=0,診斷方法同上镐确。 3.入棧的 PSR 在異常處理過程之中被破壞包吝,使得在返回時內(nèi)核嘗試進入 ARM 狀態(tài) |
UNDEFINSTR | 1.使用了 CM3 不支持的指令 2.代碼段中的數(shù)據(jù)被破壞 3.連接時加載了 ARM 目標碼。請檢查編譯階段的位置 4.指令對其的問題源葫。例如诗越,在使用 GNU 工具鏈時,忘記了 .ascii后使用 .align息堂,就有可能導致下一條指令沒有對齊 |
解讀SCB寄存器時應首先根據(jù)HFSR寄存器判斷產(chǎn)生Hardfault的原因嚷狞,如果確認是fault上訪的情況,則依次檢查BFSR储矩、UFSR和HFSR確定具體的錯誤原因和地址感耙。