Scatter文件的编写及分析

来源:互联网 发布:达内 财报数据 编辑:程序博客网 时间:2024/05/19 14:37

今天拿了被同事扔一边的ARM培训资料翻阅,读至scatter一节,发现写得甚是精辟。之前看的很多国人写得文章,未免有简单问题复杂化之嫌。而ARM的RVCT手册又偏冗长,不易让人立刻看到重点。今归纳如下:

scatter基本点:
1. 编译后输出的映像文件中各段是首尾相连的,中间没有空闲的区域,它们的先后关系是根据链接时参数的先后次序决定的 armlinker -file1.o file2.o ……
2. scatter用于将编译后的映像文件中的特定段加载到多个分散的指定内存区域
3. 有2类域region:执行域(execution region,一般是ram区域)和加载域(load region,一般是rom区域)
4. 加载域:就是编译之后得到的二进制文件烧写到rom中的这一段区域,所有的代码RO、预定义变量RW、堆栈之类清不清空无关紧要的大片内存区域ZI,都包括在其中
5. 执行域:就是把加载域进行‘解压缩’后的样子。比如:RO没有变动还是在ROM中,RW被移到了SRAM中,而ZI被放置在SDRAM中
6. scatter本身并不能对映像实现‘解压缩’,编译器读入scatter文件之后会根据其中的各种地址生成启动代码,实现对映像的加载,而这一段代码就是* (InRoot$$Sections)它是__main()的一部分。这就是在汇编启动代码的最后跳转到__main() 而不是跳向main()的原因之一。
7. 起始地址与加载域重合的执行域成为root region,* (InRoot$$Sections)必须放在这个执行域中,否则链接的时候会报错。*(+RO)包含了* (InRoot$$Sections),所以如果在root region中用到了*(+RO)可以不再指定* (InRoot$$Sections),

scatter语法:
ROM_LOAD 0x00000000
{
     ROM 0x00000000 0x003FFFFF       
    { 
       vectors.o (+RO,+FIRST)
      * (InRoot$$Sections)       ; All library sections that must be in a root region
      *(+RO)
    }

     SRAM 0x00400000 0x003FFFFF
    {
        * (+RW,+ZI)
    }

    SDRAM1 0x41000000 UNINIT
    { 
         stack.o (+ZI) ; stack.s中定义了top_of_stack为长度为1的space,指定栈顶地址
    }  

    SDRAM2 +0 UNINIT
    {    
        heap.o (+ZI)
    }

    
}

注解:
1. ROM_LOAD是加载域。这里只有一个,也可以有多个(rom地址不连续的情况)
2. ROM、SRAM、SDRAM1、SDRAM2是执行域,有多个。第一个执行域必须和加载域地址重合,因为ARM的复位地址就是加载域的起始地址(有bootstrap的话加载域址就是bootstrap执行完后的跳转地址)
3. vectors.o (+RO, +FIRST) 中断向量表放在最开头
4. ROM 0x00000000 0x003FFFFF; 加载域名 起始地址 最大允许长度;‘最大允许长度’也可以省略,但缺点是编译器不会检查段是否溢出和别的段重叠了。‘起始地址’= +0表示紧接着上一段开始的连续地址。
5. * (InRoot$$Sections)是复制代码的代码
6. UNINT关键字表示不进行初始化清零


值得注意的是:在一个scatter文件中,同一个.o文件不能出现2次,即使是在2个不同的加载域中也不可以,否则会报错:Ambiguous selectors found for *.o,错误的例子:

LOAD1 0x00000000
{
    EXE1
    {
            Init.o
     }
}

LOAD2 0xFFFF0000
{
    EXE2
    {
            Init.o
     }
}

想起了中学里哲学课上老师让解释为什么人不能两次踏入同一条河流,当年稀里糊涂的写的答案,老师批了个大差,回去有没有补上,今天居然在这里遇到了老问题。。。推测是编译器自动生成的scatter载入代码InRoot$$Sections不支持把同一obj搬移2次。
这就带来一个问题:如果希望把同一段代码(如中断跳转表)载入2份拷贝到不同的地址,咋整?一个笨办法是自己写一段代码搬移程序来代替编译器自动生成的搬移代码,但前提是需要搞懂映像文件的组织,增加了工作量。投机一点的方法是在makefile中把一个.o文件复制并重新起一个名字,然后把它们传递给armlink。另外,猜测scatter语法可能包含诸如+duplicate之类的关键字来允许同一段的多个副本,懒得翻ARM手册,请哪位知情者留言告知,谢过

