关于RT-Thread的__bss_end - __bss_start的问题解析

电子说

1.2w人已加入

描述

Q1. ELF bss == (bss_end - .stack)?
Q1. 编译完成后,ELF解析的bss数值3372,并不等于bss_end - bss_start,而是等于bss_end - sstack。

Step 1. 在RT-Thread Studio中创建一个基于4.0.5和STM32L431RCTx的工程。

编译完成后,得到的输出结果是

arm-none-eabi-size --format=berkeley "rtthread.elf"
text data bss dec hex filename
53632 1816 3372 58820 e5c4 rtthread.elf
Used Size(B) Used Size(KB)
Flash: 55448 B 54.15 KB
RAM: 5188 B 5.07 KB
Flash大小:55448 = text + data,因为data段的初始值存放在Flash中。
RAM大小:5188 = data + bss,因为R/W变量存放在RAM中。
Step 2. 贴上RT-Thread Studio生成的部分map文件

.stack 0x20000718 0x400 load address 0x0800d898
0x20000718 . = ALIGN (0x4)
0x20000718 _sstack = .
0x20000b18 . = (. + _system_stack_size)
fill 0x20000718 0x400
0x20000b18 . = ALIGN (0x4)
0x20000b18 _estack = .
0x20000b18 __bss_start = .
.bss 0x20000b18 0x92c load address 0x0800dc98
0x20000b18 . = ALIGN (0x4)
0x20000b18 _sbss = .
*(.bss)
.bss 0x20000b18 0x1c c:/rt-threadstudio/repo/extract/toolchain_support_packages/arm/gnu_tools_for_arm_embedded_processors/5.4.1/bin/../lib/gcc/arm-none-eabi/5.4.1/armv7e-m/fpu/crtbegin.o
(.bss. )
.bss.rt_tick 0x20000b34 0x4 ./rt-thread/src/clock.o
.bss.idle 0x20000b38 0x80 ./rt-thread/src/idle.o
.bss.rt_thread_stack
......
.bss.uart_obj 0x2000123c 0xdc ./drivers/drv_usart.o
*(COMMON)
COMMON 0x20001318 0x4 ./rt-thread/src/kservice.o
0x20001318 rt_assert_hook
......
COMMON 0x20001440 0x4 ./libraries/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal.o
0x20001440 uwTick
0x20001444 . = ALIGN (0x4)
0x20001444 _ebss = .
*(.bss.init)
0x20001444 __bss_end = .
0x20001444 _end = .
使用__bss_end - __bss_start = 0x20001444 - 0x20000b18 = 0x92C = 2348D
使用__bss_end - _sstack = 0x20001444 - 0x20000718 = 0xD2C = 3372D
关注RT-Thread Studio给出的信息,只包含了text,data,bss三个信息,其中,data和bss都是存放在RAM中的。

因此,从分析中可知,Studio编译完成后:

ELF分析工具给出的bss数据,包括了*.lds文件中的.stack段, .bss段和.COOMMON段。
data数据对应.data段。
.bss + .COMMON + .stack == ELF bss。
Q2. Which kind of variables are placed in .Common?
Q2. .bss和.common部分的关系?

Step 1. 在main.c中声明新全局变量global_uint32_a

rt_uint32_t global_uint32_a;
因为仅仅声明了变量,而没有使用该变量,因此编译器优化了这个变量,没有为它实际分配地址,编译后,ELF bss没有变化,依然是3372。

Step 2. 在main函数中添加对变量的使用,防止编译器优化

在main函数中增加一条使用LOD_D打印global_uint32_a的语句,发现bss数据从3372变化成3376。

打开map文件,查看global_uint32_a。此时,发现global_uint32_a被放入到了COMMON段中。

COMMON 0x20001440 0x4 ./libraries/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal.o
0x20001440 uwTick
COMMON 0x20001444 0x4 ./applications/main.o
0x20001444 global_uint32_a
0x20001448 . = ALIGN (0x4)
0x20001448 _ebss = .
*(.bss.init)
0x20001448 __bss_end = .
0x20001448 _end = .
Step 3. 修改global_uint32_a的声明,在声明前加入static

static rt_uint32_t global_uint32_a;
编译后,将gloabal_a放入了.bss段中

.bss.global_uint32_a 0x20001318 0x4 ./applications/main.o
不妨查找map中的COMMON段的变量,rt_object_put_hook, rt_object_take_hook等,它们均是全局变量,在某个*.c文件中被声明,在其他文件中使用extern方式声明,在多个文件中均有使用。

RT-Thread代码中的.bss的变量,均使用了static声明,该变量的作用域仅限于本文件。若外部文件需要使用,则可以以函数调用形式,返回该变量的地址即可。使用这种方式,防止了全局变量满天飞,在模块间的解耦较好。

Step 4. 在main.c声明新变量rt_uint8_t类型的新变量global_uint8_b

再次声明一个rt_uint8_t类型的新变量global_uint8_b,不使用static描述,且打印该变量防止编译器优化该变量。

rt_uint8_t global_uint8_b;
LOG_D("%d, %d",global_uint32_a, global_uint8_b);
编译后,ELF bss变化成3380,而不是预期的从3376变化成3377。原因是RAM 4字节对齐,在lds文件中使用ALIGN(4)的方式进行了约束。

