分享

ADS Linker以及Scatter文件的学习笔记

 goodwangLib 2020-01-22

    最近玩2440接触到了scatter文件,之前用STM32写代码时并没有关心过它,因为STM32的储存器结构比较简单,所以直接由MDK生成。

    今天读了下ADS_LinkerGuide手册,现将一些心得总结一下。

    1. 关于IMAGE(映像)、Execution Region(执行域)、Load Region(加载域)、Output Section(输出段)、Input Section(输入段)

        为了弄明白scatter文件,首先需要清楚这些基本概念。

        IMAGE文件的结构由以下定义:包含的域和输出段的数量,当它们加载到存储器中时的地址,当IMAGE被执行时它们的地址。

        域、输出段与输入段之间的关系如下图所示:

      

            1) 输入段是目标文件(Object File)中的代码(RO)和需要初始化的数据(RW)以及不需要初始化的数据(ZI)。RO、RW以及ZI就是输入段的属性(attributes),链接器在链接时就会搜索目标文件的各种段,并将属性相同的输入段组合到一起。这里补充一下,从C语言编程的角度来看,RO段储存的就是程序代码以及定义的常量,全局变量和静态局部变量若赋了初值就放在RW属性的段中,若未初始化则放在ZI段中;ZI段里的数据由于不用初始化,只需要清零,所以生成的映像文件中不会包含ZI属性的部分,从加载域也可以看出ZI段没有被包含进来。

             2) 输出段由连续的且拥有相同属性的输入段组成,输出段的属性就是输入段的属性。

             3) 域由若干个连续的输出段组成(不要求输出段的属性相同),输出段在域中排列的顺序根据属性确定:RO、RW然后是ZI。

                 域又分为加载域和执行域。它们之间主要的区别是域中的输出段在“加载时”和“执行时”的地址可能不同。

                 加载指的是程序运行之前(完成准备工作前、CPU执行到这部分之前),段(包括RO和RW)所储存的位置。可以加载到ROM或RAM中,根据储存的要求确定。

                 执行域中各个(输出)段的地址就是程序运行起来时的地址。

                 加载域中的输出段(RO,RW)与执行域中的输出段(RO,RW)是一一对应的,只是地址不同(Load View与Execution Vew)。

                 从加载域到执行域的转换是通过编译器产生的初始化代码完成的,也就是 [ 启动代码-> _main函数 -> main函数 ] 里面的_main函数完成的。

                 这个函数由编译器产生。它负责将加载域中的输出段(地址A)搬移到执行段指定的地址(B)中,需要搬移的可能是RO和RW段。

                 而ZI段本来就没有在加载域中,所以直接把执行域指定的ZI段地址空间清零即可。

                 加载域只关系到程序运行之前在存储器中位置(例如可能有2片FLASH,就要有2个加载域,放在不同的地址下),而_main函数也需要加载域的信息,以完成搬移工作。

                 '加载”并不一定是要加载到ROM中,也可以加载到RAM里。各个段然后通过“初始化”(如_main)传送到执行域所确定的地址上。

                 下面几张图可以帮助理解。

 

                 加载域和执行域的3种可能的情形:

 

                 例如对于STM32:

                 (1) 若程序在内部FLASH中执行,那么RO输出段在加载域中的地址就是烧进FLASH的地址,而执行域也是在这个地址上。

                 (2) 某一段代码被储存在FLASH中,并放在SRAM中执行,那么这个RO段被烧写到FLASH里(地址A),这是它在的执行域里的地址。

                       然后初始化代码将这个段从地址A搬移到执行域所确定的地址B上。

                 (3)  加载域中RW段是用于初始化变量的数据,而程序运行在运行时变量在SRAM里,所以要做的“搬移”就是:

                        用 加载域中RW段的内容去初始化执行域中该RW段的地址下的内容。 

    2. Scatter文件

        在生成IMAGE文件的过程中,编译器利用Scatter文件确定加载域和执行域的地址,并产生初始化代码(如_main函数,这是启动代码最后B进的那个地址)

        而链接器则利用Scatter确定的段以及段的地址讲OBJECT文件“组合”起来。         

        在清楚了基本概念之后,再来看scatter文件的格式就好理解了,下面直接上图。

         1) 首先是一种简单的情况:

    

     

        2) 然后是复杂一些的情况,从下图可以看出IMAGE被分开加载到了2块不同的ROM位置里。

       

     其实理解了段和域的概念后,Scatter的结构还是很好理解的。

     关于Scatter的语法可以参考下面这幅图:

     从Scatter的结构可以看出输出段的概念被淡化了,所以需要考虑的是“加载的地址”、“执行时地址”以及执行域由哪些输入段构成。

     其实,文件的结构很好的描述了加载域和执行域的关系:

     每个加载域将其{}里面的RO/RW输出段按顺序“打包”,加载到存储器里;然后运行程序之前,又讲这些”打好包“的输出段按照其执行域所定义的地址结构进行“分发”。

     需要注意的是Input section description。每个Input section description可以指定若干个输入段,由具体参数确定。

     其中,Module selector pattern指定输入段所在的文件,“*”表示所有文件,“*.o”则是所有的OBJECT文件。

     后面的括号指定了输入段的信息,包括RO、RW以及ZI等段属性以及段的名字(input section pattern)。注意,段的属性前必须加'+'符号。

     在Input section description指定了输入段所在的文件以及段的信息后,链接器就会去寻找满足条件的段,并放到该执行域下。

     详细的语法参考ADS_LinkerGuider的5.2节。

     下面结合TQ2440裸奔测试程序的SCT文件分析:

