为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

GCC 链接脚本

2012-06-24 8页 pdf 275KB 46阅读

用户头像

is_357927

暂无简介

举报
GCC 链接脚本 连接脚本将我整整蒙了 1天零一个上午,做了很多实验,看了人家不少例子代码 勉强能驾驭了,让 linker按照我想要的来处理,做个笔记。 1,什么叫输入段,什么叫输出段 不知道怎么回事,我对 GCC系列的输入和输出两个单词总是进入思维死角,很简单 就是 input section 和 output section,这里不是说翻译的问题,我觉得是一种 思考的方式的问题。 我的问题就是:既然叫输入端,那输入什么?同理,输出的是什么?不知道其他人 不会不理解这个问题,我自己的话是理解了不少时间了 -...
GCC 链接脚本
连接脚本将我整整蒙了 1天零一个上午,做了很多实验,看了人家不少例子代码 勉强能驾驭了,让 linker按照我想要的来处理,做个笔记。 1,什么叫输入段,什么叫输出段 不知道怎么回事,我对 GCC系列的输入和输出两个单词总是进入思维死角,很简单 就是 input section 和 output section,这里不是说翻译的问,我觉得是一种 思考的方式的问题。 我的问题就是:既然叫输入端,那输入什么?同理,输出的是什么?不知道其他人 不会不理解这个问题,我自己的话是理解了不少时间了 -v- 所谓的输出段,是指生成的文件,例如 elf 中的每个段 所谓的输入段,是指连接的时候提供 LD的所有目标文件(OBJ)中的段。 2,lma 和 vma lma = load memory address vma = vitual memory address 如果有研究过 ADS的估计有印象,那里有个 RO BASE 和 RW BASE 和 ZI BASE,也 就是说,lma 是装载地址,vma 是运行地址,想搞清楚这两个问题,可以阅读一下 《ARM学习报告(杜云海)》作者写的很好,将这个问题分析的很透澈。lma 和 vma 只是 GCC的叫法而已,其实原理是一样的。 3,两个基本架构 OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") OUTPUT_ARCH(arm) 一句话,照抄......因为我们没有修改的余地,都是系统默认的关键字。第一句 指示系统可以有生成两种格式,默认是 elf32-arm,端格式是 little endian 4,ENTRY(__ENTRY) 指定入口点,LD的手册说,ENTRY POINT 就是程序第一条执行的指令,但是,说老 实话,我并不理解,因为这里跟我的理解矛盾了,首先,通常情况,系统需要一个 初始化的 STARTUP.S文件来初始化硬件,也就是 bootloader的第一阶段了。那么 很自然,入口点需要设置在这段代码的第一条指令中,那么正常运行的时候从第一 条指令开始运行。所以这里设置了__ENTRY为入口点,这个在汇编代码中必须得先 声明一下为全局,才能用,否则系统找不到。例如: .global __ENTRY 但是问题是,如果我用同样的办法,设置另外一个不是第一条指令的入口点,LD并 没有报错,但是问题来了,生成的文件和刚才设置入口点为 __ENTRY 的时候一模一 样,这就蒙了,到底这个入口点是怎么回事? 记得以前 ADS的时候也碰到过 entry point的问题,下载仿真的时候确实是自动跳转 到 entry point中运行。 我想到的可能的原因,第一,生成 elf 文件并不是能直接用在嵌入式平台上面裸跑 的,因为我们并没有操作系统,我们不需要 elf文件头的那些指示信息提供给操作 系统,指示系统怎么去加载文件,在嵌入式上面的完全没有那个必要,只需要将实 际的代码提取出来,直接运行就 OK,也就是 objcopy的操作,所以我觉得,在裸奔 的嵌入式系统上面,entry point是没有意义的,只需要指向整个代码最开始的指 令就 OK了。 暂时我还是不能清晰的理解这个东西。先放下。以后碰到问题再分析。 5,一个输出段的格式 section [address] [(type)] : [AT(lma)] { output-section-command output-section-command ... } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] 前面也说了,所谓的输出段是指最终生成的文件里面的段,所以一个输出段就可以 理解为最终文件里面的一个块,那么多个块合起来就是一个完成文件了。 而每个小块又分别有什么文件来组成呢?那就是输入段了。 我自己实际用到有下面的一些,其他暂时不会用。 section_name vma : AT(lma) { output-section-command output-section-command ... } [AT>lma_region] section_name 根据 ld手册说是有个确定的名字,其他没啥,自己添加一些新段也是 可以的。 默认的 4个段是必须有的 .text 代码 .rodata 常量,例如字符串什么的 .data 初始化的全局变量 .bss 没有初始化的全局变量 其实没什么,可以说,都是固定的,所以一句话,照抄。 段名字后面紧跟的是 vma ,也就是这个段在程序运行的时候的地址,例如 .text 0x30000000 : { *(.text) } 示的是代码的运行时地址为 0x30000000 假如你的 ROM在 0x0 地址,程序放在 ROM 中,那个时候程序是不能正常运行的(位置无关代码除外),必须将代码 COPY到 VMA 也就是 0x30000000 中才能正常运行。 至于那个 AT(lma) 的关键字,只指示代码连接的时候应该放在什么地方,注意好 这个英文是 load memory address,是指程序应该装载在什么地方,而不是指这个段 应该在最后生成的 bin文件的位置!!!这个东西蒙骗了我,让我郁闷了 1天。elf格 式的文件里面不但包含了代码,还包含了各种各样的信息,例如上面说的每个段的 lma 和 vma,还有其他信息都包含在里面了。 默认状态下,lma 是等于当前的 vma的,例如 .text 0x30000000 : { *(.text) *(.rodata) } .data 0x33ffff00 : { *(.data) } 例如我们基本的两个段,我们指定了.text 和.data段的 vma,但是没有指定 lma,那么 lma到底应该是多少?很简单,ld认为当前的 lma和 vma是相同的,所以 lma应该分别是 0x30000000 0x33ffff00,编译生成的 elf文件很小,但是 objcopy出来的文件却非常 巨大,达到了 60多MB,这是什么问题? elf文件很聪明,他只是保存了信息,.text段的 lma是 0x30000000,那么 elf就保存了 知道本程序的代码应该加载到 0x30000000,然后又保存了.data的 lma,我们留意到 中间有很多的地址空间是空的,并没有实际的代码,elf怎么处理?elf只保存了两个 地址和实际的代码,而对于其他空间里面的代码他并不处理,所以可以看出,最后生成 的 elf文件并不大,也就 100多 KB而已,但是后来的 OBJCOPY操作中,从 elf文件中 copy 出程序代码,这下就糟了,objcopy是从最开始的 lma开始,这里是 0x30000000一直 复制到最尾段的 lma,这里是 0x33ffff00,中间没有代码地方全部补零,那么 60多MB 的大 bin文件就是这样来的。 可以验证一下,如果手动指定开始的 lma为 0的话 .text 0x30000000 : AT(0) { *(.text) *(.rodata) } .data 0x33ffff00 : { *(.data) } 其中.text段的 lma被 AT强制指定为 0,那么 objcop出来的 bin文件相当的华丽,达到了 700多MB,为什么?都说了,从 0开始到 0x33ffff00,中间补零,字节数相当的可观呢。 一般我们常用的做法是: 1,.data段的 lma 和 vma 都是紧跟着 .text 的,或者用 ARM的说法就是 RW段紧跟着 RO段,这样的做法非常简单 .text 0x30000000 : { *(.text) *(.rodata) } .data : { *(.data) } .bss : { *(.bss) *(.COMMON) } 只指定 RO BASE,然后所有代码都是跟着 RO BASE分配,这样非常简单。 2,.data段分离出来,连接到不同的 vma运行时地址。 .text 0x30000000 : { *(.text) *(.rodata) } .data 0x31000000 : AT(LOADADDR(.text) + SIZEOF(.text)) { *(.data) } .bss : { *(.bss) *(.COMMON) } 其实也不难解决,像上面的代码那样做就 OK了,上面也分析了,如果 vma不同的话,objcopy 会一直复制,这样生成的 bin文件会很大,怎么解决?很简单,手工指定 .data段的 lma地 址 让 .data段的 lma 紧紧跟着 .text段的末尾,这样生成的 bin 文件就会很漂亮,跟第一种 办法生成的 bin文件结构一模一样!! AT(LOADADDR(.text) + SIZEOF(.text)) 这个指令大概解释一下,AT 是指定 lma 的,然后里面用了两个指令 LOADADDR ,和名字 一样 这个指令是用来求 lma 地址的! SIZEOF 也就是名字那样,求大小的 LOADADDR(.text) 求出 .text 段的 lma,注意是开始地址 SIZEOF(.text) 求出 .text 段的大小 AT(LOADADDR(.text) + SIZEOF(.text)) 的效果就是,指定 .data段的 lma在 .text段 lma 的结尾处! 这里补充一下,还有一个指令 ADDR(.text) 这个是求 vma的,不是求 lma。 另外,注意一下 .bss段的 lma和 .data段的 lma是一样的,这也反映了一个实质问题 .bss 段 只分配运行地址 vma,并不实际占空间的。 3,如果我想自己添加一些段,应该怎么去实现? 例如我要添加一个 .vector 的段,里面放的是一些数据,怎么实现? (1)如果在汇编代码里面添加,那么可以新启动一个段 例如在 2440init.S 中添加 .vector 段 .section .text .... .... (其他代码) .section .vector @ 在这里声明一个段,并且放连个数据 .word 0x55 .word 0xaa 汇编代码段的开始由 .section 声明,接着后面的都属于这个段,直到第二个 .section 声明 为止。 我这个 .vector段是需要连接到 0x33ffff00 的,非常的特殊,那么按照前面的办法 .text 0x30000000 : { *(.text) *(.rodata) } .data 0x31000000 : AT(LOADADDR(.text) + SIZEOF(.text)) { *(.data) } .bss : { *(.bss) *(.COMMON) } .vector 0x33ffff00 : AT(LOADADDR(.data) + SIZEOF(.data)) { *(.vector) } 可以看出,形式其实是一样,不过看一下,添加的段的 lma 放在 .data 段的 lma 的后面, 前面也说 看 .bss 和 .data的 lma是一样的,所以其实无视掉 .bss段就 OK了。 (2)在 C语言中怎么添加一个变量指定放到 .vector段 很简单,用 GNU扩展语法(注意了,是 GNU 系列工具通用而已,例如 gcc,这个并不是 C 的标准) 格式如下 unsigned int __attribute__((section(".vector"))) vec=0x9988; 定义一个 vec 变量,值为 0x9988,分配在 .vector 段,编译后用 objdump 一下查看汇编 代码 可以发现到 Disassembly of section .vector: 33ffff00 : 33ffff00: 00009988 .word 0x00009988 33ffff04: 00000055 .word 0x00000055 33ffff08: 000000aa .word 0x000000aa 看到没有?刚才说的在汇编代码和 C代码里面定义的数值都被连接进去了 .vector段了,vma 也正确 最后还可以看看生成的 bin 文件,看看最后的几个数据是不是就是 0x9988 0x55 0xaa ?这 样应该 就理解了整个连接的过程了吧? 4,MEMORY 命令在指定 lma中的使用 每个段都要用 AT 来指定具体的位置,其实挺烦的,我们有更加简单的办法,我们定义一个 内存区域 让,然后将所有的段都扔进去。 MEMORY { rom (rx) : ORIGIN = 0x30000000, LENGTH = 1M } 注意,我们现在要实现的是 lma,并不是 vma,也就是说在最后生成的 bin文件中怎么将所 有段合在一 起。定义一个开始地址为 0x30000000 ,也就是 lma,对应上面的 .text 段的 lma,长度自 己设,我设置 为 1M ,其实溢出会提示的,随便设就 OK了。 .text 0x30000000 : { *(.text) *(.rodata) } AT>rom .data 0x31000000 : { *(.data) } AT>rom .bss : { *(.bss) *(.COMMON) } .vector 0x33ffff00 : { *(.vector) } AT>rom 看到每个输出段的末尾都有个 AT>rom 的操作吧?应该大概猜到,通俗一点说就是:"将这个 输出段扔到 rom 指定的那个内存区域!!" ,rom是上面已经定义了,那么这些操作之后,.text .data .vector 都 乖乖的 扔进 rom 指向的那个区域,注意了,我们说的是 lma,所以不要在意那个开始地址,刚才 不是说了吗?那个 objcopy是从最开始的 lma开始 copy而已,这样出来的效果和第三点中生成的 bin文件其实 是一模一样的!! 不信的话用 UE查看一下 bin 文件的 16进制代码,或者查看连接生成的 map文件。这样做 方便很多。 既然 lma 是包含在 elf文件当中,那这个地址到底有什么用?这个我也不知道了,我猜测, 首先,elf文件 是 linux下面的可执行文件格式,跟 windows上面的 .exe文件其实一样的,看过 window的 可执行文件的 PE结构 的应该知道,真正的代码前面是有一堆标志啊,地址啊,什么的,操作系统就是通过读取这 部分信息,就知道 应该怎么将这个可执行文件加载进去。同理,elf 文件头也有一堆有用的信息。不过对于我 们的嵌入式系统 我估计应该是用不上了(我说的是裸奔),基本上都是通过 objcopy 将真正的代码弄出来烧 些到 flash里面 跑的,所以在嵌入式系统上面,这个 lma我觉得应该是没有用处的。 另外,如果用工具调试的时候,例如我用的是 openocd,如果加载 elf文件,并不需要指定 地址,openocd会 自动的加载,为什么这个神奇?我觉得应该是 elf文件里面包含了 lma 的作用吧,呵呵,其 实挺方便的。 结束语: 被这个小东西虐待了整整一天半,疯狂找资料,啃 ld as等的英文资料手册,算是实验了一 点成果出来,上面 说的技术对于我暂时的应用来说已经足够了,也足够看懂很多例子里面的 ld script了,网上 的资料基本都是 在翻译 ld 的英文手册 ...... 唉 ....
/
本文档为【GCC 链接脚本】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索