前幾天,有一位網友編譯eCos時,出現了BSS段錯誤,提示的錯誤資訊大概是:ld: address 0x2000f028 of stress_threads section .bss is not within region sram。
為什麼會出現這個編譯連結錯誤呢?首先,我們要搞清楚BSS段是幹什麼用的,然後才能針對問題進行具體分析。是以,本文主要談談BSS段問題,以及關于它的大小問題。讓我們對BSS段有一個全面深刻的認識。
問題現象
網友的問題大概如下:
arm-eabi-gcc -L/home/dell/stm32_mini/stm32_mini_install/lib -Ttarget.ld -o /home/dell/stm32_mini/stm32_mini_install/tests/kernel/current/tests/thread_gdb tests/thread_gdb.o -mcpu=cortex-m3 -mthumb -Wl,–gc-sections -Wl,-static -Wl,-n -g -nostdlib
/opt/ecos/gnutools/arm-eabi/bin/../lib/gcc/arm-eabi/4.3.2/../../../../arm-eabi/bin/ld: address 0x2000f028 of /home/dell/stm32_mini/stm32_mini_install/tests/kernel/current/tests/stress_threads section .bss is not within region sram
/home/dell/openetech-ecos/packages/pkgconf/rules.mak:173: recipe for target ‘/home/dell/stm32_mini/stm32_mini_install/tests/kernel/current/tests/stress_threads’ failed
/opt/ecos/gnutools/arm-eabi/bin/../lib/gcc/arm-eabi/4.3.2/../../../../arm-eabi/bin/ld: address 0x2000f028 of /home/dell/stm32_mini/stm32_mini_install/tests/kernel/current/tests/stress_threads section .bss is not within region sram
collect2: ld returned 1 exit status
make[1]: Leaving directory ‘/home/dell/stm32_mini/stm32_mini_build/kernel/current’
make[1]: *** [/home/dell/stm32_mini/stm32_mini_install/tests/kernel/current/tests/stress_threads] Error 1
makefile:102: recipe for target ‘tests’ failed
make[1]: *** Waiting for unfinished jobs….
make: Leaving directory ‘/home/dell/stm32_mini/stm32_mini_build’
make: *** [tests] Error 2
詳見http://52ecos.net/thread-487-7-1.html,第68樓的文章描述。
關于BSS段
從問題描述看,是程式BSS段的大小超過了sram的大小。是以,在分析問題之前,先來了解下BSS段。
我們知道,編譯出來的映像檔案,一個多個section(段)組成,如text段、data段、bss段,還有heap(堆)和stack(棧)等。它們的描述如下:
BSS段:BSS段(bss segment)通常是指用來存放程式中未初始化的全局變量的一塊記憶體區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬于靜态記憶體配置設定。
資料段:資料段(data segment)通常是指用來存放程式中已初始化的全局變量的一塊記憶體區域。資料段屬于靜态記憶體配置設定。
代碼段:代碼段(code segment/text segment)通常是指用來存放程式執行代碼的一塊記憶體區域。這部分區域的大小在程式運作前就已經确定,并且記憶體區域通常屬于隻讀, 某些架構也允許代碼段為可寫,即允許修改程式。在代碼段中,也有可能包含一些隻讀的常數變量,例如字元串常量等。
堆(heap):堆是用于存放程序運作中被動态配置設定的記憶體段,它的大小并不固定,可動态擴張或縮減。當程序調用malloc等函數配置設定記憶體時,新配置設定的記憶體就被動态添加到堆上(堆被擴張);當利用free等函數釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)
棧(stack):棧又稱堆棧, 是使用者存放程式臨時建立的局部變量,也就是說我們函數括弧“{}”中定義的變量(但不包括static聲明的變量,static意味着在資料段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的程序棧中,并且待到調用結束後,函數的傳回值也會被存放回棧中。由于棧的先進先出特點,是以棧特别友善用來儲存/恢複調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時資料的記憶體區。
問題分析
知道了BSS段,就可分析上面這個問題了。打開eCos中的stress_threads.c檔案,可看到,這個檔案中定義了很多未初始化的全局變量。其占用的大小很可能超過了程式中定義的bss段大小。
這就帶出了兩個疑問:
- 怎麼知道程式中為BSS段規劃了多少位元組空間?
- 怎麼知道編譯出來的映像占用了多少位元組空間?
先來看第一個問題。實際上,程式(準确地說是連結腳本)并未對每個section規劃多少位元組空間,實際是多少則占用多少,每個section依照設定的順序依次排列。以eCos給出的section為例:
SECTIONS
{
SECTIONS_BEGIN
SECTION_rom_vectors (flash, 0x08000000, LMA_EQ_VMA)
SECTION_RELOCS (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_text (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_fini (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_rodata (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_rodata1 (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_fixup (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_gcc_except_table (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_eh_frame (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_got (flash, ALIGN (0x8), LMA_EQ_VMA)
SECTION_sram (sram, 0x20000400, FOLLOWING (.got))
SECTION_data (ram, 0x68000000, FOLLOWING (.sram))
SECTION_bss (ram, ALIGN (0x8), LMA_EQ_VMA)
CYG_LABEL_DEFN(__heap1) = ALIGN (0x8);
SECTIONS_END
}
上面是eCos中*.ldi檔案給出的section定義。從中可看出,BSS段位于DATA段之後,BSS段後面緊接着heap段。
第二個問題。可以借助編譯器帶的工具檢視BSS段及其它各段的實際大小。
使用objdump指令檢視BSS段大小
映像檔案中,BSS段實際占用大小:
使用size指令檢視BSS段大小
對于上圖,有一點需要注意:整個elf檔案的大小 = text段大小 + data段大小,并不包含bss段大小,這是為什麼呢?
關于BSS段大小的終極解釋
不管是用objdump或者是size指令檢視到的bss段大小,它隻是個“大小”而已,它在映像檔案中不會有它的空間,隻有當映像檔案裝載運作時,才會被配置設定記憶體(并且位于data段記憶體塊之後),并且初始化為0。