ELF文件结构分析(x86 gnu版本)
为了学习使用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;
}