ELF文件结构分析(x86 gnu版本)

发布时间 2023-10-28 20:54:59作者: zephyr~

为了学习使用objdump和size命令,以simple_section.c为例进行分析。
编译环境是x86 ubuntu,首先编译这个文件。
gcc -c simple_section.c

命令解释

objdump

作用:分析二进制文件的内容信息
objdump --help

Usage: objdump <option(s)> <file(s)>
 Display information from object <file(s)>.
 At least one of the following switches must be given:
  -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
  -P, --private=OPT,OPT... Display object format specific 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
      --disassemble=<sym>  Display assembler contents from <sym>
  -S, --source             Intermix source code with disassembly
      --source-comment[=<txt>] Prefix lines of source code with <txt>
  -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
  • -d: 将代码段反汇编
  • -S:将代码段反汇编的同时,将反汇编代码和源代码交替显示,编译时需要给出-g,即需要调试信息。
  • -C:将C++符号名逆向解析。
  • -l:反汇编代码中插入源代码的文件名和行号。
  • -j section:仅反汇编指定的section。可以有多个-j参数来选择多个section。

size

作用:显示目标文件.code代码段、.data数据段、.bss段的大小。
使用size命令要分别使用-A,-G参数,查看代码段和数据段的参数,综合判断
size --help

Usage: size [option(s)] [file(s)]
 Displays the sizes of sections inside binary files
 If no input file(s) are specified, a.out is assumed
 The options are:
  -A|-B|-G  --format={sysv|berkeley|gnu}  Select output style (default is berkeley)
  -o|-d|-x  --radix={8|10|16}         Display numbers in octal, decimal or hex
  -t        --totals                  Display the total sizes (Berkeley only)
            --common                  Display total size for *COM* syms
            --target=<bfdname>        Set the binary file format
            @<file>                   Read options from <file>
  -h        --help                    Display this information
  -v        --version                 Display the program's version

readelf

readelf比objdump能显示更多的信息,比如readelf -S simple_setction.o,会显示所有的段。objdump -h只是显示了关键的几个段。
readelf --help

Usage: readelf <option(s)> elf-file(s)
 Display information about the contents of ELF format files
 Options are:
  -a --all               Equivalent to: -h -l -S -s -r -d -V -A -I
  -h --file-header       Display the ELF file header
  -l --program-headers   Display the program headers
     --segments          An alias for --program-headers
  -S --section-headers   Display the sections' header
     --sections          An alias for --section-headers
  -g --section-groups    Display the section groups
  -t --section-details   Display the section details
  -e --headers           Equivalent to: -h -l -S
  -s --syms              Display the symbol table
     --symbols           An alias for --syms
  --dyn-syms             Display the dynamic symbol table
  -n --notes             Display the core notes (if present)
  -r --relocs            Display the relocations (if present)
  -u --unwind            Display the unwind info (if present)
  -d --dynamic           Display the dynamic section (if present)
  -V --version-info      Display the version sections (if present)
  -A --arch-specific     Display architecture specific information (if any)
  -c --archive-index     Display the symbol/file index in an archive
  -D --use-dynamic       Use the dynamic section info when displaying symbols
  -x --hex-dump=<number|name>
                         Dump the contents of section <number|name> as bytes
  -p --string-dump=<number|name>
                         Dump the contents of section <number|name> as strings
  -R --relocated-dump=<number|name>
                         Dump the contents of section <number|name> as relocated bytes
  -z --decompress        Decompress section before dumping it

各个段的基本信息

objdump -h simple_section.o

simple_section.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000005f  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  000000a0  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  0000000000000000  0000000000000000  000000a8  2**2
                  ALLOC
  3 .rodata       00000004  0000000000000000  0000000000000000  000000a8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002c  0000000000000000  0000000000000000  000000ac  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000d8  2**0
                  CONTENTS, READONLY
  6 .note.gnu.property 00000020  0000000000000000  0000000000000000  000000d8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .eh_frame     00000058  0000000000000000  0000000000000000  000000f8  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

size -A simple_section.o

simple_section.o  :
section              size   addr
.text                  95      0
.data                   8      0
.bss                    4      0
.rodata                 4      0
.comment               44      0
.note.GNU-stack         0      0
.note.gnu.property     32      0
.eh_frame              88      0
Total                 275

