0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

从文件角度看Cortex-M开发—链接文件

冬至子 来源:痞子衡嵌入式 作者:痞子衡 2023-12-13 15:17 次阅读

一、 嵌入式系统中的section

在讲linker文件之前,痞子衡必须先跟大家理清一个嵌入式系统中很重要的概念-section。那么什么是section?我们写的C或者汇编source文件里都是各种应用代码,这些代码按功能可以分为很多种类,比如常量、变量、函数、堆栈等,而相同类型的代码的集合便是一个section,链接器在链接时组织数据的基本单元便是section。那么一个典型的嵌入式系统中到底有多少种section呢?下面列出了IAR里默认的所有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的详细解释请查阅IAR软件安装目录下IAR SystemsEmbedded Workbench xxxarmdocEWARM_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文件语法规则相对简单,最常用的关键字就是如下8个:

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

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

Note:上述linker语法的详细解释请查阅IAR软件安装目录下IAR SystemsEmbedded Workbench xxxarmdocEWARM_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起始地址0x10000000
  • 其余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.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.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?

番外一、自定义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呢?IAR留了个后门,在options->Linker->Input选项卡中的Keep symbols输入框里填入你想强制链接的对象名(注意是代码中的对象名,而非linker文件中的自定义段名)即可。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储器
    +关注

    关注

    38

    文章

    7151

    浏览量

    162003
  • 嵌入式系统
    +关注

    关注

    40

    文章

    3434

    浏览量

    128229
  • RAM
    RAM
    +关注

    关注

    7

    文章

    1322

    浏览量

    113708
  • IAR
    IAR
    +关注

    关注

    5

    文章

    319

    浏览量

    36290
  • Cortex-M
    +关注

    关注

    2

    文章

    224

    浏览量

    29574
收藏 人收藏

    评论

    相关推荐

    基于Cortex-M处理器做产品开发为什么受欢迎

    基于Cortex-M处理器做产品开发为什么受欢迎虽然Cortex-M系列处理器有非常多的特性,但是很容易使用,差不多所有的开发都可以用像C语言这样的高级编程语言。虽然基于
    发表于 08-27 16:11

    基于Cortex-M处理器做产品开发受欢迎的原因在这里

    虽然Cortex-M系列处理器有非常多的特性,但是很容易使用,差不多所有的开发都可以用像C语言这样的高级编程语言。 虽然基于Cortex-M系列处理器产品都大不相同(例如,有不同大小的内存
    发表于 07-04 03:25

    如何选择正确的Cortex-M处理器?

    Cortex-M处理器指令集2.1指令集简介大多数情况下,应用程序代码可以用C或其他高级语言编写。但是,对Cortex-M 处理器支持指令集的基本了解有助于开发者针对具体应用选择合适的Cor
    发表于 10-22 08:16

    基于Cortex-M原型系统建立的Cortex-M3 DesignStart原型

    采用Cortex-M原型系统建立Cortex-M3 DesignStart原型为什么选择Cortex-M原型系统?
    发表于 02-01 06:56

    Cortex-M入门资料和书籍分享

    Cortex-M入门在网上博客逛论坛也是能学到些东西的,但通常是知识点,不能构成知识面。书籍通常会系统性地讲述,通过书籍可以建立起知识面,只有建立起了知识面才算是掌握。推荐两本书:《ARM
    发表于 07-01 09:38

    分享一种5V供电的Cortex-M微控制器

    5V供电的Cortex-M微控制器,这里列出了部分5V供电的Cortex-M微控制器系列,点击链接打开官网。CypressCypress FM0+ Family of 32-bit ARM
    发表于 07-16 06:17

    cortex-m下各种微架构的区别是什么?

    cortex-m单片机在arm产品中的位置是哪里?cortex-m 单片机的类别有哪些?cortex-m下各种微架构的区别是什么?
    发表于 11-04 06:00

    ARM Cortex-M堆栈机制介绍

      大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是ARM Cortex-M堆栈机制。  今天给大家分享的这篇依旧是2016年之前痞子衡写的技术文档,花了点时间重新编排了一下
    发表于 12-16 06:26

    ARM Cortex-M内核的相关资料推荐

    8代产品,除了上一篇 《Cortex-M功能模块差异》 介绍过的CM0/CM0+、CM1、CM3、CM4、CM7,还有主打安全特性的CM23、CM33、CM35P。1.Cortex-M...
    发表于 12-27 07:21

    STM32时在新建工程下载程序的时候出现Could not stop Cortex-M device咋办

    Cortex-M device!Please check the JTAG cable.”问题首先弹出然后弹出这个。网上查找原因,尝试了n多种提到的解决办法都失败了!于是怀疑是不是文件哪里被更改...
    发表于 01-25 08:31

    ARM Cortex-M 开发实战指南入门篇(二)

    1、集成开发环境和非集成开发环境介绍嵌入式开发的第一步就是搭建开发环境,不同的硬件平台可能所需的环境还不太一样,而且还有可能出现千奇百怪的错误,本讲将讲解ARM
    发表于 04-19 17:24

    介绍Cortex-A和Cortex-M的TrustZone之间的差异

    相信关注安全和嵌入式的开发者对TrustZone都不陌生,最近看到有网友在问Cortex-A和Cortex-M的TrustZone之间的差异,我们来简单介绍下。Arm在2003年的Armv6开始
    发表于 07-13 14:45

    如何使用Ozone分析Cortex-M故障?

    故障,示例可以在此处下载。在下图中,示例应用程序已下载到SEGGER Cortex-M Trace开发板板。使用的仿真器是 J-Trace PRO V2。程序已运行至_NoThumbFunc()中
    发表于 09-23 11:26

    文件角度,了解Cortex-M开发(二)

    衡这么提问了,那答案肯定是有啦。今天痞子衡要讲的 linker 文件就属于另一种 input 文件。 linker 文件顾名思义就是嵌入式工程在链接阶段所要用到的
    的头像 发表于 11-25 17:59 327次阅读

    文件角度了解Cortex-M开发(1)

    。 尽管在平常开发中,我们都只会关注自己创建的 .c/.h/.s 源文件,但实际上我们不知不觉中也跟很多不是我们创建的源文件在打交道,那么问题来了,一个完整的嵌入式工程(以基于 ARM Cor
    的头像 发表于 10-30 10:44 319次阅读