前些天豹哥给大家讲的是,前天豹哥给我们讲的是



  大家好,作者是豹哥,猎豹的豹,犀利哥的哥。今日豹哥给大家讲的是嵌入式开发里的linker文件

  大家好,作者是豹哥,猎豹的豹,犀利哥的哥。明日豹哥给咱们讲的是嵌入式开发里的linker文件

  在前一节课source文件里,豹哥给大家系统地介绍了source文件,source文件是嵌入式工程里首屈一指的input文件,那么还有没有其他类型的input文件?既然豹哥这么提问了,那答案肯定是有啊。明天豹哥要讲的linker文件就属于另一种input文件。
  linker文件顾名思义正是嵌入式工程在链接阶段所要用到的文书,source文件在编写翻译进度完结未来(此时曾经是机械可甄其余二进制机器码数据),需求再通过链接器从而将二进制数据有序组织起来形成最后的二进制可执行文件,该二进制文件最终会被下载进芯片里面非易失性存款和储蓄器里。linker文件正是用来提示链接器怎么着协会编写翻译生成的二进制数据。
  linker文件是跟IDE生死相依的,本文以IAR
EWA安德拉M为例介绍linker文件,其他IDE下的linker文件可触类旁通。

  在前一节课source文件里,豹哥给大家系统地介绍了source文件,source文件是嵌入式工程里金榜题名的input文件,那么还有没有任何项目标input文件?既然豹哥这么提问了,那答案自然是有啊。后天豹哥要讲的linker文件就属于另一种input文件。
  linker文件顾名思义正是嵌入式工程在链接阶段所要用到的文件,source文件在编译进程连成一气之后(此时曾经是机械可辨识的二进制机器码数据),须要再通过链接器从而将二进制数据有序组织起来形成最终的二进制可执行文件,该二进制文件最后会被下载进芯片内部非易失性存款和储蓄器里。linker文件正是用来提示链接器怎样组织编译生成的二进制数据。
  linker文件是跟IDE息息相关的,本文以IAR
EWA福特ExplorerM为例介绍linker文件,其余IDE下的linker文件可触类旁通。

一 、 嵌入式系统中的section

  在讲linker文件在此以前,豹哥必须先跟大家清理3个嵌入式系统中很重庆大学的定义-section。那么哪些是section?我们写的C或许汇编source文件里都以种种应用代码,那个代码按效率能够分成很多体系,比如常量、变量、函数、堆栈等,而同样体系的代码的聚合正是五个section,链接器在链接时协会数据的宗旨单元就是section。那么三个博学多识的嵌入式系统中到底有微微种section呢?下边列出了IA中华V里暗中认可的具备section,那个常见section在延续介绍linker文件里会被提到。

//常见Section
.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.
HEAP                 // Holds the heap used for dynamically allocated data.
.intvec              // Holds the reset vector table
.noinit              // Holds __no_init static and global variables.
.rodata              // Holds constant data.
.text                // Holds the program code.
.textrw              // Holds __ramfunc declared program code.
.textrw_init         // Holds initializers for the .textrw declared section.

//较冷僻Section
.exc.text            // Holds exception-related code.
__iar_tls.$$DATA     // Holds initial values for TLS variables.
.iar.dynexit         // Holds the atexit table.
.init_array          // Holds a table of dynamic initialization functions.
IRQ_STACK            // Holds the stack for interrupt requests, IRQ, and exceptions.
.preinit_array       // Holds a table of dynamic initialization functions.
.prepreinit_array    // Holds a table of dynamic initialization functions.
Veneer$$CMSE         // Holds secure gateway veneers.

//更冷僻Section
.debug               // Contains debug information in the DWARF format
.iar.debug           // Contains supplemental debug information in an IAR format
.comment             // Contains the tools and command lines used for building the file
.rel or .rela        // Contains ELF relocation information
.symtab              // Contains the symbol table for a file
.strtab              // Contains the names of the symbol in the symbol table
.shstrtab            // Contains the names of the sections.

Note:上述section的详细解释请查阅IA福睿斯软件设置目录下\IAR
Systems\Embedded Workbench
xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文书档案里的Section
reference一节。

