搜索
bottom↓
回复: 2

mbed-os启动过程分析

[复制链接]

出0入0汤圆

发表于 2020-11-4 09:07:19 | 显示全部楼层 |阅读模式
以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

出280入168汤圆

发表于 2020-11-4 11:08:21 | 显示全部楼层
Bootstrap(引导程序)

引入CMSIS-Core文件

要使用Mbed OS,您需要按照CMSIS-Core文档中的描述为设备实现CMSIS-Core支持。 CMSIS-Core文件通常位于:mbed-os\targets\TARGET_<VENDOR>\TARGET_MCU_<FAMILY>\TARGET_<MCUNAME>\device 目录中。

启动文件

启动文件包含中断向量以及底层内核和平台初始化例程。您需要为每个Mbed OS支持的工具链提供此文件的版本。

有关启动文件的更多信息,请参见CMSIS文档。

CMSIS文档中的模板启动文件包括汇编器中的堆和堆栈区域。在Mbed OS中忽略这些区域,因为它们来自链接程序脚本。

向量表指定的初始堆栈指针应从链接程序脚本派生(如 |ARM_LIB_STACK$$ZI$$Limit| , __StackTop 或 sfe(CSTACK) ),而不是硬编码在启动文件中。

链接描述文件

添加核心文件后的下一步是为Mbed OS添加链接描述文件。为此可使用下面的链接描述文件并更改目标的定义,也可以修改现有的链接描述文件以与Mbed OS兼容。这需要为每个Mbed OS支持的工具链提供一个链接程序脚本版本。

如果要更新自己的链接描述文件,则必须:
    • 为RAM向量表保留空间。
    • 定义堆区域:
            ▪ Arm                 - 堆是ARM_LIB_HEAP区域。
            ▪ GCC_ARM         - 堆始于符号 __end__,结束于 __HeapLimit符号。
            ▪ IAR                 - 堆是HEAP区域。
        ◦ 定义启动堆栈区域:
            ▪ Arm                 - 引导堆栈位于ARM_LIB_STACK区域。
            ▪ GCC_ARM         - 引导堆栈始于符号 __StackLimit,结束于符号 __StackTop。
            ▪ IAR                 - 引导堆栈是CSTACK区域。
    • 添加可重定位应用程序的定义         - MBED_APP_START和MBED_APP_SIZE。
    • 添加启动堆栈大小的定义                 - MBED_BOOT_STACK_SIZE。
    • 添加预处理指令                         #! armcc -E (仅ARM编译器)。

出0入0汤圆

 楼主| 发表于 2020-11-4 15:43:42 | 显示全部楼层
格式不太对 比较难看 。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-11 11:18

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表