第812~826行用于安装调用门的描述符,其实也就等于是在安装调用门。
安装的调用门供其他特权级的程序使用,它们在本质上是一些例程,这些例程在上一章里使用过。所有对外公开的例程都已字符串的形式定义在SALT表中,该表位于内核数据段。
内核数据段中的SALT表简称C-SALT,位于代码清单14-1的第364~383行,属于内核数据段。该表由多个条目组成,每个条目262字节,其中,前256字节是例程的名字,后6字节是例程的地址(前4字节是例程在目标代码段内的偏移量,后2字节是例程所在代码段的选择子)。
所有例程都位于公共例程中,而公共例程的DPL是0。为了使其他特权级的程序也能使用这些例程,必须将C-SALT表中的例程地址转换成调用门。
转换过程使用了循环。转换时需要定位每一个条目,故,第812行用于将C-SALT表的起始偏移地址传送到EDI寄存器,这是第一个条目的位置,以后每次加上262,就能对准下一个条目。
循环次数是由条目数量控制的,条目数是常数salt_items,位于第386行,第813行的指令用于将它作为立即数传送到ECX寄存器。第815行,因为在转换过程中要到ECX寄存器,所以在每次循环的一开始,要先压栈保存ECX寄存器的值,然后,在loop指令执行前恢复。第816行,寄存器EDI在第812行被赋值为第一个表项起始位置,这里加上256后,就指向了第一个条目所在过程的最后6个字节起始处。其内容为过程的在段内的偏移地址。将该地址传送给寄存器EAX。第817行,寄存器EDI在第812行被赋值为第一个表项起始位置,这里加上260后,就就指向了第一个条目所在过程的最后2个字节起始处。其内容为过程所在段的段选择子。将该值传送给寄存器BX。第818行,mov cx,1_11_0_1100_000_00000B,这是描述符的属性位,可以看出,各个属性位分别是,第4~0位为:00000,表示用栈传递的参数个数为0;第11~8位为:1100,表示这是一个调用门描述符;第14~13位为:11,表是特权级DPL为3;第15位P位为:1,存在。【调用过程make_gate_descriptor】
【进入过程make_gate_descriptor】第821行,call sys_routine_seg_sel:make_gate_descriptor,调用了过程make_gate_descriptor来构造调用门的描述符。我们来到来到过程make_gate_descriptor处,位于程序第331~351处。第331~336行,解释了该过程的工作用途及相关属性。作用是构造门的描述符;输入的参数有3个,分别是EAX,门代码在段内的偏移地址;BX,门代码所在段的选择子;CX,门的类型和相关属性值。返回值为EDX:EAX=完整的描述符。第337~338行,因为在过程中要到用到EBX和ECX寄存器,所以这里将它们先压栈保护;第340行,将EAX中门代码偏移地址的数值传送给寄存器EDX;第341行,将该偏移地址与0xFFFF0000进行与操作,结果偏移地址的高16位保存,低16位清零;第342行,将寄存器DX中的值(此时已经全部为零)与CX中的值(传入的参数,为门的类型和相关属性值)进行与或操作,结果寄存器DX中的值变为门的属性值;第344行,寄存器EAX中仍然保存着门代码偏移地址的值,将其高16位清零;第345行,我们知道,寄存器BX中的值为寄存器传入的参数,是门代码所在段的段选择子。此行将寄存器EBX值逻辑左移16位后,寄存器EBX中的高16位变成了门代码所在段的段选择子,低16位清零;第346行,将第344行高16位已经清零了的EAX与高16位为选择子的EBX进行或操作,结果EAX中高16位存放门代码所在段的段选择子,低16位存放着偏移地址低16位。经过上述操作,EDX中存放着调用门描述符的高32位,EAX中存放着调动门描述符的低32位,门描述符构造完毕。第348~349行,恢复寄存器ECX、EBX的值;第351行,retf远返回,表明这个过程只能远调用。【程序返回到代码段第822行】
【进入过程set_up_gdt_descriptor】位于第264行,也位于公共例程段内。该过程的作用是在GDT表中安装一个调用门描述符。输入的参数为描述符的完整64位,EDX中方高32位,EAX中方低32位。返回一个16位的值,用寄存器CX返回,为该描述符的选择子。第267~272行,把过程中要用到的寄存器压栈保存;第274~275行,切换DS指向核心数据段,因为等下要对GDT表进行操作;第277行,将GDTR中的值储存在标号PGDT处;第279~280行,切换ES指向0~4GB段;第282行,标号pgdt处指向的字单元内容为全局描述符表的界限值,将其值(16位)扩充至32位,高16位全为零,然后传送给寄存器EBX;第283行,寄存器BX的值自加1,该值原来指向GDT的最后一个字节,加1后指向拟添加的描述符的起始位置;第284行,标号pgdt+2处指向GDT表的起始线性基地址,它加上EBX中的值就是拟添加的描述符的起始线性地址,指令执行后EBX寄存器中的地址值指向拟添加的描述符的起始地址;第286~287行,添加新的描述符;第289行,修改GDT的界限值;第291行,重新加载GDTR,使修改生效;第293行开始到298行,取得描述符的选择子,第293行,将GDT界限值给寄存器AX;第294行,寄存器DX清零;第295行,除数8传送给寄存器BX;第296行,进行16位除法,商在寄存器AX中,它的值就是描述符的实际索引值;第297行,将索引值给寄存器CX;第298行,将CX逻辑左移3位,将索引值移到高12位;第300~305行内,恢复现场;第307行,程序返回,这是一个retf远返回指令,表明调用的时候只能远调用。【过程返回到第823行】
第823行,将过程set_up_gdt_descriptor返回的调用门描述符的选择子的值(放在CX中)放到本条目入口点的段选择子处;第824行,将寄存器EDI的值加上条目长度,指向下一个条目的入口点处;第825行,弹出被压入的循环次再次给寄存器ECX;第826行,循环执行,跳转到第814行处继续执行,直到所有条目全部被置换完毕。如下图图14-012所示为安装好4个调用门描述符之后的GDT布局。
第829~830行,对刚安装好的调用门进行测试,看它好不好用。测试的结果是在屏幕上显示一行文字,意思为“系统范围内的调用门已经安装”。表面上,这是一条普通的间接绝对远调用指令call far,通过指令中给出的地址操作数,可以间接取得32位的偏移地址和16位的代码段选择子,这样的指令我们太熟悉了。但是,处理器在执行这条指令时,会用该选择子访问GDT/LDT,检查那个选择子,看它指向的是调用门描述符还是普通的代码段描述符。如果是前者,就按调用门来处理;如果是后者,还按一般的段间控制转移处理。通过调用门选择子,指令中给出的偏移量被抛弃,原因很简单,通过调用门进行控制转移不需要偏移量,偏移量已经在调用门描述符中给出了。不单单是间接绝对远调用,直接绝对远调用也是这样,如果选择子指向的是调用门,偏移量也会被忽略。
借助调用门,当程序的执行流从低特权级的代码段转入高特权级的代码段时,如果那是非依从的代码段,当前特权级也随之变为目标代码段的特权级。不过,如果调用者和被调用者的特权级相同,则特权级不会发生变化。
能通过调用门发起控制转移的指令还包括jmp,但只用在不需要从调用门返回的场合下,而且不改变当前特权级。也就是说,目标代码是在当前特权级上执行。
通过调用门进行控制转移的特权级检查,既要在转移前进行,而且,还要再控制返回时进行。完整的特权级检查过程将在本章的后面进一步说明。
检测点 14.2
1.通过调用门转移控制时,CPL、RPL和目标代码段描述符的DPL必须在数值上符合CPL≥DPL的条件;CPL、RPL和调用门描述符的DPL必须在数值上符合CPL≤DPL && RPL≤DPL的条件。即,只能通过调用门将控制转移到与当前特权级相同或者更高的代码段。
2.调用门描述符只能安装在GDT中吗?【不是,可以安装在LDT中,但不能安装在IDT中】如果某调用门描述符的值是0x0000CC0000552FC0,那么目标代码段的选择子是0x0055,段内偏移量是0x00002FC0,描述符的特权级是2,目标代码段的特权级是1,要通过此门转移控制,CPL和RPL要符合什么条件才行?调用此门的程序的CPL和RPL在数值上都要小于等于2,同时CPL要在数值上大于等于1。