① 、 嵌入式系统中的section

  在讲linker文件在此之前,豹哥必须先跟大家清理三个嵌入式系统中很重点的定义-section。那么如何是section?大家写的C只怕汇编source文件里都以各样应用代码,那些代码按成效能够分为很多类型,比如常量、变量、函数、堆栈等,而同一档次的代码的集结就是1个section,链接器在链接时组织数量的大旨单元就是section。那么3个压倒元白的嵌入式系统中到底有多少种section呢?上边列出了IACRUISER里默许的有所section,那多少个常见section在再三再四介绍linker文件里会被提到。

//常见Section
.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.
HEAP                 // Holds the heap used for dynamically allocated data.
.intvec              // Holds the reset vector table
.noinit              // Holds __no_init static and global variables.
.rodata              // Holds constant data.
.text                // Holds the program code.
.textrw              // Holds __ramfunc declared program code.
.textrw_init         // Holds initializers for the .textrw declared section.

//较冷僻Section
.exc.text            // Holds exception-related code.
__iar_tls.$$DATA     // Holds initial values for TLS variables.
.iar.dynexit         // Holds the atexit table.
.init_array          // Holds a table of dynamic initialization functions.
IRQ_STACK            // Holds the stack for interrupt requests, IRQ, and exceptions.
.preinit_array       // Holds a table of dynamic initialization functions.
.prepreinit_array    // Holds a table of dynamic initialization functions.
Veneer$$CMSE         // Holds secure gateway veneers.

//更冷僻Section
.debug               // Contains debug information in the DWARF format
.iar.debug           // Contains supplemental debug information in an IAR format
.comment             // Contains the tools and command lines used for building the file
.rel or .rela        // Contains ELF relocation information
.symtab              // Contains the symbol table for a file
.strtab              // Contains the names of the symbol in the symbol table
.shstrtab            // Contains the names of the sections.

Note:上述section的详实表达请查阅IA奥德赛软件设置目录下\IAR
Systems\Embedded Workbench
xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文书档案里的Section
reference一节。

二、解析linker文件

  知道了section概念,那便可开端深入掌握linker文件,什么是linker文件?linker文件是按IDE规定的语法写成的用来提示链接器分配各section在嵌入式系统存款和储蓄器中存放地方的文本。我们都晓得嵌入式系统存款和储蓄器首要分为两类:ROM(非易失性),RAM(易失性),所以相应的这么些section依照存放的存款和储蓄器地方不相同也分为两类天性:readonly,
readwrite。实际上linker文件的工作就是将readonly
section放进ROM,readwrite section放进RAM。
  那么到底该怎么着编写工程的linker文件呢?正如前方所言,linker文件也是有语法的,而且那语法是由IDE钦定的,所以必须求先明白IDE制定的语法规则,linker文件语法规则绝对简便易行,最常用的重庆大学字正是之类九个:

// 动词类关键字
define                // 定义各种空间范围、长度
initialize            // 设置section初始化方法
place in              // 放置section于某region中(具体地址由链接器分配)
place at              // 放置section于某绝对地址处

// 名词类关键字
symbol                // 各种空间范围、长度的标识
memory                // 整个ARM内存空间的标识
region                // 在整个ARM内存空间中划分某region空间的标识
block                 // 多个section的集合块的标识

Note:上述linker语法的详尽解释请查阅IA奥迪Q3软件安装目录下\IAR
Systems\Embedded Workbench
xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文书档案里的The linker
configuration file一节。

  到此处我们早就能够起来喜欢地写linker文件了,是或不是有点十万火急了?来呢,只须求三步走,Let’s
do it。
  此处若是MCU物理空间为:ROM(0x0 – 0x1ffff)、RAM(0x10000000 –
0x1000ffff),豹哥要写的linker供给如下:

  • 停顿向量表必须放置于ROM起首地址0x0,且必须256字节对齐
  • STACK大小为8KB,HEAP大小为1KB,且必须8字节对齐
  • SATCK必须放置在RAM发轫地址0x一千0000
  • 此外section放置在不利的region里,具体空间由链接器自动分配

二、解析linker文件

  知道了section概念,那便可起头深远摸底linker文件,什么是linker文件?linker文件是按IDE规定的语法写成的用来提示链接器分配各section在嵌入式系统存款和储蓄器中存放地点的文书。大家都知晓嵌入式系统存储器主要分为两类:ROM(非易失性),RAM(易失性),所以相应的这个section依据存放的存款和储蓄器地方分歧也分为两类性质:readonly,
