4

As a point of trying to understand the basics, I wrote [or extracted I guess] the following c code and linker script. The resulting binary works and the led blinks without issue. However, while debuging, I found that the bss_start and bss_end symbols both hold a value of 0x20000000. The bss zero fuction is basically skipped. When doing an objdump, I can see

20000000 g O .bss 00000004 timer_delayCount

So the section is 4 bytes long and located at 0x20000000 correclty. And at least bss_start is pointed to the correct memory address. However, bss_end should be pointed at 0x20000004 [I think], not 0x20000000.

I would like to now whey the bss_end symbol is not incremeting after the *(.bss), which I think contains four bytes, is placed.

The micro controller is stm32f103rb, a cortex-m3 chip. I am compling with ARM GNU GCC

My main.c file:

#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
};

My linker file:

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
}

Compiler commands:

/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
Chris
  • 490
  • 1
  • 5
  • 11
  • If `timer_delayCount` is the first and last object then start and end have the same address surely? "end" in this context is _inclusive_ `0x20000004` would be the start address of something else, not the end of the bss. Add a second object to demonstrate this. – Clifford Apr 26 '18 at 11:06
  • @Clifford: If the start and end addresses are the same when the section has one four-byte object in it, what are they when the section has no objects in it? – Eric Postpischil Apr 26 '18 at 11:32
  • @EricPostpischil : I have no idea; I am just applying Occam's Razor. I imagine a linker can remove the section entirely, but hardly matters, if nothing references the space, size is irrelevant. – Clifford Apr 26 '18 at 11:37
  • Typically gcc implementations initialize bss by (pseudo code) `memset(start_bss, 0, end_bss - start_bss)` or alternatively `while(start != end) { *start = 0; start++; }` (ARM thumb crt does the latter although the crt is probably written in assembler). So yes it makes the most sense if bss_end is something that ends with 4 in this case. – Lundin Apr 26 '18 at 14:14

1 Answers1

3

That's because an unitialized variable goes into COMMON instead of .bss. Had you initialized it with 0, then it'd go into .bss.

Look at the linker map file (if you don't have it, let the linker generate it with -Map), you should see something like this

.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

When COMMON is not specified in the linker script, the linker puts it somewhere else, probably dumps it at the end.

A more complete linker script has the following .bss section

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

Then the COMMON section would go between __bss_start__ and __bss_end__.

FYI, *(.bss*) is there to cover the -fdata-sections option, when each variable gets its own segment, so that the linker can drop unreferenced ones.

  • From what I've seen on gcc ARM systems, variables don't end up in COMMON even if not explicitly initialized to 0. Looking at one such project right now, COMMON is just some clutter in the map file at the end of bss with no actual data in it. – Lundin Apr 26 '18 at 14:37
  • @Lundin I think we had to compile with `-fno-common` to get that behavior (though that might have been with the Keil tools) Personally, the default COMMON behavior is mystifying to me. Why you would want to silently merge instantiations of variables at the global name scope instead of error out never made sense to me. – Russ Schultz Apr 26 '18 at 15:54
  • Thanks for the map file pointer. COMMON was the answer, though I don't understand why the objdump would show this symbol as belonging to the .bss section in general and not more specifically to common. Is there anyway to see this other than the map file? Like from the object file before the linker is run? Also thanks for the inform that "= 0" still counts as .bss. I thought that setting a symbol to any value at all (even zero) counts as initialization, ie... .data section. Small, but good to know info. – Chris Apr 26 '18 at 23:39
  • @Lundin With or without `-fdata-sections`? – followed Monica to Codidact Apr 27 '18 at 03:00
  • @berendi Without... I think. I think that option has to do with ELF file generation, something with separate sections versus pooling? – Lundin Apr 27 '18 at 06:44