转载自:http://hi.baidu.com/sangwf/item/af16fc51f66c6c10db163557
最近在开发walleos时,需要将汇编生成的机器指令与C代码编译生成的机器指令放到一起运行,开始我只是将两者顺序放置,能够正常的执行。但我在C代码里实现一个print_string函数时,发现了问题。比如我在函数中定义char str[] =“Hello, world!”,虽然str是在栈上分配的,不需要考虑重定位问题,可是”Hello, world!”字符串,还是被编译器放到专门的段中,然后在初始化时将其拷贝到str的数组中。有趣的是,当str的内容比较短时,编译器是直接将每个字符的asc码直接赋值到数组,也就没有了字符串重定位的问题,可是对于较长的字符串,那就需要重定位了。具体的规律我还没有详细研究,可是促使我不得不面对一个现实,要实现一个简单的链接器——wlinker。
你可能会问,ld不是已经实现了吗?我的电脑是mac系统的,而mac下的ld链接生成的执行文件是mach-o格式的,而非纯机器指令,我就需要将机器指令扣出来直接在硬件上运行。当然,如果ld能够直接链接生成纯机器指令格式的,那我就不用这么麻烦了。还有一点,linux下的ld要更易用一些,我还是想借此机会研究一下mac os下的执行文件格式。
大学编译编译原理课上,讲词法语法分析之类的比较多,但对链接器一笔带过,到了操作系统课上,又将加载器一笔带过。结果就是链接器与加载器对于许多人来说是很模糊的,并且这方面的材料也非常少,幸运的是,有个老外写的一本书《linkers and loaders》,是讲这方面最系统的,相信作者是一个从事这方面研究与开发的人。我就佩服老外这一点,对自己所研究的技术之类的,围绕某个点钻研的很深入透彻,而非广而不精。
那么什么是链接器?最开始写程序时,应该是不需要链接器的,可能整个程序只是一个文件,那么直接编译好就是可运行的。可到后来程序越来越大,分成了多个文件,那么在分别编译后,之间的互相调用必须转化为对具体地址的访问,而这一步转化与合并为一个执行程序,就是链接器要干的。那么加载器是要干什么呢?操作系统要运行一个执行程序时,就是通过加载器将执行文件加载到内存,然后跳转到机器指令的开始处执行。
这编译器、链接器与加载器之间必须约定一种文件格式,不然岂不乱了套了。一到标准问题,操作系统领域就各自为政了,总希望自己的格式能够一统江湖,unix/linux开始是a.out,后来变成了ELF,而微软是PE格式。苹果用的是mach-o格式,按照官方的文档,相比a.out,它有个创新之处是统一了执行文件与动态链接库的格式,都用mach-o来表示。当然,这个所谓的创新在ELF格式上也存在,算是标配了。而linux下有elf、.a(其实就是个elf文件的压缩包)两种格式,windows下除了pe,还有.dll动态链接库格式。
在之前的一篇文章,我介绍了mach-o的文件格式,但明显只讲了一半,这也难怪,当时我还没有将mach-o文件如何链接为一个执行文件理解清楚,直到今天下午,在我呆在宾馆无所事事的时候,花了三个小时将打印出来的mach-o格式说明文档详细研究了一遍,总算想清楚了。且考虑清楚了我需要的简单链接器如何实现。
要理解清楚链接器的工作原理,主要是要理解清楚符号表(symbal table)和重定位表(relocation table)。
首先来说符号表,其实CPU是不认识符号的,它只认识指令与地址。所以符号最终都会被地址所替换,可我们写代码时,都是用符号代替了地址。什么是符号?比如定义了一个函数,那么函数名就是一个符号,定义一个全局变量,那么变量名就是一个符号。符号表里的每一项就记录了符号名,以及符号定义所在的地址信息。和符号表对应的就是重定位表,记录了在某个位置引用了某个符号。当我们进行程序链接时,就是将机器指令、变量之类的先放到一段空间中,然后计算出符号定义所对应的地址,然后将重定位表中的每一处引用地址,用符号表中的新地址所替代。这样就完成了目标文件的链接。
这里举个例子:
int f() {
return1;
}
int f2() {
returnf();
}
编译后,符号表中就会有两项:
f 地址1
f2 地址2
重定位表中就有一项:
地址3 f
那么我们在调整了机器指令的绝对地址后,符号表中的地址也顺便调整为绝对地址,那再将重定位表中的指定位置替换掉就可以了。
不知道明白了没有?理论来说比较简单,如果有机会,还是建议读者去真的将一个执行程序解剖开,就自然明白了。
我要实现的wlinker,其功能是将汇编文件head.s生成的bin文件与C代码编译生成的mach-o文件链接到一起作为系统镜像。那我要做的就是先计算出bin文件的长度,然后将其作为基地址,将mach-o文件重新链接。这里的链接就是算出符号表的每个符号的真实地址,然后将需要重定位的地址全部都替换掉。是不是说起来很简单?等着我的wlinker发布吧~。