ARM映像文件
ARM映像文件是一个层次性结构的文件,其中包含了域(region)、输出段(output section)和输入段(input section)。各部分关系如下:
- 一个映像文件由一个或多个域组成
- 每个域包含一个或多个输出段
- 每个输出段包含一个或多个输入段
- 各输入段包含了目标文件中的代码和数据
输入段中包含了4类内容:代码、已经初始化的数据、未经初始化的存储区域、内容初始化成0的存储区域。每个输入段有相应的属性,可以为只读的(RO)、可读写的(RW)以及初始化成0的(ZI)。ARM连接器根据各输入段的属性将这些输入段分组,再组成不同的输出段以及域。
一个输出段中包含了一系列的具有相同的RO、RW和ZI属性的输入段。输出段的属性与其中包含的输入段的属性相同。在一个输出段内部,各输入段是按照一定的规则排序的,这个后面再补充。
一个域中包含了1~3个输出段,其中各输出段的属性各不相同。各输出段的排列顺序是由其属性决定的。其中,RO属性的输出段排在最前面,其次是RW属性的输出段,最后是ZI属性的输出段。一个域通常映射到一个物理存储器上,如ROM和RAM等。
ARM映像文件各组成部分的地址映射
分散加载机制允许为链接器指定映像的存储器映射信息,可实现对映像组件分组和布局的全面控制。分散加载通常仅用于具有复杂存储器映射的映像(尽管也可用于简单映像),也就是适合加载和执行时内存映射中的多个区是分散的情况。
要构建映像的存储器映射,链接器必须有:描述节如何分组成区的分组信息、描述映像区在存储器映射中的放置地址的放置信息。
分散加载区域分两类:
- 加载区:该映像文件开始运行前存放的区域,即当系统启动或加载时应用程序存放的区域。
- 执行区:映像文件运行时的区域,即系统启动后,应用程序进行执行和数据访问的存储器区域,系统在实时运行时可以有一个或多个执行块。
分散加载文件(即scatter file,后缀为.scf)
分散加载文件是一个文本文件,通过编写一个分散加载文件来指定ARM连接器在生成映像文件时如何分配RO,RW,ZI等数据的存放地址。如果不用SCATTER文件指定,那么ARM连接器会按照默认的方式来生成映像文件,一般情况下我们是不需要使用分散加载文件的。
但在某些场合,我们希望把某些数据放在指定的地址处,那么这时候SCATTER文件就发挥了非常大的作用。而且SCATTER文件用起来非常简单好用。
举个例子:比如像LPC2378芯片具有多个不连续的SRAM,通用的RAM是32KB,可是32KB不够用,我想把某个.C中的RW数据放在USB的SRAM中,那么就可以通过SCATTER文件来完成这个功能。
分散加载文件的语法:
load_region_name start_address | "+"offset [attributes] [max_size]
{
execution_region_name start_address | "+"offset [attributes][max_size]
{
module_select_pattern ["("
("+" input_section_attr | input_section_pattern)
([","] "+" input_section_attr | "," input_section_pattern)) *
")"]
}
}
- load_region: 加载区,用来保存永久性数据(程序和只读变量)的区域;
- execution_region: 执行区,程序执行时,从加载区域将数据复制到相应执行区后才能被正确执行;
- load_region_name: 加载区域名,用于“Linker”区别不同的加载区域,最多31个字符;
- start_address: 起始地址,指示区域的首地址;
- +offset: 前一个加载区域尾地址+offset 做为当前的起始地址,且“offset”应为“0”或“4”的倍数;
以上请上网查阅。
本文如下:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
CMDREG +0
{
.ANY(cmd)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
}
红色部分是新添加的。
CMDREG 表示region
+0 表示紧跟在上一个区域
.ANY表示属性
Cmd表示段
在map文件看到如下
Keil 设置
Options->linker
-
- 去掉Use Memory Layout from Target Dialog,然后Scatter File才可以编辑
- 电机Edit后面可以编辑sct文件,如上面提到的
- 注意: Misc controls一定要添加--keep=strcmd_*
不然会被编译器优化点,这时map文件看不到CMDREG区域内容
使用Scatter存储来实现读取
-
定义
typedef int(*CLI_handler)();
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
CLI_handler cmd; /* Implementation function */
char *usage; /* Usage message (short) */
};
typedef struct cmd_tbl_s cmd_tbl_t;
定义宏
#define REGISTER_CMD(name,maxargs,handler,usage) \
const cmd_tbl_t strcmd_##name __attribute__ ((section ("cmd"))) = {#name, maxargs, handler, usage}
REGISTER_CMD(
ver,
1,
do_version,
"print monitor version"
);
这个宏可以在任何包含以上宏的文件里面实现,更加灵活。
引用
extern unsigned char Load$$CMDREG$$Base[];
extern unsigned char Load$$CMDREG$$Limit[];
unsigned long cmd_addr = (unsigned long)Load$$CMDREG$$Base;
unsigned long cmd_end = (unsigned long)Load$$CMDREG$$Limit;
红色部分表示上面的CMDREG区域
强制转换
CLI_cmd_start =(cmd_tbl_t *)cmd_addr;
CLI_cmd_end = (cmd_tbl_t *)cmd_end;
编译该区域,提取字段
cmd_tbl_t *cmdtp;
int len = CLI_cmd_end - CLI_cmd_start;
for (cmdtp = CLI_cmd_start; cmdtp != CLI_cmd_start + len; cmdtp++)
{
if(0 == strcmp(name, cmdtp->name))
{
*cmd = cmdtp;
break;
}
}