【问题标题】:GCC Linker, bss section symbols equal zero lengthGCC 链接器,bss 部分符号等于零长度
【发布时间】:2018-04-26 10:08:16
【问题描述】:

为了尝试理解基础知识,我编写了[或提取我猜想]以下 c 代码和链接器脚本。生成的二进制文件可以正常工作,并且 LED 会毫无问题地闪烁。但是,在调试时,我发现 bss_startbss_end 符号的值都为 0x20000000。 bss归零功能基本被跳过。在做 objdump 时,我可以看到

20000000 g O .bss 00000004 timer_delayCount

所以该部分有 4 个字节长,位于 0x20000000 正确位置。并且至少 bss_start 指向正确的内存地址。但是,bss_end 应该指向 0x20000004 [我认为],而不是 0x20000000。

我现在想知道 bss_end 符号在我认为包含四个字节的 *(.bss) 被放置之后没有增加。

微控制器是stm32f103rb,一个cortex-m3芯片。我正在使用 ARM GNU GCC

我的 main.c 文件:

#define _stackInit 0x20005000U

volatile unsigned int * const RCC_APB2ENR = (unsigned int *)0x40021018;
volatile unsigned int * const GPIOA_CRL = (unsigned int *)0x40010800;
volatile unsigned int * const GPIOA_BSR = (unsigned int *)0x40010810;
volatile unsigned int * const GPIOA_BRR = (unsigned int *)0x40010814;
volatile unsigned int * const STK_CTRL = (unsigned int *)0xE000E010;
volatile unsigned int * const STK_LOAD = (unsigned int *)0xE000E014;
volatile unsigned int * const STK_VAL = (unsigned int *)0xE000E018;

volatile unsigned int timer_delayCount;

int main() {
    // enable GIOA clock and set PB5 to output
    *RCC_APB2ENR |= (unsigned int)0x00000004;
    *GPIOA_CRL = (unsigned int)0x44244444;

    // COnfigure Systick Timer for 1 millisecond interrupts
    *STK_VAL = 0x00;
    *STK_LOAD = 7999U; //tick every 1 ms
    *STK_CTRL = 0x07;


    while (1){
        int c, d;
        timer_delayCount = 500u;
        while(timer_delayCount != 0u);
        *GPIOA_BSR = 0x20;

        timer_delayCount = 500u;
        while(timer_delayCount != 0u);
        *GPIOA_BRR = 0x20;
    }

}


// Begin and End addresses for the .bss section. Symbols defined in linker script
extern unsigned int __bss_start__;
extern unsigned int __bss_end__;

void __attribute__ ((section(".after_vectors"))) Reset_Handler (void)
{

    // Initialize bss section by iterating and clearing word by word.
    // It is assumed that the pointers are word aligned.
    unsigned int *p = &__bss_start__;
    while (p < &__bss_end__) {
        *p++ = 0;   
    }

    main();
}

void SysTick_Handler() {
    // Decrement to coutner to zero.
    if (timer_delayCount != 0u)
    {
        --timer_delayCount;
    }
}

void __attribute__ ((section(".after_vectors"))) Default_Handler(void)
{
    while(1);
}

void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) NMI_Handler(void);
void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) HardFault_Handler(void);
void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) MemManage_Handler(void);
void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) BusFault_Handler(void);
void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) UsageFault_Handler(void);
void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) SVC_Handler(void);
void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) DebugMon_Handler(void);
void __attribute__ ((section(".after_vectors"),weak, alias ("Default_Handler"))) PendSV_Handler(void);

typedef void(* const pHandler)(void);

// The vector table.
// The linker script to place at correct location in memory.
__attribute__ ((section(".isr_vector"),used)) pHandler __isr_vectors[] =
{
    //core exceptions
    (pHandler)_stackInit,   // Inital Stack Pointer
    Reset_Handler,          // reset handler
    NMI_Handler,            // NMI handler
    HardFault_Handler,      // hard fault handler
    MemManage_Handler,      // MPU fault handler
    BusFault_Handler,       // bus fault handler
    UsageFault_Handler,     // usage fault handler
    0x00,                   // reserved
    0x00,                   // reserved
    0x00,                   // reserved
    0x00,                   // reserved
    SVC_Handler,            // SVCall handler
    DebugMon_Handler,       // debug monitor handler
    0x00,                   // reserved
    PendSV_Handler,         // PendSV handler
    SysTick_Handler,        // systick handler
};

