检查默认的链接器脚本的工作可以通过一个链接器选项发送到标准错误设备上。使用一个简单的
"hello, world" 程序,加上 verbose
选项,您就可以捕获这些内部的命令:
$ cc -o hello hello.c -Wl,--verbose 2> hello.ldscript
这会创建一个 256
行的脚本,它可以处理最常见的链接情况(我们期望如此)。AIX
开发人员应该注意这是 –bbindcmds: 选项的结果,不过我们早已说过,AIX
链接器要更加自动化。
因此您可能会问:为什么会有人对这种功能感兴趣呢?有些开发工作需要使用一些
"链接器欺骗(linker trick)"
的技巧,这是另外一种声明项目对如何构建应用程序和分布内存创建约束的方法。这种需要可以通过聪明地链接编辑操作实现,GNU
链接器的命令行选项就提供了这种机制。
GNU 汇编程序
汇编程序的任务是接受使用人们可读的格式编写的输入文件,并生成包含机器级指令的文件。然后将一个或多个文件中的对象代码传递给链接器,后者负责生成可执行模块。GNU
汇编程序可以支持很多平台;虽然每个平台都可以使用很多命令行选项,但是也有一些平台只能支持一些系统特有的特性。
与其他平台一样, GCC
驱动器可以接受使用高级语言编写的输入文件--
它可以产生汇编代码,然后再使用 GNU
汇编程序转换成目标代码。幸运的是,大部分开发人员都不需要直接使用汇编语言,GCC
驱动器会为开发人员隔离这些具体的实现细节。然而,当需求提高时,理解如何与汇编程序进行交互会非常有用;GCC
可以让汇编程序接受一些使用逗号分隔的选项来支持这种功能,它使用的语法为
-Wa [选项]。例如:
$ gcc -O -Wa,-Z,-v foo.c -o foo
这条命令使用 GCC 驱动器将文件 foo.c 编译成二进制文件
foo,并打印汇编程序的版本,即便碰到错误也是如此。
虽然您可能会发现在 Linux on POWER
上,GCC、链接器、汇编程序等的用法都与其他平台非常类似,但是汇编语言本身却有很大的区别。注意一些细节问题:RISC
架构与 CISC 架构相比,通常需要多条指令来完成一个特定的任务;POWER
也不例外。如果您查看一下 GCC
所生成的汇编代码,就会注意到其中会有很多 load 和 store
指令,同时还有其他类型的指令。熟悉汇编语言的一种有用的方法是使用高级语言(例如
C)来编写代码,并使用 GCC 的 -S
选项来保存所生成的汇编文件,或者使用 objdump
对目标代码进行反汇编。
二进制文件的组织
在上文中有关 ld 的 “GNU 链接器” 一节中,我们曾经介绍了 ELF
对象文件的一些段;这些段就是链接器最感兴趣的地方,它们就是用来生成可执行模块的。然而,几乎编译器所生成的每个对象文件中都有这些有趣的段,它们构成了您的程序的各个部分(使用对象代码格式),如表
6
所示,因此链接器知道如何对它们进行合并,从而创建一个程序或共享模块。
表 6. 二进制文件的组织
段 描述.text 可执行指令(汇编代码所转换成的指令)通常就保存在这个段中。只读常量(例如字符串常量)也可以在这个段中找到。这个平台上的文本段是只读的,这就意味着程序不能对自己进行修改;这个属性允许可执行文件的文本区可以在此程序的所有运行实例之间共享。例如,bash
shell 的每个正在运行的拷贝都会共享 bash 文件的 .text
段的一份拷贝。
.data 这就是为可执行程序保存初始化过的数据的地方。从定义中我们可以看出,数据段是可读写的,进程可以对这个段中的内容(在内存中)如何操作进行完全控制。举例来说,C
语言中那些使用编译时的值进行初始化的全局变量都可以在这个段中找到。
.bss bss
段中包含了尚未初始化的数据;这个段不在对象文件中占据任何空间,但是它定义了一段内存区域,它们在运行时被清零。C
语言中那些没有给定初始化值的全局变量在程序启动时都被定义为
0;这些变量都可以在 bss 段中找到。
链接编辑器会从指定的对象文件中搜集所有的 .text
段的内容,并合并它们来创建可执行程序的代码,所有的 .data
段都被搜集起来用来创建最终的数据段,所有的 .bss
段被合并起来创建最终的 bss
段。还要注意,每个可执行模块都包含自己的段;例如,共享模块就有自己的
.text 段和 .data 段。
汇编版本的 Hello, World!
考虑一下这个 32 位的非 PIC 汇编版本的 "Hello, World!" 它首次出现在
Hollis Blanchard 所编写的“PowerPC assembly: Introduction to
assembly on the PowerPC” 一文中(developerWorks,2002 年 7
月):
清单 2. ia32 assembly
.data # section declaration - variables only
.align 3 # make double-word
aligned
msg:
.string "Hello, world!\n"
len = . -
msg # length of our dear string
.text # section declaration - begin code
.align 3 # ensure alignment
.global _start
_start:
# write our string to stdout
li 0,4 # syscall number (sys_write)
li 3,1 # first arg: file descriptor (stdout)
# second arg: pointer to message to write
lis 4,msg@ha #
load top 16 bits of &msg
addi 4,4,msg@l # load bottom 16
bits
li 5,len # third argument: message length
sc # call kernel
# and exit
li 0,1 # syscall number (sys_exit)
li 3,1 # first argument: exit code
sc # call kernel
注意数据段位于最上面,其中定义了 printf 要打印的字符串
--它可以于 .text
段中的内容一样简单,因为它是被当作一个常量来对待的。
文本段是有许多指令组成的,因为这个程序要使用几个系统调用(而不是使用
libc 中的程序,例如 printf)来完成工作。write 系统调用是在寄存器 0
中指定的,目标文件是在寄存器 3 中指定的,指向字符串的指针是在寄存器
4 中指定的,字符串的长度是在寄存器 5
中指定的。在输出字符串之后,又要将寄存器领设置为 exit
的系统调用,并传递值 "1" 作为退出代码。
这个例子会被传递给汇编程序,从而创建一个对象文件;所生成的对象文件又被传递给链接器,后者生成一个可执行文件的映像。这一系列操作对于具有
AIX 和 Linux 背景的程序员来说会感觉非常熟悉。
检查默认的链接器脚本的工作可以通过一个链接器选项发送到标准错误设备上。使用一个简单的
"hello, world" 程序,加上 verbose
选项,您就可以捕获这些内部的命令:
$ cc -o hello hello.c -Wl,--verbose 2> hello.ldscript
这会创建一个 256
行的脚本,它可以处理最常见的链接情况(我们期望如此)。AIX
开发人员应该注意这是 –bbindcmds: 选项的结果,不过我们早已说过,AIX
链接器要更加自动化。
因此您可能会问:为什么会有人对这种功能感兴趣呢?有些开发工作需要使用一些
"链接器欺骗(linker trick)"
的技巧,这是另外一种声明项目对如何构建应用程序和分布内存创建约束的方法。这种需要可以通过聪明地链接编辑操作实现,GNU
链接器的命令行选项就提供了这种机制。
GNU 汇编程序
汇编程序的任务是接受使用人们可读的格式编写的输入文件,并生成包含机器级指令的文件。然后将一个或多个文件中的对象代码传递给链接器,后者负责生成可执行模块。GNU
汇编程序可以支持很多平台;虽然每个平台都可以使用很多命令行选项,但是也有一些平台只能支持一些系统特有的特性。
与其他平台一样, GCC
驱动器可以接受使用高级语言编写的输入文件--
它可以产生汇编代码,然后再使用 GNU
汇编程序转换成目标代码。幸运的是,大部分开发人员都不需要直接使用汇编语言,GCC
驱动器会为开发人员隔离这些具体的实现细节。然而,当需求提高时,理解如何与汇编程序进行交互会非常有用;GCC
可以让汇编程序接受一些使用逗号分隔的选项来支持这种功能,它使用的语法为
-Wa [选项]。例如:
$ gcc -O -Wa,-Z,-v foo.c -o foo
这条命令使用 GCC 驱动器将文件 foo.c 编译成二进制文件
foo,并打印汇编程序的版本,即便碰到错误也是如此。
虽然您可能会发现在 Linux on POWER
上,GCC、链接器、汇编程序等的用法都与其他平台非常类似,但是汇编语言本身却有很大的区别。注意一些细节问题:RISC
架构与 CISC 架构相比,通常需要多条指令来完成一个特定的任务;POWER
也不例外。如果您查看一下 GCC
所生成的汇编代码,就会注意到其中会有很多 load 和 store
指令,同时还有其他类型的指令。熟悉汇编语言的一种有用的方法是使用高级语言(例如
C)来编写代码,并使用 GCC 的 -S
选项来保存所生成的汇编文件,或者使用 objdump
对目标代码进行反汇编。
二进制文件的组织
在上文中有关 ld 的 “GNU 链接器” 一节中,我们曾经介绍了 ELF
对象文件的一些段;这些段就是链接器最感兴趣的地方,它们就是用来生成可执行模块的。然而,几乎编译器所生成的每个对象文件中都有这些有趣的段,它们构成了您的程序的各个部分(使用对象代码格式),如表
6
所示,因此链接器知道如何对它们进行合并,从而创建一个程序或共享模块。
表 6. 二进制文件的组织
段 描述.text 可执行指令(汇编代码所转换成的指令)通常就保存在这个段中。只读常量(例如字符串常量)也可以在这个段中找到。这个平台上的文本段是只读的,这就意味着程序不能对自己进行修改;这个属性允许可执行文件的文本区可以在此程序的所有运行实例之间共享。例如,bash
shell 的每个正在运行的拷贝都会共享 bash 文件的 .text
段的一份拷贝。
.data 这就是为可执行程序保存初始化过的数据的地方。从定义中我们可以看出,数据段是可读写的,进程可以对这个段中的内容(在内存中)如何操作进行完全控制。举例来说,C
语言中那些使用编译时的值进行初始化的全局变量都可以在这个段中找到。
.bss bss
段中包含了尚未初始化的数据;这个段不在对象文件中占据任何空间,但是它定义了一段内存区域,它们在运行时被清零。C
语言中那些没有给定初始化值的全局变量在程序启动时都被定义为
0;这些变量都可以在 bss 段中找到。
链接编辑器会从指定的对象文件中搜集所有的 .text
段的内容,并合并它们来创建可执行程序的代码,所有的 .data
段都被搜集起来用来创建最终的数据段,所有的 .bss
段被合并起来创建最终的 bss
段。还要注意,每个可执行模块都包含自己的段;例如,共享模块就有自己的
.text 段和 .data 段。
汇编版本的 Hello, World!
考虑一下这个 32 位的非 PIC 汇编版本的 "Hello, World!" 它首次出现在
Hollis Blanchard 所编写的“PowerPC assembly: Introduction to
assembly on the PowerPC” 一文中(developerWorks,2002 年 7
月):
清单 2. ia32 assembly
.data # section declaration - variables only
.align 3 # make double-word
aligned
msg:
.string "Hello, world!\n"
len = . -
msg # length of our dear string
.text # section declaration - begin code
.align 3 # ensure alignment
.global _start
_start:
# write our string to stdout
li 0,4 # syscall number (sys_write)
li 3,1 # first arg: file descriptor (stdout)
# second arg: pointer to message to write
lis 4,msg@ha #
load top 16 bits of &msg
addi 4,4,msg@l # load bottom 16
bits
li 5,len # third argument: message length
sc # call kernel
# and exit
li 0,1 # syscall number (sys_exit)
li 3,1 # first argument: exit code
sc # call kernel
注意数据段位于最上面,其中定义了 printf 要打印的字符串
--它可以于 .text
段中的内容一样简单,因为它是被当作一个常量来对待的。
文本段是有许多指令组成的,因为这个程序要使用几个系统调用(而不是使用
libc 中的程序,例如 printf)来完成工作。write 系统调用是在寄存器 0
中指定的,目标文件是在寄存器 3 中指定的,指向字符串的指针是在寄存器
4 中指定的,字符串的长度是在寄存器 5
中指定的。在输出字符串之后,又要将寄存器领设置为 exit
的系统调用,并传递值 "1" 作为退出代码。
这个例子会被传递给汇编程序,从而创建一个对象文件;所生成的对象文件又被传递给链接器,后者生成一个可执行文件的映像。这一系列操作对于具有
AIX 和 Linux 背景的程序员来说会感觉非常熟悉。
附:
Q:GNU链接器几个开关项的解释:-lm -lc -lgcc
A:
-lm 代表链接器将连接GCC的数学库libm.a -lc 代表链接器将连接GCC的标准C库libc.a -lgcc 代表链接器将连接GCC的支持库libgcc.a 在连接时,这些库的排列顺序一般为: -lm -lc -lgcc 这些库都有不同版本,根据 Endian 模式, ARM/THUMB/Interwork模式 等进行区分