小猫爪:嵌入式小知识07-MCUXpresso GCC ld链接文件解析-链接代码至RAM

  • 1 前言
  • 2 基本语法
  • 3 实例解析
  • 4 链接代码至RAM
    • 4.1 链接自定义section至RAM
    • 4.2 链接自定义.o文件至RAM
    • 4.3 链接全部代码至RAM

1 前言

我们已经简单学习完IAR和MDK链接文件的基本用法,接下来我们再简单的对ld链接文件做一下介绍。

在这里之前我们需要了解输入段和输出段,输入段就是我需要给链接器的信息,包括各种section,加载地址,链接地址,输出段就是链接器根据输入段的各种信息条件之后输出的东西。(section就是段,段就是section,有时候也叫它节,都可以。)

(注:不管是IAR的icf,还是MDK的scf,还有GNU的ld,大家的功能都是一样的,给链接器提供地址信息,让链接器按照这些地址信息将编译器编出来的section和地址一一对应生成最后MCU需要的可执行文件。所以很多东西都是想通的,唯一不同的是三者提供地址信息的方式不同而已。)

2 基本语法

ld链接文件的语法就相对比较复杂了,各种花里胡哨的符号,各种修饰符,反正就是很烦的玩意,建议大家不要深究,不值得,除非你想成为这一行的大佬级别的人物,关于ld链接文件的相关语法介绍大家可以参考文章《LD说明文档–3.LD链接脚本》,这写的很全了。

下面我就以土话介绍几个最常见的语句。

① ENTRY(Reset_Handler):这是一个设置入口函数的命令,也就是说启动后,PC最先指向Reset_Handler,然后运行它。

② MEMORY:定义地址空间,我们可在在里面定义一段段的空间,相当于IAR中的region,也就是待链接地址空间,可以将section链接到这些空间。

MEMORY
{my_mem        (RX)  : ORIGIN = 0x60000000, LENGTH = 0x00001000
}

上面语句的意思就是定义了一个region, 起始地址是0x60000000,大小为0x00001000。

③ SECTIONS:定义输出段,相当于是一个加载空间,我们会往这个加载空间里面慢慢的放section,同时给这些section链接地址空间,同时里面会放各种输出段描述和输入段描述。

SECTIONS
{.text : { *(.text) }
}

上面语句的意思就是定义了一个加载空间,然后把.text段放进去了。其中外面的.text是输出段描述,意思就是这一部分输出的数据为text类型,大括号里面的.text为输入段描述,意思就是输入的数据类型为text, 其中*则是通配符。所以这句话的意思就是我要把所有文件中的.text类型的数据放在这里,链接完成后输出的数据类型也是text类型。

④ . :这个 . 代表的是位置计数器的值,我们可以把这个值取出来,也可以改变这个值,换句话说,这个值大有用处,我们可以通过它获取记录很多信息,后面碰到后在慢慢体会。

除了上面三个,还有很多零碎的,下面我们结合一个完整的实例给大家做分解。

3 实例解析

接下来就以一个RT1050芯片的工程链接文件做分析解释。大家自行看着解释对照着语法介绍,你就会有一个非常新的了解。

