为什么要把bss段清0

本文探讨了全局变量和静态变量未初始化或初始化为0时置于.bss段的问题。介绍了为何需要在初始化代码中对.bss段进行清零操作,以确保每次运行时这些变量都能回到初始状态。
全局变量与静态变量没有初始化或初始化值为0时,都会放在.bss段。初始化为非0值,则放在.data段。
全局变量与静态变量没有初始化或初始化值为0时,都放在.bss段会产生一个问题:假如说我们定义一个全局的变量 int a = 0;我们知道这是一个初始化值为0 的全局变量,那么他会被放在.bss段,由于存储在bss段内的数据在下一次启动代码时不会被自动的重新初始化为0(即bss段清0),这就可能导致上一次运行代码过程中有可能修改了全局变量或静态变量的值,而修改过的值会被bss段保存下来,那么在下一次启动代码事我们定义的那个全局变量的值就可能不是我们第一次定义的 “0”值了,这样的话就有可能导致一些问题。
举个例子:
int a = 0//全局变量,被放在bss段
void main(void)
{
    if(a == 0){
        printf("a=0");
        a = 1;
    }
else{ 
        printf("a != 0 ");
        a = 0;
    }
}
我们第一次运行程序时,是输出:a =  0,第二次再运行就输出:a != 0了,下一次再运行 又输出:a = 0 了,这样就导致同样的代码,在不同次的运行是产生不同的结果,这绝不是我们希望看到的。所以在初始化代码是我们要对bss段清0。这样每次运行是为初始化的全局变量、静态变量和初始化为0的全局变量、静态变量就默认全是0了,这样就不会导致上面例子中的那种情况。

