程序的链接-符号解析
首先,从程序到可执行程序要经过如下几个步骤:
- 预处理
- 编译
- 汇编
- 链接
其中链接又分为如下几部:
- 确定符号引用关系(符号解析)
- 合并相关 .o (可重定位)文件(重定位)
- 确定每个符号的地址(重定位)
- 在指令中填入新的地址(重定位)
在汇编结束之后,就已经生成了二进制的可执行文件。这次我们主要介绍符号解析这一步。
符号解析
符号解析:将每个模块中引用的符号(符号引用)与某个目标模块的定义符号(符号定义)建立关联。
简单理解就是比如在某处调用了一个函数fun()。这个fun就可以看做是其符号引用,fun函数的定义可以看做是符号定义。然后符号解析就是把这个符号引用fun与他的函数定义(符号定义)连接起来,也就是建立起一个映射关系,用符号表来记录这个映射关系。
为什么呢?因为每个定义符号在代码段(函数)和数据段(变量)都分配了存储空间,这样经过符号解析之后
看一个例子:
main.c
int a[3] = {1,2,3};
void fun1(){//符号定义
int a = 1;
int b = 2;
}
int main(){
fun();//符号引用
fun1();//符号引用
return 0;
}
gcc -c main.c -o main.o
生成可重定位文件
然后用readelf -s main.o
看到符号表如下:
可以看到main函数的节数也就是Ndx 就是1说明他是在可重定位文件的第一节,以及fun1函数也是在1,这就是有符号定义的符号,对比来看fun符号Ndx是UND就是undefine,就是没有定义的意思。fun1 fun main这几个符号的作用域都是全局作用域
强符号,弱符号
强符号:有函数的定义函数名和已初始化的全局变量名
弱符号:没有函数的定义的函数声明以及未初始化的全局变量名
很好理解:
test1.c
int a;//弱符号
int b = 1;//强符号
void p();//弱符号
void fun(){//强符号
...
}
int main(){
...
}
弱一个符号被定义为一次强符号和多次若符号,则按强定义为准
objcopy
有一个objcopy命令就非常有意思了:
objcopy [OPTION] [INFILE] [OUTFILE]
他有一个选项:-W
-W symbolname, --weaken-symbol=symbolname
将指定符号变为弱符号。该选项可以多次指定
-W能干什么呢?可以在不被别人发现的情况下(也就是不该别人的源代码),将别人的函数偷换成你的实现。这种技术有时候在公司的项目会用到,这样可以避免修改太多源代码,要做的是写一个shell脚本文件就行了。
我们只要把函数原来的符号定义(原来的强符号)弱化即可,这样这个函数的所有符号引用(弱符号)就会以我们自己写的符号定义(强符号)为准。
举个例子:
源代码 main.c
#include <stdio.h>
void f1(){
printf("f1\n");
}
int main(){
f1();
return 0;
}
我想讲f1的函数实现给换掉,我就可以写一个swap.c
#include <stdio.h>
void f1(){
printf("this is my f1\n");
}
然后操作他们的.o文件 对符号引用做手脚
- 将main.c swap.c汇编为,o文件
gcc -c main.c -o main.o
gcc -c swap.c -o swap.o
- 弱化f1.o中的f1符号,将f1强引用弱化为弱引用。
objcopy -W f1 main.o
- 然后链接合并.o文件
gcc main.o swap.o -o main
运行可执行文件
会输出this is my f1.
说明函数定义已经被替换.
他还有以下功能:
--redefine-sym old=new
变更符号名称。当链接两个目标文件产生符号名称冲突时,可以使用该选项来解决
可以直接修改符号引用,这个功能顺路介绍一下,因为和符号相关。他可以将一个函数的符号变更为另一个符号。
例子:
还是main.c,将f1符号定义删除
int main(){
f1();
return 0;
}
修改swap.c,重新起一个名字f2.
#include <stdio.h>
void f2(){
printf("this is my f2\n");
}
直接编译肯定会出错,因为f1没有对应的符号定义,这个时候我们可以在不改变main.c的源代码的情况下修改f1的符号为f2
首先:
gcc -c main.c -o main.o
gcc -c swap.c -o swap.o
然后,让f1符号引用变更为f2
objcopy --redefine-sym f1=f2 main.o
最后链接并执行
gcc main.o swap.o -o main
./main
输出 this is my f2