使用readelf和objdump剖析目标文件

引言

        本文是对程序员的自我修养:链接、装载与库中第3章的学习与实践总结,通过使用工具 readelfobjdump对目标文件进行解析,学习目标文件的结构。

1. 目标文件

1.1 目标文件的定义

        编译器编译源代码后生成的文件叫做目标文件。在Linux下,使用gcc -c xxxx.c编译生成.o文件。

wezhu@bios-thinkstation-p720:~/linux_training/elf$ gcc -c simple.c
wezhu@bios-thinkstation-p720:~/linux_training/elf$ ls
a.out  core  coredump.c  hello.c  simple.c  simple.o

1.2 编译过程回顾

058e881d2ecc12424bb8117f02d3c522982.jpg

ELF文件类型

目标文件的文件类型为ELF,在Linux下对应文件后缀为.o的文件,Window下对应文件后缀为.obj的文件。使用file命令可以查看到.o和.obj文件均为ELF类型。

wezhu@bios-thinkstation-p720:~/linux_training/elf$ file ~/gr-mrb-bios/external_src/tools/ippcp_check_key/src/ippcp_check_key.o
ippcp_check_key.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
wezhu@bios-thinkstation-p720:~/linux_training/elf$  file /lib/x86_64-linux-gnu/ld-2.23.so
/lib/x86_64-linux-gnu/ld-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=c0adbad6f9a33944f2b3567c078ec472a1dae98e, stripped
wezhu@bios-thinkstation-p720:~/linux_training/elf$ file core
core: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from './a.out'
wezhu@bios-thinkstation-p720:~/linux_training/elf$ file /bin/bash
/bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=6f072e70e3e49380ff4d43cdde8178c24cf73daa, stripped

目标文件只是ELF文件的可重定位文件(Relocatable file),ELF文件一共有4种类型:Relocatable file、Executable file、Shared object fileCore Dump file.

65bd1a7e680733287ddfa0615eb63ad78d1.jpg

ELF 文件类型说明
可重定位文件(Relocatable File)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
可执行文件(Executable File)包含适合于执行的一个程序,此文件规定了 exec() 如何创建一个程序的进程映像。
共享目标文件(Shared Object File)包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。
核心转储文件(core dump)当进程意外终止时,系统可以将该进程的的地址空间的内容及终止时的一些其他信息转储到核心转储文件。
  • 示例

在Linux下,使用命令 gcc -c xxxx.c就可以编译生成.o文件

wezhu@bios-thinkstation-p720:~/linux_training/elf$ gcc -c simple.c
wezhu@bios-thinkstation-p720:~/linux_training/elf$ ls
a.out  core  coredump.c  hello.c  simple.c  simple.o

在 simple.c中,我们只加入了下面这一个函数fun,函数内容为空

void fun()
{
 
}

使用hexdump -C将simple.o打开,里面的内容有机器指令代码、数据等,我们的程序就是由这些字节组成的。对于程序员来说,使用高级语言(C/C++,Java等)实现的代码是最容易阅读和理解的,但是对于计算机来说,它只懂得机器语言,它更喜欢二进制。

我们可以使用工具readelf objdump对目标文件simple.o进行分析。为了加深对目标文件的理解,在使用readelf & objdump进行前,需要先要了解ELF文件的结构。

