ARM-GCC-LD脚本

2019-12-03来源: eefocus关键字:ARM  GCC  LD脚本

从以前的经验,链接脚本是嵌入式开发,单片机开发相当重要的一个东西。它完成的工作是做PC机软件的同志们不用关心的,但是也是很复杂的一项工作。总结来看链接脚本要告诉连接器


1:输出什么


2:输入是什么,那么obj文件


3:要用什么库,库放在什么地方


4:内存分布地址


5:提供启动代码一些全局地址变量

 


一般来说链接脚本需要搞清楚这几样事情后才能编写,那arm-gcc-ld的脚本也一定要实现这些功能。对于大多数的链接器来说,对于简单的项目不需要脚本,只是使用命令参数就可以完成了。


MEMORY:


它是用来补充SECTIONS命令的,用来描述目标CPU中可用的内存区域。它是可选的,如果没有这个命令,LD会认为SECTIONS描述的相邻的内存块之间有足够可用的内存。其实很容易理解但是却很少用(我没用过,嘿嘿),在SECTIONS中每个段的分布都没有考虑ARM能够寻址的地址中,ROM,RAM,FLASH是不是连续的。如果不是连续的怎么办?MEMORY就是设置各个区的起始位置,大小,属性的命令,在一个脚本中只能有一个。


举一个例子:


如果你的板子有两段存储,而且很遗憾的是不是连续的,一段是从0x0开始,大小为256K,另一段是从0x40000000开始的大小为4M,你可以在脚本中写入如下的代码来描述你的板子的内存信息。


1 MEMORY

2 {

3       rom (rx)  : ORIGIN = 0, LENGTH = 256K

4       ram (!rx) : org = 0x40000000, l = 4M  

5 }


很显然下面的一句用了简略标签,这并不重要,重要的是怎样使用它,不过在那之前还是想再仔细研究下MEMORY命令的细节。


MEMORY命令的语法是:


MEMORY

{

      name (attr) : ORIGIN = origin, LENGTH = len

      ...

}


name:一个用户定义的名字,Linker将在内部使用它,所以别把它和SECTIONS里用到的文件名,段名等搞重复了,它要求是独一无二的。


 


attr  :如同它的名字一样,这是内存段的属性描述。


`R'    Read-only sections.

`W'   Read/write sections.

`X'    Sections containing executable code.

`A'    Allocated sections.

`I'     Initialized sections.

`L'    Same as I.

