今天拿了被同事扔一边的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手册,请哪位知情者留言告知,谢过