前几天,有一位网友编译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。