前三篇作为理论基础,这一篇重点看一下在f103中芯片是如何启动的,执行到main函数之前都做了什么工作。

代码编译

编译器的选择

在KeilV5软件中存在两种编译器,分别是基于 Clang/LLVM的ARM Compiler 6和传统的ARM Compiler 5。两种方式可以在下面选项中选择。
在这里插入图片描述

如上图所示,选择的ARM V5,对应到Keil安装目录下则是以下几个程序
在这里插入图片描述

其中armar是用来管理静态库的,armasm是汇编器,armcc是编译器,armlink是链接器,fromelf是ARM ELF文件转换器。

在项目的编译输出日志中可以找到我们选择的编译器信息。
在这里插入图片描述

也可以从项目的生成的batch文件中找到对应的信息

SET PATH=D:\Programs\Keil_v5\ARM\ARMCC\Bin;
SET CPU_TYPE=STM32F103ZE
SET CPU_VENDOR=STMicroelectronics
SET UV2_TARGET=01-FreeRTOS
SET CPU_CLOCK=0x007A1200

编译参数设置

使用Keil软件开发f103单片机,在Keil软件中对工程的编译已经帮我们实现了,下面是对应的编译指令,写在C/C++中。
在这里插入图片描述

--c99 -c --cpu Cortex-M3 -g -O3 --apcs=interwork --split_sections 
-I ../Core/Inc 
-I ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy 
-I ../Drivers/STM32F1xx_HAL_Driver/Inc 
-I ../Middlewares/Third_Party/FreeRTOS/Source/include 
-I ../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2 
-I ../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3 
-I ../Drivers/CMSIS/Device/ST/STM32F1xx/Include 
-I ../Drivers/CMSIS/Include
-I ./RTE/_01-FreeRTOS
-I D:/Document/Arm/Packs/ARM/CMSIS/5.7.0/CMSIS/Core/Include
-I D:/Document/Arm/Packs/Keil/STM32F1xx_DFP/2.4.0/Device/Include
-D __UVISION_VERSION="534" -D _RTE_ -D STM32F10X_HD -D USE_HAL_DRIVER -D STM32F103xE
-o 01-FreeRTOS\*.o --omf_browse 01-FreeRTOS\*.crf --depend 01-FreeRTOS\*.d
  • -c 仅编译不链接
  • –cpu 目标CPU
  • –apcs=interwork 代表支持ARM/Thumb指令集混合调用;
  • –split_sections代表分段编译;
  • -I代表包含的头文件路径;
  • -D代表预处理过程中使用到的宏;
  • -o 01-FreeRTOS*.o 代表生成一系列的二进制文件
  • –omf_browse代表生成浏览信息文件,用于后续调试分析;
  • –depend 01-FreeRTOS*.d代表二进制.o文件具体由什么文件组成;

在每一个生成的.i文件中都包含对应的编译参数信息。

--c99 -c --cpu Cortex-M3 -g -O3 --apcs=interwork --split_sections -I ../Core/Inc -I ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy -I ../Drivers/STM32F1xx_HAL_Driver/Inc -I ../Middlewares/Third_Party/FreeRTOS/Source/include -I ../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2 -I ../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3 -I ../Drivers/CMSIS/Device/ST/STM32F1xx/Include -I ../Drivers/CMSIS/Include
-I.\RTE\_01-FreeRTOS
-ID:\Document\Arm\Packs\ARM\CMSIS\5.7.0\CMSIS\Core\Include
-ID:\Document\Arm\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include
-D__UVISION_VERSION="534" -D_RTE_ -DSTM32F10X_HD -D_RTE_ -DUSE_HAL_DRIVER -DSTM32F103xE
-o 01-freertos\main.o --omf_browse 01-freertos\main.crf --depend 01-freertos\main.d "../Core/Src/main.c"

汇编参数设置

在Options的窗口Asm选项下可以设置汇编器用到的参数。
在这里插入图片描述

