Keil基础知识 同为嵌入式培训中心 作者:古志东 版本:V0.1 时间:2013/8/25 1、 ARM镜像文件结构 ARM镜像文件的结构如下图所示: ARM映像文件各组成部分在存储系统中的地址有两种: 一种是映像文件在运行之前,位于存储器中时的地址,称之为加载地址; 一种是映像文件在运行时,位于存储器中的地址,称之为运行地址。 之所以有这两种地址,是因为映像文件在运行时,其中的有些域是可以移动的新的存储区域。比如,已经初始化的RW属性的数据所在的段运行之前可能保存系统的ROM中,在运行时,他被移动至RAM中。 一个映像文件,如我们将一个工程编译最终输出bin文件。这时我们将bin文件下载,下载地址被称为加载域,当程序要真正运行时还需要将加载域中的数据或代码进行搬移,这是会出现多个运行域。通常情况下有一个加载域,多个运行域,如下图所示。但是一个完整的镜像文件实际上可以包括有一个或多个加载域,一个或多个运行域,每个域包含一个或多个输出段,每个输出段包含一个或多个输入段,各输入段中包含了目标文件中的代码和数据。 输入段: 输入段中包含了四类内容:代码、已经初始化的数据、未经初始化的存储区域、内容初始化成0的存储区域。每个输入段有相应的属性,可以为只读的(RO)、可读写的(RW)以及初始化成0的(ZI)。ARM连接器根据每个输入段的属性将这些输入段分组,再组成不同的输出段及域。 输出段: 一个输出段中包含了一系列的具有相同的RO、RW和ZI属性的输入段。输出段的属性与其中包含的输入段的属性相同。在一个输出段的内部,各输入段是按照一定的规则排序
的。
域:
一个域中包含 1-3 个输出段,其中个输出段的属性各不相同。各输出段的排列顺序是由
其属性决定的。其中 RO 属性的输出段排在最前面,其次是 RW 属性的输出段,最后是 ZI
属性的输出段。一个域通常映射到一个物理存储器上,如 ROM 或 RAM。
在一个简单的嵌入式计算机系统中,存储器一般被分成 ROM 和 RAM。连接器生成的
映像被分成“Read -Only”段(包含代码和只读数据)和“Read-Write ”段(包含已初始数
据和未初始化数据,未初始化数据也叫 ZI 数据)。通常,在程序下载(烧入)的时候,它
们会被一块下载到ROM 上;而在程序开始执行时,Read-Write 段会从 ROM 被 Copy 到 RAM。
如下图所示:
通常,一个映像文件包含若干个域,各域又包含若干的输出段。ARM 连接器需要知道
如下的信息,已决定如何生成相应的映像文件。
**分组信息 决定如何将每个输入段组织成相应的输出段和域。
**定位信息 决定每个域在存储空间地址中的起始地址。
根据映像文件中地址映射的复杂程度,有两种方法来告诉 arm 连接器这些相关信息。
对于映像文件中地址映射关系比较简单的情况,可以使用命令行选项;对于映像文件中地址
映射关系比较复杂的情况,可以使用一个配置文件,这就是 keil 的 scatter file。
2、加载文件
加载文件(即 scatter file 后缀为.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 0x08000000 { ; RW data
.ANY (+RW +ZI)
}
}
2.1 Scatter file 语法
Scatter file (加载描述文件)用于指定映像文件内部各区域的装载域与运行域的位置。
arm 连接器将会根据 scatter file 生成一些区域相关的符号,他们是全局的供用户建立运行时
环境时使用。
Scatter File 实际上是一个具有简单语法规则的文本文件,可以用来描述 ARM 连接器生
成映像文件时所需的信息:
各个加载时域的加载地址、最大尺寸和属性;
从每个加载时域中分割出的运行时域;
各个运行时域的起始地址、最大尺寸和属性;
各个运行时域存储访问特性;
各个运行时域中包含的输入段;
典型的 Scatter File 构成如下图所示(以一个加载时域为例):
Load_region_name Base_designator [attribute_list] [max_size]
{
Exec_region1_name Base_designator [attribute_list] [max_size]
{
module_select_pattern(input_section_attr,input_section_pattern)
}
Exec_region2_name Base_designator [attribute_list] [max_size]
{
...
}
}
2.1.1 加载域:
Load_region_name:
加载时域名称,最大 31 个 ASCII 字符,只用于标识一个加载时域;
Base_designator:
本加载时域的起始地址,它有两种形式:
1 )base_address:本加载时域中的对象在连接时的起始地址,必须字对齐;
2 )+offset :本加载时域中的对象在连接时的起始地址是在前一个加载时域的结束地
址后偏移 offset(字节)处。
attribute_list:
本加载时域的属性,可选项;属性包括:PI,OVERLAY,ABSOLUTE,FIXED,UNINIT。
PI: 位置独立。与位置无关的代码
OVERLAY: 覆盖。只执行一次的代码,可以指示被覆盖
ABSOLUTE: 绝对地址。
FIXED: 固定地址,下载地址与执行地址具有该地址指示确定。
UNINIT: 未初始化数据。说明内存中数据杂乱
RELOC:无法明确指定执行区域具有该属性,而只能通过继承前一个执行区或父区域
获得。可重新定位的域
对于 PI,OVERLAY,ABSOLUTE,FIXED,我们只能选择一个,缺省属性为 ABSOLUTE。
一个执行区域要么直接继承其前面的执行区域的属性或者具有属性为 ABSOLUTE。
具有 PI,OVERLAY,RELOC 属性的执行区域允许其地址空间重叠,对于 BSOLUTE,
FIXED 属性执行区域地址空间重叠 Armlink 会报错。
max_size
本 加 载 时 域 的 最 大 尺 寸 ( 字 节 )。 若 实 际 尺 寸 越 界 , 连 接 器 将 会 报 错 。 默 认 为
0xFFFFFFFF。可选项;
2.1.2 运行域(输出段)
Exec_region_name:
运行时域名称,最大 31 个 ASCII 字符,除了用于标识一个运行时域外,还用来构成连
接器生成的连接符号;
Base_designator:
本运行时域的起始地址,同“加载时域” :
attribute_list:
本加载时域的属性,默认为 ABSOLUTE。可选项;常用的 FIXED 表明本域的加载地址
和运行地址是相同的,都通过 base_designator 指定, 且必须是绝对地址或 offset 为 0 ;
max_size :
本 加 载 时 域 的 最 大 尺 寸 ( 字 节 )。 若 实 际 尺 寸 越 界 , 连 接 器 将 会 报 错 。 默 认 为
0xFFFFFFFF。可选项;
2.1.3 输入段
module_select_pattern:选择的模块名称(目标文件,库文件成员,库文件),模块
名可以使用通配符(*匹配任意多个字符,?匹配任意一个字符),名称不区分字母
大小写,如:
- disp*.o (+RO):匹配所有以 disp 开始的.o 目标文件作为输入段;
- *pmic.lib(+RW):匹配所有以 pmic 结束的.lib 文件作为输入段;
- .ANY(+ZI):匹配所有前面未匹配到的输入段,并指定连接器自行安排该输入段的
位置;
每一个模块都有自己的属性,一个用逗号分割的模式列表跟随在,模块名后面。该列表
中的每个模式定义了输入段名称或输入段属性的匹配方式:
input_section_attr:按属性加载。输入段属性选择,表示该链接文件中,具有某种
属性的部分内容。
input_section_attr: 每个 input_section_attr 必须跟随在“+”后;且大小写不敏感;
RO-CODE 或 CODE
RO-DATA 或 CONST
RO 或 TEXT 同时包含 RO-CODE 和 RO-DATA
RW-DATA
RW-CODE
RW 或 DATA 同时包含 RW-CODE 和 RW-DATA
ZI 或 BSS
ENTRY 说明在当前段中存在入口点.
还有两个伪属性:
FIRST,如果各段的先后顺序比较重要时,可以使用 FIRST 标示一个执行区域的第一个
段。
LAST,如果各段的先后顺序比较重要时,可以使用 LAST 标示一个执行区域的最后一
个段。
例 1:os_main_init.o (INIT , FIRST)
FIRST 表示放于本执行区域的开始处。
例 2:*libtx.a ( RO)
RO 表示*libtx.a 的只读部分。
input_section_pattern:按段名称加载,通常为汇编代码某个区域,前面不用“+”,
用于声明某个链接文件中的特定段。
例 1:os_main_init.o (INIT , FIRST)
只包含 os_main_init.o 文件中的 INIT 段,而且需要放到最前面。
例 2:os_stackheap.o (heap)
只包含 os_stackheap.o 中的 heap 段。
2.1.4 分段加载
通常情况下一个镜像文件只有一个加载域,部分情况下需要将镜像文件进行分散加载。
如下图所示:
下面介绍需要分散加载的情况:
1、存在复杂的地址映射:例如代码和数据需要分开放在在多个区域。
2、存在多种存储器类型:例如包含 Flash,ROM,SDRAM,快速 SRAM。我们根据代码与
数据的特性把他们放在不同的存储器中,比如中断处理部分放在快速 SRAM 内部来提高响
应速度,而把不常用到的代码放到速度比较慢的 Flash 内。
3、函数的地址固定定位:可以利用 Scatter file 实现把某个函数放在固定地址,而不管
其应用程序是否已经改变或重新编译。
4、利用符号确定堆与栈:
5、内存映射的 IO:采用 scatter file 可以实现把某个数据段放在精确的地指处。
因此对于嵌入式系统来说 scatter file 是必不可少的,因为嵌入式系统采用了 ROM,
RAM,和内存映射的 IO。
6、存储器容量不足
2.1.5 分段加载的方法
在 KEIL4 中配置分段加载方法:
就是不选择 Use Memory Layout from Target Dialog,然后指定一个 Scatter File,点击 Edit 按
钮,编辑文件,如下图所示:
2.1.6 分段加载的输出
编译后 keil 会以加载域为单位输出多个文件,而且文件会放进一个文件夹中,如图所示:
例如,uart 文件夹中会出现 2 个文件。
为了实现这一目的,须在 USER 选项中添加命令如下:
如图所示,输出应为文件夹。
如下图所示,文件内容表示,将 2440init.s 的文件代码放到第一个装载域 0x0 的地方,将 uart.o
的代码放到第二个装载域 0x4800 的地址,将变量放到 RAM 装载域 0x30008000 的地址。
说明:*(InRoot$$Sections)段为 keil 库的一段声明,必须包含在第一个执行域中,其中包含
了一段数据搬移指令,可是选数据段的搬移
2.2 内建变量
连接器使用 Scatter loading file 后创建的符号表与前面的些不同。具体如图所示。
其规则如下:
对于 RO 和 RW 段
Load$$region_name $$Base 表示 region_name 区域的装载首地址;
Image$$region_name $$Base 表示 region_name 区域的执行首地址
Image$$region_name $$Length 表示 region_name 区域的长度(单位:字节)
Image$$region_name$$Limit 表示 region _name 区域的执行结束地址.
对于 ZI 段
Image$$region_name $$ZI$$Base 表示 region _name 区域的执行首地址
Image$$region_name $$ZI$$Length 表示 region _name 区域的长度(单位:字节)
Image$$region_name$$ZI$$Limit 表示 region _name 区域的 ZI 段的执行结束地址.
SectionName$$Limit 整个段的结束地址
Load: 加载区,即存放地址;
Image: 执行区,即运行地址;
Base: 区首地址;
Limit: 区尾地址;
Length: 区长度;
region_name: RO、RW、ZI、load_region_name、execution_region_name;
例如:
“RAM1”区域的首地址: Image$$RAM1$$Base
上例中“sram”段首地址: sram$$Base
这些变量由 arm 连接器生成,使用这些变量时需要通过 IMPORT 声明导入。例:
arr
编译结果为:
0x00000000 E3A00000 MOV R0,#0x00000000
0x00000004 00000000 ANDEQ R0,R0,R0
0x00000008 00000014 ANDEQ R0,R0,R4,LSL R0
0x0000000C 30000000 ANDCC R0,R0,R0
0x00000010 30000000 ANDCC R0,R0,R0
可见地址 0x04~0x10 地址分别存放了以上四个内建变量的值。
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$$Limit| ; to zero initialise
|Image$$ER_ROM1$$RO$$Base|
|Image$$ER_ROM1$$RO$$Limit|
|Image$$RW_RAM1$$RW$$Base|
|Image$$RW_RAM1$$Limit|
AREA reset,CODE,READONLY
ENTRY
mov r0,#0
DCD
DCD
DCD
DCD
End