我的链接器文件:

ENTRY(Reset_Handler)

MEMORY
{
  RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
  FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
}


SECTIONS
{
    .text : {
        *(.isr_vector)
        *(.after_vectors)
        *(.text)
    } > FLASH

    .bss : {
        __bss_start__ = .;      /* symbol for c code to initialize bss section */
        *(.bss)
        __bss_end__ = .;        /* symbol for c code to initialize bss section */
    } > RAM
}

编译器命令:

/opt/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb -g blinky-interrupt.c -o blinky-interrupt.o
/opt/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-ld -T blinky-interrupt.ld blinky-interrupt.o -o blinky-interrupt.elf

【问题讨论】:

  • 如果timer_delayCount 是第一个和最后一个对象,那么 start 和 end 肯定有相同的地址吗?在这种情况下,“end”是 inclusive 0x20000004 将是其他东西的起始地址,而不是 bss 的结尾。添加第二个对象来证明这一点。
  • @Clifford:如果节中有一个四字节对象时的起始地址和结束地址相同,那么当节中没有对象时,它们是什么?
  • @EricPostpischil:我不知道;我只是在应用奥卡姆剃刀。我想链接器可以完全删除该部分,但几乎不重要,如果没有引用空间,大小无关紧要。
  • 通常 gcc 实现通过(伪代码)memset(start_bss, 0, end_bss - start_bss)while(start != end) { *start = 0; start++; } 初始化 bss(ARM thumb crt 执行后者,尽管 crt 可能是用汇编程序编写的)。所以是的,在这种情况下,如果 bss_end 以 4 结尾是最有意义的。

标签: c gcc linker arm embedded


【解决方案1】:

这是因为一个单元化变量进入了COMMON instead of .bss。如果你用 0 初始化它,那么它会进入.bss

查看链接器映射文件(如果没有,让链接器使用-Map 生成它),您应该会看到类似这样的内容

.bss            0x0000000020000000        0x4 load address 0x00000000080000e0
                0x0000000020000000                . = ALIGN (0x4)
                0x0000000020000000                __bss_start__ = .
 *(.bss)
                0x0000000020000000                . = ALIGN (0x4)
                0x0000000020000000                __bss_end__ = .
 COMMON         0x0000000020000000        0x4 ./src/app/main.o
                0x0000000020000000                timer_delayCount

当链接器脚本中没有指定COMMON 时,链接器会将它放在其他地方,可能会在最后转储它。

更完整的链接描述文件有以下.bss 部分

  .bss :
  {
    . = ALIGN(4);
    __bss_start__ = .;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    __bss_end__ = .;
  } >RAM

那么COMMON 部分将介于__bss_start____bss_end__ 之间。

仅供参考,*(.bss*) 用于覆盖-fdata-sections 选项,当每个变量都有自己的段时,以便链接器可以删除未引用的段。

【讨论】:

  • 从我在 gcc ARM 系统上看到的情况来看,即使没有显式初始化为 0,变量也不会在 COMMON 中结束。现在看一个这样的项目,COMMON 只是在bss 末尾的映射文件,其中没有实际数据。
  • @Lundin 我认为我们必须使用-fno-common 进行编译才能获得这种行为(尽管这可能是使用 Keil 工具)就个人而言,默认的 COMMON 行为对我来说很神秘。为什么你想在全局名称范围内静默合并变量的实例而不是错误输出对我来说从来没有意义。
  • 感谢地图文件指针。 COMMON 是答案,尽管我不明白为什么 objdump 会将此符号显示为一般属于 .bss 部分,而不是更具体地属于 common。除了地图文件,还有什么可以看到的吗?就像在链接器运行之前从目标文件中一样?还要感谢您告知“= 0”仍然算作.bss。我认为将符号设置为任何值(甚至为零)都算作初始化,即... .data 部分。小,但很高兴知道信息。
  • @Lundin 有或没有-fdata-sections?
  • @berendi 没有...我想。我认为该选项与 ELF 文件生成有关,与单独的部分与池有关?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-08
  • 2015-05-27
  • 2013-12-12
  • 2017-06-14
  • 1970-01-01
  • 1970-01-01
  • 2019-01-21
相关资源
最近更新 更多