--cpu Cortex-M3 -g --apcs=interwork
-I ..\Core\Inc 
-I .\RTE\_01-FreeRTOS 
-I D:\Document\Arm\Packs\ARM\CMSIS\5.7.0\CMSIS\Core\Include 
-I D:\Document\Arm\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include 
--pd "__UVISION_VERSION SETA 534" --pd "_RTE_ SETA 1" --pd "STM32F10X_HD SETA 1" --pd "_RTE_ SETA 1" 
--list "*.lst" 
--xref -o "*.o"
--depend "*.d" 
  • –pd代表的预定义;
  • –list “*.lst” 生成汇编列表文件;
  • –xref -o “*.o” 生成交叉引用文件;
  • –depend “*.d” 生成依赖文件

链接参数设置

在Options的窗口Linker选项下可以设置链接器用到的参数。
在这里插入图片描述

--cpu Cortex-M3 *.o 
--strict --scatter "01-FreeRTOS\01-FreeRTOS.sct" 
--summary_stderr --info summarysizes --map --xref --callgraph --symbols 
--info sizes --info totals --info unused --info veneers 
 --list "01-FreeRTOS.map" 
-o 01-FreeRTOS\01-FreeRTOS.axf 
  • –strict启用严格检查模式;

  • –scatter 使用指定的分散加载描述文件\01-FreeRTOS.sct;

    LR_IROM1 0x08000000 0x00080000  {    ; load region size_region  加载区域 
      ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address  执行区域1
       *.o (RESET, +First)     			 ;所有.o文件,并强制将RESET段放在首位		
       *(InRoot$$Sections)				 ;表示ARM C库的根区,包括_main,_scatterload,_rt_entry
       .ANY (+RO)            			 ;只读代码和数据
       .ANY (+XO)                        ;可执行代码
      }
      RW_IRAM1 0x20000000 0x00010000  {  ; RW data   执行区域2 对应内部SRAM,大小为64KB
       .ANY (+RW +ZI)               	 ;可读写的数据和零初始化数据
      }
    }
    
    • 存储固件的区域地址为0x08000000,对应f103内部的flash模块地址,大小为512KB;

    • RESET段的定义为中断向量表,在F103中一共有76项中断向量表,每一项占4个字节,RESET字段大小为0x130字节;

                      AREA    RESET, DATA, READONLY
      				EXPORT  __Vectors
                      EXPORT  __Vectors_End
                      EXPORT  __Vectors_Size
      
      __Vectors       DCD     __initial_sp               ; Top of Stack
      ....
                      DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
      __Vectors_End
      
    • ANY (+RW) 包括已经初始化的全局变量.data段 + 静态变量;

    • ANY (+ZI)包括微初始化的全局变量.bss段 + 系统自动清零;

    • 根据工程01-FreeRTOS实际编译后,FLASH和RAM中的存储情况如下图所示。

      在这里插入图片描述

  • –summary_stderr将摘要信息输出到标准错误(stderr);

  • –list “01-FreeRTOS.map” 生成详细的映射文件,文件名称为01-FreeRTOS.map;

  • map文件中包括内存布局、符号地址、段大小等信息;

    ==============================================================================
    
    Section Cross References  交叉引用表
        startup_stm32f103xe.o(STACK) refers (Special) to heapauxi.o(.text) for __use_two_region_memory
        ...
        
    ==============================================================================
    
    Removing Unused input sections from the image.   移除未使用的段
        Removing main.o(.rev16_text), (4 bytes)
        ...
        Removing port.o(i.vPortEndScheduler), (32 bytes).
    
    516 unused section(s) (total 36456 bytes) removed from the image.
    
    ==============================================================================
    
    Image Symbol Table  符号表
        Local Symbols
        Symbol Name                              Value     Ov Type        Size  Object(Section)
        ../Core/Src/freertos.c                   0x00000000   Number         0  freertos.o ABSOLUTE
        ...
        .text                                    0x08000484   Section        0  exit.o(.text)
        
        Global Symbols
        Symbol Name                              Value     Ov Type        Size  Object(Section)
         __main                                   0x08000131   Thumb Code     8  __main.o(!!!main)
         ...
         
    ==============================================================================
    
    Memory Map of the image  内存映射表
      Image Entry point : 0x08000131
      Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000029dc, Max: 0x00080000, ABSOLUTE)
       Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x00002950, Max: 0x00080000, ABSOLUTE)
        Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object
        0x08000000   0x08000000   0x00000130   Data   RO            3    RESET               startup_stm32f103xe.o  
        ...
        Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08002950, Size: 0x00002008, Max: 0x00010000, ABSOLUTE)
        Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object
        0x20000000   0x08002950   0x00000004   Data   RW          198    .data               freertos.o
        ...
      
    ==============================================================================
    
    Image component sizes  各个部分的大小和总计大小
          Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name
           380         32          0          4       1720      14358   cmsis_os2.o
           ...
     ==============================================================================
    
        Total RO  Size (Code + RO Data)                10576 (  10.33kB)
        Total RW  Size (RW Data + ZI Data)              8200 (   8.01kB)
        Total ROM Size (Code + RO Data + RW Data)      10716 (  10.46kB)
    
    ==============================================================================  
    
  • -o 01-FreeRTOS\01-FreeRTOS.axf 指定输出文件,文件的名称为\01-FreeRTOS.axf;

  • –info summarysizes输出各段大小的摘要;–info sizes --info totals输出每个段的详细大小和总计信息;–info unused显示未使用的段和函数;–info veneers显示veneers信息;

  • –map生成详细的内存映射表,具体参见上述map表;

  • –xref生成交叉引用表;

  • –callgraph生成函数调用图;

  • –symbols输出符号表;