复制代码
LR_ROM1 0x30000000 0x02000000 { ; load region size_region ER_ROM1 0x30000000 0x02000000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_RAM1 0x32000000 0x02000000 { ; RW data .ANY (+RW +ZI) } RW_IRAM1 0x40000000 0x00001000 { .ANY (+RW +ZI) }}
复制代码

     此Scatter文件说明了,只有一个加载域LR_ROM1,程序被加载到0x30000000地址下(SDRAM),这个加载域的大小为0x02000000。

     有3个执行域,分别是ER_ROM1、RW_RAM1、RW_IRAM1(注: IRAM表示片内RAM,即2440那4K大小的RAM)。

     再来看ER_ROM1域:

         *.o (RESET, +First)描述的是,在所有.o的文件中找到名为RESET的段,+FIRST表示这个段在域中第一个放置(首)。

         *(InRoot$$Sections)代表名为InRoot$$Sections的段,它是由编译器产生的那段初始化代码。

         .ANY (+RO),在此.ANY 等效于 *,关于.ANY 在PDF有如下描述:

        接下来再来看一个MDK生成的STM32的SCT文件,非常简单。

复制代码
; *************************************************************; *** Scatter-Loading Description File generated by uVision ***; *************************************************************LR_IROM1 0x08000000 0x00020000  {    ; load region size_region  ER_IROM1 0x08000000 0x00020000  {  ; load address = execution address   *.o (RESET, +First)   *(InRoot$$Sections)   .ANY (+RO)  }  RW_IRAM1 0x20000000 0x00005000  {  ; RW data   .ANY (+RW +ZI)  }}
复制代码

        可以看到,RO和RW在加载域LR_ROM1中,加载(这里等同于储存)在内部FLASH 0x8000000下,运行时,RW和ZI段在0x20000000(即片内20K的SRAM中)

    3. Linker输出的符号(详见ADS_LinkerGuide的第四章)

     链接器可以输出一些符号,这些符号可以在C语言中用extern或汇编中IMPORT进来。PDF中这样介绍的:

 

    通过上面的文字可知,链接器输出的是段或域的地址。

    其实2440裸奔测试程序的启动代码就用到了这些符号:

复制代码
IMPORT |Image$$ER_ROM1$$RO$$Base| ; Base of ROM code IMPORT |Image$$ER_ROM1$$RO$$Limit| ; End of ROM code (=start of ROM data) IMPORT |Image$$RW_RAM1$$RW$$Base| ; Base of RAM to initialise IMPORT |Image$$RW_RAM1$$ZI$$Base| ; Base and limit of area IMPORT |Image$$RW_RAM1$$ZI$$Limit| ; to zero initialise
复制代码

    可以看到ER_ROM1、RE_RAM1都是.sct文件里定义的执行域名。MDK的Linker貌似有些不同,如上所示,后面必须加$$RO$$、$$RW$$才行,否则提示未定义。
    IMPORT进来的符号代表了某个地址,同样在C语言中extern进来的也是地址。因此采用如下方式定义(char数组):

复制代码
extern char Image$$ER_ROM1$$RO$$Limit[];extern char Image$$ER_ROM1$$RO$$Base[];extern char Image$$RW_RAM1$$RW$$Limit[];extern char Image$$RW_RAM1$$RW$$Base[];extern char Image$$RW_RAM1$$ZI$$Limit[];extern char Image$$RW_RAM1$$ZI$$Base[];
复制代码

     补充一点,上述2440的启动代码自己完成了初始化的工作(输出段的'搬移'),所以在启动代码后面有这么一段:

复制代码
379 [ :LNOT:THUMBCODE380 bl Main ;Do not use main() because ......381 b .382 ]
复制代码

    可以看到启动代码直接B进了主函数Main(也就是之前提到的main),跳过了编译器为我们生成的初始化代码main(即之前提到的_main,名字不同而已)。


    以上就是今天读《Ads_LinkerGuide》的笔记。等今后进一步学习后再补充。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多