静态链接大家并不陌生,本文将从二进制代码来分析静态链接的本质。
首先列出将要静态的链接的两个源文件,它们分别是a.c和b.c,最后链接成功的文件为ab。
a.c代码如下:
extern int shared; extern void swap(int * ,int *); int main(){ int a = 100; swap( &a, &shared ); }
b.c代码如下:
int shared = 1; void swap( int* a, int* b ) { *a ^= *b ^= *a ^= *b; }
首先使用命令gcc -c a.c 和gcc -c b.c 生成可重定位文件a.o和b.o。
接着使用命令objdump -h a.o和objdump -h b.o来查看可重定位文件的各个基本段。
分别显示如下:
图 1
图 2
这里主要注意File off和Size。a.o代码段长度为0x22;b.o代码段为0x4a,数据段为0x4。
然后使用命令ld a.o b.o -e main -o ab来链接两个可重定位文件。
使用命令objdump -h ab,得到下图:
图 3
此时我们不要关心File off,我们只关心VMA和Size。VMA是进程的虚拟空间,代码段从0x4000e8持续到0x400156,Size为0x6e。数据段从0x6001b0持续到0x6001b4,Size为0x4。
注意观察图1和图2,a.o的代码段长度为0x22,b.o的代码段长度为0x4a,两个加起来为0x6c,再加上位对齐2位,最后长度为0x6e。数据段也类似。
我们指导在a.c中shared变量和swap方法没有在该文件中定义,那么二进制代码会怎么处理呢?
使用objdump -d a.o,来查看:
图 4
13偏移处,be后面00000000表示的是shared变量的地址,由于此时还没有链接,所以使用默认值00000000,很显然是一个假值。
18偏移处,e8后面00000000表示的是swap函数的地址,由于此时还没有链接,所以使用默认值00000000,很显然是一个假值。
同样使用命令objdump -d b.o,来查看:
图 5
这个函数没有需要重定位的函数和变量。
我们使用objdump -d ab,来查看链接后的文件ab:
图 6
为了代码对齐,在main函数后面多了两条空指令,所以ab的长度是a.o和b.o长度之和再加上2。
我们观察到0x4000fb处be后面的数据已经不是前面的0x00000000,而是0x6001b0,我们知道这个数据表示shared变量的地址,为什么是这个值呢?请看图3,数据段的起始位置是0x6001b0,shared是唯一个变量,所以0x6001b0就是shared变量的地址。
在0x400103处e8后面的数据也已经不是前面的0x00000000,而是0x00000004。请看图3或者图6,代码段起始位置为0x4000e8,swap函数的地址为0x40010c。那么e8后面为什么是0x00000004呢?因为call转到的真正地址是call下一条指令地址0x400108+0x4,最后的真正的地址为0x40010c,正好是swap的地址。
至此,本文分析完毕,参考程序员的自我修养。