小猫爪:嵌入式小知识04-IAR icf链接文件解析
1 前言
前一章我们提到了源文件最后变成了代码的过程,这一节我们详细得介绍一下在IAR中链接过程以及如何在IAR中自定义链接地址。
2 Section、Block、Region
介绍正题之前,我们得先了解一下Section、Block、Region的概念。
2.1 Section
在《小猫爪:嵌入式小知识03-程序的组成、存储与运行(转自野火教程)》中我们说到了程序中的内容按照性质可以分成一下几个域:
所以代码在编译后就会按照上面的域分成一个个的块,也就是section,链接器链接过程中操作的基本单位就是section。IAR为了让操作更加简单和人性化,就按照以下性质:
以及程序功能作用规定了以下section:
Section | Description |
---|---|
.bss | Holds zero-initialized static and global variables. |
CSTACK | Holds the stack used by C or C++ programs. |
.data | Holds static and global initialized variables. |
.data_init | Holds initial values for .data sections when the linker directive initialize is used. |
.exc.text | Holds exception-related code. |
HEAP | Holds the heap used for dynamically allocated data. |
_ _iar_tls.$$ | DATA Holds initial values for TLS variables. |
.iar.dynexit | Holds the atexit table. |
.init_array | Holds a table of dynamic initialization functions. |
.intvec | Holds the reset vector table |
IRQ_STACK | Holds the stack for interrupt requests, IRQ, and exceptions. |
.noinit | Holds _ _no_init static and global variables. |
.preinit_array | Holds a table of dynamic initialization functions. |
.prepreinit_array | Holds a table of dynamic initialization functions. |
.rodata | Holds constant data. |
.text | Holds the program code. |
.textrw | Holds _ _ramfunc declared program code. |
.textrw_init | Holds initializers for the .textrw declared section. |
Veneer$$CMSE | Holds secure gateway veneers. |
当然我们也可以自定义section,我们在IAR的c代码中可以使用以下语法定义自己的section:
#pragma location = "name" content
name为section名, content则为具体的变量或者函数。
具体操作后面会举例说明。
2.2 Block
Block是IAR为了更加方便的管理Section而定义出的一个概念,用户可以在链接文件中定义一个Block空间,指定对齐方式和大小。下面举个常见的操作例子:
/*这一部分则是定义一个__size_cstack__大小的block,8字节对齐, 其实这就是栈的大小*/
define block CSTACK with alignment = 8, size = __size_cstack__ { };
/*这一部分则是定义了一个RW Block,并将 readwrite 和 m_usb_dma_init_data 这两个section放入 */
define block RW { first readwrite, section m_usb_dma_init_data };
后面还会对其详细介绍。
2.3 Region
IAR使用Region来表示实际的存储区,通常需要通过实际的芯片映射存储地址来定义,我们则可以通过以下语法来定义Region。
define region 名字 = mem:[from 起始地址 to 终止地址];
3 IAR 链接文件
介绍完上面三个概念,接下里来介绍链接文件。
3.1 IAR链接过程
首先我们介绍一下IAR链接文件的流程:
①决定哪些模块需要包含到应用程序中。
②选择链接目标文件用到的标准库文件。
③决定目标文件哪些section需要包含进来。
④完成RAM中的代码和变量的初始化。初始化指令可以让链接器产生额外的代码能够copy ROM中的内容到RAM中。每个通过copy完成初始化的段被分成了两个段,一个在ROM,一个在RAM。如果没有人工初始化的话,链接器回自动产生启动代码来完成初始化。
⑤通过链接文件的放置指令来决定每个section存放的位置。
⑥生成最终的包含可执行映像和调试信息的最终文件。
⑦最后生成map文件,包括了各个section的的在存储器中的最终地址,global symbol的地址和用到的存储器和库汇总。
其实除了第五步,我们经常要人为干预外(可以灵活的配置相关数据的运行区域,比如有段程序需要其非常快的执行或者有段数据需要经常得访问,那么我们则可以将其放置在RAM中,提供执行速度),其他的部分一般都是IAR自动完成的。
3.2 链接文件的组成
在第五步中,链接器会根据链接文件中的内容来决定每一个section的位置,那么一个链接文件中具体会包含下面这些内容:
- 定义可用的可编址空间(memory)
- 定义ROM或RAM的可用内存区域(region)
- 定义Block 定义应用程序初始化
- 放置section
- 使用symbols, expressions, and numbers
3.3 链接文件的常见指令
(这是我直接抄过来的:《解析IAR的ILINK链接器icf配置文件》)
下面让我们了解一下基本指令:
(1)define [ exported ] symbol name = expr;
作用:指定某个符号的值。
参数 | 描述 |
---|---|
exported | 导出该symbol,使其对可执行镜像可用 |
name | 符号名 |
expr | 符号值 |
举例:
define symbol RAM_START_ADDRESS = 0x40000000; /* 定义 RAM 起始地址 */
define symbol RAM_END_ADDRESS = 0x4000FFFF; /* 定义 RAM 结束地址 */
(2)define memory name with size = expr [, unit-size];
作用:定义一个可编址的存储地址空间(memory)。
参数 | 描述 |
---|---|
name | memory的名称 |
expr | 地址空间的大小 |
unit-size | expr的单位,可以是位(unitbitsize),缺省是字节(unitbytesize) |
举例:
define memory MEM with size = 4G;
(3)define region name = mem:[from address_start to address_end];
作用:定义一个存储地址区域(region)。一个区域可由一个或多个范围组成,每个范围内地址必须连续,但几个范围之间不必是连续的。
参数:
参数 | 描述 |
---|---|
name | region的名称 |
mem | 存储器名字(一般由define memory定义) |
address_start | 起始地址 |
address_end | 终止地址 |
举例:
define region ROM = MEM:[from 0x0 to 0xFFFF];
/* 定义 ROM region,位于地址空间MEM 中,起始地址为0x0,大小为0x10000 字节 */
define region ROM = MEM:[from 0x0 to 0xFFFF];
/* 定义 ROM region,位于地址空间MEM 中,起始地址为0x0,结束地址为0xFFFF */
(4)define block name [ with param, param… ] { extended-selectors };
作用:定义一个地址块(block);它可以是个只保留指定大小的地址空间的空块,比如栈、堆;也可以包含一系列的sections,由extended-selectors 选择。
参数 | 描述 |
---|---|
name | block 的名称 |
param | size = expr (块的大小) maximum size = expr (块大小的上限) alignment = expr (最小对齐字节数) fixed order (按照固定顺序放置sections) |
extended-selector | [ first / last ] { section-selector / block name / overlay name } |
其中:
first:最先存放;
Last:最后存放
section-selector: [ section-attribute ][ section sectionname ][object filename ]
section-attribute: [ readonly [ code | data ] | readwrite [ code | data ] | zeroinit ]
其中:
sectionname : section的名称
filename : 目标文件的名称
name : block或overlay的名称
注:这里可以按照section的属性,名称及其所在目标文件这三个过滤条件中,任意选取一个条件或多个条件进行组合,来圈定所要求的sections。
define block HEAP with size = 0x1000, alignment = 4 { };
/* 定义 HEAP block,大小为0x1000,4 字节对齐,没有内容 */
define block MYBLOCK1 = { section mysection1, section mysection2, readwrite };
/* 定义 MYBLOCK1 block,含有mysection1,mysection2,以及所有readwrite 属性的sections */
define block MYBLOCK2 = { readwrite object myfile2.o };
/* 定义 MYBLOCK2 block,含有目标文件myfile2.o 中所有readwrite 属性的sections */
define block MYBLOCK3 = { readonly code object myfile3.o };
/* 定义 MYBLOCK3 block,含有目标文件myfile3.o 中所有readonly 属性的code sections */
(5)initialize { by copy | manually } [ with param, param… ] { section-selectors };
作用:初始化sections
参数 | 描述 |
---|---|
by copy | 在程序启动时自动执行初始化 |
manually | 在程序启动时不自动执行初始化 |
param | packing = { none/compress1 / compress2 /auto } copy routine = functionname packing表示是否压缩数据,缺省是auto functionname表示是否使用自己的拷贝函数来取代缺省的拷贝函数 |
section-selectors | 同上 |
举例:
initialize by copy { readwrite }; /* 在启动时初始化所有属性为 readwrite 的sections */
(6)do not initialize { section-selectors };
作用:规定在程序启动时不需要初始化的sections;一般用于__no_init 声明的变量段(.noinit)
section-selectors :
参数 | 描述 |
---|---|
section-selectors | 同上 |
举例:
do not initialize { .noinit }; /* 在启动时不要初始化.noinit section */
(7)place at { address memory [:expr] | start of region_expr | end of region_expr } { extended-selectors };
作用: 把section 或 block 放置在某个具体的起始地址处,或者一个 region 的开始或结束处
参数 | 描述 |
---|---|
memory | memory 的名称 |
expr | 地址值,该地址必须在 memory 所定义的范围内 |
region_expr | region 的名称 |
extended-selector | 同上 |
举例:
place at end of ROM { section .checksum }; /* 把.checksum 放在 ROM region 的最后 */
place at address MEM:0x0 { section .intvec }; /* 把.intvec 放在地址 0x0 */
place at address MEM:0x1000 { section .text object myfile.o }; /* the .text section of myfile.o */
place at address MEM:0x1000 { readonly object myfile.o }; /* all read-only sections of myfile.o */
place at address MEM:0x1000 { readonly data object myfile.o }; /* all read-only data sections of myfile.o */
(8)
place in region-expr { extended-selectors };
作用:把section 或 block (按任意顺序)放置在某个region 中
参数 | 描述 |
---|---|
region-expr | region 的名称 |
extended-selector | 同上 |
举例:
place in ROM { readonly }; /* all readonly sections */
place in RAM { readwrite }; /* all readwrite sections */
place in RAM { block HEAP, block CSTACK, block IRQ_STACK }; /* heap and stacks */
place in ROM { section .text object myfile.o }; /* the .text section of myfile.o */
place in ROM { readonly object myfile.o }; /* all read-only sections of myfile.o */
place in ROM { readonly data object myfile.o }; /* all read-only data sections myfile.o */
3.4 实战演练
下面我们以RT工程的一个链接文件做一个详细的解释:
/*第一部分*/
define symbol __ram_vector_table_size__ = isdefinedsymbol(__ram_vector_table__) ? 0x00000400 : 0;
define symbol __ram_vector_table_offset__ = isdefinedsymbol(__ram_vector_table__) ? 0x000003FF : 0;
define symbol m_interrupts_start = 0x60002000;
define symbol m_interrupts_end = 0x600023FF;
define symbol m_text_start = 0x60002400;
define symbol m_text_end = 0x63FFFFFF;
define symbol m_interrupts_ram_start = 0x20000000;
define symbol m_interrupts_ram_end = 0x20000000 + __ram_vector_table_offset__;
define symbol m_data_start = m_interrupts_ram_start + __ram_vector_table_size__;
define symbol m_data_end = 0x2001FFFF;
define symbol m_data2_start = 0x20200000;
define symbol m_data2_end = 0x2023FFFF;
define symbol m_data3_start = 0x80000000;
define symbol m_data3_end = 0x81DFFFFF;
define symbol m_ncache_start = 0x81E00000;
define symbol m_ncache_end = 0x81FFFFFF;
define exported symbol m_boot_hdr_conf_start = 0x60000000;
define symbol m_boot_hdr_ivt_start = 0x60001000;
define symbol m_boot_hdr_boot_data_start = 0x60001020;
define symbol m_boot_hdr_dcd_data_start = 0x60001030;
if (isdefinedsymbol(__stack_size__)) {
define symbol __size_cstack__ = __stack_size__;
} else {
define symbol __size_cstack__ = 0x0400;
}
if (isdefinedsymbol(__heap_size__)) {
define symbol __size_heap__ = __heap_size__;
} else {
define symbol __size_heap__ = 0x0400;
}
define exported symbol __NCACHE_REGION_START = m_ncache_start;
define exported symbol __NCACHE_REGION_SIZE = m_ncache_end - m_ncache_start + 1;
define exported symbol __VECTOR_TABLE = m_interrupts_start;
define exported symbol __VECTOR_RAM = isdefinedsymbol(__ram_vector_table__) ? m_interrupts_ram_start : m_interrupts_start;
define exported symbol __RAM_VECTOR_TABLE_SIZE = __ram_vector_table_size__;
/*第二部分*/
define memory mem with size = 4G;
define region TEXT_region = mem:[from m_interrupts_start to m_interrupts_end]
| mem:[from m_text_start to m_text_end];
define region DATA_region = mem:[from m_data_start to m_data_end];
define region DATA2_region = mem:[from m_data2_start to m_data2_end];
define region DATA3_region = mem:[from m_data3_start to m_data3_end-__size_cstack__];
define region CSTACK_region = mem:[from m_data3_end-__size_cstack__+1 to m_data3_end];
define region NCACHE_region = mem:[from m_ncache_start to m_ncache_end];
/*第三部分 */
define block CSTACK with alignment = 8, size = __size_cstack__ { };
define block HEAP with alignment = 8, size = __size_heap__ { };
define block RW { first readwrite, section m_usb_dma_init_data };
define block ZI with alignment = 32 { first zi, section m_usb_dma_noninit_data };
define block NCACHE_VAR { section NonCacheable , section NonCacheable.init };
/*第四部分 */
initialize by copy { readwrite, section .textrw };
do not initialize { section .noinit };
/*第五部分 */
place at address mem: m_interrupts_start { readonly section .intvec };
place at address mem:m_boot_hdr_conf_start { section .boot_hdr.conf };
place at address mem:m_boot_hdr_ivt_start { section .boot_hdr.ivt };
place at address mem:m_boot_hdr_boot_data_start { readonly section .boot_hdr.boot_data };
place at address mem:m_boot_hdr_dcd_data_start { readonly section .boot_hdr.dcd_data };
/*第六部分 */
keep{ section .boot_hdr.conf, section .boot_hdr.ivt, section .boot_hdr.boot_data, section .boot_hdr.dcd_data };
/*第七部分 */
place in TEXT_region { readonly };
place in DATA3_region { block RW };
place in DATA3_region { block ZI };
if (isdefinedsymbol(__heap_noncacheable__)) {
place in NCACHE_region { last block HEAP };
} else {
place in DATA3_region { last block HEAP };
}
place in CSTACK_region { block CSTACK };
place in NCACHE_region { block NCACHE_VAR };
(1)第一部分主要是在定义各种符号的值,然后给下面的调用。
(2)第二部分则是定义了很多region,将RT整个存储空间分成了好几个区域。
(3)第三部分则是定义很多block。
(4)第四部分则是定义哪些section需要初始化,哪些不需要初始化。
(5)第五部分则是将部分section放入对应的地址中。
(6)第六部分则是保持住头信息数据,避免这部分数据被IAR链接器优化。
(7)第七部分则是将第三部分定义block放入相应的region中。
大家结合前面的命令语法,好好对照,非常简单。。。
(附加:有的小伙伴可能又要问了,上面你指定了自定义的sectio的位置,那那些没有指定名称的section是怎么放的?
IAR在编译过程中,没有自定义名称的section则是会按照其属性划分为三种:readonly,readwrite,zi,(也就是上面介绍语法提到的section-attribute种类)所以在icf文件中,这三种类别则是代表了那些没有名字的section,仔细研究可以看到readonly放在了ROM区,readwrite和zi放在了RAM区.)
对于icf文件,还有更加细致的应用介绍,大家请参考我的另外一篇文章:《小猫爪:嵌入式小知识05-IAR icf链接文件操作-链接代码至RAM》。
END