readwrite。实际上linker文件的做事便是将readonly
section放进ROM,readwrite section放进RAM。
  那么终归该怎么编写工程的linker文件呢?正如前方所言,linker文件也是有语法的,而且那语法是由IDE钦赐的,所以必供给先理解IDE制定的语法规则,linker文件语法规则相对简便易行,最常用的最重要字正是之类几个:

// 动词类关键字
define                // 定义各种空间范围、长度
initialize            // 设置section初始化方法
place in              // 放置section于某region中(具体地址由链接器分配)
place at              // 放置section于某绝对地址处

// 名词类关键字
symbol                // 各种空间范围、长度的标识
memory                // 整个ARM内存空间的标识
region                // 在整个ARM内存空间中划分某region空间的标识
block                 // 多个section的集合块的标识

Note:上述linker语法的详尽表明请查阅IAPRADO软件安装目录下\IAR
Systems\Embedded Workbench
xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文书档案里的The linker
configuration file一节。

  到此处大家早就足以开首喜欢地写linker文件了,是还是不是有点迫不及待了?来呢,只需求三步走,Let’s
do it。
  此处假如MCU物理空间为:ROM(0x0 – 0x1ffff)、RAM(0x一千0000 –
0x一千ffff),豹哥要写的linker供给如下:

  • 停顿向量表必须放置于ROM初始地址0x0,且务必256字节对齐
  • STACK大小为8KB,HEAP大小为1KB,且必须8字节对齐
  • SATCK必须放置在RAM初步地址0x一千0000
  • 其余section放置在不利的region里,具体空间由链接器自动分配

2.1 定义物理空间

  第②步大家先定义3块互不重叠的上空ROM_region、RAM_region、STACK_region,其中ROM_region对应的是真正的ROM空间,RAM_region和STACK_region组合成真实的RAM空间。

// 定义物理空间边界
define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__   = __ICFEDIT_region_ROM_start__ + (128*1024 - 1);
define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;
define symbol __ICFEDIT_region_RAM_end__   = __ICFEDIT_region_RAM_start__ + (64*1024 - 1);
define symbol __ICFEDIT_intvec_start__     = __ICFEDIT_region_ROM_start__;

// 定义堆栈长度
define symbol __ICFEDIT_size_cstack__      = (8*1024);
define symbol __ICFEDIT_size_heap__        = (1*1024);

// 定义各region具体空间范围
define memory mem with size = 4G;
define region ROM_region    = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];
define region STACK_region  = mem:[from __ICFEDIT_region_RAM_start__ to  __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ - 1];
define region RAM_region    = mem:[from __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__  to __ICFEDIT_region_RAM_end__];

2.1 定义物理空间

  第3步我们先定义3块互不重叠的空间ROM_region、RAM_region、STACK_region,其中ROM_region对应的是真正的ROM空间,RAM_region和STACK_region组合成真实的RAM空间。

// 定义物理空间边界
define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__   = __ICFEDIT_region_ROM_start__ + (128*1024 - 1);
define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;
define symbol __ICFEDIT_region_RAM_end__   = __ICFEDIT_region_RAM_start__ + (64*1024 - 1);
define symbol __ICFEDIT_intvec_start__     = __ICFEDIT_region_ROM_start__;

// 定义堆栈长度
define symbol __ICFEDIT_size_cstack__      = (8*1024);
define symbol __ICFEDIT_size_heap__        = (1*1024);

// 定义各region具体空间范围
define memory mem with size = 4G;
define region ROM_region    = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];
define region STACK_region  = mem:[from __ICFEDIT_region_RAM_start__ to  __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ - 1];
define region RAM_region    = mem:[from __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__  to __ICFEDIT_region_RAM_end__];

2.2 定义section集合

  第壹步是自定义section集合块,细心的情人能够看看左侧花括号里带有的都以上一节介绍的系统暗许section,我们会把拥有同等属性的section集合成到一个block里,方便下一步的停放工作。

// 定义堆栈块及其属性
define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };
define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };

// 定义section集合块
define block Vectors with alignment=256 { readonly section .intvec };
define block CodeRelocate               { section .textrw_init };
define block CodeRelocateRam            { section .textrw };
define block ApplicationFlash           { readonly, block CodeRelocate };
define block ApplicationRam             { readwrite, block CodeRelocateRam, block HEAP };

  有心上人或者会疑窦,为啥要定义CodeRelocate、CodeRelocateRam这两个block?按道理说那三个block对应的section能够独家放进ApplicationFlash和ApplicationRam,那怎么蜀犬吠日?仔细阅读过豹哥前一篇小说source文件详解的心上人一定就通晓答案了,在那篇小说里介绍的startup.c文件里有二个叫init_data_bss()的函数,这么些函数会形成起首化CodeRelocateRam块的机能,它找寻的正是CodeRelocate段名字,那个名字比系统暗中同意的textrw名字看起来更清晰易懂。

