Introduction Link to heading
This post will explain the linker script and assembly initialization before jumping to main using stm32f4 board and Hello world example from github.
The end Link to heading
I will describe this the same way i gone through it. Starting with the linking command.
arm-none-eabi-gcc -g -O2 -Wall -Tstm32_flash.ld -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Iinc -Ilib -Ilib/inc -Ilib/inc/core -Ilib/inc/peripherals src/main.c src/stm32f4xx_it.c src/system_stm32f4xx.c lib/startup_stm32f4xx.s -o main.elf -Llib -lstm32f4
Starting with main.c, well nothing interesting there. just the main, obviously.
So,I jumped to system_stm32f4xx.c, it has 2 functions but only SystemInit called somewhere else. It’s called from startup_stm32f4xx.s specifically from routine Reset_Handler.
From the snippet below, it’s clear that Reset_Handler is the bootstrap routine because it does two things
- Hardware-specific initialization with
SystemInit - Jump to
main
/* Call the clock system intitialization function.*/
bl SystemInit
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
bx lr
Reset_Handler and ARM M4 boot Link to heading
Reset_Handler is 2nd entry in interrupt vector isr_vector defined in the same file.
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
So, How is Reset_Handler is called?
stm32 RM Describes the boot process from address 0x0000 0000.
Due to its fixed memory map, the code area starts from address 0x0000 0000 (accessed through the ICode/DCode buses) while the data area (SRAM) starts from address 0x2000 0000 (accessed through the system bus). The CortexTM-M4 with FPU CPU always fetches the reset vector on the ICode bus, which implies to have the boot space available only in the code area (typically, Flash memory). STM32F4xx microcontrollers implement a special mechanism to be able to boot from other memories (like the internal SRAM).
And next section describes the memory remap to allow ICode to access the boot memories (Flash, SRAM).
Physical remap in STM32F405xx/07xx and STM32F415xx/17xx Once the boot pins are selected, the application software can modify the memory accessible in the code area (in this way the code can be executed through the ICode bus in
The way i understand it, the boot mode is selected based on BOOT1 and BOOT0. and the address range starting 0x00000000 gets remapped to the boot source. Table 3 shows that mapping.
And finally it mentions, the first 2 entries that the core need. So, we know that core will start executing from address at 0x00000004. in our case, 0x080000000 + 0x00000004.
The BOOT pins are also resampled when the device exits the Standby mode. Consequently, they must be kept in the required Boot mode configuration when the device is in the Standby mode. After this startup delay is over, the CPU fetches the top-of-stack value from address 0x0000 0000, then starts code execution from the boot memory starting from 0x0000 0004.
So, we know what the program will execute somewhere after 0x08000000 and we need to put the address of Reset_Handler at offset 0x00000004.
how is that done?
The linker black magic Link to heading
stm32_flash.ld defines the memory regions and how sections will be linked into the final ELF.
The memory map Link to heading
MEMORY defines the memory map available on the system bus. Note that FLASH origin at 0x08000000 and RAM origin at 0x20000000 as defined by RM
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K
}
ist_vector Link to heading
This section puts isr_vector section into the FLASH memory region and being the first one, it will be placed at address 0x080000000. Remember the memory remap from the last section? this means that Reset_Handler will be at the correct address.
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
Dumping the elf section after linking with arm-none-eabi-objdump -x main.elf shows the ist_vector at address 0800000.
Sections:
Idx Name Size VMA LMA File off Algn
0 .isr_vector 00000188 08000000 08000000 00008000 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
text Link to heading
Same as isr_vector, the .text section is the executable code will go into the FLASH region.
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
} >FLASH
with objdump, we see that .text section starts right after isr_vector at 0x08000188
1 .text 00000ea8 08000188 08000188 00008188 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
data and BSS Link to heading
Data and BSS are used for static variables in each object file. data is used to allocate variables with initial value. BSS used for variables without initial value.
There are 3 important parts here
_sidatabefore the.datasection.data : AT ( _sidata )while defining the data section_sdataand_edata
/* used by the startup to initialize data */
_sidata = .;
/* Initialized data sections goes into RAM, load LMA copy after code */
.data : AT ( _sidata )
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM
By doing _sidata =.;, we store the current address(that depends on how big sections were) in _sidata.
Next, By using AT for .data, we define the VMA and LMA to be different. in this case
VMAwill be the address from RAM (from>RAM)LMAwill be the address we are at the ELF(or in this case, FLASH).
What are LMA and VMA and why they are important?
The problem is data are read/write regions so, it lives in the RAM but we need to allocate initial values in the FLASH . We will see that Reset_Handler will need to copy that from the flash to the RAM.
But Linker will use VMA to resolve and relocate the variables. More details at ld doc.
With odjdump, We can see the difference between LMA and VMA.
Note that wasn’t the case in .text and .bss.
Idx Name Size VMA LMA File off Algn
5 .data 00000458 20000000 08001040 00010000 2**3
6 .jcr 00000004 20000458 08001498 00010458 2**2
7 .bss 00000024 2000045c 0800149c 0001045c 2**2
So, at this points we have 3 addresses
_sidata: address of data in elf (AKA flash) relative 08000000_sdata: start address for data section the ram relative 20000000_edata: end address for data section the ram.
for BSS, it’s same. There are addresses _sbss and _ebss like we have _sdata and _edata.
What Does Reset_handler do? Link to heading
At this point, we have done the following:
- Went through ARM M4 boot sequence and saw how it eventually calls
Reset_Handler. - Understood how the linker script tells the linker where to loads elf sections in FLASH and RAM regions.
Now that Reset_Handler is called. It does 4 things:
- Initialize the data section by copying from the flash to the ram. This is done by
LoopCopyDataInitand using_sidata,_sdataand_edata. - Initialize BSS section in ram with zeros. This is done by
LoopFillZerobssand uses_sbssand_ebss. - Calls
SystemInit - Calls
main
Note, __libc_init_array seems like call-back from compiler/libc to initialized things before calling the main. Not even trying to go there!
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
/* Zero fill the bss segment. */
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
ldr r3, = _ebss
cmp r2, r3
bcc FillZerobss
/* Call the clock system intitialization function.*/
bl SystemInit
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main