stm32-Hardfault及内存溢出的查找方法
STM32內存結構
1.要點
1.1 兩種存儲類型: RAM 和 Flash
RAM可讀可寫,在STM32的內存結構上,RAM地址段分布[0x2000_0000, 0x2000_0000 + RAM size)
Flash只讀,在STM32的內存結構上,Flash地址段[0x0800_0000, 0x2000_0000)
1.2 六類存儲數據段: .data/.bss/.text/.constdata/heap/stack
.data數據段: 用來存放初始化了但不是初始化為0的全局變量(global)和靜態變量(static)。它是可讀可寫的
.bss(Block Started by Symbol)數據段: 用于存放沒有初始化或初始化為0的全局變量和靜態變量,可讀可寫,如果沒有初始化, 系統會將變量初始化為0.
.text代碼段: 用來放程序代碼(code), 在代碼編譯完成后, 長久只讀存放于此.
.constdata只讀常量數據段: const限定的數據類型存放與此,只讀.
heap堆區: 通常只我們說的動態內存分配,使用內存分配器(memory allocator)管理, malloc/free進行申請和釋放
stack棧區: 在代碼執行時用來保存函數的局部變量和參數。其操作方式類似于數據結構中的棧,是一種“后進先出”(Last In First Out,LIFO)的數據結構。這意味著最后放到棧上的數據,將會是第一個從棧上移走的數據,對于哪些暫時存儲的信息,和不需要長時間保存的信息來說,LIFO這種數據結構非常理想。在調用函數或過程后,系統通常會清除棧上保存的局部變量、函數調用信息及其它信息。棧的頂部通常在可讀寫的RAM區的最后,其地址空間通常“向下減少”,即當棧上保存的數據越多,棧的地址就越小。
1.3 三種存儲屬性區: RO/RW/ZI
RO (Read Only ): 只讀區域, 需要長久保存,燒寫到Rom/Flash段,上文數據段的.text段和.constdata段屬于此屬性區(有時.constdata 段也被叫做 RO-data段, 和這個廣義的RO注意區分)
RW (Read Write): 可讀可寫的初始化了的全局變量和靜態變量段,上文中的.data段屬于RW區
ZI (Zero Init): 沒有進行初始化或者初始化為0,系統上電時會主動把此區域數據進行0初始化,上文的.bss段就是. 另外, 可翻看Keil工具編譯的map文件,Heap和Stack區也進行了Zero的屬性標注, 因此, Heap和Stack也可認為是ZI區域
RW區比較特別, 可讀可寫但又進行了初始化,因為RAM中的數據是掉電不可保存的,因此RW區的.data段數據也需要保存在Rom/Flash里面,上電時候再將此類數據復制到RAM區域讀寫使用。而ZI區域數據不需要掉電保存,直接上電時初始化為0即可使用,因此不需要保存在ROM中。這樣,計算RAM/ROM占用空間的公式:
ROM Size = .text + .constdata + .data (RO + RW)
RAM Size = .bss + .data (ZI + RW)
這里RAM size計算時未考慮Stack和Heap區, 實際size是大于此的, 因為這兩個區域具備動態變化的復雜性,難于估計。
定義一個全局數組變量舉例:
1. static unsigned char test[1024]; ?//全局、未初始化, ZI區,不影響ROM size
2. static unsigned char test[1024] = {0}; ?//全局、初始化為0, ZI區,不影響ROM size
3. static unsigned char test[1024] = {1}; ?//全局、初始化為非0, RW(.data)區,ROM Size 擴大
1.4 擴展說說Heap
在STM32的啟動代碼startup_*.s文件中,一般這樣定義了堆大小:
Heap_Size ? ? ?EQU ? ? 0x200;
在實際使用中, 這個區域可能比1.2節提到的簡潔描述更為復雜。
很多小項目沒有使用內存分配器: 由于各種原因(RAM不足、程序簡單、etc),一些所必須的大塊或固定內存直接使用數組的方式定義使用,繞開了內存分配器。那么這個時候, Heap_Size 的存在是沒有意義的, Heap_Size 定義越大,越浪費空間,可以直接Heap_Size定義為0。這個時候, 本來該堆區提供的空間可能定義在了.bss段(全局/靜態數組沒有初始化)、或.data(全局/靜態數據初始化為非0)、或Stack上(使用了局部數組變量, Tips: 但大的數組不建議定義在stack, 否則可能棧溢出)
重新實現內存分配器:沒有直接將內存分配器直接映射在堆區,而是先定義大的數組內存(可能在.bss或.data, 為避免在ROM存儲, 最好在.bss), 再將這塊內存給內存分配器支配使用
內存分配器直接使用Heap區: 這個時候就要計算好預留多少空間給Stack區, 留多了,Stack用不上浪費;留少了極可能造成Stack溢出而程序崩潰
除了使用自帶RAM外,同時使用外部擴展RAM: 這就需要內存分配器來管理好幾塊地址不連續的RAM空間了
Stm32的keil編譯連接如上圖所示。
編譯信息包含以下幾個部分:
? ? 1)Code: 代碼段,存放程序的代碼部分
? ? 2)RO-data:只讀數據段, 存放程序中定義的常量;
? ? 3)RW-data: 讀寫數據段,存放初始化為非0值的全局變量
? ? 4)ZI-data: 零數據段,存放未初始化的全局變量及初始化為0的變量;
編譯完工程會生成一個. map 的文件,該文件說明了各個函數占用的尺寸和地址,在文件的最后幾行也說明了上面幾個字段的關系:
Total RO ?Size (Code + RO Data) ? ? ? ? ? ? ? ?46052 ( ?44.97kB)
Total RW ?Size (RW Data + ZI Data) ? ? ? ? ? ? 36552 ( ?35.70kB)
Total ROM Size (Code + RO Data + RW Data) ? ? ?46212 ( ?45.13kB)
? ? 1)RO Size 包含了 Code 及 RO-data,表示程序占用Flash空間的大小
? ? 2)RW Size 包含了RW-data及ZI-data,表示運行時占用的RAM的大小
? ? 3)ROM Size 包含了Code, RO Data以及RW Data, 表示燒寫程序所占用的Flash空間的大小
STM32中程序占用內存容量
Keil MDK下Code, RO-data,RW-data,ZI-data這幾個段:
Code存儲程序代碼。
RO-data存儲const常量和指令。
RW-data存儲初始化值不為0的全局變量。
ZI-data存儲未初始化的全局變量或初始化值為0的全局變量。
占用的Flash=Code + RO Data + RW Data;
運行消耗的最大RAM= RW-data+ZI-data;
這個是MDK編譯之后能夠得到的每個段的大小,例如下圖Program Size 中的Code R0 RW ZI
可以計算出占用的FLASH = 34456+456+172=34.26kB,占用的RAM=172+18908=18.63kB
那么堆棧是如何分配的呢,堆棧的內存占用就是在上面RAM分配給RW-data+ZI-data之后的地址開始分配。
堆:編譯器調用動態內存分配的內存區域。
棧:程序運行的時候局部變量的地方,先進后出,這種結構適合程序調用,所以局部變量用數組太大了都有可能造成棧溢出
堆棧溢出容易導致HaltFault。
堆棧大小的設置在啟動文件start_stmf103xb.s中(以STM32F103為例):
全局變量被未知原因改變的解決方法
在開發的過程中總會碰到一些奇怪的問題,仿真的時候一看,發現是某個全局變量被莫名其妙改變了,導致整個函數判斷都出了問題。
全局變量可能會被改變的原因有以下幾點:
1.自己改的(廢話~):好好查看這個變量被誰調用了
2.全局變量字節未對齊:
有一次調試的時候發現一個變量定義成局部變量就能正常運行,而定義成全局變量就不能運行了。局部變量能運行說明我程序的邏輯是沒問題的,找原因的時候一看是我全局變量經常會莫名其妙被改變。找了一圈發現這個變量根本沒被其他函數使用。
后面通過仿真,得到該變量的地址(假設為0x1002)。地址除以4之后發現不是一個整數,這才發現是這個變量字節未4字節對齊導致的。至于為什么不對齊,我也不知道!
解決方法:使用 attribute((aligned(4))) 修飾,使其4字節對齊,就完美解決了。
3.指針未初始化:
假如你定義的變量是指針類型的話,沒有給他初始化則會導致該指針是個野指針,里面的值是不確定的。
總之開發的過程中,少用全局變量,要用的話盡量用結構體,做好分層,提高代碼的可閱讀性和移植性。
stm32-hardfault產生的原因分析
其實野指針,數組越界,堆棧溢出等等,都是由于觸發了總線異常、存儲器管理異常、使用異常中的一個或多個,才觸發了hardfault。
1.內存的溢出,包括堆棧的溢出。
其 中 單 片 機 內 存 和 堆 棧 的 關 系 , 可 以 參 考
http://blog.csdn.net/c12345423/article/details/53004747
2.越界訪問。
這常指數組的使用,具體來說,訪問只有5個元素的數組的第6個元素時,就出
現了越界訪問。而這一錯誤,常常出現于數組作為函數參數傳入時,由于只傳入
指針,而函數中不確定指針訪問的平移量,就可能出現越界訪問的錯誤。值得注
意的是,C語言并沒有越界訪問的編譯查詢,也就是說,在編譯時不會檢測是否
存在越界訪問。
3.錯誤使用flash造成的異常錯誤。
一是由于在使用flash存儲數據時,其存儲空間有可能和代碼區重疊;二是由于
自身需求,需要轉換指向flash的指針的指向類型,如轉換成float*,使指針在
flash上以4個單位的間隔移動,但是由于flash是分區的,如果區首地址和被轉
換指針之間的間隔不是4的倍數也會出現錯誤。
4.這一年大家都是自己畫PCB,自己寫程序,有時候會發現PCB的焊接也會造成
HardFault,并且這種錯誤從程序開始就會存在。
5.野指針尋址,除以0(也可能得出inf而不會產生錯誤)等常見C語法錯誤。
?
Kinetis MCU 采用 Cortex-M4 的內核,該內核的 Fault 異常可以捕獲非法的內存訪問和非法的編程行為。Fault異常能夠檢測到以下幾類非法行為:
總線 Fault: 在取址、數據讀/寫、取中斷變量、進入/退出中斷時寄存器堆棧操作(入棧/出棧)時檢測到內存訪問錯誤。
存儲器管理 Fault: 檢測到內存訪問違反了內存保護單元(MPU, MemoryProtection Unit)定義的區域。
用法 Fault: 檢測到未定義的指令異常,未對其的多重加載/存儲內存訪問。如果使能相應控制位,還可以檢測出除數為零以及其他未對齊的內存訪問。
硬 Fault: 如果上述的總線 Fault、存儲器管理 Fault、用法 Fault 的處理程序不能被執行(例如禁能了總線 Fault、存儲器管理Fault、用法Fault 的異常或者在這些異常處理程序中又出現了新的Fault)則觸發硬Fault。
為了解釋所述的 Fault 中斷處理程序的原理,這里重述一下當系統產生異常時 MCU 的處理過程:
1. 有一個壓棧的過程,若產生異常時使用 PSP(進程棧指針),就壓入到 PSP 中,若產生異常時使用MSP(主棧指針),就壓入MSP 中。
2. 會根據處理器的模式和使用的堆棧,設置 LR 的值(當然設置完的LR 的值再壓棧)。
3. 異常保存,硬件自動把 8 個寄存器的值壓入堆棧(8 個寄存器依次為 xPSR、PC、LR、R12以及 R3~R0)。如果異常發生時,當前的代碼正在使用PSP,則上面8 個寄存器壓入PSP; 否則就壓入MSP。
當系統產生異常時,我們需要兩個關鍵寄存器值,一個是 PC ,一個是 LR (鏈接寄存器),通過 LR找到相應的堆棧,再通過堆棧找到觸發異常的PC 值。將產生異常時壓入棧的 PC 值取出,并與反匯編的代碼對比就能得到哪條指令產生了異常。這里解釋一下關于 LR 寄存器的工作原理。如上所述,當 Cortex-M4 處理器接受了一個異常后,寄存器組中的一些寄存器值會被自動壓入當前棧空間里,這其中就包括鏈接寄存器(LR )。這時的 LR 會被更新為異常返回時需要使用的特殊值(EXC_RETURN)。
關于EXC_RETURN 的定義如下,其為 32 位數值,高 28 位置 1,第 0 位到第三位則提供了異常返回機制所需的信息,如下表所示。可見其中第 2 位標示著進入異常前使用的棧是 MSP還是PSP。在異常處理過程結束時,MCU 需要根據該值來分配 SP 的值。這也是本方法中用來判斷所使用堆棧的原理,其實現方法可以從后面_init_hardfault_isr 中看到。
?另外,我們可以利用 MQX 的控制臺串口輸出Fault 異常信息來幫助調試。編寫Fault 處理程序時,將啟動代碼中默認的Fault 處理程序跟換成自己需要的Fault 處理程序。需要注意的是,由于是在中斷中進行打印輸出,MQX的控制臺串口只能使用POLL 輪詢模式的驅動,不能使用中斷模式的驅動。
用戶可以編寫自定義的硬 Fault 處理程序_int_hardfault_isr,修改 MQX 的中斷向量定義vector.c,把里面的DEFAULT_VECTOR 代碼段換成下面的代碼。當系統出現硬Fault 異常時,將會調用自定義的Fault 處理_int_hardfault_isr函數。在這個函數,我們可以通過StackTrace-back 回溯出現問題的代碼。
我們可以在_int_hardfault_isr 函數里將出現異常時的寄存器、堆棧、狀態寄存器等信息打印出來。如果系統出現異常時,一般情況都會通過串口控制臺打印出LR,PC的值。然后根據編譯器生成的map 文件,找到出現問題的具體函數。
?
從上圖的串口輸出我們可以看到 PC 和 LR 寄存器值,PC 的值為 0x56c6,我們根據匯編代碼可以找到出現問題的指令。從而大大縮小了查找出現問題的范圍,可以幫助開發人員快速定位問題的根本原因。
附錄Fault異常中斷處理代碼:
// hard fault handler in C, // with stack frame location as input parameter void hard_fault_handler_c (unsigned int * hardfault_args) {unsigned int stacked_r0;unsigned int stacked_r1;unsigned int stacked_r2;unsigned int stacked_r3;unsigned int stacked_r12;unsigned int stacked_lr;unsigned int stacked_pc;unsigned int stacked_psr;stacked_r0 = ((unsigned long)hardfault_args[0]);stacked_r1 = ((unsigned long)hardfault_args[1]);stacked_r2 = ((unsigned long)hardfault_args[2]);stacked_r3 = ((unsigned long)hardfault_args[3]);stacked_r12 = ((unsigned long)hardfault_args[4]);stacked_lr = ((unsigned long)hardfault_args[5]);stacked_pc = ((unsigned long)hardfault_args[6]);stacked_psr = ((unsigned long) hardfault_args[7]);printf ("\n\n[Hard faulthandler - all numbers in hex]\n");printf ("R0 = %x\n",stacked_r0);printf ("R1 = %x\n",stacked_r1);printf ("R2 = %x\n",stacked_r2);printf ("R3 = %x\n",stacked_r3);printf ("R12 = %x\n",stacked_r12);printf ("LR [R14] = %x subroutine call return address\n",stacked_lr);printf ("PC [R15] = %x program counter\n", stacked_pc);printf ("PSR = %x\n",stacked_psr);/******************* Add yourdebug trace here ***********************/_int_kernel_isr(); }/* hard fault interrupt handler */ void _int_hardfault_isr( ) {__asm("TST LR, #4");__asm("ITE EQ");__asm("MRSEQ R0,MSP");__asm("MRSNE R0,PSP");__asm("Bhard_fault_handler_c"); }?
在沒有JTAG的情況下,通過串口打印出堆棧信息:
/* Private typedef -----------------------------------------------------------*/enum { r0, r1, r2, r3, r12, lr, pc, psr};/* Private define ------------------------------------------------------------*//* Private macro -------------------------------------------------------------*//* Private variables ---------------------------------------------------------*/extern __IO uint16_t ADC_InjectedConvertedValueTab[32];uint32_t Index = 0;/* Private function prototypes -----------------------------------------------*/void Hard_Fault_Handler(uint32_t stack[]);/* Private functions ---------------------------------------------------------*/static void printUsageErrorMsg(uint32_t CFSRValue){printf("Usage fault: \r\n");CFSRValue >>= 16; // right shift to lsbif((CFSRValue & (1<<9)) != 0) {printf("Divide by zero \r\n");}if((CFSRValue & (1<<8)) != 0) {printf("Unaligned access \r\n");}}static void printBusFaultErrorMsg(uint32_t CFSRValue){printf("Bus fault: \r\n");CFSRValue = ((CFSRValue & 0x0000FF00) >> 8); // mask and right shift to lsb}static void printMemoryManagementErrorMsg(uint32_t CFSRValue){printf("Memory Management fault: \r\n");CFSRValue &= 0x000000FF; // mask just mem faults}static void stackDump(uint32_t stack[]){static char msg[80];sprintf(msg, "R0 = 0x%08x\r\n", stack[r0]); printf(msg);sprintf(msg, "R1 = 0x%08x\r\n", stack[r1]); printf(msg);sprintf(msg, "R2 = 0x%08x\r\n", stack[r2]); printf(msg);sprintf(msg, "R3 = 0x%08x\r\n", stack[r3]); printf(msg);sprintf(msg, "R12 = 0x%08x\r\n", stack[r12]); printf(msg);sprintf(msg, "LR = 0x%08x\r\n", stack[lr]); printf(msg);sprintf(msg, "PC = 0x%08x\r\n", stack[pc]); printf(msg);sprintf(msg, "PSR = 0x%08x\r\n", stack[psr]); printf(msg);}void Hard_Fault_Handler(uint32_t stack[]){static char msg[80];//if((CoreDebug->DHCSR & 0x01) != 0) {printf("\r\nIn Hard Fault Handler\r\n");sprintf(msg, "SCB->HFSR = 0x%08x\r\n", SCB->HFSR);printf(msg);if ((SCB->HFSR & (1 << 30)) != 0) {printf("Forced Hard Fault\r\n");sprintf(msg, "SCB->CFSR = 0x%08x\r\n", SCB->CFSR );printf(msg);if((SCB->CFSR & 0xFFFF0000) != 0) {printUsageErrorMsg(SCB->CFSR);}if((SCB->CFSR & 0xFF00) != 0) {printBusFaultErrorMsg(SCB->CFSR);}if((SCB->CFSR & 0xFF) != 0) {printMemoryManagementErrorMsg(SCB->CFSR);}}stackDump(stack);__ASM volatile("BKPT #01");//}while(1);}__ASM void HardFault_Handler_a(void){IMPORT Hard_Fault_HandlerTST lr, #4ITE EQMRSEQ r0, MSPMRSNE r0, PSPB Hard_Fault_Handler}/*** @brief This function handles Hard Fault exception.* @param None* @retval None*/void HardFault_Handler(void){/* Go to infinite loop when Hard Fault exception occurs */HardFault_Handler_a();}?
總結
以上是生活随笔為你收集整理的stm32-Hardfault及内存溢出的查找方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Manifest is not vali
- 下一篇: 3.2 使用断言