Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

一、pmtest1.asm

 

下面对pmtest1.asm中的文件进行简单的分析

1.首先在先定义一个定义全局描述符表GDT的节[SECTION.gdt],GDT为操作系统提供了段式存储机制。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

其中包括三个描述符,分别为 LABEL_GDT,LABEL_DESC_CODE32和LABEL_DESC_VIDEO,每一个描述符定义了一个段,第一个描述符段基址和段界限都为0;第二个描述符指向一个还没有规定大小的代码段,其段基址为0;第三个描述符指向的段基址为0B8000h,顾名思义正是显存。

 

下面这三行表明了GDT的长度,界限和基地址

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

下面来定义段选择子,段选择子是描述符索引,相当于数组的下标。这里分别定义了两个段选择子,其值也就等于所对应的段相对于GDT基地址的偏移量。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

2.下面来定义[SECTION.s16]节

首先对各个寄存器和栈指针进行初始化

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

对之前定义的32位代码段描述符LABEL_DESC_CODE32进行初始化:首先将32位代码段的物理地址赋值给了eax寄存器,下面只需要把eax的值放入段描述符中即可定义32位代码段的段基址、段界限、段属性。由于在段描述符中的内存排列并不是按照段基址、段界限、段属性这样的顺序,所以将eax中保存的这个值分成三部分分别赋给LABEL_DESC_CODE32的相应偏移位置。也就是说下面程序的功能是把

32位程序段的物理首地址放到程序段描述符的段基址中,以便跳转到保护模式时,可以使用选择子选用程序段描述符,从而得到32位程序段的物理首地址。

 

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

然后将eax寄存器清零,而GDT的段地址储存在ds段寄存器中,偏移地址为LABEL_GDT,将GDT的物理地址GdtPtr数据结构之中,GdtPtr共有6个字节

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

将GdtPtr中指示的6字节加载到寄存器gdtr中,gdtr和GdtPtr的结构完全一致

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

由于保护模式下中断处理机制与实模式不同,接下来需要关闭中断

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

通过操作端口92h打开A20地址线

这里需要注意的是:A20地址线并不是打开保护模式的关键,只是在保护模式下,不打开A20地址线,你将无法访问到所有的内存

保护模式下,A20关闭(始终为0),则用户的地址只能是:0 - (1MB-1), 2 - (3MB-1), 4 - (5MB-1),我们可以这样设想,A20为个位数(以1MB为单位),如果它始终为0,你永远不可能让这个数变成奇数。保护模式下,A20开启,则可以访问全地址,没有奇偶MB的问题

 

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

将cr0寄存器的第0位PE位置为1,因为当此位为0时,CPU运行在实模式下,当此位置为1时,CPU运行在保护模式中

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

实际上,在执行完mov cr0,eax这行之后,CPU就已经运行在实模式下了,但是此时cs寄存器中的值仍为实模式下的值因此需要执行,这里使用的是段选择子+偏移量的寻址方式,也正是保护模式下的寻址方式。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

跳转至描述符DESC_CODE32对应的段地址,也就是LABEL_DESC_CODE32的地址

3.[SECTION .s32]节

这节的主要内容是将一个红色的字符“P”保存在显存中,并计算了代码节的长度,将其保存在SegCode32Len中

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

 

二、pmtest2.asm

下面来简单解释一下pmtest2.asm这段代码做了些什么事情:这段代码的目的实际上是为了完成一次实模式下跳转到保护模式然后对大地址内存进行读写操作再跳转回实模式的操作。由于在实模式下内存寻址空间仅仅只有1MB,因此这部分代码新增了一个数据段和一个名为TEST的段,其中TEST段的基地址位于5MB处,这远远超出了理论上实模式的寻址范围,代码想要完成的事情首先是要从TEST段的首地址位置读出8字节的内容,然后再向TEST段的首地址处写入完全不同的8个字节(这部分字符保存在新增的数据段中),然后再从5MB地方读出8个字节。也就是说,如果能够跳转成功,那么两次得到的字符串的将会是完全不同的,反之则会失败。

 

下面我们来看一下具体代码所做的事情:

首先先来看GDT这部分,与pmtest1相比之下,pmtest2的代码部分中新增了多个描述符,包括NORMAL、DATA、TEST,STACK等,接下来我们将在具体执行过程中来一一分析。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

