日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

stm32-Hardfault及内存溢出的查找方法

發(fā)布時間:2024/3/13 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 stm32-Hardfault及内存溢出的查找方法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

STM32內(nèi)存結(jié)構(gòu)

1.要點
1.1 兩種存儲類型: RAM 和 Flash
RAM可讀可寫,在STM32的內(nèi)存結(jié)構(gòu)上,RAM地址段分布[0x2000_0000, 0x2000_0000 + RAM size)
Flash只讀,在STM32的內(nèi)存結(jié)構(gòu)上,Flash地址段[0x0800_0000, 0x2000_0000)
1.2 六類存儲數(shù)據(jù)段: .data/.bss/.text/.constdata/heap/stack
.data數(shù)據(jù)段: 用來存放初始化了但不是初始化為0的全局變量(global)和靜態(tài)變量(static)。它是可讀可寫的
.bss(Block Started by Symbol)數(shù)據(jù)段: 用于存放沒有初始化或初始化為0的全局變量和靜態(tài)變量,可讀可寫,如果沒有初始化, 系統(tǒng)會將變量初始化為0.
.text代碼段: 用來放程序代碼(code), 在代碼編譯完成后, 長久只讀存放于此.
.constdata只讀常量數(shù)據(jù)段: const限定的數(shù)據(jù)類型存放與此,只讀.
heap堆區(qū): 通常只我們說的動態(tài)內(nèi)存分配,使用內(nèi)存分配器(memory allocator)管理, malloc/free進行申請和釋放
stack棧區(qū): 在代碼執(zhí)行時用來保存函數(shù)的局部變量和參數(shù)。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧,是一種“后進先出”(Last In First Out,LIFO)的數(shù)據(jù)結(jié)構(gòu)。這意味著最后放到棧上的數(shù)據(jù),將會是第一個從棧上移走的數(shù)據(jù),對于哪些暫時存儲的信息,和不需要長時間保存的信息來說,LIFO這種數(shù)據(jù)結(jié)構(gòu)非常理想。在調(diào)用函數(shù)或過程后,系統(tǒng)通常會清除棧上保存的局部變量、函數(shù)調(diào)用信息及其它信息。棧的頂部通常在可讀寫的RAM區(qū)的最后,其地址空間通常“向下減少”,即當棧上保存的數(shù)據(jù)越多,棧的地址就越小。

1.3 三種存儲屬性區(qū): RO/RW/ZI
RO (Read Only ): 只讀區(qū)域, 需要長久保存,燒寫到Rom/Flash段,上文數(shù)據(jù)段的.text段和.constdata段屬于此屬性區(qū)(有時.constdata 段也被叫做 RO-data段, 和這個廣義的RO注意區(qū)分)
RW (Read Write): 可讀可寫的初始化了的全局變量和靜態(tài)變量段,上文中的.data段屬于RW區(qū)
ZI (Zero Init): 沒有進行初始化或者初始化為0,系統(tǒng)上電時會主動把此區(qū)域數(shù)據(jù)進行0初始化,上文的.bss段就是. 另外, 可翻看Keil工具編譯的map文件,Heap和Stack區(qū)也進行了Zero的屬性標注, 因此, Heap和Stack也可認為是ZI區(qū)域
RW區(qū)比較特別, 可讀可寫但又進行了初始化,因為RAM中的數(shù)據(jù)是掉電不可保存的,因此RW區(qū)的.data段數(shù)據(jù)也需要保存在Rom/Flash里面,上電時候再將此類數(shù)據(jù)復制到RAM區(qū)域讀寫使用。而ZI區(qū)域數(shù)據(jù)不需要掉電保存,直接上電時初始化為0即可使用,因此不需要保存在ROM中。這樣,計算RAM/ROM占用空間的公式:

ROM Size = .text + .constdata + .data (RO + RW)
RAM Size = .bss + .data (ZI + RW)

這里RAM size計算時未考慮Stack和Heap區(qū), 實際size是大于此的, 因為這兩個區(qū)域具備動態(tài)變化的復雜性,難于估計。