如何对Bss段清零?在链接脚本中标记bss段的开始和结束位置,然后在初始化代码的时候把开始标记和结束标记之间的内容清0。
<think>我们想要在Makefile中检查生成的ELF文件BSS是否为0。根据提供的引用,ELF文件包含多个(sections),其中BSS(.bss)通常存储未初始化的全局变量和静态变量,在程序加载时会被初始化为0。但有时我们可能希望确保BSS的大小为0(例如,在某些嵌入式系统中避免使用未初始化变量)。 我们可以通过以下步骤实现: 1. 使用`readelf`命令(GNU binutils的一部分)来获取ELF文件信息。 2. 从输出中提取.bss的大小(通常以字节为单位)。 3. 在Makefile中检查这个大小是否为0。 具体步骤: 步骤1:使用`readelf -S <elf_file>`可以列出所有的(sections)。例如: $ readelf -S my_program.elf 步骤2:在输出中,.bss的信息通常如下所示: [ 5] .bss NOBITS 00000000 000114 000004 00 WA 0 0 4 其中,第5列(这里为000004)就是.bss的大小(16进制,单位为字节)。 步骤3:我们可以使用`grep`和`awk`来提取这个大小。注意:.bss可能被重命名(例如在嵌入式系统中可能是.bss或_bss等),因此需要匹配确切的名。但通常使用".bss"。 步骤4:将提取的大小与0比较。 在Makefile中,我们可以这样写: ```makefile # 假设目标ELF文件是$(TARGET).elf check_bss: # 使用readelf获取.bss的大小(16进制字符串) $(eval BSS_SIZE_HEX := $(shell readelf -S $(TARGET).elf | grep '\.bss' | awk '{print $$6}')) # 将16进制字符串转换为十进制数值(注意:如果.bss不存在,变量可能为空) $(eval BSS_SIZE := $(shell echo $$((0x$(BSS_SIZE_HEX))))) # 检查大小是否为0 @if [ $(BSS_SIZE) -ne 0 ]; then \ echo "Error: .bss section size is $(BSS_SIZE) bytes, expected 0"; \ exit 1; \ else \ echo ".bss section size is 0, good."; \ fi ``` 注意:如果.bss不存在,上述命令可能会出错。因此,我们可以先检查.bss是否存在: 改进版本: ```makefile check_bss: @if ! readelf -S $(TARGET).elf | grep -q '\.bss'; then \ echo "Warning: .bss section not found. Assuming size 0."; \ exit 0; \ fi; \ BSS_SIZE_HEX=$$(readelf -S $(TARGET).elf | grep '\.bss' | awk '{print $$6}'); \ if [ -z "$$BSS_SIZE_HEX" ]; then \ echo "Error: Failed to get .bss section size"; \ exit 1; \ fi; \ BSS_SIZE=$$((0x$$BSS_SIZE_HEX)); \ if [ $$BSS_SIZE -ne 0 ]; then \ echo "Error: .bss section size is $$BSS_SIZE bytes, expected 0"; \ exit 1; \ else \ echo ".bss section size is 0, good."; \ fi ``` 但是,在Makefile中,我们通常使用shell命令,而不是Makefile的eval(因为eval在解析时执行,而我们需要在规则执行时执行)。因此,我们将其写在一个shell脚本片中。 另外,注意`readelf`输出的列可能因版本不同而有所变化。因此,我们可以先查看一下readelf的输出格式,确保第6列是大小(在readelf的输出中,标题行有列名,其中有一列是“Size”)。上面的例子中,我们假设第6列是大小(因为从输出示例中我们看到第6列是000004,即4字节)。 如果readelf的输出列数不一致,我们可以使用以下命令来确保获取大小: ```makefile check_bss: @echo "Checking .bss section size in $(TARGET).elf ..."; \ if ! readelf -S $(TARGET).elf | grep -q '\.bss'; then \ echo "Warning: .bss section not found. Assuming size 0."; \ exit 0; \ fi; \ # 使用readelf的宽输出模式(-W)可以避免列被截,然后使用grep和awk BSS_SIZE_HEX=$$(readelf -SW $(TARGET).elf | grep '\.bss' | tr -s ' ' | cut -d ' ' -f 7); \ if [ -z "$$BSS_SIZE_HEX" ]; then \ echo "Error: Failed to get .bss section size"; \ exit 1; \ fi; \ BSS_SIZE=$$((0x$$BSS_SIZE_HEX)); \ if [ $$BSS_SIZE -ne 0 ]; then \ echo "Error: .bss section size is $$BSS_SIZE bytes, expected 0"; \ exit 1; \ else \ echo ".bss section size is 0, good."; \ fi ``` 解释: readelf -SW: 使用宽输出模式,避免列被截。 tr -s ' ': 将多个空格压缩为单个空格,以便用cut按空格分割。 cut -d ' ' -f 7: 取第7列(因为readelf -SW的输出中,信息每行有多个列,且以空格分隔,其中第7列为Size)。 但是,更可靠的方法是使用awk来处理多个空格: ```makefile check_bss: @echo "Checking .bss section size in $(TARGET).elf ..."; \ if ! readelf -SW $(TARGET).elf | grep -q '\.bss'; then \ echo "Warning: .bss section not found. Assuming size 0."; \ exit 0; \ fi; \ BSS_SIZE_HEX=$$(readelf -SW $(TARGET).elf | grep '\.bss' | awk '{print $$6}'); \ # 注意:在宽输出模式下,readelf的列标题如下: # [Nr] Name Type Addr Off Size ES Flg Lk Inf Al # 所以Size是第6列(从1开始数,第1列是[Nr],第2列是Name,...,第6列是Size) if [ -z "$$BSS_SIZE_HEX" ]; then \ echo "Error: Failed to get .bss section size"; \ exit 1; \ fi; \ BSS_SIZE=$$((0x$$BSS_SIZE_HEX)); \ if [ $$BSS_SIZE -ne 0 ]; then \ echo "Error: .bss section size is $$BSS_SIZE bytes, expected 0"; \ exit 1; \ else \ echo ".bss section size is 0, good."; \ fi ``` 注意:上面的$$6表示awk中的第6列,因为Makefile中$需要转义为$$,而awk中的列号用$n表示,所以这里用$$6表示awk程序中的$6。 但是,我们也可以使用更精确的匹配,避免匹配到其他包含.bss名字的: ```makefile BSS_SIZE_HEX=$$(readelf -SW $(TARGET).elf | grep -w '\.bss' | awk '{print $$6}'); ``` 使用`-w`选项来匹配整个单词。 最后,我们可以将这个检查作为构建过程的一部分,例如在链接后执行: ```makefile $(TARGET).elf: $(OBJS) $(LD) ... -o $@ $(MAKE) check_bss ``` 或者,直接在链接后的命令中执行: ```makefile $(TARGET).elf: $(OBJS) $(LD) ... -o $@ @echo "Checking .bss section..."; \ ... # 将上面的check_bss的脚本写在这里 ``` 但是,为了晰,建议单独写一个规则,然后在链接后调用。 注意:如果.bss的大小不为0,则构建失败。 另外,引用[1]中提到,链接脚本中如果没有指定bss的地址,链接器会自动分配。因此,确保链接脚本中正确设置了.bss,并且我们检查的是最终输出的ELF文件。 引用[2][3][4]提供了ELF文件格式的信息,这有助于理解readelf的输出。 因此,完整的Makefile示例: ```makefile TARGET = my_program all: $(TARGET).elf $(TARGET).elf: $(OBJS) $(LD) $(LDFLAGS) -o $@ $^ check_bss: $(TARGET).elf @echo "Checking .bss section size in $< ..."; \ if ! readelf -SW $< | grep -q -w '\.bss'; then \ echo "Warning: .bss section not found. Assuming size 0."; \ exit 0; \ fi; \ BSS_SIZE_HEX=$$(readelf -SW $< | grep -w '\.bss' | awk '{print $$6}'); \ if [ -z "$$BSS_SIZE_HEX" ]; then \ echo "Error: Failed to get .bss section size"; \ exit 1; \ fi; \ BSS_SIZE=$$((0x$$BSS_SIZE_HEX)); \ if [ $$BSS_SIZE -ne 0 ]; then \ echo "Error: .bss section size is $$BSS_SIZE bytes, expected 0"; \ exit 1; \ else \ echo ".bss section size is 0, good."; \ fi # 在链接后自动检查 build: $(TARGET).elf check_bss .PHONY: check_bss build ``` 这样,执行`make build`就会在链接后检查.bss的大小。 注意:如果.bss的大小为0,则检查通过;否则报错并退出。 另外,在某些情况下,即使程序中没有未初始化的全局变量,编译器也可能生成一个很小的.bss(例如用于对齐等)。所以,要求.bss0可能过于严格,除非有特殊需求(如某些安全认证要求),否则通常不需要检查。 如果程序中确实需要全局变量,但希望它们被初始化为0,那么应该将它们放在.data(并初始化为0)而不是.bss。但是,这样会增加可执行文件的大小(因为.data需要存储初始值0),而.bss只需要在程序加载时由加载器清零,并不占用文件空间。 因此,这个检查可能用于确保没有使用任何未初始化的全局变量(即没有.bss),而所有变量都被显式初始化了。 引用: [^1]: 链接脚本中未指定bss的地址,链接器会自动分配。 [^2][^3][^4]: ELF文件格式的说明,帮助我们理解如何解析ELF文件。 相关问题: 1. 如何检查ELF文件中其他(如.data)的大小? 2. 如何在Makefile中检查多个ELF文件? 3. 如何忽略特定名称的.bss(例如在多个模块中)?</think>在 Makefile 中检查 ELF 文件BSS 是否为 0,可以通过解析 ELF 文件头信息实现。以下是完整解决方案: ### 解决方案:使用 `readelf` 命令解析 ELF ```makefile # 在 Makefile 中添加以下规则 check_bss: @echo "Checking .bss section size..." @if readelf -S $(TARGET).elf | grep '\.bss' | awk '{ exit $$6 != 0 }'; then \ echo "✅ Success: .bss section size is 0"; \ else \ echo "❌ Error: .bss section is NOT 0!"; \ readelf -S $(TARGET).elf | grep '\.bss'; \ exit 1; \ fi # 在编译后自动检查 $(TARGET).elf: $(OBJS) $(LD) -Ttext 0x87800000 -o $@ $^ $(MAKE) check_bss # 编译后立即检查 ``` ### 关键点说明 1. **核心命令解析**: ```bash readelf -S $(TARGET).elf | grep '\.bss' | awk '{ exit $6 != 0 }' ``` - `readelf -S`:显示 ELF 文件头表(Section Headers)[^4] - `grep '\.bss'`:过滤出 .bss 信息 - `awk`:检查第6列(Size列)是否为0,非0时返回错误 2. **ELF 头表示例**: ``` [Nr] Name Type Addr Off Size ... [ 3] .bss NOBITS 87811000 001100 000004 ... ``` - **第6列(Size)**:表示 .bss 大小(16进制) - **NOBITS 类型**:表示该文件中不占用空间[^4] 3. **错误处理**: - 如果 .bss 存在且非0:显示详细信息并退出构建 - 如果 .bss 不存在:命令自动返回成功(grep 无匹配) ### 高级增强版(支持精确检查) ```makefile check_bss: @SIZE_HEX=$$(readelf -SW $(TARGET).elf | awk '/\.bss/{print $$6}'); \ if [ -z "$$SIZE_HEX" ]; then \ echo "⚠️ Warning: .bss section not found"; \ elif [ $$((0x$$SIZE_HEX)) -ne 0 ]; then \ echo "❌ Error: .bss size=$$((0x$$SIZE_HEX)) bytes"; \ exit 1; \ else \ echo "✅ .bss size=0"; \ fi ``` ### 原理解析 1. **BSS 特点**: - 存储未初始化的全局/静态变量 - 在 ELF 文件中不占用实际空间(NOBITS) - 加载时由加载器初始化为0[^2] 2. **为何需要检查**: - 嵌入式系统中避免意外使用未初始化变量 - 确保无冗余内存占用 - 满足安全认证要求(如 MISRA C) > **提示**:如果明确要求无 .bss ,可在链接脚本中添加: > ```ld > /DISCARD/ : { *(.bss*) } > ``` > 这样任何 .bss 都会导致链接失败[^1] ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值