① IAR 链接
② EWARM_DevelopmentGuide.ENU.pdf
ICF是链接脚本文件后缀名,类似于gcc的lds和ADI SHARC DSP的ldf。它们都是用于Linker组成生成可执行文件的。
ICF文件模板存放于在IAR安装目录。以笔者使用的STM32F103VC为例。它的ICF存放路径如下:
D:\Program Files\IAR Systems\Embedded Workbench 7.5\arm\config\linker\ST\stm32f103xC.icf
linker
目录之下,其实还有很多目录,对应于不同公司。可见很多公司都基于Cortex-M
开发了自己芯片。
/*###ICF### Section handled by ICF editor, don't touch! /
/-Editor annotation file-/
/* IcfEditorFile="TOOLKIT_DIR\config\ide\IcfEditor\cortex_v1_0.xml" */
/-Specials-/
define symbol ICFEDIT_intvec_start = 0x08000000;
/-Memory Regions-/
define symbol ICFEDIT_region_ROM_start = 0x08000000;
define symbol ICFEDIT_region_ROM_end = 0x0803FFFF;
define symbol ICFEDIT_region_RAM_start = 0x20000000;
define symbol ICFEDIT_region_RAM_end = 0x2000BFFF;
/-Sizes-/
define symbol ICFEDIT_size_cstack = 0x1000;
define symbol ICFEDIT_size_heap = 0x1000;
/ End of ICF editor section. ###ICF###*/
define memory mem with size = 4G;
define region ROM_region = mem:[from ICFEDIT_region_ROM_start to ICFEDIT_region_ROM_end];
define region RAM_region = mem:[from ICFEDIT_region_RAM_start to ICFEDIT_region_RAM_end];
define block CSTACK with alignment = 8, size = ICFEDIT_size_cstack { };
define block HEAP with alignment = 8, size = ICFEDIT_size_heap { };
initialize by copy { readwrite };
do not initialize { section .noinit };
place at address mem:ICFEDIT_intvec_start { readonly section .intvec };
place in ROM_region { readonly };
place in RAM_region { readwrite,
block CSTACK, block HEAP };
模板定义:
需要重点讲解的知识点是。
region, block和section三个概念。region是一个地址空间,可以位于RAM,也可以位于ROM。它是最顶层的范围(必须指定地址空间)。block可以理解为section的集合,它可以有自己的属性,例如模板中的with alignment = 8
和size = ICFEDIT_size_cstack
。block的属性以逗号分割。section属于最底层的单元,可以有修饰,例如模板中的readonly
和readwrite
。注意block和section都直接存放于region。section可以存放于block。
模板中提到的/*###ICF### Section handled by ICF editor, don't touch! /
和/ End of ICF editor section. ###ICF###*/
之前的内容不要更改。这一段有IAR内嵌编辑器维护。使用方式如下:
Project => Options => Config => Edit
也就是指定Project ICF的地方。
介绍用例之前,有个明显疑问是“为什么需要改动ICF?”。每个项目都有每个项目的需求。
例如,建荣的蓝牙芯片(8051内核,并非ARM核,仅用于说明改动ICF的动机)采用bank方式存放固件。一个固件可能存放于多个bank。出于性能考虑,希望同一个模块尽可能存放于同一个bank,减少bank切换次数,达到优化性能的目的。
例如,有一组初始化函数,可以存放用同同一个section。这样可能循环调用,比较便于编程。
例如,bootloader可以通过在固定内存区域存放一些参数(甚至是函数),app启动之后再固定位置获取信息。
如此种种,自定义ICF是有意义的。
...
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };
define symbol __ICFEDIT_size_myblock__ = 0x200;
define block MYBLOCK with alignment = 4, size = __ICFEDIT_size_myblock__ {
readonly section mysection
};
initialize by copy { readwrite };
...
place in ROM_region { readonly, block MYBLOCK};
...
本ICF中,笔者定义了一个block和一个section。自定义的block存放于模板中的ROM_region。自定义的section存放于自定义的block当中。
void CallBack_A(void)
{
printf("%s-%04d\n", __FUNCTION__, __LINE__);
}
void CallBack_B(void)
{
printf("%s-%04d\n", __FUNCTION__, __LINE__);
}
typedef void (*plugin_t)(void);
#define PLUGIN(i, f) \
__root const plugin_t Plugin_##i @ "mysection" = f;
PLUGIN(A, CallBack_A);
PLUGIN(B, CallBack_B);
引用section的语法注意。本处使用@ mysection
方式的目的是为了宏定义方便。其实还有其他方式的。例如#pragma location="mysection"
。
void main(void)
{
...
plugin_t p = NULL;
uint32_t pBegin = 0;
uint32_t pEnd = 0;
...
#pragma section ="mysection"
pBegin = (uint32_t)__section_begin("mysection");
pEnd = (uint32_t)__section_end("mysection");
printf("mysection begin = %p\n", (void *)pBegin);
printf("mysection end = %p\n", (void *)pEnd);
while(pBegin != pEnd)
{
p = *((plugin_t *)pBegin);
p();
pBegin += sizeof(plugin_t);
}
...
}
section可以通过编译器内建宏__section_begin
和__section_end
来定位。这两个内建宏都是void *
的类型。使用的时候,需要进行强制转换还原。另外,在C语言角度需要注意__section_begin
是指向变量自身的地址,而使用的是内容。所以需要类似进行p = *((plugin_t *)pBegin);
的强制转换。
连接的结果可以在MAP文件确认一下。
...
Plugin_A 0x0805580c 0x4 Data Gb main.o [1]
Plugin_B 0x08055810 0x4 Data Gb main.o [1]
...
MYBLOCK 0x0805580c 0x200
mysection 0x0805580c 0x8
mysection const 0x0805580c 0x4 main.o [1]
mysection const 0x08055810 0x4 main.o [1]
MYBLOCK const 0x08055814 0x1f8
...
可见,一切如想象中顺利。再确认一下输出。
mysection begin = 805580c
mysection end = 8055814
CallBack_A-0359
CallBack_B-0364
可见,打印的地址805580c和MAP一致,输出结果与预期一致,一切如想象中顺利。