编译过程及输出文件

在这里插入图片描述

整个编译过程涉及三类文件,第一类是c源文件,位于各个目录下,第二类是汇编文件startup_stm32f103xe.s,第三类是编译器所携带的库文件,在这个项目中对应的文件为c_w.l,位于Keil IDE的安装目录下。首先对源文件和汇编文件进行编译,生成对应的.o文件,而c_w.l文件中已经存放好编译成功的.o文件;目标.o文件准备好后,进行链接操作,将相同的段组合在一起,分析函数之间的调用关系,最后使用fromelf文件将映像文件转换成二进制文件。
在这里插入图片描述

具体的编译过程

以main.c文件为例,分析源文件的编译过程。

  • 预处理:预处理包括宏和头文件展开等,预处理后会生成.i文件;

    main._i文件内容:

    --c99 -c --cpu Cortex-M3 -g -O3 --apcs=interwork --split_sections -I ../Core/Inc -I ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy -I ../Drivers/STM32F1xx_HAL_Driver/Inc -I ../Middlewares/Third_Party/FreeRTOS/Source/include -I ../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2 -I ../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3 -I ../Drivers/CMSIS/Device/ST/STM32F1xx/Include -I ../Drivers/CMSIS/Include
    -I.\RTE\_01-FreeRTOS
    -ID:\Document\Arm\Packs\ARM\CMSIS\5.7.0\CMSIS\Core\Include
    -ID:\Document\Arm\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include
    -D__UVISION_VERSION="534" -D_RTE_ -DSTM32F10X_HD -D_RTE_ -DUSE_HAL_DRIVER -DSTM32F103xE
    -o 01-freertos\main.o --omf_browse 01-freertos\main.crf --depend 01-freertos\main.d "../Core/Src/main.c"
    
  • 编译:使用编译器对.i文件进行编译,编译后会生成.o文件、.crf文件、.d文件

    • 其中.crf文件为交叉引用文件,包含了浏览信息;
    • .d文件描述了.o文件所对应的依赖;
    "D:\Programs\Keil_v5\ARM\ARMCC\Bin\ArmCC" --Via "01-freertos\main.__i"
    

    在这里插入图片描述

  • 链接:使用链接器将.o文件链接

    "D:\Programs\Keil_v5\ARM\ARMCC\Bin\ArmLink" --Via "01-FreeRTOS\01-FreeRTOS.lnp"
    
    • 01-FreeRTOS\01-FreeRTOS.lnp文件是链接器的参数

      --cpu Cortex-M3
      "01-freertos\startup_stm32f103xe.o"
      "01-freertos\main.o"
      ...
      "01-freertos\port.o"
      --strict --scatter "01-FreeRTOS\01-FreeRTOS.sct"
      --summary_stderr --info summarysizes --map --load_addr_map_info --xref --callgraph --symbols
      --info sizes --info totals --info unused --info veneers
      --list "01-FreeRTOS.map" -o 01-FreeRTOS\01-FreeRTOS.axf
      
  • 生成二进制

    "D:\Programs\Keil_v5\ARM\ARMCC\Bin\fromelf.exe" "01-FreeRTOS\01-FreeRTOS.axf" --i32combined --output "01-FreeRTOS\01-FreeRTOS.hex"
    
