.bss段和.data段引起的文件大小增加

本文讲述了在嵌入式开发中遇到的bin文件过大问题,源于BSS段和DATA段处理不当。作者通过深入解析内存四区、栈区、堆区、代码区和.bss/data段,揭示了问题原因,并提供了反汇编分析和Makefile链接设置的解决方案。
摘要由CSDN通过智能技术生成

起因

起因是我在编写 imx6ull EPIR 定时器的时候发现编译的 bin 文件居然有 69kb,而之前编译的 bin 文件只有 3kb 左右,这明显是有有问题的,而导致问题的原因就是 .bss 段和 .data 端没有处理好,在解决问题前,我们先了解什么是 bss 段和 data 段

内存四区

在C语言学习中我们会听到 “内存四区” 这个概念,分别是栈区、堆区、全局区、代码区

栈区一般存放函数的局部变量、形参等,函数执行完成就自动回收
堆区由程序员手动分配和释放空间,如使用 malloc、free 在程序运行过程中,如果程序员不手动释放,生命周期和程序相同
全局区用于存放全局变量和静态变量
代码区存放执行代码的区域

bss段 和 data 段

在嵌入式系统中,一个程序的组成一般由 text 段、bss 段、data 段组成,当然还有栈区、堆区等等,这里不做讨论,我们主要分析 text 段、bss 段、data 段这三段

texttext 段是程序的代码段,它是只读区域,不能被修改。当我们在链接的时候通常会给 text 段链接一个地址作为程序运行的起始地址,运行程序时就会将代码加载到指定内存中运行
bss存放程序中未初始化的全局变量的区域
data存放已经初始化的全局变量和 static 变量的区域。值得注意的是,如果初始化的值为0,那么它会存放在 bss 段,而不是 data 段

这里有个概念值得注意: bss 段不占实际的磁盘空间(不是内存!在生成可执行文件时,这个文件会占用 pc 的磁盘空间,而不占用内存空间。只有运行程序时才会申请和占用内存),而 data 段是需要占磁盘空间的。那么为什么都是存放全局变量,bss 段却不占磁盘空间呢?这也是我开头提到的问题的关键所在。

① 首先我们要明确的一点是 bss 存储的是为初始化的全局变量,而这些全局变量最后都会被初始化为0(正点原子的代码将 bss 段全部清零了的)

② 这表示系统开始时可以不管这块区域,因为反正 bss 段的数据都是 0,那我们就可以只记录下存储大小和地址等信息,等到我们程序执行的时候才分配相应的空间

③ 而 data 段的数据是不为 0 的数据,所以我们必须要为其分配一个内存来存数据,如果和 bss 段一样的话,在程序运行的时候我们就不知道 data 段原来的数据究竟是多少了,这也是 data 段需要占磁盘空间的原因

问题

经过上面对 bss 段和 data 段的了解,我们在来看看开头的问题
这是 EPIT 定时器的初始化代码

#include "epit_timer.h"

unsigned int cou = 2;

void epit1_init(unsigned int fre, unsigned int ms)
{
    /* 分频值范围为1~4096 */
    if (fre > 4096)
    {
        return;
    }

    /* 关闭epit1定时器 */
    EPIT1->CR &= ~(1 << 0);

    /* 设置epit1时钟来源 */
    EPIT1->CR |= (1 << 24);

    /* 设置epit1分频系数 */
    EPIT1->CR = (((EPIT1->CR) & ~(0XFFF << 4)) | ((fre - 1) << 4));

    /* 设置epit1工作模式 */
    EPIT1->CR |= (1 << 3);

    /* 使能epit1比较中断 */
    EPIT1->CR |= (1 << 2);

    /* 设置epit1初始值来源 */
    EPIT1->CR |= (1 << 1);

    /* 设置epit1重装载值 */
    EPIT1->LR = ms;

    /* 设置epit1比较值 */
    EPIT1->CMPR = 0;

    /* 使能 GIC 中对应的中断 */
    GIC_EnableIRQ(EPIT1_IRQn);

    /* 注册epit1比较中断函数 */
    system_register_irqhandler(EPIT1_IRQn, (system_irq_handler_t)epit1_irqhandler, NULL);

    /* 开启epit1定时器 */
    EPIT1->CR |= (1 << 0);
}

/* epit1比较中断事件函数 */
void epit1_irqhandler(unsigned int gicciar, void *param)
{
    /* 判断比较事件发生 */
    if (EPIT1->SR & (1 << 0)) 
    {
        if(cou % 2 == 0)
        {
            led_on();
        }
        else
        {
            led_off();
        }
        cou++;
    }
    /* 清除epit1中断标志 */
    EPIT1->SR |= (1 << 0);
}

这是一段初始化 EPIT定时器的代码,跟原子哥不同的是我中断函数采用的是一个全局变量 cou 来做 led 翻转。我们编译一下,可以看到大小为 69.6kb
image
这明显是不可能的!通过对比实验,我把问题锁定在了 unsigned int cou = 2; 这个全局变量上面,屏蔽掉这个变量我们的程序又恢复了正常大小为 3.4kb,当时我就迷茫了。

查看反汇编

按照原子哥的方法:遇事不决,先看反汇编。然后我打开反汇编文件,很快就发现了问题的所在。

