二进制实用程序(objdump, readelf,ar, nm等)

GNU 二进制实用程序Binutils (GNU binary utilities)包括:objdump、readelf、addr2line、strip、ar、nm、ldd、ngprof、gcov等。

一。 objdump - 显示二进制文件信息

objdump可以根据目标文件来生成可读性比较好的汇编文件。常用的命令如下:

gcc -g3 test.c -o test.o
objdump -Slz test.o
objdump命令介绍,可以通过man objdump在linux环境下获取到。
       [-a] [-b bfdname |
       --target=bfdname] [-C] [--debugging]
       [-d] [-D]
       [--disassemble-zeroes]
       [-EB|-EL|--endian={big|little}] [-f]
       [-h] [-i|--info]
       [-j section | --section=section]
       [-l] [-m machine ] [--prefix-addresses]
       [-r] [-R]
       [-s|--full-contents] [-S|--source]
       [--[no-]show-raw-insn] [--stabs] [-t]
       [-T] [-x]
       [--start-address=address] [--stop-address=address]
       [--adjust-vma=offset] [--version] [--help]
       objfile...
 
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
  -S, --source             Intermix source code with disassembly
  -s, --full-contents      Display the full contents of all sections requested
  -g, --debugging          Display debug information in object file
  -e, --debugging-tags     Display debug information using ctags style
  -G, --stabs              Display (in raw form) any STABS info in the file
  -W, --dwarf              Display DWARF info in the file
  -t, --syms               Display the contents of the symbol table(s)
  -T, --dynamic-syms       Display the contents of the dynamic symbol table
  -r, --reloc              Display the relocation entries in the file
  -R, --dynamic-reloc      Display the dynamic relocation entries in the file
  @<file>                  Read options from <file>
  -v, --version            Display this program's version number
  -i, --info               List object formats and architectures supported
  -H, --help               Display this information

--archive-headers
-a 显示档案库的成员信息,与 ar tv 类似

     objdump -a libpcap.a
     和 ar -tv libpcap.a 显示结果比较比较
     显然这个选项没有什么意思。

--adjust-vma=offset
     When   dumping   information, first add offset to all
     the section addresses.   This is useful if the   sec-
     tion   addresses   do   not correspond   to the symbol
     table, which can happen when   putting   sections   at
     particular   addresses when using a format which can
     not represent section addresses, such as a.out.

-b bfdname
--target=bfdname
     指定目标码格式。这不是必须的,objdump能自动识别许多格式,
     比如:objdump -b oasys -m vax -h fu.o
     显示fu.o的头部摘要信息,明确指出该文件是Vax系统下用Oasys
     编译器生成的目标文件。objdump -i将给出这里可以指定的
     目标码格式列表

--demangle
-C 将底层的符号名解码成用户级名字,除了去掉所有开头
    的下划线之外,还使得C++函数名以可理解的方式显示出来。

--debugging
     显示调试信息。企图解析保存在文件中的调试信息并以C语言
     的语法显示出来。仅仅支持某些类型的调试信息。

--disassemble
-d 反汇编那些应该还有指令机器码的section

--disassemble-all
-D 与 -d 类似,但反汇编所有section

--prefix-addresses
     反汇编的时候,显示每一行的完整地址。这是一种比较老的反汇编格式。
     显示效果并不理想,但可能会用到其中的某些显示,自己可以对比。

--disassemble-zeroes
     一般反汇编输出将省略大块的零,该选项使得这些零块也被反汇编。

-EB
-EL
--endian={big|little}
     这个选项将影响反汇编出来的指令。
     little-endian就是我们当年在dos下玩汇编的时候常说的高位在高地址,
     x86都是这种。

--file-headers
-f 显示objfile中每个文件的整体头部摘要信息。

--section-headers
--headers
-h 显示目标文件各个section的头部摘要信息。

--help 简短的帮助信息。

--info
-i 显示对于 -b 或者 -m 选项可用的架构和目标格式列表。

--section=name
-j name 仅仅显示指定section的信息

--line-numbers
-l 用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用
    使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求
    编译时使用了-g之类的调试编译选项。

--architecture=machine
-m machine
     指定反汇编目标文件时使用的架构,当待反汇编文件本身没有描述
     架构信息的时候(比如S-records),这个选项很有用。可以用-i选项
     列出这里能够指定的架构

--reloc
-r 显示文件的重定位入口。如果和-d或者-D一起使用,重定位部分以反汇
    编后的格式显示出来。

--dynamic-reloc
-R 显示文件的动态重定位入口,仅仅对于动态目标文件有意义,比如某些
    共享库。

--full-contents
-s 显示指定section的完整内容。

     objdump --section=.text -s inet.o | more

--source
-S 尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,
    效果比较明显。隐含了-d参数。

--show-raw-insn
     反汇编的时候,显示每条汇编指令对应的机器码,除非指定了
     --prefix-addresses,这将是缺省选项。

--no-show-raw-insn
     反汇编时,不显示汇编指令的机器码,这是指定 --prefix-addresses
     选项时的缺省设置。

--stabs
     Display the contents of the .stab, .stab.index, and
     .stab.excl sections from an ELF file.   This is only
     useful   on   systems   (such as Solaris 2.0) in which
     .stab debugging symbol-table entries are carried in
     an ELF section. &

二。 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 {          unsigned long   a_midmag;    /* 魔数和其它信息 */          unsigned long   a_text;      /* 文本段的长度 */          unsigned long   a_data;      /* 数据段的长度 */          unsigned long   a_bss;       /* BSS段的长度 */          unsigned long   a_syms;      /* 符号表的长度 */          unsigned long   a_entry;     /* 程序进入点 */          unsigned long   a_trsize;    /* 文本重定位表的长度 */          unsigned long   a_drsize;    /* 数据重定位表的长度 */  };
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:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4003c0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          2704 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 26