输出文件类型

在这里插入图片描述

上述图片来源于https://blog.csdn.net/m0_60633015/article/details/143427274

启动过程

参考stm32的手册,在芯片启动的过程中首先会从地址0x00000000获取栈的地址,然后从0x00000004地址处开始区址执行。可以根据BOOT0/1引脚决定将什么存储区域映射到地址0x00000000,一般常见的启动方式是从FLASH启动,此时BOOT0/1都处于接地的状态,地址0x08000000映射到地址0x00000000。

早期行为

在这里插入图片描述

  • 为SP赋值,将0x00000000处的数值写入SP寄存器,SP=0x20002008;

  • 将0x00000004地址处的内容写入PC寄存器,PC=0x08000264;

  • 跳转至PC所指的地址空间执行,也就是执行复位的中断服务函数;
    在这里插入图片描述

Reset_Handler函数

SystemInit
  • 地址:0x080010c6(地址依据具体环境)

  • 函数内容

    void SystemInit (void)
    {
    #if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
      #ifdef DATA_IN_ExtSRAM
        SystemInit_ExtMemCtl(); 
      #endif /* DATA_IN_ExtSRAM */
    #endif 
    
      /* Configure the Vector Table location -------------------------------------*/
    #if defined(USER_VECT_TAB_ADDRESS)
      SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
    #endif /* USER_VECT_TAB_ADDRESS */
    }
    
  • 函数功能

    如果数组在外部存储中,对外部存储器进行初始化,如果需要配置向量表的位置,则按照要求进行配置;

