兰陵月 发表于 2017-12-1 22:31:44

X86汇编语言-从实模式到保护模式—笔记(27)-第13章 程序的动态加载和执行(5)

本帖最后由 兰陵月 于 2017-12-5 21:51 编辑

下面学习主引导扇区程序的代码内容。图13-001中,程序第9行~第10行,将当前代码段的值给SS,将栈指针寄存器的值设定为0x7C00,设置完成后,内存中的情况如下图图13-002:图13-001中,第14行中,“”的理解,标号“pgdt”在程序第217行,用伪指令声明了一个字长的“0”,紧接着声明了一个双字长的数据“0x00007e00”。我们知道标号在编译后将变成一个相对于程序起始位置(即0)的一个数据。所以双字“0x00007e00”在程序中的起始位置为“pgdt+0x02”,又因为主引导扇区程序是加载到物理地址“0x00007c00”处开始执行的,因此双字“0x00007e00”处的物理地址实际上是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处。这个栈段虽然是在主引导扇区程序这里建立的,但是程序进入内核之后,它同时也会作为内核的栈段。4#描述符(索引值“4”):它是一个显示缓冲区描述符,段的基本数据为:基地址0x000B800,界限0x07FFF,粒度为字节的可读可写向上扩展的段。该描述符的低32位组合起来为:0x80007FFF,高32位组合起来为:0x0040920B。它的低32位放在偏移EBX+0x20处,高32位放在偏移EBX+0x24处。程序第22行~第37行,依次安装了这5个描述符(0#描述符跳过)。第37行执行完毕后,内存中的情况如下图图13-005:相关的段描述符安装完毕之后,当然就要加载全局描述符表寄存器(GDTR)—即初始化GDTR,这样才能处理器再以后要访问GDT中的段描述符时,才知道GDT在哪里,段描述符是否符合规则。加载GDTR需要两个数据,一是32位的全局描述符表GDT线性基地址,这个我们已经在程序第218行进行了声明。二是全局描述符表GDT的表界限值。而我们知道GDT的表界限值等于表的大小减1,我们现在的GDT表中有5个段描述符,每个段描述符是8个字节,因此表的大小为40字节,所以表界限值为40-1,即39。它的数值实际就是最后一个描述符的最后一个字节的偏移值,即EBX+0x27,此程序中EBX值为0x00,所以最后一个描述符的最后一个字节在GDT所在的段内的偏移为0x27。初始化GDTR的指令是LGDT,它后面跟着一个6字节(48位)的内存区域,前两个字节存放表界限值(16位),后4个字节(32位)存放GDT表的线性基地址。如图13-006中,程序第40行把16位的GDT界限值放在标号“pgdt”处,这样从标号“pgdt”开始的6个字节就可以用LGDT指令进行加载。程序第42行,加载48位内存区域的数据到GDTR。GDTR初始化后,其内容如下图图13-007:
GDT相关设置完毕后,我们还需要做的工作就是打开A20、设置寄存器CR0的PE位,当然在设置寄存器CR0的PE位前,我们还要关中断,因为进入保护模式后,16位实模式下的BIOS中断都不能再用,到时要重新设置中断。CR0的PE位置1后,处理器立即进入了保护模式,但是还只是16位的保护模式。为了彻底干净地进入32位保护模式,还需要清空处理器的流水线,并串行化处理器,这是程序第55行做的事情,用了一个JMP DWORD 0x1000:flush指令,因为已经进入了保护模式,所以这里的“0x1000”不再是段地址,而是段选择子。上述这些具体的操作在上一章讲的很清楚了,这里不再重复。源程序如下图图13-008。
程序执行完第55行后,主引导扇区程序就真正进入了32位保护模式。
页: [1]
查看完整版本: X86汇编语言-从实模式到保护模式—笔记(27)-第13章 程序的动态加载和执行(5)