从生成的map文件中可以看到,global_uint8_b被放置在COMMON中,且下方还有ALIGN(4)和fill(填充)的描述。

COMMON 0x20001448 0x1 ./applications/main.o
0x20001448 global_uint8_b
0x2000144c . = ALIGN (0x4)
fill 0x2000144d 0x3
Step 5. 在board.c中声明同名、同类型的global_uint8_b

在board.c中声明同名、同类型的global_uint8_b,且在rt_hw_board_init函数中对global_uint8_b变量赋值为2,防止编译器优化。

rt_uint8_t global_uint8_b;
RT_WEAK void rt_hw_board_init()
{
global_uint8_b = 2;
.......
}

未在board.c中声明global_uint8_b之前的编译结果和map文件节选如下所示,global_uint8_b在map文件中出现了3次。

/ board.c中声明变量之前 /
arm-none-eabi-size --format=berkeley "rtthread.elf"
text data bss dec hex filename
53680 1816 3380 58876 e5fc rtthread.elf
/ map文件节选,省略部分内容 /
Allocating common symbols
Common symbol size file
global_uint8_b 0x1 ./applications/main.o
.....
.bss.global_uint32_a
0x20001318 0x4 ./applications/main.o
*(COMMON)
COMMON 0x2000131c 0x4 ./rt-thread/src/kservice.o
0x2000131c rt_assert_hook
.....
COMMON 0x20001448 0x1 ./applications/main.o
0x20001448 global_uint8_b
0x2000144c . = ALIGN (0x4)
fill 0x20001449 0x3
.....
global_uint8_b ./applications/main.o
在board.c中声明global_uint8_b之后的编译结果和map文件节选如下所示。global_uint8_b在map文件中出现了3次,但是同时main.o和board.o均引用了该变量。

/ board.c中声明变量之后 /
arm-none-eabi-size --format=berkeley "rtthread.elf"
text data bss dec hex filename
53692 1816 3380 58888 e608 rtthread.elf
/ map文件节选,省略部分内容 /
Allocating common symbols
Common symbol size file
global_uint8_b 0x1 ./drivers/board.o
.....
.bss.global_uint32_a
0x20001318 0x4 ./applications/main.o
*(COMMON)
COMMON 0x2000131c 0x4 ./rt-thread/src/kservice.o
0x2000131c rt_assert_hook
.....
COMMON 0x20001448 0x1 ./drivers/board.o
0x20001448 global_uint8_b
0x2000144c . = ALIGN (0x4)
fill 0x20001449 0x3
.....
global_uint8_b ./applications/main.o
./drivers/board.o

对比在board.c中声明变量global_uint8_b的前后,发现ELF bss均没有变化。从map文件中可以看到,在链接过程中,main.o文件中实际用到的是board.o的值。实际整个工程中只有一个global_uint8_b变量,属于弱符号。

因为board.c中的rt_hw_board_init函数先于main.c中的main函数运行,所以将程序下载到开发板中,会发现main函数中打印出来的global_uint8_b的值为2。

Step 6. 将main.c中的global_uint8_b限制为static

保持上述在board.c中的修改,将main.c中的global_uint8_b变量用static描述。

编译之后ELF bss增加4个字节,变化成3384,整个工程中会有两个global_uint8_b变量,且位于不同的地址。

main.c中的global_uint8_b位于.bss,是个强符号
board.c中的global_uint8_b位于COMMON,是个弱符号
下方内容是将main.c中的global_uint8_b用static修饰之后的map文件节选。global_uint8_b在map文件中出现了4次,且main.o和board.o的变量位于不同地址。

/ main.c中的变量用static限制 /
arm-none-eabi-size --format=berkeley "rtthread.elf"
text data bss dec hex filename
53692 1816 3384 58892 e60c rtthread.elf
/ map文件节选,省略部分内容 /
Allocating common symbols
Common symbol size file
global_uint8_b 0x1 ./drivers/board.o
.....
.bss.global_uint32_a
0x20001318 0x4 ./applications/main.o
.bss.global_uint8_b
0x2000131c 0x1 ./applications/main.o
*(COMMON)
fill 0x2000131d 0x3
COMMON 0x20001320 0x4 ./rt-thread/src/kservice.o
0x20001320 rt_assert_hook
.....
COMMON 0x2000144c 0x1 ./drivers/board.o
0x2000144c global_uint8_b
0x20001450 . = ALIGN (0x4)
fill 0x2000144d 0x3
.....
global_uint8_b ./drivers/board.o

虽然board.c中的rt_hw_board_init函数先于main.c中的main函数运行,且在board.c中global_uint8_b的值修改成2,但是将程序下载到开发板中,打印出来的global_uint8_b的值为0。因为两者实际处于不同地址。

小结

综上所述,形成如下小结:

ELF bss包括了:.stack, .bss, .COMMON,它们的地址均位于RAM。
.bss段中的是强符号,.COMMON段中的是弱符号。
RT-Thread的启动代码中_bss_start和_bss_end部分的差值,是强符号的.bss部分和弱符号的.COMMON部分之和,启动代码对这部分进行清零操作。
多个文件中有同名的变量不可怕,要有强、弱之分。
加了static修饰的全局变量在.bss;未加static修饰的全局变量在.COMMON。
多字节对齐,方便CPU对数据进行快速访问。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分