一个映像文件中可以包含多个域(region),在加载和运行映像文件时,每个域可以有不同的地址。每个域可以包括多达3个输出段,每个输出段是由若干个具有相同属性的输入段组成。这样在生成映像文件时,ARM链接器就需要知道下述两个信息。

  • 分组信息    决定各域中的输出段是由哪些输入段组织而成;

  • 定位信息    决定各域在存储空间中的起始地址。

    根据映像文件中地址映射的复杂程度,有两种方法来告诉ARM链接器这些相关的信息。对于映像文件中地址映射关系比较简单的情况,可以使用命令行选项;对于映像文件中地址映射关系比较复杂的情况,可以使用一个scatter配置文件。Scatter文件又称为分散加载文件,将重点讲解如何编写scatter文件。
1、Scatter文件结构
    Scatter文件是一个文本文件,使用BNF语法来描述ARM链接器生成映像文件时所需要的信息。具体来说,在scatter文件中可以指定下列信息:
  • 各个加载时域的加载时起始地址、最大尺寸和属性;

  • 每个加载时域包含的输出段;

  • 各个输出段的运行时起始地址、最大尺寸、存储访问特性和属性;

  • 各个输出段中包含的输入段。

    一个Scatter文件包含若干个加载域,一个加载域包含若干个输出段,一个输出段由若干个具有相同属性的输入段组成,其结构如图1所示。
                
                                   图1 Scatter文件结构示意图
    ① 加载时域的描述
    加载时域包括名称、起始地址、属性、最大尺寸和一个运行时域的列表。使用BNF语法描述,加载时域的格式如下所示:
Load_name      base_designator         attribute      max_size
{
……
}
  • Load_name   运行时域名称,它除了唯一地标识一个运行时域外,还用来构成链接器生成的链接符号;

  • base_designator 用来表示本加载时域的起始地址,它可以有两种格式表示:起始地址或偏移量; 

  • attribute   本加载时域的属性,其可能的取值为下面之一,默认的取值为ABSOLUTE:

  •     PI          位置无关属性;

  •     RELOC       重定位;

  •     ABSOLUTE    绝对地址; 

  • max_size 最大尺寸,如果本加载时域的实际尺寸超过了该值,链接器将报告错误。默认的取值为0xFFFFFFFF。

    ② 输出段的描述
    输出段包括名称、起始地址、属性、最大尺寸和一个输入段的集合。使用BNF语法描述,输出段的格式如下所示:
output_name     base_designator     attribute       max_size
{
……
}
  • output_name 输出段的名称,它用来唯一地标识一个输出段,还用来构成链接器生成的链接符号。

  • base_designator 用来表示本输出段的起始地址,它可以有两种格式:起始地址值或偏移量。

  • attribute   表示本输出段的属性,其可能的取值如下所示:

  •     PI          位置无关属性

  •     RELOC       重定位

  •     ABSOLUTE    绝对地址

  •     FIXED       固定地址

  •     UNINIT      未初始化的数据

  • max_size    指定本输出段的最大尺寸。

    ③ 输入段的描述
    输入段里描述了一个文本字符串的模式,匹配该模式的输入段都将被包含在当前域中。模式中可以使用匹配符,符号"*"代表零个或者多个字符,符号"?"代表单个字符。进行匹配时,所有字符是大小写无关的。
    下面介绍一些使用scatter文件配置映像文件地址映射模式的例子。在本例中,映像文件包括一个加载时域和3个连续的输出段,这种模式适合于那些将其他程序加载到RAM中的程序,如操作系统的引导程序和Angel等。
    例子    一个简单的scatter文件 