2.2 定义section集合

  第3步是自定义section集合块,细心的情人能够见见左侧花括号里带有的都以上一节介绍的系统暗中认可section,大家会把全体同等属性的section集合成到多少个block里,方便下一步的停放工作。

// 定义堆栈块及其属性
define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };
define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };

// 定义section集合块
define block Vectors with alignment=256 { readonly section .intvec };
define block CodeRelocate               { section .textrw_init };
define block CodeRelocateRam            { section .textrw };
define block ApplicationFlash           { readonly, block CodeRelocate };
define block ApplicationRam             { readwrite, block CodeRelocateRam, block HEAP };

  有心上人恐怕会疑窦,为啥要定义CodeRelocate、CodeRelocateRam那四个block?按道理说那八个block对应的section能够独家放进ApplicationFlash和ApplicationRam,那干什么节外生枝?仔细阅读过豹哥前一篇文章source文件详解的恋人肯定就通晓答案了,在那篇文章里介绍的startup.c文件里有三个叫init_data_bss()的函数,那个函数会做到开始化CodeRelocateRam块的机能,它找寻的正是CodeRelocate段名字,那么些名字比系统私下认可的textrw名字看起来更清晰易懂。

2.3 安置section集合

  第叁步正是处理放置那三个section集合块了,在停放集合块在此之前还有initialize
manually语句,为何会有那个话语?依然得结合前面提及的startup.c文件里的init_data_bss()函数来说,那一个函数是开发者自身完毕的data,bss段的开首化,所以那边需求通告IDE,你不需求再帮本身做开端化学工业作了。

// 设置初始化方法
initialize manually { readwrite };
initialize manually { section .data};
initialize manually { section .textrw };
do not initialize   { section .noinit };

// 放置section集合块
place at start of ROM_region { block Vectors };
//place at address mem:__ICFEDIT_intvec_start__ { block Vectors };
place in ROM_region          { block ApplicationFlash };
place in RAM_region          { block ApplicationRam };
place in STACK_region        { block CSTACK };

  当然要是你指望IDE帮你活动初步化data,bss,textrw段,那么能够用下面语句替换initialize
manually语句。

initialize by copy { readwrite, section .textrw };

  设置好初叶化方法后,就是停放section集合块了,放置格局首要有二种,place
in和place
at,前者用于钦命空间块放置(不点名具体地址),后者是钦点具体地址放置。

  至此三个主干的linker文件便马到成功了,是否so easy?

2.3 安置section集合

  第1步正是处理放置那个section集合块了,在停放集合块在此以前还有initialize
manually语句,为啥会有那一个话语?照旧得结合前面提及的startup.c文件里的init_data_bss()函数来说,那几个函数是开发者本人达成的data,bss段的发轫化,所以那里须求文告IDE,你不须要再帮笔者做开始化学工业作了。

// 设置初始化方法
initialize manually { readwrite };
initialize manually { section .data};
initialize manually { section .textrw };
do not initialize   { section .noinit };

// 放置section集合块
place at start of ROM_region { block Vectors };
//place at address mem:__ICFEDIT_intvec_start__ { block Vectors };
place in ROM_region          { block ApplicationFlash };
place in RAM_region          { block ApplicationRam };
place in STACK_region        { block CSTACK };

  当然倘使你期望IDE帮你活动开首化data,bss,textrw段,那么可以用下边语句替换initialize
manually语句。

initialize by copy { readwrite, section .textrw };

  设置好开端化方法后,正是停放section集合块了,放置形式重要有二种,place
in和place
at,前者用于钦点空间块放置(不点名具体地址),后者是钦赐具体地址放置。

  至此叁当中坚的linker文件便马到成功了,是或不是so easy?