2.我们再来看一下32位代码段[section .s32]

 32位代码段是工作在保护模式下的代码,所以其寻址方式采用了段选择子。首先将各个寄存器进行初始化,使ds指向数据段;es指向5MB处的TEST段;gs指向显存;ss指向堆栈段,并将栈顶地址赋给了esp寄存器。

 

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

接下来将在屏幕的第十行显示字符串,并设置为黑底红字

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

显示完毕以后,这段代码调用了四个函数,分别是DispReturn、TestRead、TestWrite,下面来逐一查看各函数的作用

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

DispReturn函数:顾名思义这个函数就是显示字符串结束后的返回函数,它模拟了一个回车符号的显示,而实际上是为了将下一个字符显示在下一行的开头

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

TestRead函数:这个函数进行一共进行八个循环,每次将es寄存器中的一个字符保存到al寄存器中,然后调用DispAL函数将al寄存器中的字符显示到屏幕上来,一共显示八个字符。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

TestWrite函数:与TestRead函数相对应,这个函数首先保存esi和edi寄存器的值,这是因为edi寄存器始终指向要显示的下一个字符的位置,以免在显示过程中产生混乱;接着不断的将si指向的储存单元读入al寄存器之中,当al的值不为空时,就把al中的字符填入es的相应偏移之中。也就是实现了向TEST段进行写数据的操作。需要补充的是:这里的OffsetStrTest既是字符串相对于LABEL_DATA

的偏移量,又是其在数据段中的偏移(因为数据段的基地址也正是LABEL_DATA的物理地址)

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

下面再来顺便看一下DispAL函数:这个函数默认al中已经存放着数字,当al的数字大于9时,对这个数字减去0AH,相当于取模操作,否则的话将al中的数字载入显存,在屏幕中显示出来。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

执行完上面的几个函数之后,代码将由 jmp SelectorCode16:0 这个语句跳转到一个新的段:[SECTION .s16code],这个段把SelectorNormal的值赋给了ds,es,fs,gs,gs,ss等寄存器,并将cr0寄存器的PE位清零,这是在为跳转回到实模式做准备。而结尾处的跳转指令看似段地址选择为了0,实际上在s16段的开始部分将会对段地址作出相应的修改,使程序能够正常的返回到实模式之中。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

 

 

3.接下来我们来看一下16位代码段

16位代码段的前半部分主要是对于各种段描述符的初始化过程,包括 16 位代码段描述符、32 位代码段描述符、数据段描述符、堆栈段描述符等等过程,与之前分析过的pmtest1.asm相差不大。而在这之后有一个LABEL_REAL_ENTRY,这是由保护模式跳转回实模式时所进入的地址,在这个地址下重新设置各个寄存器的值,恢复sp的值,然后关闭A20地址线,打开中断,将控制权重新交还给DOS。

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

 

至此完成了一次由实模式跳转至保护模式再返回实模式的操作。

 

 

三、pmtest3.asm

这段代码的主要作用也是由实模式跳转到保护模式并打印出一个字符,然后再次跳转回实模式并返回DOS,与之前的pmtest2不同的是,这段代码新增对于LDT(局部描述符表的使用过程。下面我们来具体看一下代码:

首先还是来看GDT的部分,GDT中新增了一个LDT的描述符,以及对应的选择子

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

下面我们找到LDT对应的段,可以看到LDT描述符和选择子的定义与GDT几乎相同,而这里仅仅定义了一个LDT的描述符—— LABEL_LDT_DESC_CODEA,让我们查看一下它的代码

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

可以看到CODEA所做的事情也非常简单,就是将一个“L”红色字符在屏幕的第十行显示出来

Oranges操作系统-保护模式pmtest1、pmtest2、pmtest3代码分析

可以看到LDT和GDT的区别并没有在这些代码中显示出来,而实际上,它们的区别在于选择子稍有不同,LDT的选择子中多了一个SA_TIL的属性,SA_TIL将选择子的TI位置为1,那么此时系统将从当前的LDT中寻找相应的描述符。

LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。LDTR记录局部描述符表的起始位置,与GDTR不同LDTR的内容是一个段选择子。由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。LDTR可以在程序中通过使用lldt指令随时改变。