二进制实用程序(objdump, readelf,ar, nm等)(收藏)
2013-01-28 12:49:58
分类: LINUX GNU 二进制实用程序Binutils (GNU binary utilities)包括:objdump、readelf、addr2line、strip、ar、nm、ldd、ngprof、gcov等。 一。 objdump - 显示二进制文件信息 objdump可以根据目标文件来生成可读性比较好的汇编文件。常用的命令如下: gcc -g3 test.c -o test.o --archive-headers objdump -a libpcap.a --adjust-vma=offset -b bfdname --demangle --debugging --disassemble --disassemble-all --prefix-addresses --disassemble-zeroes -EB --file-headers --section-headers --help 简短的帮助信息。 --info --section=name --line-numbers --architecture=machine --reloc --dynamic-reloc --full-contents objdump --section=.text -s inet.o | more --source --show-raw-insn --no-show-raw-insn --stabs 二。 readelf -- 显示elf文件信息 LINUX 平台下三种主要的可执行文件格式:a.out(assembler and link editor output 汇编器和链接编辑器的输出)、COFF(Common Object File Format 通用对象文件格式)、ELF(Executable and Linking Format 可执行和链接格式)。 a.out 文件包含 7 个 section,格式如下: exec header(执行头部,也可理解为文件头部) text segment(文本段) data segment(数据段) text relocations(文本重定位段) data relocations(数据重定位段) symbol table(符号表) string table(字符串表) 执行头部的数据结构: struct exec { a.out 的格式非常紧凑,只包含了程序运行所必须的信息(文本、数据、BSS),而且每个 section 的顺序是固定的。这种结构缺乏扩展性,a.out 文件中包含符号表和两个重定位表,这三个表的内容在连接目标文件以生成可执行文件时起作用。在最终可执行的 a.out 文件中,这三个表的长度都为 0。a.out 文件在连接时就把所有外部定义包含在可执行程序中,如果从程序设计的角度来看,这是一种硬编码方式,或者可称为模块之间是强藕和的。a.out 是早期UNIX系统使用的可执行文件格式,由 AT&T 设计,现在基本上已被 ELF 文件格式代替。a.out 的设计比较简单,但其设计思想明显的被后续的可执行文件格式所继承和发扬。这里我们着重介绍elf格式。 1. elf格式介绍 Executable and linking format(ELF)文件是Linux系统下的一种常用目标文件(object file)格式,有三种主要类型: (1)适于连接的可重定位文件(relocatable file),可与其它目标文件一起创建可执行文件和共享目标文件 (.obj or .o) (2)适于执行的可执行文件(executable file),用于提供程序的进程映像,加载的内存执行。 (3)共享目标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进 程映像。 ELF文件内容有两个平行的视角:一个是程序连接角度,另一个是程序运行角度。 ELF header在文件开始处描述了整个文件的组织,Section提供了目标文件的各项信息(如指令、数据、符号表、重定位信息等),Program header table指出怎样创建进程映像,含有每个program header的入口,Section header table包含每一个section的入口,给出名字、大小等信息。
段由若干个节(Section)构成,节头表对每一个节的信息有相关描述。对可执行程序而言,节头表是可选的。ELF头部是一个关于本文件的路线图 (road map),从总体上描述文件的结构。下面是ELF头部的数据结构: typedef struct { unsigned char e_ident[EI_NIDENT]; /* 魔数和相关信息 */ Elf32_Half e_type; /* 目标文件类型 */ Elf32_Half e_machine; /* 硬件体系 */ Elf32_Word e_version; /* 目标文件版本 */ Elf32_Addr e_entry; /* 程序进入点 */ Elf32_Off e_phoff; /* 程序头部偏移量 */ Elf32_Off e_shoff; /* 节头部偏移量 */ Elf32_Word e_flags; /* 处理器特定标志 */ Elf32_Half e_ehsize; /* ELF头部长度 */ Elf32_Half e_phentsize; /* 程序头部中一个条目的长度 */ Elf32_Half e_phnum; /* 程序头部条目个数 */ Elf32_Half e_shentsize; /* 节头部中一个条目的长度 */ Elf32_Half e_shnum; /* 节头部条目个数 */ Elf32_Half e_shstrndx; /* 节头部字符表索引 */ } Elf32_Ehdr; 2. readelf 命令 readelf命令可以显示符号、段信息、二进制文件格式的信息等,这在分析编译器如何工从源代码创建二进制文件时非常有用。 $ readelf -h a.out ELF Header: 三。 addr2line -- 将地址对应到文件名和行号 四。 as -- GNU汇编器 as工具主要用来将汇编语言编写的源程序转换成二进制形式的目标代码。Linux平台的标准汇编器是GAS,它是Gnu GCC编译器所依赖的后台汇编工具,通常包含在Binutils软件包中。 l ld GNU的链接器 五。 ld -- GNU链接程序 同as一样,ld也是GNU Binutils工具集中重要的工具,Linux使用ld作为标准的链接程序,由汇编器产生的目标代码是不能直接在计算机上运行的,它必须经过链接器的处 理才能生成可执行代码,链接是创建一个可执行程序的最后一个步骤,ld可以将多个目标文件链接成为可执行程序,同时指定了程序在运行时是如何执行的。 六。 ar ar命令可以用来创建、修改库,也可以从库中提出单个模块。库是一单独的文件,里面包含了按照特定的结构组织起来的其它的一些文件(称做此库文件的 member)。原始文件的内容、模式、时间戳、属主、组等属性都保留在库文件中。有关ar命令使用的例子,(引自http://blog.163.com/huang_bp/blog/getBlog.do?bid=fks_083075087083082069083086083095085084082066085095094064083):
七。nm nm用来列出目标文件的符号清单。下面是nm命令的格式: nm[-a|--debug-syms][-g|--extern-only][-B] 八。objcopy 它可以把目标文件的内容从一种文件格式复制到另一种格式的目标文件中。通过objcopy可以实现对elf不同模块的分析,同时实现类似strip 的功能。 九。ldd ldd 命令可以用来产看应用程序对库的依赖关系,在应用程序移植过程中很有用。可以首先确认要移植平时是否支持应用程序所依赖的库。例如: $ ldd aout 十。size size 列出目标模块或文件的代码尺寸 $ size a.out 十一。ranlib ranlib 生成索引以加快对归档文件的访问,并将结果保存到这个归档文件中,在索引中列出了归档文件各个成员所定义的可重分配目标文件。ar -s可以实现类似的功能。 十二。strings strings 打印可打印的目标代码字符(至少4个字符),打印字符多少可以控制.对于其它各式的文件,打印字符串。打印某个文件的可打印字符串,这些字符串最少4个字 符长,也可以使用选项“-n”设置字符串的最小长度。默认情况下,它只打印目标文件初始化和可加载段中的可打印字符;对于其他类型的的文件它打印整个文件 的可打印字符,这个程序对于了解非文本文件内容很有帮助。 十三。strip 删除目标文件中的全部或者特定符号,这样可以减小可执行文件的大小。在嵌入式应用中,可执行文件一般存放在flash中,空间有限,因此在产品 release的过程中采用strip对程序进行裁剪很有必要。 十三。gprof gprof是Linux下一个强有力的程序分析工具。能够以“日志”的形式记录程序运行时的统计信息:程序运行中各个函数消耗的时间和函数调用关 系,以及每个函数被调用的次数等等。从而可以帮助程序员找出众多函数中耗时最多的函数,也可以帮助程序员分析程序的运行流程。相信这些功能对于分析开源代 码的程序员来说,有着相当大的诱惑力;同时我们也可以借助gprof进行性能优化和分析。 用gprof对程序进行分析主要分以下三个步骤: l 用编译器对程序进行编译,加上-pg参数。 l 运行编译后的程序。 l 用gprof命令查看程序的运行时信息。 先以一个简单的例子演示一下吧。随便找一个能够运行的程序的源代码,比如下面的文件test.c: int IsEven(int x) { return 0 == x & 1; } int main(int argc, char *argv[] { int i = 0; while(++i < 1000) IsEven(i); } 首先,用以下命令进行编译: [root@localhost]#gcc –o test –pg test.c 然后,运行可执行文件test. [root@localhost]#./test 运行后,在当前目录下将生成一个文件gmon.out,这就是gprof生成的文件,保存有程序运行期间函数调用等信息。 最后,用gprof命令查看gmon.out保存的信息: [root@localhost]#gprof test gmon.out –b 这样就有一大堆信息输出到屏幕上,有函数执行单间,函数调用关系图等等,如下: Flat profile: Each sample counts as 0.01 seconds. no time accumulated % cumulative self self total time seconds seconds calls Ts/call Ts/call name 0.00 0.00 0.00 1000 0.00 0.00 IsEven(int) Call graph granularity: each sample hit covers 2 byte(s) no time propagated index % time self children called name 0.00 0.00 1000/1000 main [7] [8] 0.0 0.00 0.00 1000 IsEven(int) [8] ----------------------------------------------- Index by function name [8] IsEven(int) 以上介绍了gprof最简单的使用方法,下面针对其使用过程中的三个步骤详细说明。 编译和链接 上面的例子中,程序比较简单,只有一个文件。如果源代码有多个文件,或者代码结构比较复杂,编译过程中先生成若干个目标文件,然后又由链接器将这些 目标文件链接到一起,这时该怎么使用gprof呢? 对于由多个源文件组成的程序,编译时需要在生成每个.o文件的时候加上-pg参数,同时在链接的时候也要加上-pg参数。对于链接器不是GCC的情 况,如ld,又有特殊的要求。 同时,-pg参数只能记录源代码中各个函数的调用关系,而不能记录库函数的调用情况。要想记录每个库函数的调用情况,链接的时候必须指定库函数的动 态(或者静态)链接库libc_p.a,即加上-lc_p,而不是-lc。 还要说明的是,如果有一部分代码在编译时指定了-pg参数,而另一部分代码没有指定,则生成的gmon.out文件中将缺少一部分函数,也没有那些 函数的调用关系。但是并不影响gprof对其它函数进行记录。 运行 编译好的程序运行时和运行一般的程序没有什么不同,只是比正常的程序多生成了一个文件gmon.out。注意,这个文件名是固定的,没法通过参数的 设置进行改变。如果程序目录中已经有一个gmon.out,则它会被新的gmon.out覆盖掉。 关于生成的gmon.out文件所在的目录,也有以下约定:程序退出时所运行的文件所在目录就是生成的gmon.out文件所在的目录。如果一个程 序执行过程中调用了另一个程序,并在另一个程序的运行中终止,则gmon.out会在另一个程序所在的目录中生成。 还有一点要注意的就是当程序非正常终止时不会生成gmon.out文件,也因此就没法查看程序运行时的信息。只有当程序从main函数中正常退出, 或者通过系统调用exit()函数而退出时,才会生成gmon.out文件。而通过底层调用如_exit()等退出时不会生成gmon.out。 查看 查看程序运行信息的命令是gprof,它以gmon.out文件作为输入,也就是将gmon.out文件翻译成可读的形式展现给用户。其命令格式如 下: gprof [可执行文件] [gmon.out文件] [其它参数] 方括号中的内容可以省略。如果省略了“可执行文件”,gprof会在当前目录下搜索a.out文件作为可执行文件,而如果省略了gmon.out文 件,gprof也会在当前目录下寻找gmon.out。其它参数可以控制gprof输出内容的格式等信息。最常用的参数如下: l -b 不再输出统计图表中每个字段的详细描述。 l -p 只输出函数的调用图(Call graph的那部分信息)。 l -q 只输出函数的时间消耗列表。 l -e Name 不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。 l -E Name 不再输出函数Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数Name 及其子函数所用的时间。 l -f Name 输出函数Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。 l -F Name 输出函数Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。 l -z 显示使用次数为零的例程(按照调用计数和累积时间计算)。 不过,gprof不能显示对象之间的继承关系,这也是它的弱点. 参考文献: 1. http://www./course/6_system/linux/Linuxjs/2008813/135859.html 2. http://www./course/3_program/c++/cppjs/2008624/128167.html 3. http://www./course/3_program/vc/vc_js/20090307/159174.html 4. 程序分析工具gprof介绍 http://www.cnblogs.com/huangpeng/archive/2009/02/17/1392456.html 5. 熟悉binutils工具集 http://yunli.blog.51cto.com/831344/186727 (推荐) |
|