.code代码段

从反汇编内容可以看到,代码段长度为0x5e+1等于0x5f=95,需要加上最后的一个字节的retq指令
与objdump -h和size -A显示的代码段大小相同。

objdump -d simple_section.o

simple_section.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <func1>:
   0:	f3 0f 1e fa          	endbr64
   4:	55                   	push   %rbp
   5:	48 89 e5             	mov    %rsp,%rbp
   8:	48 83 ec 10          	sub    $0x10,%rsp
   c:	89 7d fc             	mov    %edi,-0x4(%rbp)
   f:	8b 45 fc             	mov    -0x4(%rbp),%eax
  12:	89 c6                	mov    %eax,%esi
  14:	48 8d 3d 00 00 00 00 	lea    0x0(%rip),%rdi        # 1b <func1+0x1b>
  1b:	b8 00 00 00 00       	mov    $0x0,%eax
  20:	e8 00 00 00 00       	callq  25 <func1+0x25>
  25:	90                   	nop
  26:	c9                   	leaveq
  27:	c3                   	retq

0000000000000028 <main>:
  28:	f3 0f 1e fa          	endbr64
  2c:	55                   	push   %rbp
  2d:	48 89 e5             	mov    %rsp,%rbp
  30:	48 83 ec 10          	sub    $0x10,%rsp
  34:	c7 45 f8 01 00 00 00 	movl   $0x1,-0x8(%rbp)
  3b:	8b 15 00 00 00 00    	mov    0x0(%rip),%edx        # 41 <main+0x19>
  41:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 47 <main+0x1f>
  47:	01 c2                	add    %eax,%edx
  49:	8b 45 f8             	mov    -0x8(%rbp),%eax
  4c:	01 c2                	add    %eax,%edx
  4e:	8b 45 fc             	mov    -0x4(%rbp),%eax
  51:	01 d0                	add    %edx,%eax
  53:	89 c7                	mov    %eax,%edi
  55:	e8 00 00 00 00       	callq  5a <main+0x32>
  5a:	8b 45 f8             	mov    -0x8(%rbp),%eax
  5d:	c9                   	leaveq
  5e:	c3                   	retq

.data数据段

数据段存放的是已初始化的全局静态变量和局部静态变量
所以只保留了simple_section.c文件的global_init_var和static_var两个变量,共8个字节。

.rodata只读数据段

printf使用的字符串常量"%d\n",一共四个字符放到了".rodata段"。

.bss段(block started by symbol)

.bss段存放的是未初始化的全局变量和局部静态变量
所以只保留了simple_section.c代码的global_uinit_var和static_var2。但是我们看到该测试代码只有4个字节,即只有static_var2。
通过查看符号表,我们可以看到global_uinit_var是一个"COMMON"符号。这跟编译器的实现有关。有些编译器会将全局未初始化变量放到目标文件的.bss段(arm gnu编译器)。有些则不放(x86 gnu编译器),只是预留一个未定义全局变量符号。等最终链接可执行文件的时候才会分配到.bss段。

其他段

段的名字以"."作为前缀,表示这些段的名字是为系统保留的

  • .rodata1
    Read only Data,这种段里存放的是只读数据,比如字符串常量、全局CONS
    变量。跟".rodata"一样
  • .comment
    存放的是编译器版本信息,比如字符串:"GCC:(GNU1) 4.2.0'
  • .debug
    调试信息
  • .dynamic
    动态链接信息
  • hash
    符号哈希表
  • .line
    调试时的行号表,即源代码行号与编译后指令的对应表
  • .note
    额外的编译器信息。比如程序的公司名、发布版本号等
  • .strtab
    String Table.字符串表,用于存储ELF文件中用到的各种字符串
  • .symtab
    Symbol Table。符号表
  • .shstrtab
    SectionString Table。段名表
  • .plt / .got
    动态链接的跳转表和全局入口表
  • .init / .fini
    程序初始化与终结代码段。"C++全局构造与析构"会用到

自定义段

GCC提供的扩展机制,将指定的变量和函数放到所处的段。
"__attribute__((section("name")))"

simple_section.c示例代码

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 a;
}