嵌入式IDE(1):IAR中ICF链接文件详解和实例分析

最近在使用NXP的提供的MCUXPresso IDE,除了Eclipse固有的优点外,我觉得它最大的优点就是在链接脚本的生成上,提供了非常直观的GUI配置界面。但这个IDE仅仅支持NXP相关的产品,而且调试的性能在某些情况下并不理想。而我们用得比较多的IDE是Keil和IAR,这两个IDE都有自己生成链接脚本的格式,本篇文章就来介绍一下与IAR的链接脚本生成相关的.icf(IAR Configuration File)后缀的IAR配置文件。

文章目录

  • [1 内存映射](#1 内存映射)
  • [2 ICF语法分析](#2 ICF语法分析)
    • [2.1 工程的ICF文件](#2.1 工程的ICF文件)
    • [2.2 define [exported] symbol和isdefinedsymbol](#2.2 define [exported] symbol和isdefinedsymbol)
    • [2.3 define memory、define region、区域表达式和define block](#2.3 define memory、define region、区域表达式和define block)
    • [2.4 initialize by copy和do not initialize](#2.4 initialize by copy和do not initialize)
    • [2.5 place at、keep和place in](#2.5 place at、keep和place in)

本来打算把ICF文件中的每一个指令的格式都详细地介绍一遍,但发现里面的指令太多了,而且很多都用不到。完整的指令请参考:<EWARM_DevelopmentGuide.ENU.pdf>中的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

对于我们的工程来说,有以下几个内存:

  1. 两个256KB的紧耦合内存DTCMITCM
  2. 两个带ECC的片内RAM:OC1OC2
  3. 在映射的起始地址为0x30000000的FlexSPI1接口上接了一个16MB的NOR Flash
  4. 在映射的起始地址为0x80000000的FlexSPI2接口上接了一个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__在其它地方都没有定义,即__ram_vector_table_size____ram_vector_table_offset__的值都为0。所以,m_interrupts_ram_startm_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_startm_data_end;m_data2_startm_data2_end;m_data3_startm_data3_end;m_ncache_startm_ncache_end;m_qacode_startm_qacode_end

这三个变量分别定义了DTCMOC2SDRAM(可缓存部分)、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 exprrepeat序列中从前一个内存范围开始的偏移,默认大小为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了,其中CSTACKHEAP分别为栈和堆的块,它要求这里面的内存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在这个块中;ZIRW类似,它额外要求32字节对齐;NCACHE_VARQACCESS_CODEQACCESS_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)、readonlyzi为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是否会存放有初始值的变量,手动定义sectioninitialize by copydo 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
} ];

该指令用于将sectionsblocks放置在特定地址或者区域的开头或末尾。
(2)keep

复制代码
keep
{
[ { section-selectors | block name }
[ , {section-selectors | block name }... ] ]
}
[ except
{
section-selectors
} ];

这里的keep和链接脚本ld文件中的keep的作用一样,用于控制链接器在生成可执行文件或库时保留特定的sectionsblocks,防止链接器优化过程中丢弃未被引用的sectionsblocks
(3)place in

复制代码
[ "name": ]
place [ noload ] in region-expr
[ with mirroring to mirror_address ]
{
extended-selectors
}
[ except{
section-selectors
} ];

place in会防止sectionblock到一个特定的区域。如果有多个sectionblock,则它们之间放置的顺序是随机的,如果想指定这个顺序可以用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的最后面。

相关推荐
Warren982 小时前
十分钟学会Git
java·ide·笔记·git·gitee
灏瀚星空5 小时前
在 Visual Studio Code 中高效使用 Pylance:配置、技巧与插件对比
ide·经验分享·vscode·python·编辑器·学习方法
北漂老男孩7 小时前
IntelliJ IDEA 调试技巧指南
java·ide·intellij-idea
Lunar*10 小时前
在 VSCode 远程开发环境下使用 Git 常用命令
ide·git·vscode
牛轧糖nhh21 小时前
Visual Studio2022 中的键盘注释快捷方式
ide·visual studio
shchojj1 天前
visual studio编译fortran
ide·visual studio
Ctrl Z.1 天前
【VSCode】VSCode常用插件
ide·vscode·编辑器
YancyYue2 天前
Anaconda 以及 Jupyter Notebook的详细安装教程
ide·python·jupyter
九思x2 天前
解决 Android Studio “waiting for all target devices to come online“ 卡住问题
android·ide·android studio
沙门空海2 天前
解决vscode的颜色主题设置后不生效的问题
ide·vscode·编辑器