番外一、自定义section

  有耐心看到此间的对象,豹哥必须得放个大招奖励一下,后面讲的都是怎么处理连串暗中认可段,那么有没有大概在代码里自定义段呢?想象一下你有诸如此类的要求,你须求在您的应用里开发一块1KB的可更新的数据区,你想把这些数据区钦点到地址0x18000

  • 0x183ff的限量内,你需求在应用里定义4 Byte的只读config
    block常量指向这一个可更新数据区首地址(这段config
    block只会被外表debugger恐怕bootloader更新),如何做到?

    // C文件中
    /////////////////////////////////////////////////////
    // 用@操作符钦赐变量myConfigBlock[4]放进自定义.myBuffer section
    const uint8_t myConfigBlock[4] @ “.myBuffer” = {0x00, 0x01, 0x02, 0x03};

    // Linker文件中
    /////////////////////////////////////////////////////
    // 自定义钦赐的mySection_region,并把.myBuffer放到那几个region
    define region mySection_region = mem:[from 0x0x18000 to 0x183ff];
    place at start of mySection_region { readonly section .myBuffer };

  上边做到了将代码中的常量放入自定义段?,那么怎么将代码中的函数也放进自定义段呢?继续看下来

// C文件中
/////////////////////////////////////////////////////
// 用#pragma location指定函数myFunction()放进自定义.myTask section
#pragma location = ".myTask"
void myFunction(void)
{
    __NOP();
}

// Linker文件中
/////////////////////////////////////////////////////
// 把.myTask放到mySection_region
place in mySection_region { readonly section .myTask };

  看起来大功告成了,最终还有七个注意事项,假设myConfigBlock在代码中尚无被引述,IDE在链接的时候恐怕会忽视这一个变量(IDE认为它没用,所以优化了),那么怎么让IDE强制链接myConfigBlock呢?IAKoleos留了个后门,在options->Linker->Input选项卡中的Keep
symbols输入框里填入你想强制链接的对象名(注意是代码中的对象名,而非linker文件中的自定义段名)即可。

Note:关于番外内容的越来越多细节请查阅IA奔驰M级软件设置目录下\IAR
Systems\Embedded Workbench
xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文书档案里的Pragma
directives一节。

  至此,嵌入式开发里的linker文件豹哥便介绍完毕了,掌声在哪个地方~~~

番外一、自定义section

  有耐心看到那里的朋友,豹哥必须得放个大招奖励一下,前边讲的都是怎么处理系统私下认可段,那么有没有大概在代码里自定义段呢?想象一下您有如此的要求,你须求在你的施用里开发一块1KB的可更新的数据区,你想把那一个数据区钦赐到地址0x1七千

  • 0x183ff的限制内,你要求在选取里定义4 Byte的只读config
    block常量指向这些可更新数据区首地址(那段config
    block只会被表面debugger或然bootloader更新),怎么做到?

    // C文件中
    /////////////////////////////////////////////////////
    // 用@操作符钦赐变量myConfigBlock[4]放进自定义.myBuffer section
    const uint8_t myConfigBlock[4] @ “.myBuffer” = {0x00, 0x01, 0x02, 0x03};

    // Linker文件中
    /////////////////////////////////////////////////////
    // 自定义钦命的mySection_region,并把.myBuffer放到这几个region
    define region mySection_region = mem:[from 0x0x18000 to 0x183ff];
    place at start of mySection_region { readonly section .myBuffer };

  上边做到了将代码中的常量放入自定义段?,那么怎么将代码中的函数也放进自定义段呢?继续看下去

// C文件中
/////////////////////////////////////////////////////
// 用#pragma location指定函数myFunction()放进自定义.myTask section
#pragma location = ".myTask"
void myFunction(void)
{
    __NOP();
}

// Linker文件中
/////////////////////////////////////////////////////
// 把.myTask放到mySection_region
place in mySection_region { readonly section .myTask };

  看起来旗开马到了,最终还有二个注意事项,假如myConfigBlock在代码中并未被引述,IDE在链接的时候恐怕会忽视这几个变量(IDE认为它没用,所以优化了),那么怎么让IDE强制链接myConfigBlock呢?IA奔驰M级留了个后门,在options->Linker->Input选项卡中的Keep
symbols输入框里填入你想强制链接的对象名(注意是代码中的对象名,而非linker文件中的自定义段名)即可。

Note:关于番外内容的更加多细节请查阅IA陆风X8软件安装目录下\IAR
Systems\Embedded Workbench
xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文书档案里的Pragma
directives一节。

  至此,嵌入式开发里的linker文件豹哥便介绍达成了,掌声在哪里~~~

相关文章

网站地图xml地图