ENTRY(Reset_Handler)   //设置入口地址HEAP_SIZE  = DEFINED(__heap_size__)  ? __heap_size__  : 0x0400; //设置堆大小
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x0400; //设置栈大小MEMORY     //定义链接地址空间
{/*定义只读空间m_interrupts ,起始地址0x00000000, 大小0x00000400 */m_interrupts   (RX)  : ORIGIN = 0x00000000, LENGTH = 0x00000400 /*定义只读空间m_text,起始地址0x00000400, 大小0x0001FC00*/m_text         (RX)  : ORIGIN = 0x00000400, LENGTH = 0x0001FC00/*定义读写空间m_data,起始地址0x20000000, 大小0x00020000*/m_data         (RW)  : ORIGIN = 0x20000000, LENGTH = 0x00020000/*定义读写空间m_data2s ,起始地址0x20200000, 大小0x00000400*/m_data2        (RW)  : ORIGIN = 0x20200000, LENGTH = 0x00040000
}SECTIONS //定义输出段
{__NCACHE_REGION_START = ORIGIN(m_data2); //将m_data2的起始地址赋值给__NCACHE_REGION_START __NCACHE_REGION_SIZE  = 0;//__NCACHE_REGION_SIZE赋值0.interrupts : //输出段描述,表示这一段是中断向量表{__VECTOR_TABLE = .; //把当前位置计数器的值赋值给__VECTOR_TABLE ,位置计数器的值一开始默认为0__Vectors = .;//把当前位置计数器的值赋值给__VECTOR_TABLE ,位置计数器的值一开始默认为0. = ALIGN(4); //这句话不是再给位置计数器赋值,而是给它增加限制条件,表示其增加一次增加4,在内存中的表现即为4字节对齐KEEP(*(.isr_vector))//放置所有文件中的.isr_vector section,*是通配符,表示所有,就跟我们搜索文件使用*时一样的。使用KEEP的意思就是告诉编译器这段数据非常重要,不要把它当成垃圾优化掉了. = ALIGN(4);} > m_interrupts  //将这一输出段链接至m_interrupts区域处,所以中断向量表的链接地址就是m_interrupts的起始地址.text :   //定义输出段,就是一个名字,大家可以自己取{. = ALIGN(4); //4字节对齐*(.text)                 /*放置所有文件的.text段(code) */*(.text*)                /*放置所有文件的.text*段(code) */*(.rodata)               /*放置所有文件的.rodata段(constants, strings, etc.) */*(.rodata*)              /*放置所有文件的.rodata*段(constants, strings, etc.) */*(.glue_7)               /*同上*/*(.glue_7t)              /*同上*/*(.eh_frame)             /*同上*/KEEP (*(.init))          KEEP (*(.fini)). = ALIGN(4);} > m_text       //将这一输出段链接至m_text区域处.ARM.extab :   //定义输出段,就是一个名字,大家可以自己取{*(.ARM.extab* .gnu.linkonce.armextab.*) //} > m_text   //将这一输出段链接至m_text区域处.ARM :   //定义输出段,就是一个名字,大家可以自己取{__exidx_start = .;*(.ARM.exidx*)  __exidx_end = .;} > m_text   //将这一输出段链接至m_text区域处.ctors :    //定义输出段,就是一个名字,大家可以自己取{__CTOR_LIST__ = .;KEEP (*crtbegin.o(.ctors))  //放置*crtbegin.o中的.ctors段,并保证不被优化KEEP (*crtbegin?.o(.ctors)) //同上/*下面的语句中出现了EXCLUDE_FILE函数,这个函数的意思就是把括号里面的除外,意思就是说放置所有文件除了*crtend?.o *crtend.o文件的 .ctors段,因为在上面已经放置过了*/KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)) /*下面的语句中出现了SPORT函数,SOPT是SORT_BY_NAME的别名,意思是放置.ctors.*段的时候,按照名字的排列顺序来放置*/KEEP (*(SORT(.ctors.*)))  KEEP (*(.ctors))__CTOR_END__ = .;} > m_text   //将这一输出段链接至m_text区域处.dtors :   //定义输出段,就是一个名字,大家可以自己取{__DTOR_LIST__ = .;KEEP (*crtbegin.o(.dtors))  KEEP (*crtbegin?.o(.dtors))KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors))KEEP (*(SORT(.dtors.*)))KEEP (*(.dtors))__DTOR_END__ = .;} > m_text.preinit_array :   //定义输出段,就是一个名字,大家可以自己取{/*下面的出现了PROVIDE_HIDDEN, 意思就是后面的这个符号__preinit_array_start 只能在链接器中被使用,外部文件是不能调用的,与它相反的还有PROVIDE, PROVIDE表示这个符号可以被外部调用,而且如果外部文件也定义了同样的符号也不会发生冲突,优先使用外部定义值,后面会出现很多PROVIDE*/PROVIDE_HIDDEN (__preinit_array_start = .);KEEP (*(.preinit_array*))PROVIDE_HIDDEN (__preinit_array_end = .);} > m_text.init_array :   //定义输出段,就是一个名字,大家可以自己取{PROVIDE_HIDDEN (__init_array_start = .);KEEP (*(SORT(.init_array.*)))KEEP (*(.init_array*))PROVIDE_HIDDEN (__init_array_end = .);} > m_text.fini_array :   //定义输出段,就是一个名字,大家可以自己取{PROVIDE_HIDDEN (__fini_array_start = .);KEEP (*(SORT(.fini_array.*)))KEEP (*(.fini_array*))PROVIDE_HIDDEN (__fini_array_end = .);} > m_text__etext = .;   __DATA_ROM = .; __VECTOR_RAM = ORIGIN(m_interrupts);__RAM_VECTOR_TABLE_SIZE_BYTES = 0x0;.data : AT(__DATA_ROM)  //AT的作用就是给当前输出段指定加载地址{. = ALIGN(4);__DATA_RAM = .;__data_start__ = .;     *(m_usb_dma_init_data)*(.data)               *(.data*)              KEEP(*(.jcr*)). = ALIGN(4);__data_end__ = .;        } > m_data__NDATA_ROM = __DATA_ROM + (__data_end__ - __data_start__);.ncache.init : AT(__NDATA_ROM){__noncachedata_start__ = .;  *(NonCacheable.init). = ALIGN(4);__noncachedata_init_end__ = .;   } > m_data. = __noncachedata_init_end__;.ncache :{*(NonCacheable). = ALIGN(4);__noncachedata_end__ = .;   } > m_data__DATA_END = __NDATA_ROM + (__noncachedata_init_end__ - __noncachedata_start__);text_end = ORIGIN(m_text) + LENGTH(m_text);ASSERT(__DATA_END <= text_end, "region m_text overflowed with text and data")/* Uninitialized data section */.bss :{/* This is used by the startup in order to initialize the .bss section */. = ALIGN(4);__START_BSS = .;__bss_start__ = .;*(m_usb_dma_noninit_data)*(.bss)*(.bss*)/*放置COMMON块,关于COMMON块是链接器为弱符号所制定的编译解决方案,本质上其实就是bass段,感兴趣的小伙伴可以自行去搜一搜*/*(COMMON)  . = ALIGN(4);__bss_end__ = .;__END_BSS = .;} > m_data.heap :{. = ALIGN(8);__end__ = .;PROVIDE(end = .);__HeapBase = .;. += HEAP_SIZE;__HeapLimit = .;__heap_limit = .;} > m_data.stack :{. = ALIGN(8);. += STACK_SIZE;} > m_data __StackTop   = ORIGIN(m_data) + LENGTH(m_data);__StackLimit = __StackTop - STACK_SIZE;PROVIDE(__stack = __StackTop);.ARM.attributes 0 : { *(.ARM.attributes) }/*ASSERT表示断言,跟C中的assert功能是一摸一样的*/ASSERT(__StackLimit >= __HeapLimit, "region m_data overflowed with stack and heap")
}

