读书人

保护模式准备知识-书中pmtest5代码详细

发布时间: 2013-03-13 10:56:58 作者: rapoo

保护模式预备知识--书中pmtest5代码详细注释

教材选择一个操作系统的实现,作者于渊,看此书前最好了有汇编语言,保护模式下的汇编(参考清华杨季文的80X86汇编语言程序设计教程),C语言,计算机组成原理,微机接口,操作系统相关知识。

一、80386的寄存器结构

80386微处理器共有7类34个寄存器,通用寄存器组、段寄存器、指令指针和标志寄存器、系统地址寄存器、控制寄存器、调试寄存器、测试寄存器。前四类寄存器的示意图1。其中描述符高速缓冲寄存器0-31存放段首地址,0-19存放段界限,0-11存放段属性,LDTR与此类似。后面我们会看到当装入选择子后,对应的描述符恰好是32位,20位,12位共8个字节,正好能装入高速缓存寄存器。GDTR中0-31为GDTR的首地址,0-15为GDTR的界限,为什么GDTR的界限比其他的少4位呢,因为我们GDTR不需要那么大。另外控制寄存器CR0,CR1,CR2,CR3,如图2。CR0的处理器工作模式如图3。

保护模式准备知识-书中pmtest5代码详细注释

图1 通用寄存器组、段寄存器、指令指针和标志寄存器、系统地址寄存器示意图

保护模式准备知识-书中pmtest5代码详细注释

图2 控制寄存器

保护模式准备知识-书中pmtest5代码详细注释

图3 PG/PE位与处理器工作模式


二、80386描述符

此部分请看http://blog.csdn.net/jltxgcy/article/details/8656101,里面详细介绍了描述符包括,存储段描述符(代码段,数据段,堆栈段),系统描述符(任务状态段TSS,局部描述符表LDT),门描述符(调用门,任务门,中断门,陷阱门),还介绍了选择子,同时介绍了pm.inc中的代码。


三、80386 16位实模式与32位保护模式下指令执行

1、16位实模式下指令执行过程

①代码被加载到内存,段寄存器被赋值,从CS左移4位加上IP的值,形成20位地址,再加上高12位地址全部为0,形成32位地址,到此地址取得命令。注意由于没有开A20地址线,所以还保留8086的地址环绕现象。

②IP=IP+所指指令的长度。

③执行指令(个别指令执行的时候修改CS,IP),跳到①继续执行。


2、32位保护模式下指令执行过程(省略了严格的检查机制)

段选择子装入CS,CS描述符高速缓存寄存器装入代码段描述符,包括首地址,界限,属性。

①CS描述符高速缓存寄存器中基地址+EIP(偏移),形成32位地址,到此地址取得命令

②EIP=EIP+所指指令的长度

③执行指令(个别指令执行的时候修改CS描述符高速缓存寄存器,EIP),跳到①继续执行。


四、特权级变化时堆栈的变化及调用门特权级规则

1、特权级变化时堆栈变化如图4,一般用call之前都会push参数,所以会出现参数。

保护模式准备知识-书中pmtest5代码详细注释

图4 有特权级变换的转移时堆栈变化

每个任务最多在4个特权级间转移,所以每个任务需要四个堆栈,可是我们只有一个SS和ESP,那么当发生堆栈切换,我们该从哪里获得其他堆栈的SS和ESP呢?实际上这里涉及到一个新的食物TSS,它是一个数据结构,里面包含着许多字段,32位TSS如图5。

TSS包含很多字段,但是在这里,我们只关心偏移4到偏移27的3个SS和3个ESP。当堆栈发生变化时,内层的SS和ESP就是从这里取得的,比如,我们当前所在的是ring3,当转移到ring1时,堆栈将被自动切换到由ss1和esp1指定的位置。由于只有在外层到内层(低特权级到高特权级)切换时新堆栈才会从TSS中取得,所以TSS中没有位于最外层的ring3的堆栈信息。

保护模式准备知识-书中pmtest5代码详细注释

图 5 32位(每个是4个字节)的TSS


2、调用门特权级规则

保护模式准备知识-书中pmtest5代码详细注释

CPL小于等于DPL_G,RPL小于等于DPL_G,因为访问门控制符就像访问数据段,访问数据段也是CPL小于等于DPL_D,RPL小于等于DPL D。G代表门描述符的特权级,D代表数据段描述符的特权级。

