同自我修养——编译和链接 来源于 程序员的自我修改-链接.装载.库 一书 ,又看了一章~
撰写不易,转载需注明出处:http://blog.csdn.net/jscese/article/details/50161675本文来自 【jscese】的博客!
executable
windows: PE(Portable executable)
linux: EIF(Executable Linkable Format)
Relocatable file : linux:.o ; windows:.obj
Executable file : linux:/bin/xxx ; windows:.exe
Share Object file : linux:.so ; windows:.dll
从编译流程上来说,最终一个或多个relocatable file 经过link 打包成为executable file ,也就是linux 下的EIF
一般指的目标文件,在linux上来说 即是编译完成得到的 .o
首先以一个simplesection.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;
}
先不纠结 为什么这么写,编译为.o
gcc -c simplesection.c
同目录下得到 simplesection.o
查看simplesection.o 的组成可用到两个工具 readelf objdump
.o中数据分 段-section 存放,可使用objdump查看段表如下:
jscese@:~/jscese_code/object_file_construct$ objdump -h simplesection.o
simplesection.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File offset Algn
0 .text 00000050 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 00000090 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 00000098 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 00000098 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002b 0000000000000000 0000000000000000 0000009c 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000c7 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000c8 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
使用readelf 查看命令:readelf -S simplesection.o 格式有点不同而已 ,具体的段表信息含义可参考
\kernel\include\uapi\linux\elf.h 中定义的 Elf64_Shdr 结构体
这里可以看到的有6个section
可根据每一个的size 以及对应后面的 file offset 的大小,按照顺次排布在文件中,容易理解
其中内容分布规则如下:
编译的机器指令-代码段 —— .code or .text
已初始化 全局/局部静态变量 - 数据段 —— .data
未初始化 全局/局部静态变量 —— . bss
只读的数据内容 —— .rodata
特别注意的是CONTENTS代表是否有内容,.bss 很特殊的section,提前说明一下:
未初始 运行时开辟 默认都为0,bss作为记录未初始化全局以及局部静态所占大小的作用,
不真实存在于文件中的section,所以不占文件空间,在section table 段表中记录
【有的不在bss段预留全局未初始化变量,而是预留一个common 全局变量符号,看编译器】
这里有个小点,上面表信息中.text section大小为0x50 即为80个byte ,当使用size 命令查看simplesection.o时:
jscese@:~/jscese_code/object_file_construct$ size simplesection.o
text data bss dec hex filename
172 8 4 184 b8 simplesection.o
显示的.text 为172,原因如下:
size默认是运行在”Berkeley compatibility mode”下。在这种模式下,会将不可执行的拥有”ALLOC”属性的只读段归到.text段下,
很典型的就是.rodata段。如果你使用”size -A obj.o”,
那么size会运行在”System V compatibility mode”,此时,用objdump -h和size显示的.text段大小就差不多了。
jscese@:~/jscese_code/object_file_construct$ size -A simplesection.o
simplesection.o :
section size addr
.text 80 0
.data 8 0
.bss 4 0
.rodata 4 0
.comment 43 0
.note.GNU-stack 0 0
.eh_frame 88 0
Total 227
这就对了!
分开存放 指令区域 数据区域 目的:
1:文件被装载执行, 指令 数据分别映射到两个虚拟区域,指令对应的区域设置为只读,数据区域设置为可读写,保证指令被恶心修改
2:cache 命中率问题,分开存放 ,提升CPU取指命中率
3:程序多副本运行时的 指令部分只需存在一份即可,只读共享,节省内存,同时一些只读的数据部分也是可以共享,存单一副本
查看.o 中的 代码段 .text section 内容,使用 objdump -s 查看十六机制,-d 反汇编为汇编代码:
jscese@:~/jscese_code/object_file_construct$ objdump -s simplesection.o
simplesection.o: file format elf64-x86-64
Contents of section .text:
0000 554889e5 4883ec10 897dfc8b 45fc89c6 UH..H....}..E...
0010 bf000000 00b80000 0000e800 000000c9 ................
0020 c3554889 e54883ec 10c745f8 01000000 .UH..H....E.....
0030 8b150000 00008b05 00000000 01d00345 ...............E
0040 f80345fc 89c7e800 0000008b 45f8c9c3 ..E.........E...
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 25640a00 %d..
...
最左为偏移,这里跟之前section在文件中的偏移要区分,这里只是section中数据的偏移值
如上面在文件中.text段的file offset 为00000040 ,那么文件中00000041 位置的字节应为0x55 ,00000090位置的为0xc3
中间四列即为0x50 80个字节,最后一列为ASCALL
反汇编 : objdump -d simplesection.o
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: bf 00 00 00 00 mov $0x0,%edi
15: b8 00 00 00 00 mov $0x0,%eax
1a: e8 00 00 00 00 callq 1f <func1+0x1f>
1f: c9 leaveq
20: c3 retq
0000000000000021 <main>:
21: 55 push %rbp
22: 48 89 e5 mov %rsp,%rbp
25: 48 83 ec 10 sub $0x10,%rsp
29: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
30: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 36 <main+0x15>
36: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 3c <main+0x1b>
3c: 01 d0 add %edx,%eax
3e: 03 45 f8 add -0x8(%rbp),%eax
41: 03 45 fc add -0x4(%rbp),%eax
44: 89 c7 mov %eax,%edi
46: e8 00 00 00 00 callq 4b <main+0x2a>
4b: 8b 45 f8 mov -0x8(%rbp),%eax
4e: c9 leaveq
4f: c3 retq
数据与汇编指令的对应关系,详情可去查汇编指令与对应机器码
static int itest1=0;
static int itest2=1;
itest1存.bss段 itest2 存.data段 ,
因为未初始化的变量默认即为0 ,在bss段预留位置,在文件中不占空间,优化将itest1放在bss段
查看符号表:
jscese@:~/jscese_code/object_file_construct$ objdump -t simplesection.o
simplesection.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 simplesection.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000004 l O .data 0000000000000004 static_var.1594
0000000000000000 l O .bss 0000000000000004 static_var2.1595
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000004 global_init_var
0000000000000004 O *COM* 0000000000000004 global_uninit_var
0000000000000000 g F .text 0000000000000021 func1
0000000000000000 *UND* 0000000000000000 printf
0000000000000021 g F .text 000000000000002f main
可以看到 global_uninit_var 并没有存在.bss 中 ,.bss中只有static_var2 ,
这也是上面的section table中.bss只有4byte的原因,细心的应该注意到上面提到过特殊的编译器,不会把未定义全局变量放到.bss ,而是预留了一个COMMON的符号,这里就是了:
0000000000000004 O COM 0000000000000004 global_uninit_var
要是考虑一些地址布局等问题,我们可以自定义某个变量存放到某个段,gcc提供了这样的机制:
__attribute__((section("name"))) //修饰
修改上面simplesection.c ,把初始化的全局变量 放到.bss段里面去:
__attribute__((section(".bss"))) int global_init_var =84;
再次看下符号表:
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 simplesection.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l O .data 0000000000000004 static_var.1594
0000000000000004 l O .bss 0000000000000004 static_var2.1595
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .bss 0000000000000004 global_init_var
0000000000000004 O *COM* 0000000000000004 global_uninit_var
0000000000000000 g F .text 0000000000000021 func1
0000000000000000 *UND* 0000000000000000 printf
0000000000000021 g F .text 000000000000002f main
可以看到从.data 到了 .bss