二进制代码工具基础知识
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,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。