三。 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):

  1. 要创建一个库,请输入:

    ar -v -q lib.a strlen.o strcpy.o

    如果 lib.a 库不存在,则此命令创建它,并将文件 strlen.o 和 strcpy.o 的副本输入其中。如果 lib.a 库存在,则此命令在不检查相同成员的情况下,将新的成员添加到末尾。v 标志设置详细方式,在此方式中 ar 命令在其进行时显示进程报告。

  2. 要显示库的目录,请输入:
    ar -v -t lib.a

    此命令列出了 lib.a 库的目录,显示类似于 ls -l 命令的输出的长列表。要只列出成员文件名称,则省略 -v 标志。

  3. 要替换或添加新成员到库中,请输入:
    ar -v -r lib.a strlen.o strcat.o

    此命令替换成员 strlen.o 和 strcat.o。如果 lib.a 如示例 1 中显示的那样创建,则替换 strlen.o 成员。因为不存在名为 strcat.o 的成员,所以它被添加到库的末尾。

  4. 要指定在何处插入新成员,请输入:
    ar -v -r -b strlen.o lib.a strcmp.o

    此命令添加 strcmp.o 文件,并将该新成员置于 strlen.o 成员之前。

  5. 要更新一个已经更改过的成员,请输入:
    ar -v -r -u lib.a strcpy.o

    此命令替换现有 strcpy.o 成员,但仅当文件 strcpy.o 自从最后一次添加到库后已经修改时才替换它。

  6. 要更改库成员的顺序,请输入:
    ar -v -m -a strcmp.o lib.a strcat.o strcpy.o

    此命令将成员 strcat.o 和 strcpy.o 移动到紧跟在 strcmp.o 成员之后的位置。保留 strcat.o 和 strcpy.o 成员的相对顺序。换句话说,如果在移动之前 strcpy.o 成员在 strcat.o 成员之前,那么(移动后)它依旧如此。

  7. 要解压缩库成员,请输入:
    ar -v -x lib.a strcat.o strcpy.o

    此命令将成员 strcat.o 和 strcpy.o 分别复制到名为 strcat.o 和 strcpy.o 的文件。

  8. 要解压缩并重命名一个成员,请输入:
    ar -p lib.a strcpy.o >stringcopy.o

    此命令将成员 strcpy.o 复制到一个名为 stringcopy.o 的文件。

  9. 要删除一个成员,请输入:
    ar -v -d lib.a strlen.o

    此命令从 lib.a 库中删除成员 strlen.o。

  10. 要从多个用 ld 命令创建的共享模块中创建一个压缩文档库,请输入:
    ar -r -v libshr.a shrsub.o shrsub2.o shrsub3.o ...

    此命令从名为 shrsub.o、shrsub2.o、shrsub3.o 等等的共享模块中创建名为 libshr.a 的压缩文档库。要编译并链接使用 libshr.a 压缩文档库的 main 程序,请使用以下命令:

    cc -o main main.c -L/u/sharedlib -lshr

    main 程序现在是可执行的。main 程序引用的任何符号(包含在libshr.a 压缩文档库中)已经因延迟分辨率而作了标记。-l 标志指定应在 libshr.a 库中搜索这些符号。

  11. 要列出 lib.a 的内容(忽略任何 32 位目标文件),请输入:
    ar -X64 -t -v lib.a
  12. 要从 lib.a 解压缩所有 32 位的目标文件,请输入:
    ar -X32 -x lib.a
  13. 要列出 lib.a 中的所有文件,无论是 32 位、64 位或非对象,请输入:
    ar -X32_64 -t -v lib.a

七。nm

 nm用来列出目标文件的符号清单。下面是nm命令的格式:

nm[-a|--debug-syms][-g|--extern-only][-B]
[-C|--demangle][-D|--dynamic][-s|--print-armap]
[-o|--print-file-name][-n|--numeric-sort]
[-p|--no-sort][-r|--reverse-sort][--size-sort]
[-u|--undefined-only][-l|--line-numbers][--help]
[--version][-tradix|--radix=radix]
[-P|--portability][-fformat|--format=format]
[--target=bfdname][objfile...]

如果没有为nm命令指出目标文件,则nm假定目标文件是a.out。下面列出该命令的任选项,大部分支持“-”开头的短格式和“-“开头的长格式。 -A、-o或--print-file-name:在找到的各个符号的名字前加上文件名,而不是在此文件的所有符号前只出现文件名一次。

八。objcopy

它可以把目标文件的内容从一种文件格式复制到另一种格式的目标文件中。通过objcopy可以实现对elf不同模块的分析,同时实现类似strip的功能。

九。ldd

ldd 命令可以用来产看应用程序对库的依赖关系,在应用程序移植过程中很有用。可以首先确认要移植平时是否支持应用程序所依赖的库。例如:

$ ldd aout
        libc.so.6 => /lib64/tls/libc.so.6 (0x0000002a9566c000)
        /lib64/ld-linux-x86-64.so.2 (0x0000002a95556000)

十。size

size 列出目标模块或文件的代码尺寸

$ size a.out
   text    data     bss     dec     hex filename
   1241     512       8    1761     6e1 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.diybl.com/course/6_system/linux/Linuxjs/2008813/135859.html

2. http://www.diybl.com/course/3_program/c++/cppjs/2008624/128167.html

3. http://www.diybl.com/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 (推荐)

你可能感兴趣的:(二进制实用程序(objdump, readelf,ar, nm等))