wezhu@bios-thinkstation-p720:~/linux_training/elf$ hexdump -C simple.o
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 3e 00 01 00 00 00  00 00 00 00 00 00 00 00  |..>.............|
00000020  00 00 00 00 00 00 00 00  10 02 00 00 00 00 00 00  |................|
00000030  00 00 00 00 40 00 00 00  00 00 40 00 0b 00 08 00  |....@.....@.....|
00000040  55 48 89 e5 90 5d c3 00  47 43 43 3a 20 28 55 62  |UH...]..GCC: (Ub|
00000050  75 6e 74 75 20 35 2e 34  2e 30 2d 36 75 62 75 6e  |untu 5.4.0-6ubun|
00000060  74 75 31 7e 31 36 2e 30  34 2e 31 31 29 20 35 2e  |tu1~16.04.11) 5.|
00000070  34 2e 30 20 32 30 31 36  30 36 30 39 00 00 00 00  |4.0 20160609....|
00000080  14 00 00 00 00 00 00 00  01 7a 52 00 01 78 10 01  |.........zR..x..|
00000090  1b 0c 07 08 90 01 00 00  1c 00 00 00 1c 00 00 00  |................|
000000a0  00 00 00 00 07 00 00 00  00 41 0e 10 86 02 43 0d  |.........A....C.|
000000b0  06 42 0c 07 08 00 00 00  00 00 00 00 00 00 00 00  |.B..............|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  01 00 00 00 04 00 f1 ff  00 00 00 00 00 00 00 00  |................|
000000e0  00 00 00 00 00 00 00 00  00 00 00 00 03 00 01 00  |................|
000000f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000100  00 00 00 00 03 00 02 00  00 00 00 00 00 00 00 00  |................|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 03 00 03 00  |................|
00000120  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000130  00 00 00 00 03 00 05 00  00 00 00 00 00 00 00 00  |................|
00000140  00 00 00 00 00 00 00 00  00 00 00 00 03 00 06 00  |................|
00000150  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000160  00 00 00 00 03 00 04 00  00 00 00 00 00 00 00 00  |................|
00000170  00 00 00 00 00 00 00 00  0a 00 00 00 12 00 01 00  |................|
00000180  00 00 00 00 00 00 00 00  07 00 00 00 00 00 00 00  |................|
00000190  00 73 69 6d 70 6c 65 2e  63 00 66 75 6e 00 00 00  |.simple.c.fun...|
000001a0  20 00 00 00 00 00 00 00  02 00 00 00 02 00 00 00  | ...............|
000001b0  00 00 00 00 00 00 00 00  00 2e 73 79 6d 74 61 62  |..........symtab|
000001c0  00 2e 73 74 72 74 61 62  00 2e 73 68 73 74 72 74  |..strtab..shstrt|
000001d0  61 62 00 2e 74 65 78 74  00 2e 64 61 74 61 00 2e  |ab..text..data..|
000001e0  62 73 73 00 2e 63 6f 6d  6d 65 6e 74 00 2e 6e 6f  |bss..comment..no|
000001f0  74 65 2e 47 4e 55 2d 73  74 61 63 6b 00 2e 72 65  |te.GNU-stack..re|
00000200  6c 61 2e 65 68 5f 66 72  61 6d 65 00 00 00 00 00  |la.eh_frame.....|
00000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*

ELF文件结构

        和class文件类似,ELF文件存放数据的格式也是固定的,计算机在解析目标文件时,就是按照它每个字段的数据结构进行逐字解析的。ELF文件结构信息定义在/usr/include/elf.h中,整个ELF文件的结构如下图:

ELF文件的结构

image2019-8-16_15-53-53.png?version=1&modificationDate=1565942036451&api=v2

  • ELF Header

        ELF Header是ELF文件的第一部分,64 bit的ELF文件头的结构体如下:

typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf64_Half    e_type;                 /* Object file type */
  Elf64_Half    e_machine;              /* Architecture */
  Elf64_Word    e_version;              /* Object file version */
  Elf64_Addr    e_entry;                /* Entry point virtual address */
  Elf64_Off     e_phoff;                /* Program header table file offset */
  Elf64_Off     e_shoff;                /* Section header table file offset */
  Elf64_Word    e_flags;                /* Processor-specific flags */
  Elf64_Half    e_ehsize;               /* ELF header size in bytes */
  Elf64_Half    e_phentsize;            /* Program header table entry size */
  Elf64_Half    e_phnum;                /* Program header table entry count */
  Elf64_Half    e_shentsize;            /* Section header table entry size */
  Elf64_Half    e_shnum;                /* Section header table entry count */
  Elf64_Half    e_shstrndx;             /* Section header string table index */
} Elf64_Ehdr;

     接下来我们会使用到第一个分析目标文件的工具readelf,通过man readelf命令,我们可以查到readelf的作用就是用来显示ELF文件的信息.

SYNOPSIS
       readelf [-a|--all]
               [-h|--file-header]
               [-l|--program-headers|--segments]
               [-S|--section-headers|--sections]
               [-g|--section-groups]
               [-t|--section-details]
               [-e|--headers]
               [-s|--syms|--symbols]
               [--dyn-syms]
               [-n|--notes]
               [-r|--relocs]
               [-u|--unwind]
               [-d|--dynamic]
               [-V|--version-info]
               [-A|--arch-specific]
               [-D|--use-dynamic]
               [-x <number or name>|--hex-dump=<number or name>]
               [-p <number or name>|--string-dump=<number or name>]
               [-R <number or name>|--relocated-dump=<number or name>]
               [-z|--decompress]
               [-c|--archive-index]
               [-w[lLiaprmfFsoRt]|
                --debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=frames-interp,=str,=loc,=Ranges,=pubtypes,=trace_info,=trace_abbrev,=trace_aranges,=gdb_index]]
               [--dwarf-depth=n]
               [--dwarf-start=n]
               [-I|--histogram]
               [-v|--version]
               [-W|--wide]
               [-H|--help]
               elffile...
 