定義一個全局數(shù)組變量舉例:

1. static unsigned char test[1024]; ?//全局、未初始化, ZI區(qū),不影響ROM size
2. static unsigned char test[1024] = {0}; ?//全局、初始化為0, ZI區(qū),不影響ROM size
3. static unsigned char test[1024] = {1}; ?//全局、初始化為非0, RW(.data)區(qū),ROM Size 擴大
1.4 擴展說說Heap
在STM32的啟動代碼startup_*.s文件中,一般這樣定義了堆大小:

Heap_Size ? ? ?EQU ? ? 0x200;

在實際使用中, 這個區(qū)域可能比1.2節(jié)提到的簡潔描述更為復雜。

很多小項目沒有使用內(nèi)存分配器: 由于各種原因(RAM不足、程序簡單、etc),一些所必須的大塊或固定內(nèi)存直接使用數(shù)組的方式定義使用,繞開了內(nèi)存分配器。那么這個時候, Heap_Size 的存在是沒有意義的, Heap_Size 定義越大,越浪費空間,可以直接Heap_Size定義為0。這個時候, 本來該堆區(qū)提供的空間可能定義在了.bss段(全局/靜態(tài)數(shù)組沒有初始化)、或.data(全局/靜態(tài)數(shù)據(jù)初始化為非0)、或Stack上(使用了局部數(shù)組變量, Tips: 但大的數(shù)組不建議定義在stack, 否則可能棧溢出)
重新實現(xiàn)內(nèi)存分配器:沒有直接將內(nèi)存分配器直接映射在堆區(qū),而是先定義大的數(shù)組內(nèi)存(可能在.bss或.data, 為避免在ROM存儲, 最好在.bss), 再將這塊內(nèi)存給內(nèi)存分配器支配使用
內(nèi)存分配器直接使用Heap區(qū): 這個時候就要計算好預留多少空間給Stack區(qū), 留多了,Stack用不上浪費;留少了極可能造成Stack溢出而程序崩潰
除了使用自帶RAM外,同時使用外部擴展RAM: 這就需要內(nèi)存分配器來管理好幾塊地址不連續(xù)的RAM空間了

Stm32的keil編譯連接如上圖所示。

編譯信息包含以下幾個部分:
? ? 1)Code: 代碼段,存放程序的代碼部分
? ? 2)RO-data:只讀數(shù)據(jù)段, 存放程序中定義的常量;
? ? 3)RW-data: 讀寫數(shù)據(jù)段,存放初始化為非0值的全局變量
? ? 4)ZI-data: 零數(shù)據(jù)段,存放未初始化的全局變量及初始化為0的變量;

編譯完工程會生成一個. map 的文件,該文件說明了各個函數(shù)占用的尺寸和地址,在文件的最后幾行也說明了上面幾個字段的關(guān)系:

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中程序占用內(nèi)存容量
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

那么堆棧是如何分配的呢,堆棧的內(nèi)存占用就是在上面RAM分配給RW-data+ZI-data之后的地址開始分配。

堆:編譯器調(diào)用動態(tài)內(nèi)存分配的內(nèi)存區(qū)域。

棧:程序運行的時候局部變量的地方,先進后出,這種結(jié)構(gòu)適合程序調(diào)用,所以局部變量用數(shù)組太大了都有可能造成棧溢出

堆棧溢出容易導致HaltFault。

堆棧大小的設(shè)置在啟動文件start_stmf103xb.s中(以STM32F103為例):

全局變量被未知原因改變的解決方法

在開發(fā)的過程中總會碰到一些奇怪的問題,仿真的時候一看,發(fā)現(xiàn)是某個全局變量被莫名其妙改變了,導致整個函數(shù)判斷都出了問題。
全局變量可能會被改變的原因有以下幾點:

1.自己改的(廢話~):好好查看這個變量被誰調(diào)用了

