自己动手学编译器的链接和加载(一)

最近学习链接器的链接和装载过程。

首先说说一个程序从源代码到可执行文件的流程(以linux平台上c程序为例):

 

第一步预编译过程的命令如下:

gcc -E test.c -o test.i 或 cpp test.c > test.i

由.c文件生成.i预处理文件

第二步:

gcc -S test.i -o test.s

由.i生成.s汇编文件

第三步:

as test.s -o test.o 或

gcc -c test.s -o test.o

生成目标文件

第四步:

ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i686-linux-gnu/4.6.3/crtbeginT.o -L/usr/lib/gcc/i686-linux-gnu/4.6.3 -L/usr/lib -L/usr/lib -L/lib test.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/i686-linux-gnu/4.6.3/crtend.o /usr/lib/crtn.o

现在就是来看看这一坨乱七八糟的东西是啥

这里先从最基本的看起吧。

首先看看目标文件的格式,在linux上中间文件的格式和可执行文件的格式很像,差别无非是中间文件中一些引用的函数和变量的地址是相对地址,经过链接后在可执行文件中变成了绝对地址

linux上可执行文件的格式是ELF(Executable Linkable Format)

以下是一个实例小程序test.c 这个东西应该是非常具备典型行的。

 1 #include<stdio.h>
 2 
 3 int a = 1; 
 4 double b;
 5 static int e;
 6 
 7 static int c = 2; 
 8 static double d; 
 9 
10 void func(int x, double y){
11 printf("%d, %f\n", x, y); 
12 }
13 int main(){ 
14 
15 static int c_m = 4;
16 static double d_m; 
17 
18 int a_m = 3;
19 double b_m; 
20 func(a_m, d_m);
21 return 0;
22 }

以下是test.c代码与生成的test.o文件一些最主要段的对应关系:

这个是通过objdump -h test.o命令得来的:

size表示段的大小,VMA表示段的虚拟内存地址,LMA表示段的加载地址,在链接之前都是0,file off表示段在文件中的偏移量

.text是文件代码段,存放可执行代码,局部变量的声明和定义也是在其中。

.data是数据段,存放已初始化的全局变量,全局静态变量,和局部静态变量。

.bss存放的是未初始化的全局静态变量,局部静态变量,可能还有未初始化的全局变量(这个取决于不同的编译器,我的gcc编译器是不将未初始化的全局变量放在.bss中的,只是声明了一个符号,等到最终链接成可执行文件的时候再在.bss段分配空间)

从上图也可以看到,.bss段的CONTENTS属性不存在,即表明这个段实际上是不存在的,只是标示了符号而已。

还有一点,变量的存放是要字节对齐的,故一个int和两个double实际上占了24个字节。

.rodata是只读数据段,在这里存放的是printf("%d, %f\n", x, y);中的d和f

.comment段.note.GNU-stack和.eh_frame暂时不管了

可以使用objdump -s -d test.o察看各个段中的内容:

先看section .text中的内容:

对照程序的反汇编结果

 可以发现.text中存放的正是func和main函数的指令0000-002e存放func的指令。002f-005c存放的是main的指令。

在看section .data:

发现从低地址至高地址12个字节依次存放的是a, c, c_m值

看section .rodata:

从后面的ASII码就知道他存放的确实是%d,%f两个只读的数据

现在已经基本弄清楚了ELF文件的一些布局和结果,之后再继续研究研究。

posted on 2012-05-19 16:33  渊蓝之蓝  阅读(699)  评论(0编辑  收藏  举报