加载器首先是在硬盘上读取用户文件(程序),因为我们肯定是要事先知道用户头部的(总长度,所有段地址等),所以要预先读取一个扇区,又因为每个扇区是512个字节,但是用户程序可能很大,而在实模式下,每个段的大小最大是64KB,所以每加载一个扇区,都重新设定段(每个段地址只要往后移动0x20就可以了),这样就能把程序加载到内存中了(当然用户程序也不能太大,但是在实模式下运行的程序一般都是很小的)。
当然了,我们预先读取了一个扇区,所以我们向上取整(当出现余数的时候-1)。所以我们可以使用以下代码读取程序。一定要注意,在每次读取一个扇区的时候,si要跟着变化(+1)
因为在我们自己写的系统中,我们可以强制规定应用程序的头部应该包含什么东西,现在我们可以规定头部是这样的(参照书上的)。
我们可以看到用户头部有程序的总长度(用于主引导程序确定要读取多少个扇区),程序的入口点(包括偏移位置,用于决定主引导程序跳进程序后究竟在哪执行代码),段的总数和所有段地址(相对地址,在编译期间已经确定,需要主引导程序重定位)。
接下来我们就要开始段的重定位了,所谓段的重定位,其实这个名词看上去很高大上,而事实上就是在用户程序头部的所有段地址都加上加载位置,并且把这个加载位置转化为段的偏移地址罢了,而我们已经规定了用户程序在内存加载的地址是phy_base,接下来我们直接读取头部的内容,然后首先给入口点重定位,然后就是所有段地址重定位。
这里我们可以看到,我们使用了adc命令,是带进位的加法,执行adc命令的时候,最终结果会加上CF的值,这和第七章的那个最后的那个1-1000的相加的方法是一样的,注意的是,因为我们之前读扇区的时候,故意把读入的数据存放位置也按扇区512字节来划分了(改变了ds的值),在最后一定要记得把ds的值还原为在用户头部的段地址。这里有很多易错的地方,第一个就是bx是偏移量,而不是用内存寻址找到的量(粗心就会放错),另外就是书上用的是两次左移命令(shr和ror)使物理地址变偏移地址的,个人觉得不是很必要,直接用32位除法就可以了,用bx作为除数的时候,bx的原来值一定要注意要保留,不然就会出错!
最后我们可以使用上述指令直接跳到用户程序执行了,因为这个时候ds指示的是用户程序头部,而0x04刚好是偏移地址,0x06刚好是段地址,用远转移指令直接跳转,会把cs的值设为0x06的值,IP的值设为0x04的值。
★PART2:执行用户程序
1. 过程调用