DESCRIPTION
       readelf displays information about one or more ELF format object files.  The options control what particular information to display.

  使用readelf -h simple.o来进行对Header的解析,通过man readelf命令同样可以查询到对-h参数的说明,
        -h用来显示ELF header的相关信息。

-h
--file-header
  Displays the information contained in the ELF header at the start of the file.

   Header中主要存放的是一些基本信息,通过Header中的信息,我们可以确定后面其他字段的大小和起始地址,通常比较关心的部分是:ELF文件类型、是32bit还是64bit、Header部分大小、Section部分大小和拥有Section的个数等。

        结合Elf64_Ehdr来看,对应解析结果如下:

image2019-8-16_16-54-38.png?version=1&modificationDate=1565945680577&api=v2

  • Section

       完成了对Header的解析,再接着分析Section部分,Section对应结构体如下:

typedef struct
{
  Elf64_Word  sh_name;    /* Section name (string tbl index) */
  Elf64_Word  sh_type;    /* Section type */
  Elf64_Xword sh_flags;   /* Section flags */
  Elf64_Addr  sh_addr;    /* Section virtual addr at execution */
  Elf64_Off sh_offset;    /* Section file offset */
  Elf64_Xword sh_size;    /* Section size in bytes */
  Elf64_Word  sh_link;    /* Link to another section */
  Elf64_Word  sh_info;    /* Additional section information */
  Elf64_Xword sh_addralign;   /* Section alignment */
  Elf64_Xword sh_entsize;   /* Entry size if section holds table */
} Elf64_Shdr;

Section部分主要存放的是机器指令代码和数据,执行命令readelf -S -W simple.o对Section部分的解析,解析结果和Elf64_Shdr也是一一对应的。

wezhu@bios-thinkstation-p720:~/linux_training/elf$ readelf -S -W simple.o
There are 11 section headers, starting at offset 0x108:
 
Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        0000000000000000 000040 000006 00  AX  0   0  4
  [ 2] .data             PROGBITS        0000000000000000 000048 000000 00  WA  0   0  4
  [ 3] .bss              NOBITS          0000000000000000 000048 000000 00  WA  0   0  4
  [ 4] .comment          PROGBITS        0000000000000000 000048 00002b 01  MS  0   0  1
  [ 5] .note.GNU-stack   PROGBITS        0000000000000000 000073 000000 00      0   0  1
  [ 6] .eh_frame         PROGBITS        0000000000000000 000078 000038 00   A  0   0  8
  [ 7] .rela.eh_frame    RELA            0000000000000000 0004b0 000018 18      9   6  8
  [ 8] .shstrtab         STRTAB          0000000000000000 0000b0 000054 00      0   0  1
  [ 9] .symtab           SYMTAB          0000000000000000 0003c8 0000d8 18     10   8  8
  [10] .strtab           STRTAB          0000000000000000 0004a0 00000e 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

对于这部分内容,通常我们比较的Section是.text(存放代码)、.data(存放全局静态变量和局部静态变量).bss(存未初始化的全局变量和局部静态变量) ,在后面会对这几个段分别分进行解析。

根据readelf -S -W simple.o的输出结果,我们可以算出整个simple.o的组成部分和起始地址,使用ls -l 命令查看simple.o的大小,和simple.o结束地址0x000004d0是吻合的。

wezhu@bios-thinkstation-p720:~/linux_training/elf$ ls -l simple.o
-rw-rw-r-- 1 wezhu wezhu 1224 Aug 16 15:22 simple.o

e589b324317263e3eb695dbdaa38337e0d0.jpg

解析目标文件

分析完ELF文件结构,接着来解析一个目标文件。首先,准备好源码SimpleSection.c,执行命令gcc -c SimpleSection.c生成目标文件SimpleSection.o。

int printf(const char* format, ...);
 
