在linux下用于链接的程序是ld,链接有一个好处,可以指定最终生成的可执行文件的起始虚拟地址。它是用-Ttext参数来指定的,所以咱们可以执行以下命令完成链接:
ld kernel/main.o -Ttext 0xc0001500 -e main -o kernel/kernel.bin
从左到右说一下参数,-Ttext 指定起始虚拟地址为0xc0001500,这个地址是设计好的,为什么用这个地址,咱们将来在加载内核时会告诉大家,在此大伙儿先淡定一下。其中 –o的意义也是指定输出的文件名,至于-e,还是要看一下官方帮助。
ld –help回车后,输出的信息太多,咱们只看下面的-e参数:
-e ADDRESS, --entry ADDRESS Set start address
-e和--entry一样,字面上的意思是用来指定程序的起始地址。注意,不要被迷惑了,虽然说是指定起始地址,但参数不仅可以是数字形式的地址,而且可以是符号名,这和汇编中的标号也是地址是一样的道理。总之它是用来指定程序从哪里开始执行。
为了让大家更清楚-e的意思,咱们不加-e参数试试,如图
经过这样的链接操作,ld报错发出了警告,提示找不到入口符号(entry symbol)_start,默认地址为00000000c0001500。这个_start是什么呢?一个程序总该有个入口地址,这个地址表示的是程序将从哪里开始执行。要知道,并不是程序体中的第一个字节就是程序的起始地址,因为在程序的开头可能有函数声明或数据定义,想想咱们的汇编文件loader.S,它最前面的部分可不是指令而是一堆数据,而我们在设计它的时候,知道它的入口地址不在程序开始处,所以在mbr中直接跳入了loader.S的loader_start标号处,跨过了程序开头的数据部分。这还仅仅是由一个loader.S生成loader.bin,并且是我们提前知道入口地址的情况,如果当多个文件拼合成一个可执行文件时,计算机如何知道程序的入口在哪里呢?也就编译后的程序应该从哪句代码开始执行呢?这入口代码可说不准是哪一个了。由于程序内的地址是在链接阶段编排(也就是重定位)的,所以在链接阶段必须要明确入口地址才行,于是链接器规定,默认只把名为_start的函数做为程序的入口地址,即默认的entry symbol是_start,除非另行指定。
大家看到了,代码中并没有_start这个符号,链接器ld找不到该起始地址,所以发出了警告。既然缺少_start符号,那现在把主函数main改成_start试试,代码如下:
1 //int main(void) {
2 int _start(void) {
3 while(1);
4 return 0;
5 }
好啦,编译链接一气呵成,整个过程没出任何问题,如图:
上图中,我们还用file命令查看了最终生成的kernel.bin文件,您看,它已经是executable啦,即可执行文件。悄悄提示一下,该文件放到虚拟机上运行也是没问题的。
本节内容摘自《操作系统真象还原》