_main函数
  • 地址:0x08000130(地址依据具体环境)

  • 函数内容

    0x08000130 F000F802  BL.W     0x08000138 __scatterload
    0x08000134 F000F83A  BL.W     0x080001AC __rt_entry
    0x08000138 A00A      ADR      r0,{pc}+0x2C  ; @0x08000164
    0x0800013A E8900C00  LDM      r0,{r10-r11}
    0x0800013E 4482      ADD      r10,r10,r0
    0x08000140 4483      ADD      r11,r11,r0
    0x08000142 F1AA0701  SUB      r7,r10,#0x01
    0x08000146 45DA      CMP      r10,r11
    0x08000148 D101      BNE      0x0800014E
    0x0800014A F000F82F  BL.W     0x080001AC __rt_entry
    0x0800014E F2AF0E09  ADR.W    lr,{pc}-0x07  ; @0x08000147
    0x08000152 E8BA000F  LDM      r10!,{r0-r3}
    0x08000156 F0130F01  TST      r3,#0x01
    0x0800015A BF18      IT       NE
    0x0800015C 1AFB      SUBS     r3,r7,r3
    

    这部分内容保存在c_w.l二进制的库中,ARM公司不对外公开,所以只能根据汇编文件大致分析其功能;首先是调用scatterload函数,地址为0x08000138,然后调用rt_entry函数,地址为0x080001AC。

    _scatterload

    负责将程序所需的代码和数据搬运到它们在内存中的正确位置,并为未初始化的变量清零,为后续运行准备好一个干净、正确的内存环境。

    来自于《ARM Compiler C Library Startup and Initialization》文档的描述:

    在这里插入图片描述

    伪代码表示,来自于deepseek:

    void __scatterload(void) {
        // 初始化跳转表
        void *handler1 = (void*)(0x08000164 + 0x27CC);
        void *handler2 = (void*)(0x08000164 + 0x27EC);
        void *current_handler = handler1;
        
        // 遍历区域表
        while (current_handler != handler2) {
            // 从区域表加载一个条目
            struct RegionTableEntry entry = *current_handler;
            current_handler += sizeof(entry);
            
            // 准备跳转参数
            void *func = entry.handler;
            if ((uint32_t)func & 1) {
                // 相对地址处理
                func = (void*)((uint32_t)handler1 - 1 - (uint32_t)func);
                func = (void*)((uint32_t)func | 1);
            }
            
            // 调用处理函数
            func(entry.load_addr, entry.exec_addr, entry.size);
        }
        
        // 所有区域处理完成,进入运行时
        __rt_entry();
    }
    
    • 区域表定义
    struct RegionTableEntry {
        uint32_t load_addr;   // r0: 源地址(加载视图)
        uint32_t exec_addr;   // r1: 目标地址(执行视图)
        uint32_t size;        // r2: 大小
        uint32_t handler;     // r3: 处理函数指针 + 标志位
    };
    
    • 区域表实例

      Load Address Execution Address Execution Size Handler Address
      0x08002950 0x20000000 0x0000008c 0x0800016c(handler_copy)
      0x080029DC 0x2000008c 0x00001f7c 0x08000188(handler_zi)
    • 在区域表中定义了两个handler函数,一个是handler_copy,对应_scatterload_copy函数,另外一个是handler_zi,对应__scatterload_zeroinit。

    • handler_copy 实现将存储在flash地址处0x08002950位置的可读可写Data数据拷贝至RAM地址0x20000000

      //汇编函数
      0x0800016C 3A10      SUBS     r2,r2,#0x10
      0x0800016E BF24      ITT      CS
      0x08000170 C878      LDM      r0!,{r3-r6}
      0x08000172 C178      STM      r1!,{r3-r6}
      

      在map文件中也可以佐证这一点。

      在这里插入图片描述

      例如在地址0x20000010的数据来自于0x08002960,该地址存储的是SystemCoreClock变量的数值,在Mememory表中查找这两个地址的内容,数据相同,均为0x007A0012

      在这里插入图片描述

    • __scatterload_zeroinit函数清零未初始化的全局/静态变量 (ZI 数据),对应的汇编代码如下:

      0x08000188 2300      MOVS     r3,#0x00
      0x0800018A 2400      MOVS     r4,#0x00
      0x0800018C 2500      MOVS     r5,#0x00
      0x0800018E 2600      MOVS     r6,#0x00
      0x08000190 3A10      SUBS     r2,r2,#0x10
      0x08000192 BF28      IT       CS
      0x08000194 C178      STM      r1!,{r3-r6}
      0x08000196 D8FB      BHI      0x08000190
      

      在map文件中同样可以佐证这一点

      在这里插入图片描述

      实际内存区域的数据均为0.

      在这里插入图片描述

  • rt_entry函数:负责设置堆和栈,负责初始化用到的库,调用真正的main函数。

    0x080001AC F000F945  BL.W     0x0800043A __user_setup_stackheap
    0x080001B2 F7FFFFF7  BL.W     0x080001A4 __rt_lib_init
    0x080001B6 F000FFE4  BL.W     0x08001182 main
    0x080001BA F000F963  BL.W     0x08000484 exit
    

    1. __user_setup_stackheap函数:

    0x0800043C F000F82C  BL.W     0x08000498 __user_libspace
    0x08000452 F7FFFF15  BL.W     0x08000280 __user_initial_stackheap
    

    __user_initial_stackheap函数定义在stm32f103xe.s中

    __user_initial_stackheap
                     LDR     R0, =  Heap_Mem
                     LDR     R1, =(Stack_Mem + Stack_Size)
                     LDR     R2, = (Heap_Mem +  Heap_Size)
                     LDR     R3, = Stack_Mem
                     BX      LR
                     ALIGN
                     ENDIF
                     END
    

    2. __rt_lib_init函数,负责初始化程序所引用的所有标准 C/C++ 库功能,为执行用户 main() 函数准备好完整的运行时环境。R0 R1会作为入口参数,传递至__rt_lib_init。

    1.	_fp_init			//初始化浮点环境
    2.	_init_alloc			//初始化堆操作的函数使用的数据结构,会使用R0/R1的数值
    3.	_rand_init			//初始化随机数生成器
    4.	_get_lc_collate		//获取指向包含 LC_COLLATE 语言环境类别设置的默认数据块的指针
    5.	_get_lc_ctype		//获取指向包含 LC_CTYPE 语言环境类别设置的默认数据块的指针
    6.	_get_lc_monetary	//获取指向包含 LC_MONETARY 语言环境类别设置的默认数据块的指针
    7.	_get_lc_numeric		//获取指向包含 LC_NUMERIC 语言环境类别设置的默认数据块的指针
    8.	_get_lc_time		//获取指向包含 LC_TIME 语言环境类别设置的默认数据块的指针
    9.	_atexit_init		//为传递给 atexit() 的函数指针设置 C 库的存储
    10.	_signal_init		//设置包含每个信号编号的当前处理程序的存储
    11.	_fp_trap_init		//设置库的存储
    12.	_clock_init			//读取clock() 使用的定时器的当前值
    13. _getenv_init		//检索任何需要的数据
    14.	_initio				//设置 stdio 内部状态
    15.	_ARM_get_argv		//获取传递给 main() 的 argc 和 argv 值
    16.	_alloca_initialize	//将 alloca 列表指针设置为 NULL
    17.	_ARM_exceptions_init//设置 C++ 异常处理状态
    18.	__cpp_initialize__aeabi_ //调用顶级 C++ 对象的构造函数
    

    上述函数具体的每一项请参考《ARM C and C++ Libraries and Floating-Point Support Reference》文档。

    3.main函数

    真正的main函数。

