ELF文件格式基础介绍
ELF文件
全称为(Executable and Linkable Format)
概念
ELF文件本质上是一种数据结构,数据的载体。
于ELF相关的几个概念:
- Section
组成ELF内容的最基本单元。
具体的可见上图.dynsym,.dynstr,.hash,.bss,.txt,……
每一个都是一个section
- Segment
执行过程中的加载映射的最小单元,一个Segment即是多个Section的集合。
- Linkable View
ELF 未被加载到内存执行前,以 section 为单位的数据组织形式
- Executable View
ELF 被加载到内存后,以 segment 为单位的数据组织形式
看到这可以会有一个问题——“关于为什么有Section,Segment,Linkable View,Executable View“
其实很简单,ELF被切分成两个大的过程 编译,执行
- Section & Linkable View是编译过程的数据呈现形式
- Segment & Executable View是运行过程中的数据呈现形式
构建过程 | |
---|---|
链接过程 |
基础Section介绍
.text:可谓是最重要的部分,存放 CPU
执行的机器码。
.rela.text:记录 .text
Section
中的重定向地址,简单来说就是 .text
中的某些使用到的地址是不可用的,需要借助 .rela.text
重新计算地址。当别的 Section
也需要重新计算地址时也会有一个对应的 .rela.xxx
的 Section
,例如 .rela.plt
。
.symtab:符号表,描述了我们定义的方法和全局变量等等,我们可以通过符号表中的信息定位到这些符号所对应的地址。
.shstrtab / .strtab:字符表,描述程序中用到的字符,比如代码中用到的方法名,变量名等等。它的记录方法很简单就是一个字符串相对于该表的偏移量,每一个字符串结束都用 \0
表示。
.data:存放已经初始化后的全局变量。
.bss:存放没有初始化的全局变量。
.rodata:存放常量。
Binutils
了解一个概念的最好手段,一定是手动实践
Binutils 是 GNU开源的一系列处理二进制的工具。
当然这里的二进制包含ELF,我们可以借助这部分工具check一下ELF文件是否是和概念上说的一致。
Note:急急国王可直接跳到objdump,readelf介绍部分,动手实践
ld
ld命令 是GNU的连接器,将目标文件连接为可执行程序。
这个指令通常不会单独去使用,通常是由编译器调用
collect2是ld链接器的
1 | COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' |
as
test.c
1 |
|
main.c
1 |
|
C语言编译成汇编
1 | gcc -S test1.c main.c |
使用汇编进行汇编
1 | as -ac main.s |
1 | as -ac test1.s |
addr2line
和名称和符合,address to line
将地址对应到行号
(注意需要有符号表才可以读取)
准备工作
书写测试代码
1 |
|
编译(加入符号表)
1 | gcc -g -o main.out main.c |
运行一下
1 | ➜ c ./main.out |
读取函数地址
1 | ➜ c readelf -s main.out |
使用addr2line
1 | ➜ c addr2line -e main.out 0x000000000000114a |
对照一下源代码发现,index索引正常
ar
A utility for creating, modifying and extracting from archives
ar是一个一个用于创建,修改,导出archives格式文件的工具(即.a文件)
创建
创建.a文件
1 |
|
编译.o文件
1 | ➜ c gcc -o test.o -c test.c |
创建
1 | -r 插入或者replace源文件 |
查看
1 | t 查看指定archive文件的内容 |
修改
编写文件
1 |
|
编译
1 | ➜ c gcc -o test2.o -c test2.c |
插入
1 | ➜ c ar rc modified.a test.a test2.o |
查看文件
1 | ➜ c file modified.a |
删除文件
1 | d删除指定的file |
查看
1 | ➜ c ar t modified.a |
导出
把之前创建的test.o,test1.o合并成一个.a文件
1 | ➜ c ar cr all.a test.o test2.o |
创建文件 & 放入 & 进入路径
1 | ➜ c mkdir test |
提取导出
1 | ➜ test ar x all.a |
查看
1 | ➜ test ll |
c++filt
用于读取c++的mangled name输出复原后的函数名称
cpp是支持面向对象的,是支持函数重载的,代价是c++编译器需要对C++ 函数名做变换。
会依据函数名,函数参数生成另外一个函数名称,这个过程叫name mangling
我们可以编写一部分代码来check一下
代码编写
1 |
|
编译
1 | g++ -o a.out a.cpp |
读取下子符号表
1 | readelf -s a.out |
1 | ...... |
_Z表示开头
13表示函数名称为13个字符(不信你自己数)
v表示函数参数列表为void
切入正题,c++filt有啥用?
复原mangled name
1 | ➜ c c++filt _Z13helloWorldCppv |
nm
用于查看二进制文件中的符号表信息
使用
1 | include <stdio.h> |
编译
1 | gcc -o testNm testnm.c |
nm查看符号表
输出结果有三行
相对偏移,类型,符号内容
1 | ➜ c nm testNm |
- B (大写): 表示未初始化的数据段(BSS段)中的全局变量。在这个例子中,
__bss_start
和__TMC_END__
是这种类型的符号。 - b (小写): 与大写B相似,表示未初始化的数据段中的局部变量。在这个例子中,
completed.0
是这种类型的符号。 - D: 表示已初始化的数据段中的全局变量。在这个例子中,
__data_start
是这种类型的符号。 - W: 表示弱引用,这些符号可能会被链接器优化或者被其他强符号覆盖。在这个例子中,
__cxa_finalize
和data_start
是这种类型的符号。 - T: 表示代码段中的全局函数。在这个例子中,
_fini
、main
、printHello
和_start
是这种类型的符号。 - t: 与大写T相似,表示代码段中的局部函数。在这个例子中,
deregister_tm_clones
、__do_global_dtors_aux
、frame_dummy
和register_tm_clones
是这种类型的符号。 - U: 表示未定义的符号,需要在链接时解析。在这个例子中,
__libc_start_main
和printf
是这种类型的符号。 - R: 表示只读数据段中的全局变量。在这个例子中,
_IO_stdin_used
是这种类型的符号。 - w: 与大写W相似,表示弱引用的全局符号。在这个例子中,
__gmon_start__
、_ITM_deregisterTMCloneTable
和_ITM_registerTMCloneTable
是这种类型的符号。 - V: 表示弱引用的局部符号,类似于小写b。
——By ChatGPT
objcopy
Copies and translates object files.
拷贝转换文件格式
复制
仅仅针对于二进制格式的文件
objcopy: supported targets: elf64-x86-64 elf32-i386 elf32-iamcu elf32-x86-64 pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big pe-x86-64 pe-bigobj-x86-64 pe-i386 srec symbolsrec verilog tekhex binary ihex plugin
1 | ➜ c objcopy a.out b.out |
格式转换
objcopy -O 输出转化格式 in out
1 | ➜ c objcopy -O elf32-x86-64 a.out b.out |
objdump
Displays information from object files.
汇编
1 | ➜ c objdump -d a.out |
1 |
|
这里注意下不是只有代码段有代码
.init
.plt
.plt.got
.text
.fini
都有代码。
显示符号表
1 | ➜ c objdump -t a.out |
1 | a.out: file format elf64-x86-64 |
显示所有section
1 | ➜ c objdump -h a.out |
1 | Sections: |
显示重定位表
编辑
1 |
|
编译
1 | ➜ c gcc -o tester.o -c tester.c |
查看重定位表
1 | ➜ c objdump -t tester.o |
查看动态链接表
1 | ➜ c objdump -T a.out |
readelf
readelf
是一个用于查看 ELF(Executable and Linkable Format,可执行与可链接格式)文件信息的命令行工具。ELF 是一种常见的二进制文件格式,用于执行文件、共享库和目标文件。
读取文件头
1 | ➜ c readelf -h a.out |
读取section header & sections
1 | ➜ c readelf -S a.out |
读取program header & segments
1 | ➜ c readelf -l a.out |
读取符号表
1 | ➜ c readelf -s a.out |
读取重定位表
1 | ➜ c readelf -r a.out |
size
用于查看各sections的大小分布
1 | ➜ c size a.out |
这个表格中的各列的含义如下:
text
:代码段的大小,包含可执行代码。data
:数据段的大小,包含初始化的全局和静态变量。bss
:未初始化的数据段(Block Started by Symbol)的大小,包含未初始化的全局和静态变量。dec
:所有段的总大小(text + data + bss
)。hex
:以十六进制表示的总大小。filename
:目标文件或可执行文件的名称。
1 | ➜ c size -A a.out |
strings
“strings” 命令用于在二进制文件中查找并打印可打印字符组成的字符串。
1 | ➜ c strings a.out |
其他
这部分内容由于不是重点不做介绍,但是为了知识的完整性列一下
可执行工具
- gold
gold - The GNU ELF linker(同ld)
区别
ld是一个默认的linker,性能也一般
gold是新一代的linker,内存占用更少,有更强的性能。
- dlltool
用于修复Windows平台dll动态链接库的工具
- ranlib
在早期的 UNIX 系统中,
ar
工具并不会自动创建符号表索引,而需要使用ranlib
工具来显式地为静态库添加索引。ranlib
的存在是为了解决链接速度的问题。——ChatGPT
- strip
strip
是一个用于去除可执行文件或目标文件中的符号表、调试信息以及其他不必要信息的工具。这个工具通常用于减小二进制文件的大小,特别是在发布产品版本时。
- windmc
windmc
是 Windows 平台上的一个工具,用于处理 Windows 消息资源文件(.mc
文件),生成消息定义文件(.h
文件)和二进制消息资源文件(.rc
文件)。——ChatGPT
- windres
windres
是用于处理 Windows 资源文件的工具,它通常用于将资源文件(如图标、位图、对话框模板等)编译成 Windows 可执行文件中的二进制资源。
- nlmconv
Converts object code into an NLM
nlmconv
是 Novell NetWare 操作系统中的一个实用程序,用于将 NetWare Loadable Module (NLM) 的源代码或可执行文件转换为 NLM 格式。
- gprof
Displays profiling information
用于展示性能分析信息
- gprofng
Collects and displays application performance data
收集展示性能数据
library
- libctf
libctf
是用于处理 CTF(Compact C Type Format)格式的库。CTF 是一种用于表示和交换 C 编程语言中类型信息的二进制格式。这种格式通常用于支持调试信息、静态分析和其他需要了解程序中类型信息的工具。
- libbfd
libbfd
是 GNU Binutils 工具集中的一部分,用于处理二进制文件的库。它提供了对多种目标文件格式(例如 ELF、COFF、Mach-O 等)的统一接口,以便进行操作、解析和生成二进制文件。
- libopcodes
libopcodes
是 GNU Binutils 工具集中的一部分,提供了一套用于解析和处理操作码(opcodes)的库。这个库允许开发者编写工具,能够解析和操作各种体系结构的指令集。
- libsframe
A library for manipulating the SFRAME debug format
用于操作 SFRAME 调试格式的库
- elfedit
elfedit
是一个用于修改 ELF(Executable and Linkable Format,可执行与可链接格式)文件的命令行工具。主要是用来修改指令架构,文件类型(动态链接,执行文件)