2.全局變量字節(jié)未對齊:
有一次調(diào)試的時候發(fā)現(xiàn)一個變量定義成局部變量就能正常運行,而定義成全局變量就不能運行了。局部變量能運行說明我程序的邏輯是沒問題的,找原因的時候一看是我全局變量經(jīng)常會莫名其妙被改變。找了一圈發(fā)現(xiàn)這個變量根本沒被其他函數(shù)使用。
后面通過仿真,得到該變量的地址(假設(shè)為0x1002)。地址除以4之后發(fā)現(xiàn)不是一個整數(shù),這才發(fā)現(xiàn)是這個變量字節(jié)未4字節(jié)對齊導致的。至于為什么不對齊,我也不知道!
解決方法:使用 attribute((aligned(4))) 修飾,使其4字節(jié)對齊,就完美解決了。

3.指針未初始化:
假如你定義的變量是指針類型的話,沒有給他初始化則會導致該指針是個野指針,里面的值是不確定的。

總之開發(fā)的過程中,少用全局變量,要用的話盡量用結(jié)構(gòu)體,做好分層,提高代碼的可閱讀性和移植性。

stm32-hardfault產(chǎn)生的原因分析

其實野指針,數(shù)組越界,堆棧溢出等等,都是由于觸發(fā)了總線異常、存儲器管理異常、使用異常中的一個或多個,才觸發(fā)了hardfault。

1.內(nèi)存的溢出,包括堆棧的溢出。
其 中 單 片 機 內(nèi) 存 和 堆 棧 的 關(guān) 系 , 可 以 參 考
http://blog.csdn.net/c12345423/article/details/53004747
2.越界訪問。
這常指數(shù)組的使用,具體來說,訪問只有5個元素的數(shù)組的第6個元素時,就出
現(xiàn)了越界訪問。而這一錯誤,常常出現(xiàn)于數(shù)組作為函數(shù)參數(shù)傳入時,由于只傳入
指針,而函數(shù)中不確定指針訪問的平移量,就可能出現(xiàn)越界訪問的錯誤。值得注
意的是,C語言并沒有越界訪問的編譯查詢,也就是說,在編譯時不會檢測是否
存在越界訪問。
3.錯誤使用flash造成的異常錯誤。
一是由于在使用flash存儲數(shù)據(jù)時,其存儲空間有可能和代碼區(qū)重疊;二是由于
自身需求,需要轉(zhuǎn)換指向flash的指針的指向類型,如轉(zhuǎn)換成float*,使指針在
flash上以4個單位的間隔移動,但是由于flash是分區(qū)的,如果區(qū)首地址和被轉(zhuǎn)
換指針之間的間隔不是4的倍數(shù)也會出現(xiàn)錯誤。
4.這一年大家都是自己畫PCB,自己寫程序,有時候會發(fā)現(xiàn)PCB的焊接也會造成
HardFault,并且這種錯誤從程序開始就會存在。
5.野指針尋址,除以0(也可能得出inf而不會產(chǎn)生錯誤)等常見C語法錯誤。

?

Kinetis MCU 采用 Cortex-M4 的內(nèi)核,該內(nèi)核的 Fault 異常可以捕獲非法的內(nèi)存訪問和非法的編程行為。Fault異常能夠檢測到以下幾類非法行為:
  總線 Fault: 在取址、數(shù)據(jù)讀/寫、取中斷變量、進入/退出中斷時寄存器堆棧操作(入棧/出棧)時檢測到內(nèi)存訪問錯誤。
  存儲器管理 Fault: 檢測到內(nèi)存訪問違反了內(nèi)存保護單元(MPU, MemoryProtection Unit)定義的區(qū)域。
  用法 Fault: 檢測到未定義的指令異常,未對其的多重加載/存儲內(nèi)存訪問。如果使能相應控制位,還可以檢測出除數(shù)為零以及其他未對齊的內(nèi)存訪問。
  硬 Fault: 如果上述的總線 Fault、存儲器管理 Fault、用法 Fault 的處理程序不能被執(zhí)行(例如禁能了總線 Fault、存儲器管理Fault、用法Fault 的異常或者在這些異常處理程序中又出現(xiàn)了新的Fault)則觸發(fā)硬Fault。

