下面学习主引导扇区程序的代码内容。
图13-001中,程序第9行~第10行,将当前代码段的值给SS,将栈指针寄存器的值设定为0x7C00,设置完成后,内存中的情况如下图图13-002:
图13-001中,第14行中,“[cs:pgdt+0x7c00+0x02]”的理解,标号“pgdt”在程序第217行,用伪指令声明了一个字长的“0”,紧接着声明了一个双字长的数据“0x00007e00”。我们知道标号在编译后将变成一个相对于程序起始位置(即0)的一个数据。所以双字“0x00007e00”在程序中的起始位置为“pgdt+0x02”,又因为主引导扇区程序是加载到物理地址“0x00007c00”处开始执行的,因此双字“0x00007e00”处的物理地址实际上是pgdt+0x7c00+0x02。因此“[cs:pgdt+0x7c00+0x02]”的值实际上就是“0x00007e00”,我们声明这个双字数据的本意是将它作为GDT准备加载到的目标位置。
因为这个目标位置数据是一个32位数据(它刚好紧接着代码段结束处,见图13-002),但现在处理器还在16位实模式下运行,这种情况下内存寻址方式只能采用16位段地址:16位偏移地址的方法,而不能直接使用32位的地址数据。因此我们需要通过一些方法,将32位物理地址变成16位段地址:16位偏移地址的形式。图13-001中,程序第14行~第17行,进行一次32位除法(此时虽然是16位模式,但还是可以使用32位寄存器,可以进行32位除法),被除数为EDX:EAX,除数为EBX。将EDX寄存器清零,EAX中放置双字“0x00007e00”,除数为双字“16”(这里16是10进制的,因为它的前面没有加“0x”),除法结束后,商在EAX中,它作为段地址使用;余数在EDX中,它作为偏移地址使用。程序第19行、第20行,将段地址值传送给寄存器DS,将偏移地址的值传送给寄存器EBX。程序执行到这里之后,内存中情况如下图图13-003:
程序第20行执行完之后,处理器中有关寄存器的值,如下图图13-004。
知道了GDT的起始段地址和偏移地址,我们就可以在GDT中安装主引导扇区程序进入保护模式后需要用到的相关段描述符了。
0#描述符(索引值“0”):处理器规定GDT的第一个描述符必须是空描述符。不管是空描述符还是有用的描述符,反正都要占用8个字节。所以它的低32位是从偏移EBX+0x00开始的一个双字,高32位是从偏移EBX+0x04开始的一个双字。
1#描述符(索引值“1”):它是一个数据段描述符,段的基本数据为:基地址为0x00000000,段界限为0xFFFFF,粒度为4KB的可读可写向上扩展的段,它是可以访问整个0~4GB内存的。该描述符的低32位组合起来为:0x0000ffff,高32位组合起来为:0x00cf9200。它的低32位放在偏移EBX+0x08处,高32位放在偏移EBX+0x0c处。
2#描述符(索引值“2”):它是一个代码段描述符,段的基本数据为:基地址为0x00007C00,界限为0x001FF,粒度为字节的只执行向上扩展的段。该描述符的低32位组合起来为:0x7c0001ff,高32位组合起来为:0x00409800。它的低32位放在偏移EBX+0x10处,高32位放在偏移EBX+0x14处。
3#描述符(索引值“3”):它是一个堆栈段描述符,段的基本数据为:基地址0x00007c00,界限为0xFFFFE,粒度为4KB的可读可写向下扩展的段。该描述符的低32为组合起来为:0x7C00FFFE,高32位组合起来为:0x00CF9600。它的低32位放在偏移EBX+0x18处,高32位放在偏移EBX+0x1C处。这个栈段虽然是在主引导扇区程序这里建立的,但是程序进入内核之后,它同时也会作为内核的栈段。