嵌入式IDE(1):IAR中ICF链接文件详解和实例分析
最近在使用NXP提供的MCUXPresso IDE,除了Eclipse固有的优点外,我觉得它最大的优点就是在链接脚本的生成上,提供了非常直观的GUI配置界面,而且生成的链接脚本也是GCC规范的连接脚本。但这个IDE仅仅支持NXP相关的产品,而且调试的性能在某些情况下并不理想。而我们用得比较多的IDE是Keil和IAR,这两个IDE都有自己生成链接脚本的格式,本篇文章就来介绍一下与IAR的链接脚本生成相关的.icf(IAR Configuration File)后缀的IAR配置文件。
文章目录
- 1 内存映射
- 2 ICF语法分析
- 2.1 工程的ICF文件
- 2.2 define [exported] symbol和isdefinedsymbol
- 2.3 define memory、define region、区域表达式和define block
- 2.4 initialize by copy和do not initialize
- 2.5 place at、keep和place in
本来打算把ICF文件中的每一个指令的格式都详细地介绍一遍,但发现里面的指令太多了,而且很多都用不到。完整的指令请参考:中的The linker configuration file章节。
所以本篇文章就以I.MX RT1176的IAR工程中的ICF文件为例进行分析,然后详细理解一下每个用到的指令格式。对于本节的ICF例子,除了RT1176内部的几个RAM外,还接了NOR Flash和SDRAM。所以如果懂了这个ICF配置文件,对于其它MCU的配置文件来说也不会有太大的问题。
1 内存映射
首先来看一下整个工程的内存映射表格:
类型 名称 起始地址 大小 Flash NOR Flash 0x30000000 0x1000000 RAM SDRAM 0x80000000 0x3000000 RAM NCACHE_REGION 0x83000000 0x1000000 RAM SRAM_DTC_cm7 0x20000000 0x40000 RAM SRAM_ITC_cm7 0x0 0x40000 RAM SRAM_OC1 0x20240000 0x80000 RAM SRAM_OC2 0x202c0000 0x80000 RAM SRAM_OC_ECC1 0x20340000 0x10000 RAM SRAM_OC_ECC2 0x20350000 0x10000 对于我们的工程来说,有以下几个内存:
- 两个256KB的紧耦合内存DTCM和ITCM
- 两个带ECC的片内RAM:OC1、OC2、OC_ECC1和OC_ECC2。
- 在映射的起始地址为0x30000000的FlexSPI1接口上接了一个16MB的NOR Flash
- 在映射的起始地址为0x80000000的SEMC接口上接了一个64MB的SDRAM。其中,前48MB用于可缓存的区域,后16MB(NCACHE_REGION)用于不可缓存区域,通常直接与硬件进行交互的buffer需要设置为不可缓存。
2 ICF语法分析
2.1 工程的ICF文件
针对上面的内存映射,官方的SDK中提供的ICF文件如下:
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 = 0x30002000; define symbol m_interrupts_end = 0x300023FF; define symbol m_text_start = 0x30002400; if (isdefinedsymbol(__use_flash64MB__)) { define symbol m_text_end = 0x33FFFFFF; } else{ define symbol m_text_end = 0x30FFFFFF; } 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 = 0x2003FFFF; define symbol m_data2_start = 0x202C0000; define symbol m_data2_end = 0x2033FFFF; define symbol m_data3_start = 0x80000000; define symbol m_data3_end = 0x82FFFFFF; define symbol m_ncache_start = 0x83000000; define symbol m_ncache_end = 0x83FFFFFF; define exported symbol __NCACHE_REGION_START = m_ncache_start; define exported symbol __NCACHE_REGION_SIZE = m_ncache_end - m_ncache_start + 1; define symbol m_qacode_start = 0x00000000; define symbol m_qacode_end = 0x0003FFFF; define exported symbol m_boot_hdr_conf_start = 0x30000400; define symbol m_boot_hdr_ivt_start = 0x30001000; define symbol m_boot_hdr_boot_data_start = 0x30001020; define symbol m_boot_hdr_dcd_data_start = 0x30001030; define symbol m_boot_hdr_xmcd_data_start = 0x30001040; /* Sizes */ 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 __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 QACODE_region = mem:[from m_qacode_start to m_qacode_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 }; define block QACCESS_CODE { section CodeQuickAccess }; define block QACCESS_DATA { section DataQuickAccess }; initialize by copy { readwrite, section .textrw, section CodeQuickAccess, section DataQuickAccess }; 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 }; place at address mem: m_boot_hdr_xmcd_data_start { readonly section .boot_hdr.xmcd_data }; keep{ section .boot_hdr.conf, section .boot_hdr.ivt, section .boot_hdr.boot_data, section .boot_hdr.dcd_data, section .boot_hdr.xmcd_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 NCACHE_region { block NCACHE_VAR }; place in CSTACK_region { block CSTACK }; place in QACODE_region { block QACCESS_CODE }; place in DATA_region { block QACCESS_DATA };
下面来一段段分析上面的ICF文件。
2.2 define [exported] symbol和isdefinedsymbol
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 = 0x30002000; define symbol m_interrupts_end = 0x300023FF; define symbol m_text_start = 0x30002400; if (isdefinedsymbol(__use_flash64MB__)) { define symbol m_text_end = 0x33FFFFFF; } else{ define symbol m_text_end = 0x30FFFFFF; } 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 = 0x2003FFFF; define symbol m_data2_start = 0x202C0000; define symbol m_data2_end = 0x2033FFFF; define symbol m_data3_start = 0x80000000; define symbol m_data3_end = 0x82FFFFFF; define symbol m_ncache_start = 0x83000000; define symbol m_ncache_end = 0x83FFFFFF; define exported symbol __NCACHE_REGION_START = m_ncache_start; define exported symbol __NCACHE_REGION_SIZE = m_ncache_end - m_ncache_start + 1; define symbol m_qacode_start = 0x00000000; define symbol m_qacode_end = 0x0003FFFF;
这一段中出现了两个ICF语法:
(1)isdefinedsymbol(name):当name被定义了返回1,否则返回0
(2)define symbol:定义一个变量
- 语法:define [ exported ] symbol name = expr;
- 参数:name为变量名,expr为变量的值,exported可省略,若定义则可以在程序中使用extern来获取此变量的值
现在来分析一下上面的链接文件:
(1)__ram_vector_table__在其它地方没有定义,即__ram_vector_table_size__和__ram_vector_table_offset__的值都为0。所以,m_interrupts_ram_start和m_interrupts_ram_end都为0x20000000。
实际上,由于程序是运行在NOR Flash中的,程序镜像起始处的中断向量表也映射到了NOR Flash中,而不是保存在RAM中。所以实际上上面的这几个变量并没有被使用到,可以直接忽略。
真正使用的向量表变量是m_interrupts_start(0x30002000)和m_interrupts_end(0x300023FF),长度为0x3FF+1=0x400,可以去启动的.s数一下,程序最开始的向量长度确实是填充到了0x400处。
- 至于为什么向量表从NOR Flash的0x2000偏移处开始,这是因为I.MX系列单片机都需要一个IVT头供芯片固有的ROM BootLoader进行引导,这个头在使用NOR Flash XIP时,长度为0x2000。这里不用过多纠结。
(2)m_text_start(0x30002400)和m_text_end(0x30FFFFFF)紧跟着向量表,就是后续的代码段链接的位置了,大小为16MB。
(3)m_data_start和m_data_end;m_data2_start和m_data2_end;m_data3_start和m_data3_end;m_ncache_start和m_ncache_end;m_qacode_start和m_qacode_end
这三个变量分别定义了DTCM、OC2、SDRAM(可缓存部分)、SDRAM(不可缓存部分)和ITCM的内存起始和结束地址。
(4)__NCACHE_REGION_START和__NCACHE_REGION_SIZE:定义了不可缓存内存的起始和结束地址,这个部分用了export,这是因为不可缓存部分需要在程序的MPU代码中进行配置。
接着往下分析:
/* 这里定义的是IVT头中不同参数的偏移,这里不做分析 */ define exported symbol m_boot_hdr_conf_start = 0x30000400; define symbol m_boot_hdr_ivt_start = 0x30001000; define symbol m_boot_hdr_boot_data_start = 0x30001020; define symbol m_boot_hdr_dcd_data_start = 0x30001030; define symbol m_boot_hdr_xmcd_data_start = 0x30001040; /* Sizes */ 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 __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__;
(1)__size_cstack__和__size_heap__为程序的栈、堆大小的相关变量,后面会使用到。
- 实际上在这个工程中使用了FreeRTOS,所以只需要保证这里面的栈大小能够运行FreeRTOS的初始化函数就行了,后面的堆、栈都由FreeRTOS管理,从分配给FreeRTOS的空间中分配。
(2)__VECTOR_TABLE(0x30002000)、__VECTOR_RAM(0x30002000)和__RAM_VECTOR_TABLE_SIZE(0)
将这三个变量export给程序。实际上在这个工程中没有使用到这三个变量,这三个变量原本是用来将保存在Flash中的向量表拷贝到RAM中的,所以如果使用的是non-XIP的Flash,如NAND Flash,就会用到这三个变量。
2.3 define memory、define region、区域表达式和define block
先来看一下下面将新出现的ICF语法:
(1)define memory :定义一块内存
- 语法:define memory [ name ] with size = size_expr [ ,unit-size ];
- 参数:name为内存名,expr为内存大小,unit-size可省略,若定义它必须赋值为bitsize_expr(位)或bytesize_expr(字节),表示前面内存大小的单位,默认为字节。
(2)define region:定义一块可以放置特定的代码段和数据段的区域。一个区域由一个或多个内存范围组成,每个内存范围都是在特定内存中连续的字节序列。
- 语法:define [ ram | rom ] region_name = region-expr;
- 参数:region_name为区域名,[ ram | rom ]可省略,分别表示该region为RAM或ROM。region-expr是区域表达式,使用区域表达式可以组合多个内存范围,这些内存范围可以不连续,甚至不在同一块内存中。在(3)中介绍。
(3)区域表达式
- 语法:[ memory-name: ][from expr { to expr | size expr } [ repeat expr [ displacement expr ]]]
- 参数:memory-name为内存区域的名称,如果只有一块内存,可省略此项;from expr { to expr | size expr分别为内存区域的起始地址、终止地址和大小;repeat expr表示同一个内存中分的多个内存范围;displacement expr是repeat序列中从前一个内存范围开始的偏移,默认大小为size。
同时区域之间还可以有一些运算:
- A | B:A和B的并集
- A & B:A和B的交集
- A - B:A排除B的集合
(4)define block:块指令定义了一个连续的内存区域,该区域可能包含一组可能为空的段或其他块。
语法:
define [ movable ] block name [ with param, param... ] { extended-selectors } [ except { section-selectors } ]; 其中param可以为下面之一: size = expr minimum size = expr maximum size = expr expanding size alignment = expr end alignment = expr fixed order alphabetical order static base [basename]
块指令比较复杂,参数比较多,有很多参数也用不到,这里就不具体地解释每一个参数了,下面会直接通过解释例子中的几个块指令的含义来帮助大家看懂ICF文件。具体指令的定义可以参考手册p521的define block directive。
继续往下分析ICF文件,接下来就是定义一些内存、区域和块:
/* 定义一整个大小为2^32=4G的内存,即芯片的最大寻址范围 */ 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 QACODE_region = mem:[from m_qacode_start to m_qacode_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 }; define block QACCESS_CODE { section CodeQuickAccess }; define block QACCESS_DATA { section DataQuickAccess };
上面的脚本中定义了多个内存区域,其中TEXT_region即代码段的范围,即NOR Flash中0x30002000后开始放代码的区域;QACODE_region即ITCM的区域;DATA_region即DTCM的区域;DATA2_region即SRAM_OC2的区域;DATA3_region即SDRAM的可cacheable区域;CSTACK_region为栈的区域,这里定义为DATA3_region的最后__size_cstack__字节区域;NCACHE_region即SDRAM的non-cacheable区域。
接下来就是定义多个block了,其中CSTACK和HEAP分别为栈和堆的块,它要求这里面的内存8字节对齐,大小分别为__size_cstack__和__size_heap__;RW中的first是一个extended-selectors表达式(参考p540),这里表示将readwrite块放置在包含RW块(即RW块的父集)的最前面,这里可以定义多个section-selectors,用逗号隔开,所以后面的section m_usb_dma_init_data定义了一个名为m_usb_dma_init_data的section在这个块中;ZI与RW类似,它额外要求32字节对齐;NCACHE_VAR、QACCESS_CODE和QACCESS_DATA 都是定义了一个特定名称section在这个block中。
- 比如这里section定义的m_usb_dma_init_data,可以在程序中使用#pragma(location=m_usb_dma_init_data)或__attribute__((section("m_usb_dma_init_data")))来定义变量到RW中
- readwrite(RW)、readonly和zi为ICF文件内置的三个block,分别为读写段、只读段和bss段。readwrite段默认包含了程序中有初始值的变量,readonly段默认包含了程序的代码,zi段默认包含了程序中没有初始值的变量。
2.4 initialize by copy和do not initialize
(1)initialize by copy
语法(具体参考P527):
initialize { by copy | manually } [ with param, param... ] { section-selectors } [ except { section-selectors } ];
这里的by copy表示复制一个段,这也很好理解,比如对于RW段来说,只要不是bss段的有初始值的变量,这些初始值是会占据编译出来的image的大小的,也就是这些初始值是保存在Flash中,然后上电后再拷贝到RAM中的,这里定义的RW段是RAM,所以再"copy"一段到Flash中。
(2)do not initialize:与initialize by copy相反,一般用于bss段
继续往下看ICF文件:
initialize by copy { readwrite, section .textrw, section CodeQuickAccess, section DataQuickAccess }; do not initialize { section .noinit };
就是根据定义的某个section是否会存放有初始值的变量,手动定义section到initialize by copy或do not initialize。
2.5 place at、keep和place in
(1)place at
[ "name": ] place [ noload ] at { address [ memory: ] address | start of region_expr [ with mirroring to mirror_address ] | end of region_expr [ with mirroring to mirror_address ] } { extended-selectors } [ except { section-selectors } ];
该指令用于将sections和blocks放置在特定地址或者区域的开头或末尾。
(2)keep
keep { [ { section-selectors | block name } [ , {section-selectors | block name }... ] ] } [ except { section-selectors } ];
这里的keep和链接脚本ld文件中的keep的作用一样,用于控制链接器在生成可执行文件或库时保留特定的sections和blocks,防止链接器优化过程中丢弃未被引用的sections和blocks。
(3)place in
[ "name": ] place [ noload ] in region-expr [ with mirroring to mirror_address ] { extended-selectors } [ except{ section-selectors } ];
place in会防止section和block到一个特定的区域。如果有多个section和block,则它们之间放置的顺序是随机的,如果想指定这个顺序可以用block表达式,一般用不到。
继续往下看链接脚本:
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 }; place at address mem: m_boot_hdr_xmcd_data_start { readonly section .boot_hdr.xmcd_data }; keep{ section .boot_hdr.conf, section .boot_hdr.ivt, section .boot_hdr.boot_data, section .boot_hdr.dcd_data, section .boot_hdr.xmcd_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 NCACHE_region { block NCACHE_VAR }; place in CSTACK_region { block CSTACK }; place in QACODE_region { block QACCESS_CODE }; place in DATA_region { block QACCESS_DATA };
上面的place at就是将后面大括号里的section放置到前面指定的地址中,而这些section可以在程序中使用例如__attribute__((section(".boot_hdr.boot_data"), used))的语句放置到指定段中。这里的几个段实际上是I.MX RT系列单片机的启动头,通过这种方式可以在C文件中更改启动头的内容。
- 上面定义的6个section应该都是readonly才对,我的猜测是可写可不写,因为这里已经强制了放置的地址,这里的地址都在Flash,应该默认就已经表示都是readonly。可惜IAR没有生成像Makefile那样标准的链接脚本,不然可以对比一下前后的差别。
后面的place in就是将前面定义的各个block(由多个section组成)放置到前面定义的各个region(代表一个或多个地址范围)中。其中last block HEAP中的last和前面遇到的first一样,也是extended-selectors中的定义,表示放置该region的最后面。
- 上面定义的6个section应该都是readonly才对,我的猜测是可写可不写,因为这里已经强制了放置的地址,这里的地址都在Flash,应该默认就已经表示都是readonly。可惜IAR没有生成像Makefile那样标准的链接脚本,不然可以对比一下前后的差别。
- 实际上在这个工程中使用了FreeRTOS,所以只需要保证这里面的栈大小能够运行FreeRTOS的初始化函数就行了,后面的堆、栈都由FreeRTOS管理,从分配给FreeRTOS的空间中分配。
- 至于为什么向量表从NOR Flash的0x2000偏移处开始,这是因为I.MX系列单片机都需要一个IVT头供芯片固有的ROM BootLoader进行引导,这个头在使用NOR Flash XIP时,长度为0x2000。这里不用过多纠结。