為了解釋所述的 Fault 中斷處理程序的原理,這里重述一下當系統(tǒng)產(chǎn)生異常時 MCU 的處理過程:
  1. 有一個壓棧的過程,若產(chǎn)生異常時使用 PSP(進程棧指針),就壓入到 PSP 中,若產(chǎn)生異常時使用MSP(主棧指針),就壓入MSP 中。
  2. 會根據(jù)處理器的模式和使用的堆棧,設(shè)置 LR 的值(當然設(shè)置完的LR 的值再壓棧)。
  3. 異常保存,硬件自動把 8 個寄存器的值壓入堆棧(8 個寄存器依次為 xPSR、PC、LR、R12以及 R3~R0)。如果異常發(fā)生時,當前的代碼正在使用PSP,則上面8 個寄存器壓入PSP; 否則就壓入MSP。

  當系統(tǒng)產(chǎn)生異常時,我們需要兩個關(guān)鍵寄存器值,一個是 PC ,一個是 LR (鏈接寄存器),通過 LR找到相應的堆棧,再通過堆棧找到觸發(fā)異常的PC 值。將產(chǎn)生異常時壓入棧的 PC 值取出,并與反匯編的代碼對比就能得到哪條指令產(chǎn)生了異常。這里解釋一下關(guān)于 LR 寄存器的工作原理。如上所述,當 Cortex-M4 處理器接受了一個異常后,寄存器組中的一些寄存器值會被自動壓入當前棧空間里,這其中就包括鏈接寄存器(LR )。這時的 LR 會被更新為異常返回時需要使用的特殊值(EXC_RETURN)。
  關(guān)于EXC_RETURN 的定義如下,其為 32 位數(shù)值,高 28 位置 1,第 0 位到第三位則提供了異常返回機制所需的信息,如下表所示。可見其中第 2 位標示著進入異常前使用的棧是 MSP還是PSP。在異常處理過程結(jié)束時,MCU 需要根據(jù)該值來分配 SP 的值。這也是本方法中用來判斷所使用堆棧的原理,其實現(xiàn)方法可以從后面_init_hardfault_isr 中看到。

?另外,我們可以利用 MQX 的控制臺串口輸出Fault 異常信息來幫助調(diào)試。編寫Fault 處理程序時,將啟動代碼中默認的Fault 處理程序跟換成自己需要的Fault 處理程序。需要注意的是,由于是在中斷中進行打印輸出,MQX的控制臺串口只能使用POLL 輪詢模式的驅(qū)動,不能使用中斷模式的驅(qū)動。
  用戶可以編寫自定義的硬 Fault 處理程序_int_hardfault_isr,修改 MQX 的中斷向量定義vector.c,把里面的DEFAULT_VECTOR 代碼段換成下面的代碼。當系統(tǒng)出現(xiàn)硬Fault 異常時,將會調(diào)用自定義的Fault 處理_int_hardfault_isr函數(shù)。在這個函數(shù),我們可以通過StackTrace-back 回溯出現(xiàn)問題的代碼。

我們可以在_int_hardfault_isr 函數(shù)里將出現(xiàn)異常時的寄存器、堆棧、狀態(tài)寄存器等信息打印出來。如果系統(tǒng)出現(xiàn)異常時,一般情況都會通過串口控制臺打印出LR,PC的值。然后根據(jù)編譯器生成的map 文件,找到出現(xiàn)問題的具體函數(shù)。

?

從上圖的串口輸出我們可以看到 PC 和 LR 寄存器值,PC 的值為 0x56c6,我們根據(jù)匯編代碼可以找到出現(xiàn)問題的指令。從而大大縮小了查找出現(xiàn)問題的范圍,可以幫助開發(fā)人員快速定位問題的根本原因。

  附錄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();}

  • ?

    總結(jié)

    以上是生活随笔為你收集整理的stm32-Hardfault及内存溢出的查找方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。