X86汇编语言-从实模式到保护模式—笔记(29)-第13章 程序的动态加载和执行(7)
本帖最后由 兰陵月 于 2017-12-5 21:51 编辑【13.2.3安装内核的段描述符】把内核加载内存中,还要安装内核各个段的段描述符,这样内核才能正常运行起来。换句话说,我们还要为GDT添加新的描述符。在进入保护模式前,我们在程序的第42行运行了lgdt ,现在我们要重新从标号pgdt处取得GDT的基址,为其添加描述符,并修改它的大小,然后用lgdt指令重新加载一次GDTR寄存器,使修改生效。但是有一个问题,标号pgdt所指向的内存区域位于主引导程序内,而我们当前正在保护模式下执行主引导程序。保护模式下的代码段只是用来执行的,是否能读出,取决于其描述符的类别字段,但无论如何它都不能写入。对代码段实施保护的意思是通过代码段描述符不能修改段中的内容,但不意味着通过其他描述符做不到。我们可以仔细思考一下,标号pgdt所指向的内存位置不单单是在主引导程序内,同时也是4GB内存空间的一部分,而我们恰恰拥有一个指向全部4GB内存空间的描述符,而且它有可读可写的属性。标号pgdt在数值上等于它距离段首的偏移量,也就是编译阶段的汇编地址,主引导程序的物理起始是0x00007C00,故pgdt在4GB段内的偏移量是0x00007C00+pgdt。这样,为了得到GDDT的基地址,代码清单13-1第96行,使用了指令:
mov esi,;指令中的表达式是在编译阶段计算的,默认的段寄存器是DS,当这条指令执行时,处理器用DS描述符高速缓存器中的32位线性基地址0x00000000加上用该表达式计算出的偏移量来访问内存。现在我们已经取得了GDT的线性基地址,可以对其进行操作,创建安装与内核有关的其他段描述符。创建段描述符的条件就是应该知道描述符的各个细节。段的基址、段界限、粒度、访问属性等等。有些数据我们可以在内核的头部看到,有些数据的属性是我们设定的。头部的相关数据如下图图13-015: 在本程序中,内核各个段的段基址等于内核加载地址加上各个段的汇编地址,这个汇编地址如上图,放在相应的位置,可以直接取出;各个段的段界限可以通过计算得出,因为每个段有着确定的先后次序,而且是紧挨着的,因此用后一个段的起始汇编地址减去当前段的起始汇编地址,就可以得到当前段的长度,用当前段的长度再减去1,就是当前段的段界限。段的属性我们在程序中直接给定,各属性位在描述符的原来位置,其他位置置0。各个段的描述符情况如下:5#描述符,索引号“5”,内核公用例程段描述符:段基址=内核加载地址+公用例程段汇编地址;段界限=内核数据段汇编地址-公用例程段汇编地址-1;段属性=0x00409800(字节粒度的代码段)。6#描述符,索引号“6”,内核数据段:段基址=内核加载地址+内核数据段汇编地址;段界限=内核代码段汇编地址-内核数据段汇编地址-1;段属性=0x00409200(字节粒度的数据段)。7#描述符,索引号“7”,内核代码段:段基址=内核加载地址+内核代码段汇编地址;段界限=程序总长度-内核代码段汇编地址-1;段属性=0x00409800(字节粒度的代码段)。得到每个描述符的三个基本数据后,程序通过调用一个过程make_gdt_descriptor来生成段描述符。过程如图13-016:
该过程有三个参数,其中寄存器EAX为线性基地址,EBX为段界限(使用的时候只用低20位),ECX为描述符的属性位(各属性位都在原始位置,其他没用到的位置0),过程结束后通过EDX:EAX返回一个完整的描述符。过程分析:1、重温一下描述符的基本结构图,如图13-017
2、现在我们手中有段基地址数据共32位,在寄存器EAX中;有段界限数据共32位(低20位有效),在寄存器EBX中;我们还有描述符的属性(各属性位都在原始位置,其他位都是0)。(1)构造描述符的低32位。描述符低32位中低16位(位0~15)为段界限的0~15位,高16位(位16~31)中为段基址0~15。程序第201行,把寄存器EAX中的数据传送给寄存器EDX,暂时储存下来。第202行,寄存器EAX中的值逻辑左移16位,之后,EAX中的低16位就到了高16位,EAX中的低16位,也就是寄存器AX的值为0。而寄存器EBX的低16位的值,也就是寄存器BX的值是段界限的位0~15。将寄存器AX和BX进行或操作,AX中的内容将变为BX,也即段界限的位0~15。经过上述操作,寄存器EAX中的高16位目前是段基地址的0~15位,低16位目前是段界限的0~15位,整个EAX寄存器的值就构成了描述符的低32位。(2)构造描述符的高32位。描述符的高32位构造起来相对比较麻烦。前面我把段基址数据给了EDX,同时低16位我们已经在描述符的低32中使用了,因此,程序第205行,and edx,0xffff0000,将EDX的低16位清零,保留我们需要的高16位。第206行,rol edx,8,将edx的值循环左移8位,这样,edx原来的高8位就到了低8位。第207行,用bswap指令将edx的高字和低字部分进行交换,经过交换,段基址的第16~23位就到了edx的第0~7位,段基址的第24~31位就到了edx的第24~31位,edx的其余部分仍然为零。Bswap指令的用法如下:
经过上面的设置,我们已经段基址中剩余的数据在edx中进行了固定,现在我们要固定段界限数据中还未使用的高4位(其低16位已经到了EAX的低16位)。段界限数据在寄存器EBX中,其他低16位内容为段界限的低16位(也就是寄存器BX的内容),高16位中的低4位有效。Ebx中的低16位我们放在了描述符的低32位,不再需要了,所以第209行,xor bx,bx,将ebx的低16位清零,这样ebx中仅第16位~19位有效,其他位都是零。第210行,or edx,ebx,执行后寄存器edx中段基址、段界限的剩余各位都已经固定完毕,其他位就是给属性位预留的位置。程序第212行,or edx,ecx,执行完毕之后,描述符的高32位就构造完成了。第214行,过程返回。此过程执行完毕之后,EDX中是段描述符的高32位,EAX中是段描述符的低32位。程序第106行、第107,mov ,eaxmov ,edx将描述符的低32位传送到5#描述符的相关位置。其实这个偏移位置也是可以由程序计算出来的,而不需要由我们直接在程序中给出。第110~129行,按同样的方法安装其他的两个描述符。第131行,还是通过4GB的数据段访问pgdt,修改它的界限值。现在GDT中总共有8个描述符了,所以它的总长度是64字节。相应地,界限值为63。第133行,通过4GB的数据段访问pgdt,重新加载GDTR,使上面那些对GDT的修改生效。至此,内核已经全部加载完成,并且完成内核的段描述符的添加。下图图13-019是目前内存状态示意图:
第135行,jmp far 此处,jmp far是间接绝对远转移指令,关键字“far”的作用是告诉编译器,该指令应当编译成一个远转移。处理器执行这条指令后,用段寄存器DS高速缓存器描述符中的段基地址加上指令中给出的偏移地址得到一个地址,首先从该地址处取出一个48位的数据,前32位作为偏移地址,后16位作为段选择子。本程序内,选择子代表是内核代码段。第135行执行完毕之后,程序跳转进入内核执行。
页:
[1]