首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

GNU Binutils

GNU Binutils

GNU BinutilsGNU binutils 包括一套用来构造和使用二进制文件所需要的工具。其中两个最为关键的 binutils 是 GNU 链接器 ld 和 GNU                汇编程序 as。这两个工具是 GNU 工具链中的两个完整部分,通常是由 GCC 前端进行驱动的。然而,了解如何直接引导 GNU                工具链的这些组件将非常有用。本节将介绍如何控制这两个工具并选择其他 binutils 以便易于移植,并介绍 POWER                架构特有的一些功能,以及常见的一些问题和误解。
GNU 链接器链接是创建一个可执行程序的最后一个步骤。GNU 链接器可执行程序,或者称为链接编辑器,是                ld,它的角色是将对象文件合并成可执行程序,同时指定程序在运行时是如何执行的。GNU 链接器使用一个命令语言脚本来控制链接过程;默认情况下,ld                是由一组内部命令进行控制的,这些命令可以进行扩展或覆盖。强调可移植性和灵活性在 GCC                的功能中是非常明显的一条,它可以为很多不同的编译环境生成链接脚本,并向 ld                传递定制过的链接脚本,而不用手工进行干预。相反,在其他某些系统中,链接编辑器则很难实现这一点。例如,AIX                的链接器就需要自己处理定制过程,而不是在一个预链接的编译步骤中来完成。
本节将介绍 GCC 驱动器与链接器之间的交互,提供 64 位 PowerPC ELF ABI 的一些详细信息,并探索 Linux on POWER                上链接器实现的一些异常之处。
由于链接器的角色是将对象代码集合在一起,并产生一个可执行模块,因此编译器驱动器会将所期望的参数传递给                ld。这包括:应用程序对象文件,启动代码(在进程启动且调用 main()                之前所运行的代码),可能有用的包和所依赖的共享模块清单,库搜索路径(在链接时和调用时使用),已经平台所需要的实现特有的选项。ld                会从左到右遍历命令行,并使用这个顺序来确定如何查找/引用哪些符号。
Linux on POWER 对象文件
Linux on POWER 使用 ELF                    对象文件格式。这种灵活且可扩展的格式可以很好地满足平台的需要,另外还有几个特殊的段。                列出了这些特殊的段,并对其进行了介绍:
表 4. 特殊的段段描述.glink包含对全局链接代码的支持。模块间的函数调用(例如主程序和                            libc.so)需要加载一个函数描述符,其中这个描述符波包含了目标程序的地址和目标模块的 TOC 值。(详细信息请参看下面的                            TOC。)这种机制是由过程链接表(.plt)段和 .glink 段实现的。.toc这是 TOC                            的一部分,每个模块都有这样一个段,它是加载全局信息使用的一个字典。作为 TOC 的一部分,.toc                            段包含了初始化信息。.tocbss其作用类似于一个 .bss 段,但是为 TOC                            保存了一个尚未初始化的数据区。.got全局偏移量表(Global Offset Table)保存在                            .got 段中,由此可以访问全局数据项(在本模块之外也是可见的)。注意,尽管数据可以加载到 .toc 或 .tocbss                            段中,但是全局数据通常都是通过 GOT 进行寻址的。 .plt包含了对模块间调用的支持。每个模块(主程序,共享对象)都包含了自己的                            TOC,因此也都有自己的 TOC                            定位点。这个段的内容是由动态链接器进行填充的,它支持一个用来支持惰性符号解析的简单函数描述符集合(特例)。
以下节选自“64-bit PowerPC ELF ABI Supplement”:
ELF 处理器特有的实现通常会定义一个 GOT (‘Global Offset Table’)段用来保存位置无关的代码。有些 ELF                处理器特有的实现,包括 32 位的 PowerPC Processor Supplement,定义了一个小的数据段。有时会使用相同的寄存器来对                GOT 和这个小的数据段进行寻址。
64 位的 PowerOpen ABI 定义了一个 TOC (‘Table of Contents’)段。TOC 结合了 GOT                和这个小数据段的功能。
您可以看到虽然 32 位和 64 位的模块提供了相同的功能,但是它们的组织方式却是不同的。由于 64 位模块在 ELF 中引入了这个 TOC                的概念(这来自于 AIX),请考虑以下细节。
内容列表
正如前文中介绍的一样,每个 64 位模块都包含一个 TOC。这就意味着您的 "hello,                world!" 至少包含两个模块:main 程序和 libc,因此包含两个 TOC。每个 TOC 都有一个 "知名的" TOC                定位点,这通常可以在进程运行时从寄存器 2 中找到;TOC                寄存器的值会随着执行过程从一个模块跳到另外一个模块而发生变化。这个定位点支持访问一个模块的各种全局数据的机制(例如全局的外部变量、全局静态变量以及函数描述符)。
使用 TOC 意味着要使用两级间接寻址。例如,要访问一个全局变量,程序要使用 TOC                定位点(rc2)来查找指向这个变量的指针的位置。在为一个模块外部的调用查找函数描述符时,又会发生同样的操作。不过可以将数据保存在                TOC(而不是使用一个指向数据的指针),这样就避免了另外一级的间接寻找。记住,(在 64 位模式中)TOC                中保存的每个数据不管是指针还是实际数据,都必须是 8 个字节或少于 8 个字节。现在请考虑一下这对 TOC 大小的影响。
TOC 相对寻址使用了一条指令,它限定于只能使用 16 位的偏移量。TOC 可以保存 65,536 个字节,在 64 位模式中,这可以用来存放                8,192 个 GOT 项。对于大型应用程序来说,您可能会看到这些空间还不够用。GNU 链接器有几个选项来处理超过 TOC 最大值的应用程序。从                2.15.90 版本的 GNU Binutils 开始,TOC 如果溢出,在链接时会自动被分隔为多个                TOC,但是对于一个特殊的结果,我们也可以使用在 中列出的选项。此处,这些选项通常都是作为                GCC 驱动程序的参数指定的。
表 5. TOC 选项TOC                            选项描述-mfull-toc这个选项是处理 TOC                            使用的默认值。它会让链接器为可执行程序或共享对象(换而言之,就是一个模块)分配一个 TOC。如果目前的 64K                            空间不足以实现链接编辑的功能,那么链接器就会报告一个错误消息说 TOC 空间已经溢出了。-mno-fp-in-toc这个标记可以减少 TOC                            中使用的空间。通常,编译器会将浮点值直接放到 TOC 中。(AIX/GCC 开发人员可能会在自己的 32                            位开发经验中见过这个选项。)这个标记可以防止将这些值保存到小数据段中,从而空出一些 TOC                            空间给其他项使用。与此有关的一个标记是 no-sum-in-toc。-mno-sum-in-toc这个标记通知 GCC                            在运行时生成代码来计算一个地址和一个常量之和,而不是将它们的和放到 TOC 中。这个选项(还有                            no-fp-in-toc 选项)都可以用来保留一部分 TOC                            空间,但是所生成的代码更大、速度更慢。-mminimal-toc如果使用                            no-fp-in-toc 和 no-sum-in-toc                            标记还不能释放足够的 TOC 空间,那么就可以使用 minimal-toc                            标记为每个(对象)文件生成一个单独的 TOC。这会生成很多很小的 TOC 项。虽然这解决了 TOC                            溢出的问题(因为现在有无穷个 TOC 了,每个都是 64K),但是这样所生成的代码会更大、速度也更慢。使用 TOC                            来指定函数地址的优点从根本上被遗弃了。
longcallPOWER 指令集提供了 bl(或称为分支链接)指令给模块内部的子程序调用使用。这种格式的指令可以使相对寻址范围达到 64MB,或                2^26(从调用位置开始计算)。然而,有时可能会达到这个限制,因此必须使用另外一种机制实现从 A 处到 B                处的寻址。–mlongcall                选项就可以使用函数指针机制(用于模块间的调用)。换言之,每个函数调用都类似于一个模块外调用。这突破了 64M                相对地址的限制,代价是可能会稍微增加函数调用的负载。它还支持 longcall 的用法,其优先级比 -mlongcall                选项更高。longcall (1) 会对所有后续函数声明都应用这个属性;longcall (0)                可以停止对后续函数应用这个属性。
对于开发人员来说,幸运的是 GNU for Linux on POWER 上的链接器可以快速生成需要实现这个解决方案所需要的代码。正如 AIX                链接器一样,在 64 位模式中,并不需要担心 –mlongcall。这个特性对于 32 位的 GNU                链接器来说是不可用的,包括 SLES9 和 RHEL4。然而,在可以自由下载的 GCC 源代码中包括了这个特性。如果您有一个 32 位的应用程序在                SLES9 或 RHEL4 上运行,它调用的位置超过了 64MB 的限制,那么您就只能重新编写代码,或者将其编译为 64 位模式。由于 32 位和                64 位应用程序在 Linux on POWER 运行时是可以并存的,因此在这种情况中,我们建议您将其编译为 64 位模式。
链接器脚本GNU 链接器提供了一种命令语言,可以用来控制链接编辑的操作。虽然对于那些早已熟悉 GCC 开发工具的人来说,这并没有什么奇怪;但是对于 AIX                开发人员来说,他们需要理解 AIX 和 Linux on POWER 上所存在的区别。尽管(XCOFF)对象文件的定义和 AIX                链接器的行为都是自动的,但是 GNU ld 可以对如何以及在何处合并对象文件的各个段进行更加灵活的控制。让我们来考虑一下这种脚本的基本属性。
GNU ld                会自动对一个内部脚本进行操作。您可以添加或替换这些内部命令。在特定的条件下(例如,使用只出现一次的命令),必须使用定制的链接器脚本提供完整的命令集;否则,可以使用脚本为链接器添加常用的操作。在添加定制操作时,您可以在命令行中对链接器脚本简单地进行命令(链接器假设所有非对象文件都是链接器脚本)。在替换链接器默认的(内部)命令时,您可以使用                --T 或 --script= 选项。
检查默认的链接器脚本的工作可以通过一个链接器选项发送到标准错误设备上。使用一个简单的 "hello, world" 程序,加上 verbose                选项,您就可以捕获这些内部的命令:
1
$ cc -o hello hello.c -Wl,--verbose 2> hello.ldscript




这会创建一个 256 行的脚本,它可以处理最常见的链接情况(我们期望如此)。AIX 开发人员应该注意这是                –bbindcmds: 选项的结果,不过我们早已说过,AIX 链接器要更加自动化。
因此您可能会问:为什么会有人对这种功能感兴趣呢?有些开发工作需要使用一些 "链接器欺骗(linker trick)"                的技巧,这是另外一种声明项目对如何构建应用程序和分布内存创建约束的方法。这种需要可以通过聪明地链接编辑操作实现,GNU                链接器的命令行选项就提供了这种机制。
返回列表