Load_1   0x4000             ;定义加载时域的名称为Load_1,起始地址为0x4000
{
         ER_RO    + 0     ;输出段名ER_RO,地址偏移量0,所以起始地址为0x4000
          { *( + RO) }       ;通配符*,包含了所有的RO属性的输入段,它们被连续放置
         ER_RW    + 0     ;输出段名称ER_RW,起始地址为前一个输出段的结束地址加偏移量0
          { *( +  RW) }      ;本输出段包含所有的RW属性的输入段,它们被连续放置
ER_ZI 0x5000       ;输出段名称ER_ZI,起始地址为0x5000
          { *( +  ZI) }      ;本输出段包含了所有的ZI属性的输入段,它们被连续放置
}
    按照例 scatter文件的描述,ARM链接器会生成相应的映像文件地址映射关系,如图2所示。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />                   <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
                         图2 程序运行时地址映射关系
 
2、固定时域
    任何一个映像文件都需要指定一个初始入口点(initial entry point),它是影响文件运行时的入口点。初始入口点必须位于一个固定域中,所谓固定域是指该域的加载时地址和运行时地址是相同的。如果初始入口点不是位于一个固定域中,ARM链接器在链接时会产生下面的错误信息。
    L6203E:Entry point (0x0000 0000) lies within non-root region 32 bit RAM
    使用scatter文件时,可以有下面两种方法来设置固定域。
 
    ① 设置输出段地址
    第1种方法是设定一个加载域中第1个输出段的运行地址,使其和该加载域的加载地址相同。这样该输出段就是一个固定域。
    例1就使用这种方法确定固定域。其中,加载域LR_1的起始地址为0x8000,输出段ER_RO的起始地址指定为0x8000,与加载域LR_1的起始地址相同,因此,输出段ER_RO是一个固定域,并且是映像文件的初始入口点。
    例1 指定固定域
LR_1 0x08000                ;加载域LR_1的起始地址为0x8000
{
     ER_RO 0x08000          ;输出段ER_RO的起始地址为0x8000
     {
*( +  RO)          ;包含了所有的RO数据,包含初始入口点
     }
     ;其他部分内容
}
    ② 设置输出段属性
    第2种方法通过将某个输出段的属性设置成FIXED。
    例2指定固定域
LR_1 0x8000                 ;加载时域LR_1的起始地址为0x8000
{
     ER_RO    0x8000
     {
         *( +  RO)          ;除了init.o之外的其他RO数据
     }
     ER_INIT 0x9000 FIXED   ;设置输出段属性为FIXED,确定固定域
     {
     init.o( +  RO)         ;本输出段包含了init.o,包含映像文件的初始入口点
     }
     ;其他部分内容
}
 
3、一个实际系统的例子
    在一个嵌入式设备中,为了保持好的性价比,通常在系统中存在多种存储器。在一个实际的ARM开发板中,可能包括片内Flash、RAM和片外Flash、RAM。在本例中,我们假设用ARM芯片构造了一个嵌入式系统,包含了8KB片内Flash存储器、16KB片内RAM存储器、起始地址为0x80000000的片外Flash和起始地址为0x81000000的片外RAM,其地址空间分配关系如图3所示。
    在这样的ARM系统中,我们编写了程序,并且按照例3中的分散加载文件对映象文件的地址进行分配。分配后的地址映像关系如图4所示。
         
         图3 ARM系统中的地址空间                         图4 地址映像关系
    从图4中可以看出:可执行代码都放在片外Flash中,并且Vectors向量表放在片外Flash的起始地址上;Startup目标文件的数据放置在片内RAM中,堆栈放在片内RAM的顶端;其他数据放置在片外RAM中,堆空间紧跟其后。
    例3 片外Flash启动程序的scatter文件
ROM_LOAD  0x80000000                 ;定义加载区名称ROM_LOAD,起始地址0x80000000
{
     ROM_EXE  0x80000000              ;定义执行代码空间,起始地址与加载域地址相同
    {
        Startup.o (vectors,  +First)     ;首先放置Startup.o文件的向量表vectors
        * ( +RO)               ;后面地址空间放置其他RO属性代码
 
     IRAM  0x40000000                     ;定义数据空间
    {   Startup.o ( +RW, +ZI)   }
 
STACKS  0x40004000  UNINIT           ;定义堆栈空间
    {    stack.o ( +ZI)    }
 
    ERAM  0x81000000                     ;定义数据空间
    {   * ( +RW, +ZI)      }             ;剩下未指定空间的所有数据
   
    HEAP + 0  UNINIT                     ;定义堆空间
    {     heap.o ( +ZI)    }