elf文件结构:
文件头
整个elf文件的
基本属性,包括elf类型(可重定向、可执行、共享目标文件),字节序(大小端)等等。使用readelf -h 可以查看文件头
段表
各个段的信息,包括段名,段的长度,各段在文件中的偏移等,包括:
1. 段名
2. 段类型:包括程序段(代码段、数据段)、无效段、符号表、字符串表、重定位表、符号表的哈希表、动态链接信息等
3. 段标志位:是否可写,是否可执行等
4. 段虚地址:如果该段可被加载,则为被加载后在进程地址空间中的虚拟地址
5. 段偏移
… …
使用readelf -S 可以查看段表。从下边的段表中可以看到,strtab 字符串表位于下标为29 的段中,与文件头中的“Section headerstring table index: 29”相对应。如下
这里大胆猜测一下,elf 被解析的过程可能是下边这样的:
1. 根据文件头中的“start of section header” 找到段表的所在位置
2. 根据段表中的段偏移等信息,找到每一个段
zqxl@ubuntu:~/work/practice/_3.4_SimpleSection$ readelf -S simplesection.o
There are 30 section headers, starting at offset 0x1a28:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000000254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000000274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000002b8 000002b8
00000000000000a8 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000360 00000360
0000000000000084 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000000003e4 000003e4
000000000000000e 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000000003f8 000003f8
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000418 00000418
00000000000000c0 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000000004d8 000004d8
0000000000000018 0000000000000018 AI 5 22 8
[11] .init PROGBITS 00000000000004f0 000004f0
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000000510 00000510
0000000000000020 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000000530 00000530
0000000000000008 0000000000000008 AX 0 0 8
[14] .text PROGBITS 0000000000000540 00000540
00000000000001d2 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 0000000000000714 00000714
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 0000000000000720 00000720
000000000000000a 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 000000000000072c 0000072c
0000000000000044 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000000770 00000770
0000000000000128 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000200db8 00000db8
0000000000000008 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000200dc0 00000dc0
0000000000000008 0000000000000008 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000200dc8 00000dc8
00000000000001f0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000200fb8 00000fb8
0000000000000048 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000201000 00001000
0000000000000018 0000000000000000 WA 0 0 8
[24] Test_Section PROGBITS 0000000000201018 00001018
0000000000000004 0000000000000000 WA 0 0 4
[25] .bss NOBITS 000000000020101c 0000101c
000000000000000c 0000000000000000 WA 0 0 4
[26] .comment PROGBITS 0000000000000000 0000101c
0000000000000029 0000000000000001 MS 0 0 1
[27] .symtab SYMTAB 0000000000000000 00001048
0000000000000690 0000000000000018 28 46 8
[28] .strtab STRTAB 0000000000000000 000016d8
0000000000000243 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 0000191b
000000000000010b 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
重定位表
字符串表
各段对应的源码
-
BSS段:Block Started by Symbol,存放未初始化的全局变量。BSS段属于静态内存分配。
-
数据段:存放已初始化的全局变量。数据段属于静态内存分配。
-
代码段:code segment/text segment,存放程序执行代码。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
-
其他段:如下图
另外:
全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量都在栈上分配空间。.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化,因此造成了上述情况。bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小。
data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。 数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。当这个内存区进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。
各个段的内容
使用objdump -s -d
将所有段的内容以16进制打印出来
zqxl@ubuntu:/work/myprojects/code3-1$ objdump -s -d simple_section.o
simple_section.o: file format elf64-x86-64
Contents of section .text:
0000 554889e5 4883ec10 897dfc8b 45fc89c6 UH..H....}..E...
0010 488d3d00 000000b8 00000000 e8000000 H.=.............
0020 0090c9c3 554889e5 4883ec10 c745fc01 ....UH..H....E..
0030 0000008b 15000000 008b0500 00000001 ................
0040 d089c7e8 00000000 b8000000 00c9c3 ...............
Contents of section .data:
0000 5a000000 59000000 Z...Y...
Contents of section TestSection:
0000 7e000000 ~...
Contents of section .rodata:
0000 613d2564 0a00 a=%d..
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520372e .GCC: (Ubuntu 7.
0010 342e302d 31756275 6e747531 7e31382e 4.0-1ubuntu1~18.
0020 30342e31 2920372e 342e3000 04.1) 7.4.0.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 24000000 00410e10 8602430d ....$....A....C.
0030 065f0c07 08000000 1c000000 3c000000 ._..........<...
0040 00000000 2b000000 00410e10 8602430d ....+....A....C.
0050 06660c07 08000000 .f......
Disassembly of section .text:
0000000000000000 <func1>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 8b 45 fc mov -0x4(%rbp),%eax
e: 89 c6 mov %eax,%esi
10: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 17 <func1+0x17>
17: b8 00 00 00 00 mov $0x0,%eax
1c: e8 00 00 00 00 callq 21 <func1+0x21>
21: 90 nop
22: c9 leaveq
23: c3 retq
0000000000000024 <main>:
24: 55 push %rbp
25: 48 89 e5 mov %rsp,%rbp
28: 48 83 ec 10 sub $0x10,%rsp
2c: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
33: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 39 <main+0x15>
39: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 3f <main+0x1b>
3f: 01 d0 add %edx,%eax
41: 89 c7 mov %eax,%edi
43: e8 00 00 00 00 callq 48 <main+0x24>
48: b8 00 00 00 00 mov $0x0,%eax
4d: c9 leaveq
4e: c3 retq
elf 文件查看常用命令
readelf:查看elf文件
-h:查看elf文件头
-S:段表结构
objdump:查看elf文件
-s:打印所有段的内容
-d:将指令内容反汇编
size:查看elf各段的长度
将变量放入指定的名为“FOO1”的段中
__attribute__((section("FOO1"))) int var=126;
参考
https://www.cnblogs.com/ggds/p/8324761.html
附录 源码
int printf(const char *format,...);
int global_init_var=90;
int g_uninit_var;
__attribute__((section("Test_Section"))) int var=126;
void func1(int i)
{
printf("a=%d\n",i);
}
int main(void)
{
static int var = 89;
static int var2;
int a=1;
int b;
func1(var + var2);
return 0;
}