`!'    Invert the sense of any of the following attributes.


别怪我懒,确实不想再打一遍这个的翻译,而且很久没用英文的俺翻译的估计也不好。总体来说,它是属性就行了。 


ORIGIN:这是起始地址


LENGTH:段长


由此可见上面那段实例显示ROM和RAM的明确位置,而且还显示了他们的只能,一个存代码,一个除了存代码什么都可以。


 


接着就是老问题了,怎么用这个。如果仅仅是规定我的板子有什么特点又不用的话那就是脱了裤子放屁,多此一举。这个问题留在SECTIONS命令中回顾。


 


SECTIONS:


它是脚本文件中最重要的元素,不可缺省。它的作用就是用来描述输出文件的布局。


SECTIONS命令的语法:


SECTIONS


{

       ...

      secname start BLOCK(align) (NOLOAD) : AT ( ldadr )

      { contents } >region :phdr =fill

      ...

}


这么多的参数中,只有secname和contents是必须的,其他都是可选的参数。也就说它的最简单的格式就是:


SECTIONS


{

       ...

      secname  : {


                 contents


      } 

      ...

}


但是注意:secname前后的两个空格是必须的,否则就是不合法输入。


secname定义了段名,其实最开始就忽略了一个重要的因素,arm-gcc-ld脚本需要描述输入和输出,而表面上一看却看不出来什么是输入什么事输入,其实secname和contents就是描述这两个信息的参数。secname是输出文件的段,即输出文件有哪些段,而contents就是描述输出文件的这个段从哪些文件里抽取而来。明确这个了就不难理解为什么SECTIONS命令什么都可以不要就是不能没有这两个参数了。


secname:定义段,但是别以为定义的段一定要是教科书上写的.data,.text这些科班的必须品,你甚至可以创建一个段来放一个美女的图片。


contents:它的语法开始复杂起来了,但是你可以简单的把输入文件写到代码中:


                   .data : { main.o led.o}


但是结果被列的目标文件中所有的代码都被链接到.data中去了,显然不大符合我们的要求啊。那么还有一种写法:


               .data : {


                                 main.o(.data)


                                 main.o(.text) // 也可以这样写 main.o(.data  .text)或者main.o(.data , .text)


                                led.o(.data)


                   }


 


 这个写法让只有被选中的文件的特殊段被链接到输出文件的.data段了。当然,我们似乎还有更好的写法:


               .data : {


                                 *(.data)


                   }


  这样的话,所有目标文件的.data段都被连接到了输出文件中了(这似乎是最常用的方法)。


核心的部分讲完了,开始回顾前面说到了的那些参数:


start:强制链接地址。也许没有讲清楚的是,在SECTIONS中,各个段是按次序排列的,前一个段用到什么地方下一个段接着用,而start就是强迫链接器将当前的段连接到指定的地址中。


.data  0x400000000 : { ..... }


BLOCK(align):说实话,没看懂。只知道用的时候用的比较多的是ALIGN(4)这样的标记,表示排列地址的时候按4的倍数排列,这样做的理由很简单,系统会快。


AT(addr):实现存放地址和加载地址不一致的功能,AT表示在文件中存放的位置,而在内存里呢,按照普通方式存储。至于用处,目前在下不知。


>region:好戏来了,这个region就是前面说的MEMORY命令定义的位置信息。表明当前section所放置的mem有什么特点,如果不符合会怎么样呢?不晓得嘛。


其他略了吧,累了,主要是没中文资料(屁话,有了还用我刻薄吗),其实有点日文资料也行啊,英文我比较苦手)。


注释:


和C语言一样的哦,/**/


其它:


其实ARM-GCC-LD脚本还真的和别的不一样,它的功能要强一些,从manual看,它有三大功能:


1:设置入口函数


2:定义一个变量并赋值


3:描述输入输出文件的链接规则


 


其实上面的介绍是功能3,1和2都没有讲过。对于arm-gcc-ld脚本来说设置入口函数和定义变量可以在SECTIONS命令大括号里,也可以在外面。语法是:


ENTRY(symbol)


这个symbol应该是某个函数,或者是汇编代码里的一个入口。然而,其实ARM-GCC-LD有很多种方式定义入口,所以当你看到你的脚本里没有这句话而板子运行的很正常的时候别大吃一斤。


1:在连接的时候使用-e参数。


2:在脚本里使用ENTRY


3:如果定义过start这个入口(如果你在汇编里如果本身就有这个名字叫start的入口,那么不用特别的声明也可以)


4:SECTION中.text的第一个入口函数


5:地址为0的指令


其实看了这个我们可以理解是ARM对入口的一个选择优先级,1和2是一样的,显示的指明入口,这也是推荐的方法,没人会觉得程序员故弄玄虚是什么好事情。3是连接器的智能吧,而4和5就是无奈的选择了,程序员没干好的事情CPU只要猜着来处理了,有.text段的话就从它开始执行吧,连.text都没有的就从0x00000000开始执行,至于执行到哪里去了,火星人知道。


 


关于定义变量,其实一般的脚本都会有的,目的只有一个,给汇编启动代码提地址信息。比如说,一段需要清零的区域在脚本里定义了,而脚本自己不是变形金刚,他不能主动给你清零的,需要你自己的启动代码来清零,清零的代码当然在汇编的启动代码里,它怎么知道需要清零的内存在什么地方?就靠脚本里定义的变量了。没错,事情就是这样的,那也就说一定会有一个提取地址的方法将地址赋给变量了哦,yes!就是小小的一个点"."。


RAM_START = .;


定义了一个RAM_START变量,地址是当前的地址,什么是当前的地址啊?就是链接器在连接的时候根据前面的段排列后的当前位置。当然也可以设置当前位置的值,不过最好不要小于前面排列需要的最小内存。


. = 0x00000000


定义当前地址为0x0。



常用的基本上就这么多了,看一个实例吧:


 1 SECTIONS

 2 {

 3     .= 0x30000000;

 4     .text : { *(.text) }


 9     .data : { *(.data) }

10     .rodata : { *(.rodata) }


11     Image_ZI_Base = .;

12     .bss : { *(.bss) }

13     Image_ZI_Limit = .;


21     .debug_info     0 : { *(.debug_info)  }

22     .debug_line     0 : { *(.debug_line)  } 

23     .debug_abbrev   0 : { *(.debug_abbrev)}

24

[1] [2]
关键字:ARM  GCC  LD脚本 编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic481819.html 本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:iOS 逆向之ARM汇编
下一篇:关于ARM的内核架构

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

ARM的启动分析
基于ARM的芯片多数为复杂的片上系统,这种复杂系统里的多数硬件模块都是可配置的,需要由软件来设置其需要的工作状态。因此在用户的应用程序之前,需要由专门的一段代码来完成对系统的初始化。由于这类代码直接面对处理器内核和硬件控制器进行编程,一般都是用汇编语言。一般通用的内容包括:中断向量表初始化存储器系统初始化堆栈初始化有特殊要求的断口,设备初始化用户程序执行环境改变处理器模式呼叫主应用程序 1. 中断向量表ARM要求中断向量表必须放置在从0地址开始,连续8X4字节的空间内。每当一个中断发生以后,ARM处理器便强制把PC指针置为向量表中对应中断类型的地址值。因为每个中断只占据向量表中1个字的存储空间,只能放置一条ARM指令,使
发表于 2019-12-05
ARM的启动分析
arm交叉编译器gnueabi、none-eabi、arm-eabi、gnueabihf的区别
命名规则交叉编译工具链的命名规则为:arch [-vendor] [-os] [-(gnu)eabi] [-gcc]arch – 体系架构,如ARM,MIPSvendor – 工具链提供商os – 目标操作系统eabi – 嵌入式应用二进制接口(Embedded Application Binary Interface)注意没有vendor时,用none代替;没有os支持时,也用none代替同进没有vendor和os支持时,只用一个none代替,比如arm-none-eabi中的none表示既没有vendor也没有os支持。根据对操作系统的支持与否,ARM GCC可分为支持和不支持
发表于 2019-12-04
arm交叉编译器gnueabi、none-eabi、arm-eabi、gnueabihf的区别
浅析ARM汇编语言子例程设计方法
引言在嵌入式软件系统开发过程中,大量使用C语言进行应用程序开发以提高开发效率。同时,系统中经常包含一些决定整个系统性能的关键模块,此时为了获得最佳性能,经常使用汇编语言编写它们,或者某些特殊情况下,例如操作硬件等,也必须使用汇编语言。函数是C语言中一个重要的概念,在汇编语言中经常使用子例程或过程(subroutine or procedure)表达同样的概念,本文使用术语子例程。本文首先介绍ARM汇编语言子例程设计的一般方法,并以此为基础提出一种新的基于堆栈帧的设计方法,同时介绍与C语言交互技术。1 一般方法在ARM汇编语言中一般使用BL(Branch and Link)指令调用某个子例程,BL指令首先将返回地址保存
发表于 2019-12-04
浅析ARM汇编语言子例程设计方法
iOS逆向工程之Hopper中的ARM指令
一、Hopper中的ARM指令ARM处理器就不多说了,ARM处理器因为低功耗等原因,所以大部分移动设备上用的基本上都是ARM架构的处理器。当然作为移动设备的Android手机,iPhone也是用的ARM架构的处理器。如果你想对iOS系统以及你的应用进一步的了解,那么对ARM指令集的了解是必不可少的,ARM指令集应该也算得上是iOS逆向工程的基础了。当你使用Hopper进行反编译时,里边全是ARM的指令,那是看的一个爽呢。下面就是使用Hopper打开MobileNote.app的一个Hopper的界面。从主窗口中可以看到全是ARM的指令呢,如果你对ARM指令不了解,那么如何进行分析呢,对吧。所以对ARM指令的了解,是iOS逆向工程
发表于 2019-12-04
iOS逆向工程之Hopper中的ARM指令
ARM的栈指令
ARM的栈指令ARM的指令系统中关于栈指令的内容比较容易引起迷惑,这是因为准确描述一个栈的特点需要两个参数:栈地址的增长方向:ARM将向高地址增长的栈称为递增栈(Descendent Stack),将向低地址增长的栈称为递减栈(Acendant Stack)栈指针的指向位置:ARM将栈指针指向栈顶元素位置的栈称为满栈(Full Stack),讲栈指针指向即将入栈的元素位置的栈称为空栈(Empty Stack)1. 栈类型根据栈地址增长方向雨栈指针指向位置的不同,自然可以将栈分为四类:递增栈递减栈空栈EA栈ED栈满栈FA栈FD栈图1描述了四种不同类型的栈,其中虚线部分表示即将入栈的元素。图1 栈类型2. 栈指令栈的操作指令无非两种
发表于 2019-12-04
ARM的栈指令
ARM(RISC)和x86(CISC)的技术差异
RISC和CISC,这一对冤家,从诞生之日开始就处在不停的纠缠之中。直到今天,两者经过多年的发展后,都在各自领域打开了一片天地,并且相互渗透。RISC专注高性能、高性能功耗比、小体积以及移动设备领域,CISC专注桌面、高性能和民用市场。现在,RISC的代表是ARM,而CISC的代表则是我们耳熟能详的x86。那么,他们的技术差异在哪里?究竟是怎样的技术分歧带来了两者如此大的差别呢?指令集的出现要说清楚RISC和CISC,也就是今天热门的ARM和x86之间的差异,还得把时间往前推一些,观察计算机诞生之初的一些事情,才能很清楚地了解指令集、精简和复杂之间的关系。 机器语言的实例机器语言含义
发表于 2019-12-04
ARM(RISC)和x86(CISC)的技术差异
小广播
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved