Search code examples
cassemblyinitializationstartupcortex-m

Cortex m3 first instruction execution


I am using Sourcery CodeBench Lite 2012.03-56 compiler and gdb suite with texane gdb server.

Today I wanted to try FreeRTOS demo example for cheap STM32VLDISCOVERY board, I copied all the source files needed, compiled without errors but the example didn't work. I fired up debugger and noticed that example fails when it is trying to dereference pointer to GPIO registers. Global array variable which contains pointers to GPIO registers:

GPIO_TypeDef* GPIO_PORT[LEDn] = {LED3_GPIO_PORT, LED4_GPIO_PORT};

was not properly initialized and was filled with some random values. I checked preprocessor defines LED3_GPIO_PORT and LED3_GPIO_PORT and they were valid.

After some researching where the problem might be I looked at the start-up file provided for trueSTUDIO found in CMSIS lib. Original startup_stm32f10x_md_vl.S file:

    .section    .text.Reset_Handler
    .weak   Reset_Handler
    .type   Reset_Handler, %function
Reset_Handler:

/* Copy the data segment initializers from flash to SRAM */
  movs  r1, #0
  b LoopCopyDataInit

CopyDataInit:
    ldr r3, =_sidata
    ldr r3, [r3, r1]
    str r3, [r0, r1]
    adds    r1, r1, #4

LoopCopyDataInit:
    ldr r0, =_sdata
    ldr r3, =_edata
    adds    r2, r0, r1
    cmp r2, r3
    bcc CopyDataInit
    ldr r2, =_sbss
    b   LoopFillZerobss
...

During the debugging I noticed that register r1 is never initialized to zero by first instruction movs r1, #0. Register r1 is used as a counter in loop so when the execution reaches loop LoopCopyDataInit it never enters the loop since register r1 is loaded with some garbage data from previous execution. As the result of this the startup code never initializes the .data section.

When I placed two nop instructions before movs r1, #0 instruction then register r1 was initialized to 0 and the example began to work:

Modified part of startup_stm32f10x_md_vl.S file:

/* Copy the data segment initializers from flash to SRAM */
  nop
  nop
  movs  r1, #0
  b LoopCopyDataInit

This is disassembly of relevant parts of final code:

Disassembly of section .isr_vector:

08000000 <g_pfnVectors>:
 8000000:       20002000        andcs   r2, r0, r0
 8000004:       08000961        stmdaeq r0, {r0, r5, r6, r8, fp}
 ...

Disassembly of section .text:

 ...
8000960 <Reset_Handler>:
 8000960:   2100            movs    r1, #0
 8000962:   f000 b804       b.w     800096e <LoopCopyDataInit>

08000966 <CopyDataInit>:
 8000966:   4b0d            ldr     r3, [pc, #52]   ; (800099c <LoopFillZerobss+0x16>)
 8000968:   585b            ldr     r3, [r3, r1]
 800096a:   5043            str     r3, [r0, r1]
 800096c:   3104            adds    r1, #4 

As you can see the ISR vector table is properly pointing to Reset_Handler address. So, what is happening? Why the first instruction movs r1, #0 was never executed in original start-up code?

EDIT:

The original code works when I power off the board and power it back on again. I can reset the MCU multiple times and it works. When I start gdb-server then the code doesn't work, even after reset. I have to power cycle it again to work. I guess this is some debugger weirdness going on.

NOTE:

I had a look what start-up code are other people using with this MCU and they either disable interrupts or load SP register with a linker defined value which is in both cases redundant. If they got hit by this odd behaviour they would not notice it, ever.


Solution

  • Sounds like a bug in your debugger. Probably it sets a breakpoint on the first instruction and either skips it completely or somehow reexecuting it doesn't work properly. The issue could be complicated by the fact that it's a reset vector, maybe it's just not possible to reliably stop at the first instruction. Since the NOPs help, I'd recommend leaving them in place while you're developing your program.

    However, there is an alternative solution. Since it's unlikely that you'll need to modify the array, you don't really need it in writable section. To have the compiler put the array in the flash, usually it's enough to declare it as const:

    GPIO_TypeDef* const GPIO_PORT[LEDn] = {LED3_GPIO_PORT, LED4_GPIO_PORT};