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

二进制代码工具基础知识

2013-09-23 19页 pdf 203KB 35阅读

用户头像

is_176059

暂无简介

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

历史搜索

    清空历史搜索