问题:栈和堆地址如何来的?

  • 在启动文件中进行地址区域大小

    Stack_Size		EQU     0x400
    
                    AREA    STACK, NOINIT, READWRITE, ALIGN=3  //声明栈对应的段,不初始化,可读可写
    Stack_Mem       SPACE   Stack_Size   //在栈对应的段中开辟了大小为0x400的区域,命名为Stack_Mem 
    __initial_sp   //标号,代码栈顶
                                                      
    ; <h> Heap Configuration
    ;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
    ; </h>
    
    Heap_Size      EQU     0x200
    
                    AREA    HEAP, NOINIT, READWRITE, ALIGN=3  //声明堆对应的段,不初始化,可读可写
    __heap_base
    Heap_Mem        SPACE   Heap_Size   //在堆对应的段中开辟了大小为0x200的区域,命名为Stack_Mem 
    __heap_limit
    
  • 在链接时,链接器会根据sct文件,将堆安排在bss段之后,栈安排在堆之后,同时计算出__initial_sp对应的具体数值, 在map文件中也有体现;

    在这里插入图片描述

小结

在启动至mian函数的整个过程如下图所示,首先是执行reset_handler函数,如果有MMU/MPU则要在调用_main之前配置完成,接下来是执行C库中的函数,完成代码的拷贝,bss段的清理后调用rt_entry函数。在rt_entry函数中传入堆和栈的信息,初始化C库,调用真正的main函数,最后main函数退出后执行exit。
在这里插入图片描述

参考

  • https://zhuanlan.zhihu.com/p/610556139
  • https://zhuanlan.zhihu.com/p/1897039056765969381
  • AN241 - ARM Compiler C Library Startup and Initialization
    • https://developer.arm.com/documentation/dai0241/latest/
  • ARM Compiler Software Development Guide
    • https://developer.arm.com/documentation/dui0471/latest
  • ARM Compiler toolchain ARM C and C++ Libraries and Floating-Point Support Reference
    • https://developer.arm.com/documentation/dui0492/i/the-c-and-c—libraries
  • ARM Compiler toolchain Linker Reference
    • https://developer.arm.com/documentation/dui0493/latest/
  • ARM Compiler ARM C and C++ Libraries and Floating-Point Support User Guide
    65969381
    • https://developer.arm.com/documentation/dui0475/latest/
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