int global_init_var = 84;
int global_uninit_var;
 
void func1(int i)
{
    printf("%d\n", i);
}
 
int main(void)
{
    static int static_var = 85;
    static int static_var2;
 
    int a = 1;
    int b;
    func1(static_var + static_var2 + a + b);
    return 0;
}

在这部分,我们会使用另外一个命令objdump,使用man objdump查看该命令,objdump是用来显示目标文件相关信息的。

NAME
       objdump - display information from object files.
 
SYNOPSIS
       objdump [-a|--archive-headers]
               [-b bfdname|--target=bfdname]
               [-C|--demangle[=style] ]
               [-d|--disassemble]
               [-D|--disassemble-all]
               [-z|--disassemble-zeroes]
               [-EB|-EL|--endian={big | little }]
               [-f|--file-headers]
               [-F|--file-offsets]
               [--file-start-context]
               [-g|--debugging]
               [-e|--debugging-tags]
               [-h|--section-headers|--headers]
               [-i|--info]
               [-j section|--section=section]
               [-l|--line-numbers]
               [-S|--source]
               [-m machine|--architecture=machine]
               [-M options|--disassembler-options=options]
               [-p|--private-headers]
               [-P options|--private=options]
               [-r|--reloc]
               [-R|--dynamic-reloc]
               [-s|--full-contents]
               [-W[lLiaprmfFsoRt]|
                --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames]
                        [=aranges,=macro,=frames,=frames-interp,=str,=loc]
                        [=Ranges,=pubtypes,=trace_info,=trace_abbrev]
                        [=trace_aranges,=gdb_index]
               [-G|--stabs]
               [-t|--syms]
               [-T|--dynamic-syms]
               [-x|--all-headers]
               [-w|--wide]
               [--start-address=address]
               [--stop-address=address]
               [--prefix-addresses]
               [--[no-]show-raw-insn]
               [--adjust-vma=offset]
               [--special-syms]
               [--prefix=prefix]
               [--prefix-strip=level]
               [--insn-width=width]
               [-V|--version]
               [-H|--help]
               objfile...
 
DESCRIPTION
       objdump displays information about one or more object files.  The options control what particular information to display.  This information is mostly useful to programmers who are working on the compilation tools, as opposed to programmers who just want
       their program to compile and work.
  • 查看目标文件的Section

      执行命令objdump -h SimpleSection.o对Section部分进行解析,我们可以得到每个段的大小

wezhu@bios-thinkstation-p720:~/linux_training/elf$ objdump -h SimpleSection.o
 
SimpleSection.o:     file format elf64-x86-64
 
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000052  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  00000094  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  0000000000000000  0000000000000000  0000009c  2**2
                  ALLOC
  3 .rodata       00000004  0000000000000000  0000000000000000  0000009c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002b  0000000000000000  0000000000000000  000000a0  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000cb  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000058  0000000000000000  0000000000000000  000000d0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DAT

   我们的代码是存放到.text中,已初始化全局变量和局部静态变量存放在.data中,未初始化全局变量和局部静态变量存放在.bss中

4e71d43a3184fe67c3b2f884aca2d3fc39c.jpg

  • 代码段

      执行命令objdump -s -d SimpleSection.o对代码段(.text)的解析结果如下:

2118438-9ae4c840de26b89d.png?version=1&modificationDate=1565947139533&api=v2

  • 数据段和只读数据段

      执行命令objdump -s -d SimpleSection.o对数据段和只读数据段解析结果如下:

2118438-83281ea41014cdf3.png?version=1&modificationDate=1565947221610&api=v2

  • BSS段

      执行命令objdump -x -s -d SimpleSection.o打印出目标文件的符号表,通过符号表我们可以知道各个变量的存放位置,只有未初始化的局部静态变量static_var2被放到了.bss段,而global_uninit_var被放入了comment段

2118438-b20d58144ede3e3d.png?version=1&modificationDate=1565947256621&api=v2

      另外,被初始化为0的静态变量也会被放入.bss段,因为未初始变量的值也是0,经过优化后被放入.bss段,这样可以节省磁盘空间,因为.bss不占磁盘空间

      例如,下面的代码中x1会被放入.bss段,而x2被放入.data段

static int x1 = 0;
static int x2 = 12;

cf378af9d1939a5aff762ee4847f8ee9d6e.jpg

转载于:https://my.oschina.net/weitao520lin/blog/3101177

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值