二进制代码工具基础知识
GNU Binutils工具介绍
在 Linux 下建立嵌入式交叉编译环境要用
到一系列的工具链(tool-chain),主要有
比如GNU Binutils、Gcc、Glibc、Gdb 等,
它们都属于GNU的工具集。其中,GNU Binutils
是一套用来构造和使用二进制所需的工具集。
建立嵌入式交叉编译环境,Binutils工具包
是必不可少的,而且Binutils与GNU的C编译
器gcc是紧密相集成的,没有binutils,gcc
也不能正常工作的。
as GNU的汇编器
作为G...
GNU Binutils工具介绍
在 Linux 下建立嵌入式交叉编译环境要用
到一系列的工具链(tool-chain),主要有
比如GNU Binutils、Gcc、Glibc、Gdb 等,
它们都属于GNU的工具集。其中,GNU Binutils
是一套用来构造和使用二进制所需的工具集。
建立嵌入式交叉编译环境,Binutils工具包
是必不可少的,而且Binutils与GNU的C编译
器gcc是紧密相集成的,没有binutils,gcc
也不能正常工作的。
as GNU的汇编器
作为GNU Binutils工具集中最重要的工具之
一。as 工具主要用来将汇编语言编写的源程
序转换成二进制形式的目标代码。Linux平台
的
汇编器是GAS,它是GNU GCC编译器所
依赖的后台汇编工具,通常包含在Binutils
软件包中。
ld GNU的链接器
同as一样,ld也是GNU Binutils工具集中重
要的工具,Linux 使用ld作为标准的链接程
序,由汇编器产生的目标代码是不能直接在
计算机上运行的,它必须经过链接器的处理
才能生成可执行代码,链接是创建一个可执
行程序的最后一个步骤,ld可以将多个目标
文件链接成为可执行程序,同时指定了程序
在运行时是如何执行的。
· add2line 将地址转换成文件名或行号对,
以便调试程序
· ar 从文件中创建、修改、扩展文件
· gasp 汇编宏处理器
· nm 从目标代码文件中列举所有变量(包括
变量值和变量类型),如果没有指定目标
文件,则默认是a.out 文件
· objcopy objcopy工具使用GNU BSD 库,它
可以把目标文件的内容从一种文件格式
复制到另一种格式的目标文件中
在默认的情况下,GNU编译器生成的目标文件
格式为elf格式,elf文件由若干段(section)
组成,如果不作特殊指明,由C 源程序生成
的目标代码中包含如下段:.text(正文段)
包含程序的指令代码;.data(数据段)包含
固定的数据,如常量、字符串;.bss(未初
始化数据段)包含未初始化的变量、数组等。
C++源程序生成的目标代码中还包括.fin(i
析构函数代码)和.init构造函数代码)等。
链接生成的elf格式文件还不能直接下载到
目标平台来运行执行,需要通过objcopy 工
具生成最终的二进制文件。连接器的任务就
是将多个目标文件的.text、.data和.bss等
段连接在一起,而连接脚本文件是告诉连接
器从什么地址开始放置这些段。
· add2line 把程序地址转换为文件名和行号
在命令行中带一个地址和一个可执行文件名,
它就会使用这个可执行文件的调试信息指出在
给出的地址上是哪个文件以及行号。
· objdump 显示目标文件信息
objdump工具可以反编译二进制文件,也可以对
对象文件进行反汇编,并查看机器代码。
· readelf 显示 elf文件信息
readelf命令可以显示符号、段信息、二进制文
件格式的信息等,这在分析编译器如何从源代
码创建二进制文件时非常有用。
· ranlib 生成索引以加快对归档文件的访问,
并将其保存到这个归档文件中
在索引中列出了归档文件各成员所定义的可重
分配目标文件。
· size 列出目标模块或文件的代码尺寸
size 命令可以列出目标文件每一段的大小以
及总体的大小。默认情况下,对于每个目标文
件或者一个归档文件中的每个模块只产生一行
输出。
· strings 打印可打印的目标代码字符(至少
4 个字符),打印字符多少可以控制
对于其他格式的文件,打印字符串。打印某个
文件的可打印字符串,这些字符串最少4个字符
长,也可以使用选项“-n”设置字符串的最小
长度。默认情况下,它只打印目标文件初始化
和可加载段中的可打印字符;对于其他类型的
文件它打印整个文件的可打印字符,这个程序
对于了解非文本文件的内容很有帮助。
· strip 放弃所有符号连接
删除目标文件中的全部或者特定符号。
· c++filt 链接器ld使用该命令可以过滤C++
符号和Java 符号,防止重载函数冲突
· gprof 显示程序调用段的各种数据
Binutils工具软件使用
汇编器
Linux 平台的标准汇编器是GAS,它是GCC所
依赖的后台汇编工具,通常包含在binutils
软件包中。GAS使用标准的AT&T汇编语法,可
以用来汇编用AT&T格式编写的程序,例如可
以这样来编译用汇编语言编写的源程序
test.s。
[root@localhost]# as -o test.o test.s
链接器
GNU链接器使用一个命令语言脚本来控制链
接过程。默认情况下,ld是由一组内部命令
进行控制的,这些命令可以进行扩展或覆盖。
强调可移植性和灵活性在GCC 的功能中是非
常明显的一条,它可以为很多不同的编译环
境生成链接脚本,并向ld传递定制过的链接
脚本,而不用手工进行干预。
需要注意的是,在Linux 下编写应用程序(假
定采用gcc编译器)时,gcc编译器内置缺省
的连接脚本。如果采用缺省脚本,则生成的
目标代码需要操作系统才能加载运行。
就像前面讲到的,由汇编器产生的目标代码
是不能直接在计算机上运行的,它必须经过
链接器的处理才能生成可执行代码。Linux
使用ld作为标准的链接程序,比如我们可以
用下面的方法来链接上述编译的程序。
[root@localhost]# ld -s –o test test.o
这样就生成了最终的可执行程序test。
当新生成的源文件test.c(没有任何依赖)
按照下列步骤生成目标文件,
gcc -c test.c
如下方式才能将test.o链接成可执行文件,
ld -dynamic-linker /lib/ld-linux.so.2 -o
test test.o /usr/lib/crt1.o /usr/lib/crti.o
/usr/lib/crtn.o -lc
下面的glibc对其进行介绍。(为什么需要静
态、动态都需要链接???)
glibc
运行库是平台相关的,因为它与操作系统结
合得非常紧密。C语言的运行库从某种程度上
来讲是C语言的程序和不同操作系统平台之
间的抽象层,它将不同的操作系统API抽象成
相同的库函数。比如我们可以在不同的操作
系统平台下使用fread来读取文件,而事实上
fread在不同的操作系统平台下的实现是不
同的,但作为运行库的使用者我们不需要关
心这一点。虽然各个平台下的C语言运行库提
供了很多功能,但很多时候它们毕竟有限,
比如用户的权限控制、操作系统线程创建等
都不是属于标准的C语言运行库。于是我们不
得不通过其他的办法,诸如绕过C语言运行库
直接调用操作系统API或使用其他的库。
Linux和Windows平台下的两个主要C语言运
行库分别为glibc(GNU C Library)和
MSVCRT(Microsoft Visual C Run-time),我
们在下面将会分别介绍它们。
值得注意的是,像线程操作这样的功能并不
是标准的C语言运行库的一部分,但是glibc
和MSVCRT都包含了线程操作的库函数。比如
glibc有一个可选的pthread库中的
pthread_create()函数可以用来创建线程;
而MSVCRT中可以使用_beginthread()函数来
创建线程。所以glibc和MSVCRT事实上是标准
C语言运行库的超集,它们各自对C标准库进
行了一些扩展。
glibc
glibc即GNU C Library,是GNU旗下的C标准
库。最初由自由软件基金会FSF(Free
Software Foundation)发起开发,目的是为
GNU操作系统开发一个C标准库。GNU操作系统
的最初
的内核是Hurd,一个微内核的构
架系统。Hurd因为种种原因开发进展缓慢,
而Linux因为它的实用性而逐渐风靡,最后取
代Hurd成了GNU操作系统的内核。于是glibc
从最初开始支持Hurd到后来渐渐发展成同时
支持Hurd和Linux,而且随着Linux的越来越
流行,glibc也主要关注Linux下的开发,成
为了Linux平台的C标准库。
20世纪90年代初,在glibc成为Linux下的C运
行库之前,Linux的开发者们因为开发的需要,
从Linux内核代码里面分离出了一部分代码,
形成了早期Linux下的C运行库。这个C运行库
又被称为Linux libc。这个版本的C运行库被
维护了很多年,从版本2一直开发到版本5。
如果你去看早期版本的Linux,会发现/lib目
录下面有libc.so.5这样的文件,这个文件就
是第五个版本的Linux libc。1996年FSF发布
了glibc 2.0,这个版本的glibc开始支持诸
多特性,比如它完全支持POSIX标准、国际化、
IPv6、64-位数据访问、多线程及改进了代码
的可移植性。在此时Linux libc的开发者也
认识到单独地维护一份Linux下专用的C运行
库是没有必要的,于是Linux开始采用glibc
作为默认的C运行库,并且将2.x版本的glibc
看作是Linux libc的后继版本。于是我们可
以看到,glibc在/lib目录下的.so文件为
libc.so.6,即第六个libc版本,而且在各个
Linux发行版中,glibc往往被称为libc6。
glibc在Linux平台下占据了主导地位之后,
它又被移植到了其他操作系统和其他硬件平
台,诸如FreeBSD、NetBSD等,而且它支持数
十种CPU及嵌入式平台。目前最新的glibc版
本号是2.8(2008年4月)。
glibc的发布版本主要由两部分组成,一部分
是头文件,比如stdio.h、stdlib.h等,它们
往往位于/usr/include;另外一部分则是库
的二进制文件部分。二进制部分主要的就是C
语言标准库,它有静态和动态两个版本。
静态链接库
ar cr libtest.a test.o//由.o创建.a
动态链接库
gcc -shared -fPCI -o libtest.so test.o
动态的标准库我们及在本书的前面章节中碰
到过了,它位于/lib/libc.so.6;而静态标准
库位于/usr/lib/libc.a。事实上glibc除了C
标准库之外,还有几个辅助程序运行的运行
库,这几个文件可以称得上是真正的“运行
库”。它们就是/usr/lib/crt1.o、
/usr/lib/crti.o和/usr/lib/crtn.o。是不
是对这几个文件还有点印象呢?我们在第2章
讲到静态库链接的时候已经碰到过它们了,
虽然它们都很小,但这几个文件都是程序运
行的最关键的文件。
glibc启动文件
crt1.o里面包含的就是程序的入口函数
_start,由它负责调用__libc_start_main初
始化libc并且调用main函数进入真正的程序
主体。实际上最初开始的时候它并不叫做
crt1.o,而是叫做crt.o,包含了基本的启动、
退出代码。由于当时有些链接器对链接时目
标文件和库的顺序有依赖性,crt.o这个文件
必须被放在链接器命令行中的所有输入文件
中的第一个,为了强调这一点,crt.o被更名
为crt0.o,
示它是链接时输入的第一个文
件。
后来由于C++的出现和ELF文件的改进,出现
了必须在main()函数之前执行的全局/静态
对象构造和必须在main()函数之后执行的全
局/静态对象析构。为了满足类似的需求,运
行库在每个目标文件中引入两个与初始化相
关的段“.init”和“.finit”。运行库会保
证所有位于这两个段中的代码会先于/后于
main()函数执行,所以用它们来实现全局构
造和析构就是很自然的事情了。链接器在进
行链接时,会把所有输入目标文件中的
“.init”和“.finit”按照顺序收集起来,
然后将它们合并成输出文件中的“.init”和
“.finit”。但是这两个输出的段中所包含
的指令还需要一些辅助的代码来帮助它们启
动(比如计算GOT之类的),于是引入了两个目
标文件分别用来帮助实现初始化函数的
crti.o和crtn.o。
与此同时,为了支持新的库和可执行文件格
式,crt0.o也进行了升级,变成了crt1.o。
crt0.o和crt1.o之间的区别是crt0.o为原始
的,不支持“.init”和“.finit”的启动代
码,而crt1.o是改进过后,支持“.init”和
“.finit”的版本。这一点我们从反汇编
crt1.o可以看到,它向libc启动函数
__libc_start_main()传递了两个函数指针
“__libc_csu_init”和“__libc_csu_fini”,
这两个函数负责调用_init()和_finit(),我
们在后面“C++全局构造和析构”的章节中还
会详细分析。
为了方便运行库调用,最终输出文件中的
“.init”和“.finit”两个段实际上分别包
含的是_init()和_finit()这两个函数,我们
在关于运行库初始化的部分也会看到这两个
函数,并且在C++全局构造和析构的章节中也
会分析它们是如何实现全局构造和析构的。
crti.o和crtn.o这两个目标文件中包含的代
码实际上是_init()函数和_finit()函数的
开始和结尾部分,当这两个文件和其他目标
文件安装顺序链接起来以后,刚好形成两个
完整的函数_init()和_finit()。
于是在最终链接完成之后,输出的目标文件
中的“.init”段只包含了一个函数_init(),
这个函数的开始部分来自于crti.o的“.init”
段,结束部分来自于crtn.o的“.init”段。
为了保证最终输出文件中“.init”和“.finit”
的正确性,我们必须保证在链接时,crti.o
必须在用户目标文件和系统库之前,而
crtn.o必须在用户目标文件和系统库之后。
链接器的输入文件顺序一般是:
ld crt1.o crti.o [user_objects]
[system_libraries] crtn.o
由于crt1.o(crt0.o)不包含“.init”段
和“.finit”段,所以不会影响最终生成
“.init”和“.finit”段时的顺序。输出文
件中的“.init”段看上去应该如图11-8所示
(对于“.finit”来说也一样)。
在默认情况下,ld链接器会将 libc、crt1.o
等这些 CRT和启动文件与程序的模块链接起
来,但是有些时候,我们可能不需要这些文
件,或者希望使用自己的 libc和 crt1.o等
启动文件,以替代系统默认的文件,这种情
况在嵌入式系统或操作系统内核编译的时候
很常见。GCC提高了两个参数“-nostartfile”
和“-nostdlib”,分别用来取消默认的启动
文件和 C语言运行库。
其实 C++全局对象的构造函数和析构函数
并不是直接放在.init和.finit段里面的,
而是把一个执行所有构造/析构的函数的调
用放在里面,由这个函数进行真正的构造和
析构,我们在后面的章节还会再详细分析
ELF/Glib和 PE/MSVC对全局对象构造和析构
的过程。
除了全局对象构造和析构之外,.init
和.finit还有其他的作用。由于它们的特殊
性(在 main之前/后执行),一些用户监控程
序性能、调试等工具经常利用它们进行一些
初始化和反初始化的工作。当然我们也可以
使用“__attribute__((section(“.init”)))”
将函数放到.init段里面,但是要注意的是普
通函数放在“.init”是会破坏它们的结构的,
因为函数的返回指令使得_init()函数会提
前返回,必须使用汇编指令,不能让编译器
产生“ret”指令。
GNU Binutils工具介绍
as GNU的汇编器
ld GNU的链接器
Binutils工具软件使用
glibc
本文档为【二进制代码工具基础知识】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑,
图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。