通過map文件了解堆棧分配(STM32、MDK5)--避免堆棧溢出
環境:STM32F103C8T6,MDK5
在最近的一個項目的開發中,每當調用到一個函數,程序就直接跑飛。debug跟進去看不出什么邏輯錯誤,但發現函數內局部變量聲明之后,全局變量的值被清零,后來查看局部變量地址已經超出棧的范圍,於是確定是棧溢出。如果不稍微了解一下堆棧,在開發過程中可能碰到各種奇怪的錯誤。
.map和startup.s文件
MAP 文件是程序的全局符號、源文件和代碼行號信息的唯一的文本表示方法,它可以在任何地方、任何時候使用,不需要有額外的程序進行支持。
在MDK5中,在項目中雙擊Target就能自動打開.map文件。
Startup.s文件是系統的啟動文件,主要包括堆和棧的初始化配置、中斷向量表的配置以及將程序引導到main( )函數等。
Startup.s主要完成三個工作:棧和堆的初始化、定位中斷向量表、調用Reset Handler。
堆棧作用
棧(stack)空間,用亍局部變量,函數調時現場保護和返回地址,函數的形參等。
堆(heap)空間,主要用亍勱態內存分配,也就是說用 malloc,calloc, realloc 等函數分配的變量空間是在堆上。
堆棧在內存分布
在map文件中搜索STACK或者HEAP,在接近文件底部的位置可以看到SRAM的分配,如下圖。
從上圖中可以看出SRAM空間用來存放:1、各個文件中聲明和定義的全局變量、靜態數據和常量;2、HEAP區;3、STACK區。
STM32的堆棧是存放在SRAM中的,分配堆棧大小需要考慮SRAM容量。
在.map文件中的Image Symbol Table底下可以找到如下圖所示堆棧分布信息。
堆在使用時會從低地址往上加,而棧是從__initial_sp開始往下減。以上圖中的堆棧地址為例,malloc會從0x20002248開始往上加,局部變量的分配會從0x20004448開始往下減。如果入棧元素過大,使得入棧元素的地址訪問到了0x20002448之后的內容,就發生了棧溢出,首先會改變堆中的元素值,如果入棧元素夠大,可能會直接改變HEAP后面的全局變量。同理,當動態申請的內存過大時,堆中變量越界到棧中,此時就發送堆溢出。
避免產生這類錯誤的產生,程序設計時就應該考慮變量大小和堆棧大小是否合適。一個是減少過大的臨時變量和動態申請內存,另一個是在SRAM空間允許的情況下增大堆棧大小,如上圖中棧大小是8192字節,堆大小是512。
堆棧大小設置
MDK5中可以通過修改startup.s文件來設置堆棧大小,只需要修改startup.s文件中的Stack_Size和Heap_Size即可,如下圖所示。
KEIL Uvison5中默認生成的startup.s文件是只讀的,無法修改,只需要設置一下該文件的屬性,把只讀取消即可。