打印
[其他]

Cortex-M3 ARM代码编译,链接与启动过程深度分析

[复制链接]
楼主: 米多0036
手机看帖
扫描二维码
随时随地手机跟帖
21
米多0036|  楼主 | 2022-12-31 13:35 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
对OS如何处理Link&&Loader这些细节感兴趣的,可以参考书籍:

程序员的自我修养—链接、装载与库
Linkers and Loaders

使用特权

评论回复
22
米多0036|  楼主 | 2022-12-31 13:36 | 只看该作者
​ 我们这里处理的是裸程序的启动细节问题,首先我们要知道的是通过编译器和链接器之后得到的二进制可执行映像的结构。也就是说得出的那个 *.bin 文件里面长啥样?一图胜万言,上张图先。

使用特权

评论回复
23
米多0036|  楼主 | 2022-12-31 13:36 | 只看该作者
​ 大家都知道冯.诺依曼架构的计算机,它的基本思想是把“做事情的步骤和所需要的资源都提前编写好,然后让计算机自己根据需要读取操作步骤和资源,实现部分的计算自动化”。计算机的设计思想可谓是精妙的,实现真正的计算自动化也是很多科学家和工程师的夙愿。上面所说的做事情的步骤在计算机领域叫指令,所需要的资源在计算机领域叫数据。从计算机体系结构角度去看可执行映像的话,其实也就分为指令和数据两个大的部分。指令部分还是比较单一的,把各个源文件中的指令部分最后都汇聚到一起,形成所谓的text段。从功能上分,代码段只是需要CPU去读取,不需要修改,因为可以将其放在RO存储器里。数据这个部分从功能上来看,它必须支持读写,也即数据段执行时必须位于RW存储器里。从功能细节上分数据段又分为BSS段,Data段,Stack段,Heap段。从计算机体系结构角度来一一分析,从数据的生存周期角度来看,有的数据的生存周期和程序的生存周期是一致的(全局变量),有的数据的生存周期是根据使用情况即时分配和释放的(局部变量、malloc动态分配的变量)。BSS段和Data段属于全生命周期的数据,在源程序里主要是那些在文件域定义的全局变量和使用static关键字定义的全生命周期变量,Data是那些在程序里定义变量时初始化为固定值的量,BSS段是那些在程序里定义变量时未初始化的变量,这些变量在映像真正执行前会自动初始化为0。

使用特权

评论回复
24
米多0036|  楼主 | 2022-12-31 13:39 | 只看该作者
对BSS段再多说一句,BSS段在映像文件里并不占用具体的空间,因为没有任何具体的信息,只需要在映像文件中提供BSS段的起始地址和大小信息即可。

使用特权

评论回复
25
米多0036|  楼主 | 2022-12-31 13:40 | 只看该作者
在映像文件实际执行前,把BSS段要求的Data区域在实际RAM中预留出来并把这些区域初始化为0。短生命周期的数据包括Heap和Stack,它们的特点是随用随申请,用完就释放,比较灵活。Heap是一段预留出来的大空间,可以根据需求随时申请和释放,就是我们常见的malloc free函数操作的空间就是Heap 空间,这部分空间在映像里是独立出来的一段空间,见上面的程序映像图。

使用特权

评论回复
26
米多0036|  楼主 | 2022-12-31 13:41 | 只看该作者
我们看到RO(RO-CODE/CODE+RO_DATA/CONST+RW_DATA)存储在Flash Memory的地址段是:0x08000000–0x0801FFFF 共128K。

使用特权

评论回复
27
米多0036|  楼主 | 2022-12-31 13:41 | 只看该作者
RW存储(RW_CODE+RW_DATA+ZI_DATA)SRAM的地址段是:0x20000000–0x20007FFF 共32K。

使用特权

评论回复
28
米多0036|  楼主 | 2022-12-31 13:44 | 只看该作者
​ 我们拿编译好的MAP文件看一下就一目了然了:
    Total RO  Size (Code + RO Data)                12008 (  11.73kB)
    Total RW  Size (RW Data + ZI Data)              2664 (   2.60kB)
    Total ROM Size (Code + RO Data + RW Data)      12068 (  11.79kB)

使用特权

评论回复
29
米多0036|  楼主 | 2022-12-31 13:45 | 只看该作者
你可以这么理解RO包含代码段和只读数据段,RW包含数据段和BSS段。

