|
以STM32l431RC芯片平台为例
以下摘自《targets/TARGET_STM/TARGET_STM32L4/TARGET_STM32L431xx/device/TOOLCHAIN_GCC_ARM/STM32L431xx.ld》
MEMORY
{
FLASH (rx) : ORIGIN = MBED_APP_START, LENGTH = MBED_APP_SIZE
RAM (rwx) : ORIGIN = MBED_RAM_START + VECTORS_SIZE, LENGTH = MBED_RAM_SIZE + MBED_RAM1_SIZE - VECTORS_SIZE
}
ENTRY(Reset_Handler)
SECTIONS
{
.text :
{
KEEP(*(.isr_vector))
*(.text*)
KEEP(*(.init))
KEEP(*(.fini))
/* .ctors */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* .dtors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
*(.rodata*)
KEEP(*(.eh_frame*))
} > FLASH
......
.data : AT (__etext)
{
__data_start__ = .;
_sdata = .;
*(vtable)
*(.data*)
. = ALIGN(8);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(8);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(8);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array))
PROVIDE_HIDDEN (__fini_array_end = .);
KEEP(*(.jcr*))
. = ALIGN(8);
/* All data end */
__data_end__ = .;
_edata = .;
} > RAM
......
.bss :
{
. = ALIGN(8);
__bss_start__ = .;
_sbss = .;
*(.bss*)
*(COMMON)
. = ALIGN(8);
__bss_end__ = .;
_ebss = .;
} > RAM
......
}
'.isr_vector'在FLASH的起始位置,这个地方存储的是中断向量表'g_pfnVectors' 具体可查看芯片启动文件.
可以通过编译成功之后的map文件查看'g_pfnVectors'符号的存储位置是否是FLASH的起始位置.
以下摘自《your_project_build.map》
.text 0x0000000008000000 0x29924
*(.isr_vector)
.isr_vector 0x0000000008000000 0x18c BUILD/TARGET_NAME/GCC_ARM/mbed-os/targets/TARGET_STM/TARGET_STM32L4/TARGET_STM32L431xx/device/TOOLCHAIN_GCC_ARM/startup_stm32l431xx.o
0x0000000008000000 g_pfnVectors
通过查看map文件确认该符号确实在'0x0000000008000000'位于FLASH起始地址处.
以下摘自《https://gcc.gnu.org/》
# 18.20.5 How Initialization Functions Are Handled
The compiled code for certain languages includes constructors (also called initialization routines)—functions
to initialize data in the program when the program is started.
These functions need to be called before the program is “started”—that is to say, before main is called.
Compiling some languages generates destructors (also called termination routines) that should be called when the program terminates.
To make the initialization and termination functions work,
the compiler must output something in the assembler code to cause those functions to be called at the appropriate time.
When you port the compiler to a new system, you need to specify how to do this.
The best way to handle static constructors works only for object file formats which provide arbitrarily-named sections.
A section is set aside for a list of constructors, and another for a list of destructors.
Traditionally these are called '.ctors' and '.dtors'.
Each object file that defines an initialization function also puts a word in the constructor section to point to that function.
The linker accumulates all these words into one contiguous '.ctors' section.
Termination functions are handled similarly.
7.3 Vague Linkage
There are several constructs in C++ that require space in the object file but are not clearly tied to a single translation unit.
We say that these constructs have “vague linkage”.
Typically such constructs are emitted wherever they are needed, though sometimes we can be more clever.
Inline Functions
Inline functions are typically defined in a header file which can be included in many different compilations.
Hopefully they can usually be inlined, but sometimes an out-of-line copy is necessary, if the address of the function is taken or if inlining fails.
In general, we emit an out-of-line copy in all translation units where one is needed.
As an exception, we only emit inline virtual functions with the vtable, since it always requires a copy.
Local static variables and string constants used in an inline function are also considered to have vague linkage,
since they must be shared between all inlined and out-of-line instances of the function.
# VTables
C++ virtual functions are implemented in most compilers using a lookup table, known as a vtable.
The vtable contains pointers to the virtual functions provided by a class,
and each object of the class contains a pointer to its vtable (or vtables, in some multiple-inheritance situations).
If the class declares any non-inline, non-pure virtual functions, the first one is chosen as the “key method” for the class,
and the vtable is only emitted in the translation unit where the key method is defined.
Note:
If the chosen key method is later defined as inline, the vtable is still emitted in every translation unit that defines it.
Make sure that any inline virtuals are declared inline in the class body, even if they are not defined there.
以下摘自《targets/TARGET_STM/TARGET_STM32L4/TARGET_STM32L431xx/device/TOOLCHAIN_GCC_ARM/startup_stm32l431xx.S》
.section .isr_vector, "a", %progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
......
以下摘自《The GNU linker.pdf》
# 3.4.1 Setting the Entry Point
The first instruction to execute in a program is called the entry point.
You can use the ENTRY linker script command to set the entry point.
The argument is a symbol name:
ENTRY(symbol)
There are several ways to set the entry point.
The linker will set the entry point by trying each of the following methods in order, and stopping when one of them succeeds:
the '-e' entry command-line option;
the ENTRY(symbol) command in a linker script;
the value of the symbol start, if defined;
the address of the first byte of the '.text' section, if present;
The address 0.
在linker文件中明确指明了入口ENTRY(Reset_Handler)即程序开始运行处为Reset_Handler函数.
# 3.6.4.4 Input Section and Garbage Collection
When link-time garbage collection is in use ('--gc-sections'), it is often useful to mark sections that should not be eliminated.
This is accomplished by surrounding an input section's wildcard entry with KEEP(), as in KEEP(*(.init)) or KEEP(SORT_BY_NAME(*)(.ctors)).
在linker文件中使用KEEP来确保符号占位.
# 3.5.2 PROVIDE
In some cases, it is desirable for a linker script to define a symbol only if it is referenced and is not defined by any object included in the link.
For example, traditional linkers defined the symbol 'etext'.
However, ANSI C requires that the user be able to use 'etext' as a function name without encountering an error.
The PROVIDE keyword may be used to define a symbol, such as 'etext', only if it is referenced but not defined.
The syntax is PROVIDE(symbol = expression).
Here is an example of using PROVIDE to define 'etext':
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
In this example, if the program defines '_etext' (with a leading underscore), the linker will give a multiple definition error.
If, on the other hand, the program defines 'etext' (with no leading underscore), the linker will silently use the definition in the program.
If the program references 'etext' but does not define it, the linker will use the definition in the linker script.
在linker文件中使用PROVIDE描述的符号可以不被外部程序定义也可以被定义.
# 3.6.8.2 Output Section LMA
Every section has a virtual address (VMA) and a load address (LMA).
The address expression which may appear in an output section description sets the VMA.
The expression lma that follows the AT keyword specifies the load address of the section.
If neither AT nor AT> is specified for an allocatable section,
the linker will set the LMA such that the difference between VMA and LMA for the section is the same as the preceding output section in the same region.
If there is no preceding output section or the section is not allocatable, the linker will set the LMA equal to the VMA.
Every loadable or allocatable output section has two addresses.
The first is the VMA, or virtual memory address.
This is the address the section will have when the output file is run.
The second is the LMA, or load memory address.
This is the address at which the section will be loaded.
In most cases the two addresses will be the same.
An example of when they might be different is when a data section is loaded into ROM,
and then copied into RAM when the program starts up (this technique is often used to initialize global variables in a ROM based system).
In this case the ROM address would be the LMA, and the RAM address would be the VMA.
在linker文件中使用AT来指定LOAD-ADDRESS.
以下摘自《targets/TARGET_STM/TARGET_STM32L4/TARGET_STM32L431xx/device/TOOLCHAIN_GCC_ARM/startup_stm32l431xx.S》
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
/* Atollic update set stack pointer */
ldr sp, = _estack
/* 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
/* Calling the crt0 'cold-start' entry point. */
/* There __libc_init_array is called and when existing hardware_init_hook() and software_init_hook() before starting main(). */
/* software_init_hook() is available and has to be called due to initializsation when using rtos. */
bl _start
bx lr
LoopForever:
b LoopForever
.size Reset_Handler, .-Reset_Handler
以下摘自《targets/TARGET_STM/TARGET_STM32L4/TARGET_STM32L431xx/device/TOOLCHAIN_GCC_ARM/STM32L431xx.ld》
/* Set stack top to end of RAM, and stack limit move down by size of stack_dummy section */
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
_estack = __StackTop;
__StackLimit = __StackTop - MBED_CONF_TARGET_BOOT_STACK_SIZE;
PROVIDE(__stack = __StackTop);
以下摘自《The GNU linker.pdf》
We have four output sections:
.text program code;
.rodata read-only data;
.data read-write initialized data;
.bss read-write zero initialized data.
以下摘自《targets/TARGET_STM/TARGET_STM32L4/TARGET_STM32L431xx/device/TOOLCHAIN_GCC_ARM/system_stm32l4xx.c》
void SystemInit(void)
{
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state */
/* Set MSION bit */
RCC->CR |= RCC_CR_MSION;
/* Reset CFGR register */
RCC->CFGR = 0x00000000U;
/* Reset HSEON, CSSON , HSION, and PLLON bits */
RCC->CR &= 0xEAF6FFFFU;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x00001000U;
/* Reset HSEBYP bit */
RCC->CR &= 0xFFFBFFFFU;
/* Disable all interrupts */
RCC->CIER = 0x00000000U;
#ifdef VECT_TAB_SRAM
/* Vector Table Relocation in Internal SRAM */
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
#include "nvic_addr.h" // MBED
SCB->VTOR = NVIC_FLASH_VECTOR_ADDRESS; // MBED
#endif
}
Reset_Handler过程:
1、 设置SP栈顶指针为RAM结束地址.
2、 将data从FLASH拷贝到RAM中并完成相关初始化,有初始值的给初始值,无初始值的赋值零,涉及到.bss/.sidata/.data统称data
3、 执行芯片级初始化'SystemInit'完成RCC时钟配置到默认值,对于芯片而言时钟树很关键,并根据应用需要设置向量表寄存器.
4、 执行gcc底层库_start启动函数,调用software_init_hook()函数,此函数在mebd-os中有两套实现区分使用RTOS和裸机情况,此次分析带RTOS的情况.
5、 software_init_hook() ---> [mbed_init() && mbed_rtos_start()]
6、 mbed_init() ---> [mbed_mpu_manager_init() && mbed_cpy_nvic() && mbed_sdk_init() && [mbed_rtos_init() ---> osKernelInitialize()]
7、 mbed_rtos_start() ---> [mbed_start() && osKernelStart()]
8、 mbed_start() ---> [mbed_rtos_init_singleton_mutex() && [mbed_toolchain_init() ---> __libc_init_array()] && mbed_tfm_init() && mbed_main() && mbed_error_initialize() && main()]
9、 main() ---> user code
__libc_init_array题外话:
Run the C++ global object constructors.
__libc_init_array iterates through that section and calls the function pointers placed there to alloc memory for static C++ classes
or functions marked with __attribute__((constructor)) if you're just using plain C.
If you don't recall using that attribute: such functions might be part of linked libs.
void __libc_init_array (void)
{
size_t count;
size_t i;
count = __preinit_array_end - __preinit_array_start;
for (i = 0; i < count; i++)
__preinit_array_start ();
_init ();
count = __init_array_end - __init_array_start;
for (i = 0; i < count; i++)
__init_array_start ();
}
BootRAM题外话:
In all the startup scripts provided by the ST standard peripheral library, there is an entry at the end like this:
.word BootRAM
earlier in the startup, BootRAM gets defined like:
.equ BootRAM, 0xF1E0F85F
Due to its fixed memory map, the code area starts from address 0x00000000 (accessed through the ICode/DCode buses)
while the data area (SRAM) starts from address 0x20000000 (accessed through the system bus).
The Cortex®-M3 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).
STM32F10xxx microcontrollers implement a special mechanism to be able to boot also from SRAM and not only from main Flash memory and System memory.
When a STM32F1 part is reset, the memory block starting at 0x00000000 is mapped based on the configuration of the BOOT pins.
When it is set to boot from flash, that block is aliased to flash;
when it is set to run from the bootloader, that block is aliased to a block of internal ROM (around or slightly below 0x1FFFF000).
However, when it is set to boot from RAM, something very strange happens.
Instead of aliasing that memory block to SRAM, as you would expect, that memory block is aliased to a tiny (16 byte!) ROM.
On a STM32F103C8 (medium density) part, this ROM has the contents:
20005000 20000109 20000004 20000004
This data is interpreted as a vector table:
The first word causes the stack pointer to be initialized to 0x20005000, which is at the top of RAM.
The second word is the reset vector, and is set to 0x20000108 (with the low bit set to enable Thumb mode).
This address is in RAM as well, a few words beyond the end of the vector table, and it's where you're supposed to put the "magic" value 0xF108F85F.
This 0xF108F85F is actually the instruction "ldr.w pc, [pc, #-480]", which loads the real reset vector from RAM and branches to it.
The third and fourth words are the NMI and hardfault vectors.
They do not have the low bit set, so the processor will double-fault if either of these exceptions occurs while VTOR is still zero.
Confusingly, the PC will be left pointing to the vector table in RAM.
The exact contents of this ROM vary slightly from part to part.
For example, a F107 (connectivity line) has the ROM contents:
20005000 200001e1 20000004 20000004
which has the same initial SP, but a different initial PC.
This is because this part has a larger vector table, and the medium-density address would be inside its vector table.
A full list of the locations and values used is:
Low/medium density: 0x0108 (value: 0xF108F85F)
Low/medium density value line: 0x01CC (value: 0xF1CCF85F)
Note:
ST's sample files give the same value as for low/medium density parts.
I'm pretty sure this is wrong and have corrected it here, but I don't have any parts of this type to test with.
I'd appreciate feedback to confirm if this works.
All others: 0x01E0 (value: 0xF1E0F85F)
eh_frame题外话:
About eh_frame, it contains exception unwinding and source language information.
Each entry in this section is represented by single CFI (call frame information )
see, eh_frame in linuxfoundation
eh_frame_hdr, is used by c++ runtime code to access the eh_frame.
That means, it contains the pointer and binary search table to efficiently retrieve the information from eh_frame.
For exception handling, modern linux systems use an .eh_frame section.
This section is similar to .debug_frame, but includes language specific info, has a slightly more compact encoding and is loaded with the program.
When using languages that support exceptions, such as C++,
additional information must be provided to the runtime environment that describes the call frames that much be unwound during the processing of an exception.
This information is contained in the special sections .eh_frame and .eh_framehdr.
参考链接:
Initialization (GNU Compiler Collection (GCC) Internals)
Exception Frames
Memory related issue with MCU startup ( __libc_init_array )
ARM Cortex-M3 boot from RAM initial state |
阿莫论坛20周年了!感谢大家的支持与爱护!!
该献的血还是要献的。你不献他不献。难道让我去献? --- 出自坛友:lovejp1981
|