好了,到这里,一些最常见的命令大家都搞清楚了。

4 链接代码至RAM

接下我们以RT1050来实战一下学习成果。IDE为NXP的MCUXpresso,我们可以通过Properties->C/C++ Build->Setting中指定我们自己的ld链接文件:

4.1 链接自定义section至RAM

使用下面语句自定义section。

__attribute__((section(".my_code_section")))
void my_code_fun(void)
{PRINTF("hello world.\r\n");
}

如果我们不指定链接地址,查看map文件,发现它的链接地址在FLASH中,如下:

接下来在ld链接文件中给这个section指定链接地址至RAM:

MEMORY
{............/*定义我们想链接的region,起始地址为0x2001F000,也就是将其链接到RT的OCRAM(RAM),定义自己的地址空间时,避免两个空间有重合的区域*/MY_RAM (rx) : ORIGIN = 0x20210000, LENGTH = 0x10000
}SECTIONS
{............//自定义输出段,并4字节对齐,注意此处的名字不要和其他输出段名字重复.my_text : ALIGN(4)     {*(.my_code_section)      . = ALIGN(4);  } > MY_RAM
}

链接后查看map文件,发现它的链接地址已经在我们指定的RAM中,如下:

运行代码查看跳转地址:

4.2 链接自定义.o文件至RAM

在ld链接文件中使用以下代码将该文件编译后的fsl_gpio.o文件链接至我们指定的RAM空间。