87800d28 <__main_from_arm>:
87800d28:	e51ff004 	ldr	pc, [pc, #-4]	; 87800d2c <__main_from_arm+0x4>
87800d2c:	87800d01 	strhi	r0, [r0, r1, lsl #26]

Disassembly of section .data:

87811000 <cou>:
87811000:	00000002 	andeq	r0, r0, r2

Disassembly of section .bss:

87811004 <__bss_start>:
87811004:	00000000 	andeq	r0, r0, r0

87811008 <irq_nesting>:
87811008:	00000000 	andeq	r0, r0, r0

8781100c <irq_table>:
	...

这是反汇编的部分信息,我们可以看到在 .text 段后面就是 .data 段,然后就是 .bss 段,看起来是没有什么问题,但是可以注意到 text 段的结束地址为 0x87800d2c,而 data 段的起始地址却是 0x87811000,经过简单的计算就可以的出,这中间差了 64 kb,所以事情就很明了了。

  • 首先我们定义的 cuo 这个全局变量是有初始值的且为 2,所以 cuo 其实是存储在 data 段的,这点我们通过反汇编可以很明显的看到
  • 其次定义在 data 段的变量是需要占磁盘空间的,这点上面也说过,而我们程序的起始地址为 0x87800000,计算一下就可以知道,程序到 data 为止就已经占用了 68kb 的磁盘空间,所以编译出来 bin 文件的大小为
    69.6kb 也合情合理
  • 最后我们通过屏蔽 cuo 这个变量编译出来的程序只有 3.4kb 也验证了我们之前说的 bss 段并不占磁盘空间
87800cf0 <__main_from_arm>:
87800cf0:	e51ff004 	ldr	pc, [pc, #-4]	; 87800cf4 <__main_from_arm+0x4>
87800cf4:	87800cc9 	strhi	r0, [r0, r9, asr #25]

Disassembly of section .bss:

87811000 <__bss_start>:
87811000:	00000000 	andeq	r0, r0, r0

87811004 <irq_nesting>:
87811004:	00000000 	andeq	r0, r0, r0

87811008 <irq_table>:
	...

可以看到 cuo 屏蔽掉后,bss 段的起始地址变成了 0x87811000,但是我们的磁盘空间仍然只有 3.4kb。所以又有一个问题摆在眼前,是什么导致了 bss 段和 data 段跟 text 段分隔了如此之远?
我们回到 Makefile 中,在 Makefile 有我们链接实现的命令

$(TARGET).bin:$(OBJS)

#链接 -Ttext 为使用指定地址作为text文本段起始地址
#$^ 目标所有的依赖文件 等同于 $(LD) -Ttext 0x87800000 -o led_sdk.elf start.o main.o
	$(LD) -Ttext 0x87800000 -o $(TARGET).elf $^

#复制一个文件到另一个文件 -O 指定输出文件类型 binary 为二进制bin 通常用于将elf文件转成bin文件
#$@ 目标文件 等同于 $(OBJCOPY) -O binary -S led_sdk.elf led_sdk.bin
	$(OBJCOPY) -O binary -S $(TARGET).elf $@

#反汇编 -D 反汇编所有的section比如 text data bss段 -m 反汇编使用的架构如arm
	$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis

通过 Makefile 可以得知我们使用 $(LD) -Ttext 0x87800000 -o $(TARGET).elf $^ 将 text 段链接到了 0x87800000 地址,对于 bss 段和 data 段我们并没有指定链接地址,所以编译器自动将它们链接到了 0x87811000?(我猜的)。这下就真相大白了。

解决

其实导致这个问题原因也是因为我偷懒了,因为原子哥其实写了一个 lds 链接文件,但是我并没有使用,而是一直沿用最开始的链接命令

SECTIONS{

	. = 0X87800000;

	.text :

	{

		obj/start.o 

		*(.text)

	}

	.rodata ALIGN(4) : {*(.rodata*)}     

	.data ALIGN(4)   : { *(.data) }    

	__bss_start = .;    

	.bss ALIGN(4)  : { *(.bss)  *(COMMON) }    

	__bss_end = .;

}

可以看到原子哥的 lds 文件已经将 test 段的起始位置设置为了 0x87800000,然后依次是 rodata 段、data 段、bss 段,这些都放在了 text 段的后面,所以就不会出现 text 段和 data 段中间隔了 68kb 的空间。我们修改 Makefile 的链接命令为 $(LD) -Timx6ul.lds -o $(TARGET).elf $^,编译

87800d48 <__main_from_arm>:
87800d48:	e51ff004 	ldr	pc, [pc, #-4]	; 87800d4c <__main_from_arm+0x4>
87800d4c:	87800d21 	strhi	r0, [r0, r1, lsr #26]

Disassembly of section .data:

87800d50 <cou>:
87800d50:	00000002 	andeq	r0, r0, r2

Disassembly of section .bss:

87800d54 <count>:
87800d54:	00000000 	andeq	r0, r0, r0

87800d58 <irq_nesting>:
87800d58:	00000000 	andeq	r0, r0, r0

87800d5c <irq_table>:
	...

可以看到 data 段的地址已经依次在 text 段地址的后面了,好,问题解决
因为偷懒引发的问题,也算是因祸得福,加深了对程序各个段的理解

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值