引言

本文是对程序员的自我修养:链接、装载与库中第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 编译过程回顾

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 file和Core Dump file.

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文件的结构

  • 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文件的信息.

SYNOPSISreadelf [-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...DESCRIPTIONreadelf 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-headerDisplays the information contained in the ELF header at the start of the file.

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

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

  • 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

解析目标文件

分析完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是用来显示目标文件相关信息的。

NAMEobjdump - display information from object files.SYNOPSISobjdump [-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...DESCRIPTIONobjdump 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 wanttheir program to compile and work.
  • 查看目标文件的Section

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

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

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

  • 代码段

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

  • 数据段和只读数据段

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

  • BSS段

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

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

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

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

使用readelf和objdump剖析目标文件相关推荐

  1. objdump查看目标文件构成

    objdump objdump是用查看目标文件或者可执行的目标文件的构成的GCC工具 反汇编 #objdump -d cpuid2 对于其中的反汇编代码 左边是机器指令的字节,右边是反汇编结果.显然, ...

  2. Linux C目标文件

    LinuxC目标文件 宗旨:技术的学习是有限的,分享的精神是无限的. 一.目标文件格式(ELF格式) 编译器编译源代码后生成的文件叫做目标文件.目标文件是已经编译后的可执行文件,只是还没有经过链接的过 ...

  3. 《程序员的自我修养-链接-装载与库》第三章 目标文件里有什么(1)

    目录 0.引言 1.目标文件的格式 1.1 目标文件的格式及ELF文件格式的文件的分类 1.2 目标文件与可执行文件格式的小历史 2.目标文件是什么样的 2.1 程序与目标文件简介 2.2 BSS历史 ...

  4. 程序员的自我修养--链接、装载与库笔记:目标文件里有什么

    编译器编译源代码后生成的文件叫做目标文件.目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有被调整.其实它本身就是按照可执行文件格式存储的, ...

  5. C语言再学习-- readelf、objdump、nm使用详解

    在其他文章里,这三个指令用了好多次了,但是没有真正总结他们的用法,现在来讲解一下. 参看:readelf命令 参看:objdump命令 参看:nm命令 常用选项: (1)使用 readelf 显示一个 ...

  6. 目标文件(.o)结构的简单了解

    文章目录 SimpleSection.o 的结构 代码段 .text 数据段 .data和只读数据段 .rodata .bss段 其他段 今天来了解一下在编译过程中所产生的的目标文件的具体结构.(Ub ...

  7. linux 反汇编目标文件,用于查看目标文件或可执行文件的组成信息的命令:objdump命令...

    1.功能简介 objdump 命令是 GNU Binutils 二进制工具集的一员,用于查看目标文件或可执行文件的组成信息,以可读的形式打印二进制文件的内容. 2.命令格式 objdump[OPTIO ...

  8. ELF文件查看利器之readelf和objdump用法

    Android在NDK开发工具中提供了readelf,用来帮助开发者查看编译后目标文件的组成结构和具体内容. 常用的有以下几个功能选项: 1)-h或者--file-header 显示在ELF文件头里包 ...

  9. GCC编译器原理(二)------编译原理一:目标文件

    一.目标文件 在 UNIX® 和 Linux® 中,任何事物都是文件.UNIX 和 Linux 编程实际上是编写处理各种文件的代码.系统由许多类型的文件组成,但目标文件具有一种特殊的设计,提供了灵活和 ...

  10. 链接装载与库:第三章——目标文件里有什么(ELF文件结构)

    文章目录 一.ELF文件的格式 二.ELF文件是什么样的 三.挖掘SimpleSection.o 3.1 代码段 3.2 数据段和只读数据段 3.3 BSS段 3.4 其他段 3.5 自定义段 四.E ...

最新文章

  1. Javascript函数之深入浅出递归思想,附案例与代码!
  2. 编程之美3.3 计算两个字符串的相似度
  3. 如何自学python爬虫-怎样入门学习Python爬虫?
  4. android开发 权限大全
  5. struts2重定向
  6. linux complete函数,Linux驱动中completion接口浅析(wait_for_complete例子,很好)
  7. TFS 表字典(部分)
  8. 物理机安装ESXI6.7提示No Network Adapters的解决方案
  9. java 001 002_69期-Java SE-019-实用类-2-001-002
  10. icomoon.io生成字体图标
  11. 网络合作伙伴通信协议指南:NETWORK ASSOCIATES GUIDE TO COMMUNICATIONS PROTOCOLS
  12. 把.Net开发环境迁移到Linux上去
  13. IsPostBack结论
  14. 单片机74LS164C语言例子,74ls164单片机编程汇总(跑马灯/驱动数码管)
  15. 个人博客网站编写(01)
  16. RO,RW,ZI,FLASH,RAM前世今生
  17. Bert使用之一_基本使用
  18. 关于熔断器Hystrix替代方案的调研报告
  19. 清华大学出版社与SAP签署战略合作协议
  20. MAPGIS67无法显示点、无法保存点至e00的解决方法

热门文章

  1. Python计算器程序实现,支持括号与符号检测、小数、负数运算
  2. OpenDDS简单示例
  3. Open browser failed!! Please check if you have installed the browser correctly! Alt + B无效 - VS Code
  4. 三菱FX3U-1PG模块与台达A2伺服连接用法
  5. 【MOOC-生物信息学-蛋白质结构预测与分析】(占坑)
  6. 学三菱plc编程应该先学什么?
  7. 机器学习与深度学习资料整理
  8. java网络蜘蛛_基于java网络蜘蛛程序
  9. 计算机的显卡控制面板在哪里,nvidia控制面板在哪里打开
  10. 破解百度空间、新浪相册、网易、搜狐等博客图片防盗链的方法