五、pmtest5.asm代码详细分析如下:

;执行此程序显示结果为In Protect Mode now. ^-^                     ;3 C L%include"pm.inc"; 常量, 宏, 以及一些说明org0100hjmpLABEL_BEGIN  ;当程序被加载后CS等段寄存器也被赋予了初值,CS为00000010,IP为0,首地址为000000000 00000100,                 ;跳转指令,截取相对地址的最后16位,改变了IP;所有带:的,例如LABEL_GDT: 都是32位段偏移地址,后来可以截断,条件是前面16位都为0[SECTION .gdt]; GDT  在此可以直接赋上段基地址,如果在代码中那么要根据pm.inc的位置来赋值;GDT 最大长度为2的13次方再乘以8个字节,为64KB;所有的描述符都是有对应的代码,数据,堆栈,LDT,TSS,调用门则是调用现有的,之所有用调用门,是为了实现不同特权级代码之间的转移;                      段基址(32位),   段界限(32位)后被截取     , 属性(12位)LABEL_GDT:             Descriptor 0,                 0, 0   ;空描述符     LABEL_DESC_NORMAL:     Descriptor 0,            0ffffh, DA_DRW   ;Normal描述符LABEL_DESC_CODE32:     Descriptor 0,    SegCode32Len-1, DA_C+DA_32   ;非一致,32LABEL_DESC_CODE16:     Descriptor 0,            0ffffh, DA_C   ;非一致,16LABEL_DESC_CODE_DEST:  Descriptor 0,  SegCodeDestLen-1, DA_C+DA_32   ;非一致,32LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3 ;LABEL_DESC_DATA:       Descriptor 0,     DataLen-1, DA_DRW             ;DataLABEL_DESC_STACK:      Descriptor 0,        TopOfStack, DA_DRWA+DA_32   ;Stack,32LABEL_DESC_STACK3:     Descriptor 0,       TopOfStack3, DA_DRWA+DA_32+DA_DPL3 ;LABEL_DESC_LDT:        Descriptor 0,          LDTLen-1, DA_LDT   ;LDTLABEL_DESC_TSS:        Descriptor 0,          TSSLen-1, DA_386TSS   ;TSSLABEL_DESC_VIDEO:      Descriptor 0B8000h,      0ffffh, DA_DRW+DA_DPL3 ;为了让处于ring3的代码可以访问显存; 门                                            目标选择子,       偏移, DCount, 属性LABEL_CALL_GATE_TEST:Gate  SelectorCodeDest,          0,      0, DA_386CGate + DA_DPL3 ;CPL小于等于DPL_G,                                                                                                  ;RPL小于等于DPL_G; GDT 结束GdtLenequ$ - LABEL_GDT; GDT长度 $表示当前偏移地址GdtPtrdwGdtLen - 1; GDT界限 ,截取最后16位 因为2的16次方是64KBdd0; GDT基地址; GDT 选择子;低三位永远为0,因为每个描述符占8个字节。第一个是 00000000 00000000 00000000 00001000;第二个是00000000 00000000 00000000 00002000,第三个是00000000 00000000 00000000 00003000SelectorNormalequLABEL_DESC_NORMAL- LABEL_GDTSelectorCode32equLABEL_DESC_CODE32- LABEL_GDTSelectorCode16equLABEL_DESC_CODE16- LABEL_GDTSelectorCodeDestequLABEL_DESC_CODE_DEST- LABEL_GDTSelectorCodeRing3equLABEL_DESC_CODE_RING3- LABEL_GDT + SA_RPL3SelectorDataequLABEL_DESC_DATA- LABEL_GDTSelectorStackequLABEL_DESC_STACK- LABEL_GDTSelectorStack3equLABEL_DESC_STACK3- LABEL_GDT + SA_RPL3SelectorLDTequLABEL_DESC_LDT- LABEL_GDTSelectorTSSequLABEL_DESC_TSS- LABEL_GDTSelectorVideoequLABEL_DESC_VIDEO- LABEL_GDTSelectorCallGateTestequLABEL_CALL_GATE_TEST- LABEL_GDT + SA_RPL3;CPL小于等于DPL_G,RPL小于等于DPL_G; END of [SECTION .gdt][SECTION .data1] ; 数据段ALIGN32[BITS32]LABEL_DATA:SPValueInRealModedw0; 字符串PMMessage:db"In Protect Mode now. ^-^", 0; 进入保护模式后显示此字符串OffsetPMMessageequPMMessage - $$ ;相对于数据段首地址的偏移,保护模式下用,$$表示当前段的首偏移地址 StrTest:db"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0OffsetStrTestequStrTest - $$DataLenequ$ - LABEL_DATA; END of [SECTION .data1]; 全局堆栈段[SECTION .gs]ALIGN32[BITS32]LABEL_STACK:times 512 db 0TopOfStackequ$ - LABEL_STACK - 1 ;最后一个位置为ESP的开始,说明栈的最后一个位置没有数据; END of [SECTION .gs]; 堆栈段ring3[SECTION .s3]ALIGN32[BITS32]LABEL_STACK3:times 512 db 0TopOfStack3equ$ - LABEL_STACK3 - 1; END of [SECTION .s3]; TSS ---------------------------------------------------------[SECTION .tss]ALIGN32[BITS32]LABEL_TSS:DD0; BackDDTopOfStack; 0 级堆栈,现处于0级DDSelectorStack; DD0; 1 级堆栈DD0; DD0; 2 级堆栈DD0; DD0; CR3DD0; EIPDD0; EFLAGSDD0; EAXDD0; ECXDD0; EDXDD0; EBXDD0; ESPDD0; EBPDD0; ESIDD0; EDIDD0; ESDD0; CSDD0; SSDD0; DSDD0; FSDD0; GSDD0; LDTDW0; 调试陷阱标志DW$ - LABEL_TSS + 2; I/O位图基址 截取后16位DB0ffh; I/O位图结束标志TSSLenequ$ - LABEL_TSS; TSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[SECTION .s16][BITS16]LABEL_BEGIN:movax, csmovds, axmoves, axmovss, axmovsp, 0100hmov[LABEL_GO_BACK_TO_REAL+3], ax ;,ds作为段基地址LABEL_GO_BACK_TO_REAL地址截取后16位作为偏移,把cs的内容放到里面,  ;为了日后恢复实模式做准备mov[SPValueInRealMode], sp   ;同理,也是为了日后恢复实模式做准备; 初始化 16 位代码段描述符movax, csmovzxeax, axshleax, 4addeax, LABEL_SEG_CODE16 ;实模式下段基地址*16+偏移地址=保护模式下段的首地址movword [LABEL_DESC_CODE16 + 2], ax ;+2 +4 +7的原因请看pm.incshreax, 16movbyte [LABEL_DESC_CODE16 + 4], al;LABEL_DESC_CODE16 + 4截取后16位为偏移地址,因为全0所以截取无所谓movbyte [LABEL_DESC_CODE16 + 7], ah; 初始化 32 位代码段描述符xoreax, eaxmovax, csshleax, 4addeax, LABEL_SEG_CODE32movword [LABEL_DESC_CODE32 + 2], axshreax, 16movbyte [LABEL_DESC_CODE32 + 4], almovbyte [LABEL_DESC_CODE32 + 7], ah; 初始化测试调用门的代码段描述符xoreax, eaxmovax, csshleax, 4addeax, LABEL_SEG_CODE_DESTmovword [LABEL_DESC_CODE_DEST + 2], axshreax, 16movbyte [LABEL_DESC_CODE_DEST + 4], almovbyte [LABEL_DESC_CODE_DEST + 7], ah; 初始化数据段描述符xoreax, eaxmovax, dsshleax, 4addeax, LABEL_DATAmovword [LABEL_DESC_DATA + 2], axshreax, 16movbyte [LABEL_DESC_DATA + 4], almovbyte [LABEL_DESC_DATA + 7], ah; 初始化堆栈段描述符xoreax, eaxmovax, dsshleax, 4addeax, LABEL_STACKmovword [LABEL_DESC_STACK + 2], axshreax, 16movbyte [LABEL_DESC_STACK + 4], almovbyte [LABEL_DESC_STACK + 7], ah; 初始化堆栈段描述符(ring3)xoreax, eaxmovax, dsshleax, 4addeax, LABEL_STACK3movword [LABEL_DESC_STACK3 + 2], axshreax, 16movbyte [LABEL_DESC_STACK3 + 4], almovbyte [LABEL_DESC_STACK3 + 7], ah; 初始化 LDT 在 GDT 中的描述符xoreax, eaxmovax, dsshleax, 4addeax, LABEL_LDTmovword [LABEL_DESC_LDT + 2], axshreax, 16movbyte [LABEL_DESC_LDT + 4], almovbyte [LABEL_DESC_LDT + 7], ah; 初始化 LDT 中的描述符xoreax, eaxmovax, dsshleax, 4addeax, LABEL_CODE_Amovword [LABEL_LDT_DESC_CODEA + 2], axshreax, 16movbyte [LABEL_LDT_DESC_CODEA + 4], almovbyte [LABEL_LDT_DESC_CODEA + 7], ah; 初始化Ring3描述符xoreax, eaxmovax, dsshleax, 4addeax, LABEL_CODE_RING3movword [LABEL_DESC_CODE_RING3 + 2], axshreax, 16movbyte [LABEL_DESC_CODE_RING3 + 4], almovbyte [LABEL_DESC_CODE_RING3 + 7], ah; 初始化 TSS 描述符xoreax, eaxmovax, dsshleax, 4addeax, LABEL_TSSmovword [LABEL_DESC_TSS + 2], axshreax, 16movbyte [LABEL_DESC_TSS + 4], almovbyte [LABEL_DESC_TSS + 7], ah; 为加载 GDTR 作准备xoreax, eaxmovax, dsshleax, 4addeax, LABEL_GDT; eax <- gdt 基地址movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址 ; 加载 GDTRlgdt[GdtPtr] ;GDTR寄存器的值发生改变; 关中断cli; 打开地址线A20inal, 92horal, 00000010bout92h, al  ;如果不打开会形成地址环绕; 准备切换到保护模式moveax, cr0oreax, 1movcr0, eax ;cr0的PE位置为1,启动保护模式,这句话很重要,如果不启动保护模式,下面的jmp还是按照实模式下的方式跳转; 真正进入保护模式jmpdword SelectorCode32:0; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处;dword使0为32位,否则会像jmpLABEL_BEGIN 被截断;此时CS描述符高速缓存寄存器被置为LABEL_SEG_CODE32的描述符,以后就直接从这里取得首地址;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;LABEL_REAL_ENTRY:; 从保护模式跳回到实模式就到了这里movax, csmovds, axmoves, axmovss, axmovsp, [SPValueInRealMode]inal, 92h; ┓andal, 11111101b; ┣ 关闭 A20 地址线out92h, al; ┛sti; 开中断movax, 4c00h; ┓int21h; ┛回到 DOS; END of [SECTION .s16][SECTION .s32]; 32 位代码段. 由实模式跳入.[BITS32]LABEL_SEG_CODE32: ;从实模式跳入了保护模式,处于ring0级别movax, SelectorDatamovds, ax; 数据段选择子,改变了DS描述符高速缓存寄存器,以后就从这里取得首地址movax, SelectorVideomovgs, ax; 视频段选择子,改变了GS描述符高速缓存寄存器,以后就从这里取得首地址movax, SelectorStackmovss, ax; 堆栈段选择子,改变了SS描述符高速缓存寄存器,以后就从这里取得首地址movesp, TopOfStack; 下面显示一个字符串movah, 0Ch; 0000: 黑底    1100: 红字xoresi, esixoredi, edimovesi, OffsetPMMessage; 源数据偏移movedi, (80 * 10 + 0) * 2; 目的数据偏移。屏幕第 11 行, 第 1 列。cld ;esi自动加1.1:lodsb     ;从ds:esi中取数据放入al中,以DS描述符高速缓存寄存器,取得首地址,加上esi的偏移形成32位地址,取得数据testal, aljz.2mov[gs:edi], ax ;ah中存放字体的颜色,al中存放字体的内容,以GS描述符高速缓存寄存器,取得首地址,                 ;加上edi的偏移形成32位地址,存放数据addedi, 2jmp.1.2:; 显示完毕callDispReturn ;EIP变化,用ret返回; Load TSSmovax, SelectorTSS ;加载到TR选择器中,然后TR高速缓存就变化成了TR的描述符ltrax; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。pushSelectorStack3pushTopOfStack3pushSelectorCodeRing3push0retf;retf弹出CS和EIP      ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。; ------------------------------------DispReturn:pusheaxpushebxmoveax, edimovbl, 160divblandeax, 0FFhinceaxmovbl, 160mulblmovedi, eaxpopebxpopeaxret; DispReturn 结束---------------------SegCode32Lenequ$ - LABEL_SEG_CODE32; END of [SECTION .s32]; CodeRing3    [SECTION .ring3]ALIGN32[BITS32]LABEL_CODE_RING3:  ;从上面的retf跳转到此,此处为ring3特权级movax, SelectorVideomovgs, ax; 视频段选择子(目的),改变了GS描述符高速缓存寄存器,下次就从这里取得首地址movedi, (80 * 14 + 0) * 2; 屏幕第 14 行, 第 0 列。movah, 0Ch; 0000: 黑底    1100: 红字moval, '3'mov[gs:edi], ax  ;根据特权级访问原则 CPL小于等于DPL,RPL小于等于DPL,可以访问callSelectorCallGateTest:0; 测试调用门(有特权级变换),将打印字母 'C'。、;已经了TSS做为铺垫;CPL小于等于DPL_G,RPL小于等于DPL_G,DPL_B小于等于CPLjmp$ ;最后程序停止不动SegCodeRing3Lenequ$ - LABEL_CODE_RING3; END of [SECTION .ring3][SECTION .sdest]; 调用门目标段[BITS32]LABEL_SEG_CODE_DEST: ;由callSelectorCallGateTest:0进入此段代码 ring3-->ring0movax, SelectorVideomovgs, ax; 视频段选择子(目的)movedi, (80 * 12 + 0) * 2; 屏幕第 12 行, 第 0 列。movah, 0Ch; 0000: 黑底    1100: 红字moval, 'C'mov[gs:edi], ax; Load LDTmovax, SelectorLDT ;加载到LDTR选择器中,然后LDTR高速缓存就变化成了LDTR的描述符lldtaxjmpSelectorLDTCodeA:0; 跳入局部任务,将打印字母 'L'。;此时CS描述符高速缓存寄存器被置为LABEL_CODE_A的描述符,以后直接从这里取得首地址;retfSegCodeDestLenequ$ - LABEL_SEG_CODE_DEST; END of [SECTION .sdest]; LDT[SECTION .ldt]ALIGN32LABEL_LDT:;                                         段基址       段界限     ,   属性LABEL_LDT_DESC_CODEA:Descriptor       0,     CodeALen - 1,   DA_C + DA_32; Code, 32 位LDTLenequ$ - LABEL_LDT; LDT 选择子SelectorLDTCodeAequLABEL_LDT_DESC_CODEA- LABEL_LDT + SA_TIL ;TI位为1,指示从局部描述符表LDT中读取描述符; END of [SECTION .ldt]; CodeA (LDT, 32 位代码段)[SECTION .la]ALIGN32[BITS32]LABEL_CODE_A:movax, SelectorVideomovgs, ax; 视频段选择子(目的)movedi, (80 * 13 + 0) * 2; 屏幕第 13 行, 第 0 列。movah, 0Ch; 0000: 黑底    1100: 红字moval, 'L'mov[gs:edi], ax; 准备经由16位代码段跳回实模式jmpSelectorCode16:0 ;此时CS描述符高速缓存寄存器被置为LABEL_SEG_CODE16的描述符,以后直接从这里取得首地址CodeALenequ$ - LABEL_CODE_A; END of [SECTION .la]; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式[SECTION .s16code]ALIGN32[BITS16]LABEL_SEG_CODE16:; 跳回实模式:movax, SelectorNormalmovds, axmoves, axmovfs, axmovgs, axmovss, ax  ;这里所有的步骤都是为了是描述符高速缓存寄存器恢复16代码时候的状态,而CS在jmpSelectorCode16:0;的位置已经修改好了moveax, cr0andal, 11111110bmovcr0, eax ;退出保护模式LABEL_GO_BACK_TO_REAL:jmp0:LABEL_REAL_ENTRY; 段地址会在程序开始处被设置成正确的值,段基地址*16+偏移Code16Lenequ$ - LABEL_SEG_CODE16; END of [SECTION .s16code]




读书人网 >移动开发

热点推荐