使用特权

评论回复
30
米多0036|  楼主 | 2022-12-31 13:48 | 只看该作者
​ MCU的启动配置是从0x08000000地址开始启动。为节约RAM空间,我们启动时映像的代码段不搬运,直接读取Flash Memory,数据段需要可读写,因此需要将所有的数据段搬移到RAM中去。我们再看我们的启动代码startup_ac78xx.s, 我们有CopyDataInit和FillZerobss。

使用特权

评论回复
31
米多0036|  楼主 | 2022-12-31 13:49 | 只看该作者
.global  g_pfnVectors
.global  Default_Handler

/* start address for the initialization values of the .data section.
defined in linker script */
.word  _sidata
/* start address for the .data section. defined in linker script */  
.word  _sdata
/* end address for the .data section. defined in linker script */
.word  _edata
/* start address for the .bss section. defined in linker script */
.word  _sbss
/* end address for the .bss section. defined in linker script */
.word  _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */

/**
* [url=home.php?mod=space&uid=247401]@brief[/url]  This is the code that gets called when the processor first
*          starts execution following a reset event. Only the absolutely
*          necessary set is performed, after which the application
*          supplied main() routine is called.
* @param  None
* @retval : None
*/

    .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
/* 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
  bx  lr   
.size  Reset_Handler, .-Reset_Handler

使用特权

评论回复
32
米多0036|  楼主 | 2022-12-31 13:49 | 只看该作者
大致情况见下图:

使用特权

评论回复
33
米多0036|  楼主 | 2022-12-31 13:50 | 只看该作者
Link Script,它控制着如何产生最终的映像文件。在分析具体的Link Script之前,先来说Link Script里最重要的概念,Address && Offset,前面也说了,到了映像文件格式这一层面,也就剩下各种连续的内容(段)和地址(Address)了,因此地址对映像来说是一个十分重要的资源。Link Script无非就是告诉链接器哪段东西放在哪个地址上。那些段需要搬运,当然搬运也是需要地址的。

使用特权

评论回复
34
米多0036|  楼主 | 2022-12-31 13:51 | 只看该作者
来看看我们项目中用到的Link Script: 分连个层面来看,一是Memory Map相关的,一是段分配相关的。 先看Memory Map,

/* Specify the memory areas */
MEMORY
{
  FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 128K
  RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 32K
  MEMORY_B1 (rx)  : ORIGIN = 0x60000000, LENGTH = 0K
}
# (rx) 表示该区域的属性为只读与可执行属性
# (xrw)表示该区域的属性为读写与可执行属性

使用特权

评论回复
35
米多0036|  楼主 | 2022-12-31 13:52 | 只看该作者
所以也可以这么理解RO表示FLASH区域,RW表示RAM区域。

链接脚本定义了上面提到的各种段,.isr_vector,.text, .data, .bss, heap和stack等不同的段。

使用特权

评论回复
36
米多0036|  楼主 | 2022-12-31 13:53 | 只看该作者
.isr_vector
/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  /* isr_vector启动代码中断服务向量表区域从所谓的零地址0x0800 0000开始*/
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH
......
......
}

使用特权

评论回复
37
米多0036|  楼主 | 2022-12-31 13:54 | 只看该作者
.text段
/* Define output sections */
SECTIONS
{
......
......
  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
        *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH
......
......
}

使用特权

评论回复
38
米多0036|  楼主 | 2022-12-31 13:55 | 只看该作者
data段和.bss段

.data段保存的是那些已经初始化了的全局静态变量和局部静态变量。.rodata段存放的是只读数据。一般是程序里面的只读变量(如const修饰的变量和字符串变量)。.bss段存放的是未初始化的全局变量和局部变量。

使用特权

评论回复
39
米多0036|  楼主 | 2022-12-31 13:55 | 只看该作者
定义了每个段在映像文件中的排布方式,定义了有哪些段需要在运行前从FLASH中搬运到RAM中。我们拿出一个data段来进行说明。

使用特权

评论回复
40
米多0036|  楼主 | 2022-12-31 13:56 | 只看该作者
/* Define output sections */
SECTIONS
{
......
......
/* 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

  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  PROVIDE ( end = _ebss );
  PROVIDE ( _end = _ebss );
......
......
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则