MEMORY
{............/*定义我们想链接的region,起始地址为0x2001F000,也就是将其链接到RT的DTCM(RAM)*/MY_RAM (rx) : ORIGIN = 0x20210000, LENGTH = 0x10000
}SECTIONS
{............//自定义输出段,并4字节对齐,注意此处的名字不要和其他输出段名字重复.text : ALIGN(4){*(EXCLUDE_FILE(*fsl_gpio.o) .text*)   //放置所有文件除了*my_code.c的text*段*( .rodata .rodata.* .constdata .constdata.*). = ALIGN(4);} > BOARD_FLASH /*将所有文件除了*my_code.c的text*段链接至FLASH*/.my_text : ALIGN(4){*fsl_gpio.o(.text*)  //放置*my_code.c的text*段. = ALIGN(4);} > MY_RAM    //将*my_code.c的text*段链接至MY_RAM
}

链接后查看map文件,发现fsl_gpio.c中的函数的链接地址已经在我们指定的RAM中,如下:

运行代码查看跳转地址:

注意:ld链接文件的语法非常的严格,所以每一个文件的section只能被链接一次,如果链接多次则会出现链接错误,但是编译器却不报错。所以一定要巧妙地应用EXCLUDE_FILE函数来隔离相关的section。)

4.3 链接全部代码至RAM

使用以下代码将所有代码链接至我们指定的RAM中:

MEMORY
{............/*定义我们想链接的region,起始地址为0x2001F000,也就是将其链接到RT的DTCM(RAM)*/MY_RAM (rx) : ORIGIN = 0x20210000, LENGTH = 0x10000
}SECTIONS
{............//自定义输出段,并4字节对齐,注意此处的名字不要和其他输出段名字重复.text : ALIGN(4){*(.text*)  //将所有代码链接至RAM*(.rodata .rodata.* .constdata .constdata.*)//将所有只读性质的数据链接至RAM. = ALIGN(4);} > MY_RAM    //将所有代码段链接至RAM
}

在这里我们可以使用 (.text)链接所有代码的原因是NXP的SDK已经在MCUXpresso中将启动相关的代码自定义了一个叫做.after_vectors*( * 表示通配符)的段,然后将其链接至了FLASH中来保证MCU的正常启动和代码搬运,如下图为.after_vectors*段的一部分:

链接后查看map文件,发现除了除了有关启动代码之外代码的链接地址已经在我们指定的RAM中,如下:

运行代码查看跳转地址:

注意1:对于MCU来说,我们不能将所有的代码的执行域都放入至RAM中,换句话说,其实entry入口地址必须得是FLASH,因为必须运行完MCUXpresso的初始化函数,该初始化函数将完成相关代码和数据的拷贝之后,RAM中才会有代码,之后再跳转进入RAM中运行, 所以我们至少需要将与MCUXpresso的初始化相关的section链接至FLASH,不然是无法正常启动的。如果想完全将代码放入RAM中运行,则是可以直接将代码下载至RAM中,或者像RT1050这种支持non-XIP启动的MCU,则需要修改头信息,让BootROM实现所有代码的拷贝,类似于non-XIP启动。)

注意2:如果想在正常启动过程中将中断向量表链接至RAM,则需要注意两个点,第一点就是将中断向量链接至RAM的同时还要保证芯片的正常启动,我们可以通过将与启动相关的部分中断向量表拷贝副本链接至FLASH保证正常启动,再将完整的中断向量链接至RAM。第二点则是需要通过修改SCB->VTOR寄存器改变中断向量映射地址。)

END

小猫爪:嵌入式小知识07-MCUXpresso GCC ld链接文件解析-链接代码至RAM相关推荐

  1. 小猫爪:嵌入式小知识06-KEIL scf分散加载文件解析-链接代码至RAM

    小猫爪:嵌入式小知识06-KEIL scf分散加载文件解析-链接代码至RAM 1 前言 2 执行域和加载域 2 相关语法解析 3 实例解说 4 链接代码至RAM运行 4.1 链接单个section至R ...

  2. 嵌入式C语言编程中经验教训总结(二)LDS链接文件解析

    目录 为什么要用到lds链接脚本? lds文件语法结构 lds文件实例解析 好的朋友已经做过好多年的arm开发,却对底层的编译链接逻辑很少接触,主要原因在于现在大多数应用层的开发不需要从裸板开始,工程 ...

  3. 小猫爪:i.MX RT1050学习笔记20-安全启动4-实现HAB签名和HAB加密

    小猫爪:i.MX RT1050学习笔记20-安全启动4-实现HAB签名和HAB加密 1 前言 2 准备工作 2.1 下载Flashloader 2.2 下载CST 2.3 安装OpenSSL 3 实战 ...

  4. 小猫爪:i.MX RT1050学习笔记16-启动流程(Boot Flow)

    小猫爪:i.MX RT1050学习笔记16-启动流程(Boot Flow) 1 前言 2 bootROM的流程 2.1低功耗唤醒启动 2.2 正常启动 2.2.1 Serial Download 2. ...

  5. 小猫爪:嵌入式小知识01-存储器

    小猫爪:嵌入式小知识01-存储器 1 前言 2 存储器的种类 2.1 易失性存储器 2.1.1 SRAM 2.1.2 DRAM 2.1.3 SRAM和DRAM的对比 2.2 非易失性存储器 2.2.1 ...

  6. 小猫爪:嵌入式小知识10-I2S,TDM,PCM等音频格式详解

    小猫爪:嵌入式小知识10-I2S,TDM,PCM等音频格式详解 1 前言 2 I2S 3 Codec模式(左/右对齐) 3.1 左对齐(MSB对齐) 3.2 右对齐(LSB对齐) 4 DSP模式 5 ...

  7. 小猫爪:嵌入式小知识09-LCD Parallel RGB接口(转载)

    小猫爪:嵌入式小知识09-LCD Parallel RGB接口(转载) 1 前言 2 转载内容 2.1 液晶显示器 2.2 显示器的基本参数 2.3 显存 2.4 Parallel RGB接口定义 2 ...

  8. 小猫爪:汽车电子小知识02- ISO14229-1(UDS)简介

    小猫爪:汽车电子小知识02- ISO14229-1(UDS服务)简介 1 前言 2 服务数据格式 2.1 无子功能的格式 2.2 有子功能的格式 3 物理寻址和功能寻址 4 服务简介 4.1 诊断会话 ...

  9. 小猫爪:汽车电子小知识01- ISO15765(UDS on CAN)详解

    小猫爪:汽车电子小知识01- ISO15765(UDS on CAN)简介 1 前言 2 ISO15765和OSI的联系 3 ISO15765-2 网络层定义 3.1 拆包和组包(SDU和PDU) 3 ...

最新文章

  1. 矩阵为奇异工作精度_外积与复合矩阵,特征值/奇异值的乘积型受控,Hodge对偶与伴随矩阵...
  2. 工程师也是主播界“扛把子”,学员抱紧大腿痴痴等候百度AI快车道下期到来...
  3. 重装linux之后gcc等下载不了,Redhat linux下安装gcc
  4. python与html5搭建聊天室_html5 websocket 新版协议聊天室 服务端(python版)
  5. dataframe scala 修改值_python – 使用Scala的API替换DataFrame的值
  6. Tensorflow一些常用基本概念与函数(4)
  7. 没有别家钱多,没有别家人多,小型培训机构招生怎么做?
  8. mysql配置文件my.cnf详解
  9. 收音机磁棒天线4根接法_重磅彩蛋:DE1103收音机不用打磨就能用拉杆/外接天线收中波...
  10. ip=request.servervariables(Remote_Addr)获得ip显示::1
  11. 【数学分析笔记03】上确界和下确界
  12. linux opera java,Ubuntu Linux下的 Opera 安装
  13. 小程序轮播图与图片处理
  14. java web 常见面试题_2019最新Javaweb面试题及答案
  15. 万能密码 php,PHP万能密码
  16. C# Word控件 文档保护
  17. java vad_(转载)静音检测VAD算法
  18. Java中线程基础-线程间的共享-volatile
  19. 又一所“省会大学”,来了!
  20. VRTK插件详解四:部分自带案例分析

热门文章

  1. 单点登录CAS协议详解
  2. 非正常卸载后重装软件无法安装的问题
  3. VC++使用开源的zip.cpp和unzip.cpp实现压缩包的创建与解压(附源码)
  4. 【理论了解】接口测试简介以及接口测试用例设计思路
  5. 通信原理与MATLAB(十一):QAM的调制解调
  6. 啊哈C语言 第4章 重量级选手登场(第16-23讲)
  7. 洛谷P4245:【模板】MTT (CRT+三模数NTT)
  8. 2021年中国客户端游戏市场实际销售收入达588.00亿元,同比增长5.15%[图]
  9. java google Thumbnails 图片处理
  10. 【 C++ 】哈希表底层结构剖析