80386保护模式--分页机制,附pmtest8代码详解

来源:互联网 发布:2017网络实名制 编辑:程序博客网 时间:2024/06/05 04:02

一、分段机制

       逻辑地址---分段机制---线性地址---分页机制---物理地址,分页机制示意图如图1。


       页目录表中的每一项叫页目录项;页表中的每一项叫页表项。


                                                

                                                                                   图 1 页映射表结构


       页目录表PDE,及页表PTE如图2。

                              

                                                                  图 2 页目录表或者页表的表格选项

     

       AVL:供软件使用

       P:存在属性位,P=1表项有效,P=0表项无效,即是否在内存中。

       R/W位指示该表项所指定的页是否可读、写或执行。如R/W=1,对表项所指定页可进行读、写后者执行;如R/W=0,对表项所指定页可读或者执行,但不能对该指定页写。但是R/W位对页的写保护只有处理器处于用户特权级时发挥作用;当处理器处于系统特权级时,R/W位被忽略,也即总可以读、写或者执行。

       U/S位指示该表项所指定的页是否似乎用户级页。如U/S=1,表项所指定页是用户级页,可由任何特权级下执行的程序访问;如U/S=0,表项所指定页是系统页,只能由在系统特权级下执行的程序访问。

       A=1,表示已访问过对应的物理页,A=0,表示未访问过对应的物理页。

       D=1,表示已写过对应的物理页,D=0,表示未写过对应的物理页。


       pm.inc增加的几行代码:

       PG_P         EQU 1 ; 页存在属性位   0001       PG_RWR EQU0; R/W 属性位值, 读/执行    0000       PG_RWW EQU2; R/W 属性位值, 读/写/执行 0010       PG_USS         EQU0; U/S 属性位值, 系统级  0000       PG_USU         EQU4; U/S 属性位值, 用户级  0100


       PDE,PTE具体形象的图形如图3,线性地址到物理地址转换过程如图4:


                                                     

                                                                           图3 PDE,PTE具体形象


                                    

       图4 线性地址到物理地址的转换过程页表DDDDD001改为DDDDD000 由于abc为12位,正好每个PTE对应4KB

       每个PDE和PTE都是4个字节,左侧PDE一共4个,占16字节,右侧PTE每个框里面是1024个,所以总共占1024*4*4字节=8KB,所以总共可以指向的内存大小是1024*4*4KB=16MB。此图中CR0的内容为00211000,在形成地址的时候我们只关心高20位,用高20位加上后面12位全0形成基地址,再由页目录索引表或者页表索引找到PDE和PTE。最后高20位补上偏移地址形成物理地址。


二、pmtest8.asm代码详解

; ==========================================; pmtest8.asm; 编译方法:nasm pmtest8.asm -o pmtest8.com; ==========================================%include"pm.inc"; 常量, 宏, 以及一些说明PageDirBase0equ200000h; 页目录开始地址:2MPageTblBase0equ201000h; 页表开始地址:2M +  4K 这样的大小最多能寻址4GBPageDirBase1equ210000h; 页目录开始地址:2M + 64KPageTblBase1equ211000h; 页表开始地址:2M + 64K + 4KLinearAddrDemoequ00401000hProcFooequ00401000hProcBarequ00501000hProcPagingDemoequ00301000horg0100hjmpLABEL_BEGIN[SECTION .gdt]; GDT;                           段基址,       段界限, 属性LABEL_GDT:          Descriptor 0,              0, 0                      ; 空描述符LABEL_DESC_NORMAL:  Descriptor 0,         0ffffh, DA_DRW                 ; Normal 描述符LABEL_DESC_FLAT_C:  Descriptor 0,        0fffffh, DA_CR|DA_32|DA_LIMIT_4K; 0~4G LIMIT=limit*4k+0FFFh 左移12位再加上FFF正好是FFFFFFFF,4GLABEL_DESC_FLAT_RW: Descriptor 0,        0fffffh, DA_DRW|DA_LIMIT_4K     ; 0~4GLABEL_DESC_CODE32:  Descriptor 0, SegCode32Len-1, DA_CR|DA_32            ; 非一致代码段, 32LABEL_DESC_CODE16:  Descriptor 0,         0ffffh, DA_C                   ; 非一致代码段, 16LABEL_DESC_DATA:    Descriptor 0,      DataLen-1, DA_DRW                 ; DataLABEL_DESC_STACK:   Descriptor 0,     TopOfStack, DA_DRWA|DA_32          ; Stack, 32 位LABEL_DESC_VIDEO:   Descriptor 0B8000h,   0ffffh, DA_DRW                 ; 显存首地址; GDT 结束GdtLenequ$ - LABEL_GDT; GDT长度GdtPtrdwGdtLen - 1; GDT界限dd0; GDT基地址; GDT 选择子SelectorNormalequLABEL_DESC_NORMAL- LABEL_GDTSelectorFlatCequLABEL_DESC_FLAT_C- LABEL_GDTSelectorFlatRWequLABEL_DESC_FLAT_RW- LABEL_GDTSelectorCode32equLABEL_DESC_CODE32- LABEL_GDTSelectorCode16equLABEL_DESC_CODE16- LABEL_GDTSelectorDataequLABEL_DESC_DATA- LABEL_GDTSelectorStackequLABEL_DESC_STACK- LABEL_GDTSelectorVideoequLABEL_DESC_VIDEO- LABEL_GDT; END of [SECTION .gdt][SECTION .data1] ; 数据段ALIGN32[BITS32]LABEL_DATA:; 实模式下使用这些符号; 字符串_szPMMessage:db"In Protect Mode now. ^-^", 0Ah, 0Ah, 0; 进入保护模式后显示此字符串_szMemChkTitle:db"BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0; 进入保护模式后显示此字符串_szRAMSizedb"RAM size:", 0_szReturndb0Ah, 0; 变量_wSPValueInRealModedw0_dwMCRNumber:dd0; Memory Check Result_dwDispPos:dd(80 * 6 + 0) * 2; 屏幕第 6 行, 第 0 列。_dwMemSize:dd0_ARDStruct:; Address Range Descriptor Structure_dwBaseAddrLow:dd0_dwBaseAddrHigh:dd0_dwLengthLow:dd0_dwLengthHigh:dd0_dwType:dd0_PageTableNumberdd0_MemChkBuf:times256db0; 保护模式下使用这些符号szPMMessageequ_szPMMessage- $$ ;相对于首地址的偏移szMemChkTitleequ_szMemChkTitle- $$szRAMSizeequ_szRAMSize- $$szReturnequ_szReturn- $$dwDispPosequ_dwDispPos- $$dwMemSizeequ_dwMemSize- $$dwMCRNumberequ_dwMCRNumber- $$ARDStructequ_ARDStruct- $$dwBaseAddrLowequ_dwBaseAddrLow- $$dwBaseAddrHighequ_dwBaseAddrHigh- $$dwLengthLowequ_dwLengthLow- $$dwLengthHighequ_dwLengthHigh- $$dwTypeequ_dwType- $$MemChkBufequ_MemChkBuf- $$PageTableNumberequ_PageTableNumber- $$DataLenequ$ - LABEL_DATA; END of [SECTION .data1]; 全局堆栈段[SECTION .gs]ALIGN32[BITS32]LABEL_STACK:times 512 db 0TopOfStackequ$ - LABEL_STACK - 1; END of [SECTION .gs][SECTION .s16][BITS16]LABEL_BEGIN:movax, csmovds, axmoves, axmovss, axmovsp, 0100hmov[LABEL_GO_BACK_TO_REAL+3], axmov[_wSPValueInRealMode], sp; 得到内存数movebx, 0movdi, _MemChkBuf.loop:moveax, 0E820hmovecx, 20movedx, 0534D4150hint15h ;往es:di中一次存放20个字节jcLABEL_MEM_CHK_FAILadddi, 20incdword [_dwMCRNumber] ;ds:_dwMCRNumber,记录一共有几个20个字节cmpebx, 0jne.loopjmpLABEL_MEM_CHK_OKLABEL_MEM_CHK_FAIL:movdword [_dwMCRNumber], 0LABEL_MEM_CHK_OK:; 初始化 16 位代码段描述符movax, csmovzxeax, axshleax, 4addeax, LABEL_SEG_CODE16movword [LABEL_DESC_CODE16 + 2], axshreax, 16movbyte [LABEL_DESC_CODE16 + 4], almovbyte [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, 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; 为加载 GDTR 作准备xoreax, eaxmovax, dsshleax, 4addeax, LABEL_GDT; eax <- gdt 基地址movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址; 加载 GDTRlgdt[GdtPtr]; 关中断cli; 打开地址线A20inal, 92horal, 00000010bout92h, al; 准备切换到保护模式moveax, cr0oreax, 1movcr0, eax; 真正进入保护模式jmpdword SelectorCode32:0; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;LABEL_REAL_ENTRY:; 从保护模式跳回到实模式就到了这里movax, csmovds, axmoves, axmovss, axmovsp, [_wSPValueInRealMode]inal, 92h; ┓andal, 11111101b; ┣ 关闭 A20 地址线out92h, al; ┛sti; 开中断movax, 4c00h; ┓int21h; ┛回到 DOS; END of [SECTION .s16][SECTION .s32]; 32 位代码段. 由实模式跳入.[BITS32]LABEL_SEG_CODE32:movax, SelectorDatamovds, ax; 数据段选择子moves, axmovax, SelectorVideomovgs, ax; 视频段选择子movax, SelectorStackmovss, ax; 堆栈段选择子movesp, TopOfStackcallDispMemSize; 获取内存信息callPagingDemo; 演示改变页目录的效果; 到此停止jmpSelectorCode16:0; 显示内存信息 --------------------------------------------------------------DispMemSize:pushesipushedipushecxmovesi, MemChkBuf ;256字节开始位置movecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构.loop:;{movedx, 5;for(int j=0;j<5;j++)// 每次得到一个ARDS中的成员,共5个成员movedi, ARDStruct;{// 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type.1:;moveax dword [esi]; ds:esi-->eaxstosd;eax-->es:ediARDStruct[j*4] = MemChkBuf[j*4]; EI自动加4addesi, 4;decedx;cmpedx, 0;jnz.1;如果不为0}cmpdword [dwType], 1;if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2jne.2;如果不相等{moveax, [dwBaseAddrLow]; ds:dwBaseAddrLowaddeax, [dwLengthLow];ds:dwLengthLowcmpeax, [dwMemSize];if(BaseAddrLow + LengthLow > MemSize) ds:dwMemSizejb.2;如果小于mov[dwMemSize], eax;MemSize = BaseAddrLow + LengthLow;.2:;}loop.loop;} 从156字节不断取数据放到es:ARDStructpopecxpopedipopesiret; ---------------------------------------------------------------------------; 测试分页机制 --------------------------------------------------------------PagingDemo:movax, csmovds, ax  ;因为后面要复制的内容都是相对于32位代码段的偏移movax, SelectorFlatRWmoves, axpushLenFoopushOffsetFoo  ;ds:esi-->es:edipushProcFoo  ;escallMemCpy ;此段代码为于lib.inc中,由于以后会用C语言写,暂时不考虑addesp, 12pushLenBarpushOffsetBarpushProcBarcallMemCpy;addesp, 12pushLenPagingDemoAllpushOffsetPagingDemoProcpushProcPagingDemocallMemCpyaddesp, 12    ;至此PagingDemoProc放在内存00301000h,foo放在内存的00401000h,bar放在内存的00501000hmovax, SelectorDatamovds, ax; 恢复原来的数据段选择子moves, axcallSetupPaging; 启动分页,相对距离callSelectorFlatC:ProcPagingDemo ;执行PagingDemoProc,00301000h,分页后形成的地址还是00301000hcallPSwitch; 切换页目录,改变地址映射关系callSelectorFlatC:ProcPagingDemo;执行PagingDemoProc,00301000h,分页后形成的地址还是00301000hret; ---------------------------------------------------------------------------; 启动分页机制 --------------------------------------------------------------SetupPaging:; 根据内存大小计算应初始化多少PDE以及多少页表xoredx, edxmoveax, [dwMemSize]movebx, 400000h; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小divebxmovecx, eax; 此时 ecx 为页表的个数,也即 PDE 应该的个数testedx, edxjz.no_remainder ;如果为0incecx; 如果余数不为 0 就需增加一个页表.no_remainder:mov[PageTableNumber], ecx; 暂存页表个数,ds:PageTableNumber; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.; 首先初始化页目录movax, SelectorFlatRWmoves, axmovedi, PageDirBase0; 此段首地址为 PageDirBase0xoreax, eaxmoveax, PageTblBase0 | PG_P  | PG_USU | PG_RWW.1:stosd ;eax-->es:ediaddeax, 4096; 为了简化, 所有页表在内存中是连续的.loop.1 ;共循环ecx次; 再初始化所有页表moveax, [PageTableNumber]; 页表个数movebx, 1024; 每个页表 1024 个 PTEmulebxmovecx, eax; PTE个数 = 页表个数 * 1024movedi, PageTblBase0; 此段首地址为 PageTblBase0xoreax, eaxmoveax, PG_P  | PG_USU | PG_RWW.2:stosdaddeax, 4096; 每一页指向 4K 的空间loop.2moveax, PageDirBase0movcr3, eax ;修改cr3moveax, cr0oreax, 80000000hmovcr0, eax;启动分页jmpshort .3.3:nopret; 分页机制启动完毕 ----------------------------------------------------------; 切换页表 ------------------------------------------------------------------PSwitch:; 初始化页目录movax, SelectorFlatRWmoves, axmovedi, PageDirBase1; 此段首地址为 PageDirBase1xoreax, eaxmoveax, PageTblBase1 | PG_P  | PG_USU | PG_RWWmovecx, [PageTableNumber].1:stosdaddeax, 4096; 为了简化, 所有页表在内存中是连续的.loop.1; 再初始化所有页表moveax, [PageTableNumber]; 页表个数movebx, 1024; 每个页表 1024 个 PTEmulebxmovecx, eax; PTE个数 = 页表个数 * 1024movedi, PageTblBase1; 此段首地址为 PageTblBase1xoreax, eaxmoveax, PG_P  | PG_USU | PG_RWW.2:stosdaddeax, 4096; 每一页指向 4K 的空间loop.2; 在此假设内存是大于 8M 的moveax, LinearAddrDemoshreax, 22movebx, 4096mulebxmovecx, eaxmoveax, LinearAddrDemoshreax, 12andeax, 03FFh; 1111111111b (10 bits)movebx, 4mulebxaddeax, ecxaddeax, PageTblBase1 ;此时eax为0x00212004,这个参考图3,你就会明白的movdword [es:eax], ProcBar | PG_P | PG_USU | PG_RWWmoveax, PageDirBase1movcr3, eaxjmpshort .3.3:nopret; ---------------------------------------------------------------------------PagingDemoProc: ;为了算出LenPagingDemoAllOffsetPagingDemoProcequPagingDemoProc - $$ ;相对于当前代码段的偏移moveax, LinearAddrDemo ;00401000hcalleax ;call cs:eaxretf ;因为上面把CS和EIP都压入了栈LenPagingDemoAllequ$ - PagingDemoProcfoo: ;00401000hOffsetFooequfoo - $$  ;相对于当前代码段的偏移movah, 0Ch; 0000: 黑底    1100: 红字moval, 'F'mov[gs:((80 * 17 + 0) * 2)], ax; 屏幕第 17 行, 第 0 列。moval, 'o'mov[gs:((80 * 17 + 1) * 2)], ax; 屏幕第 17 行, 第 1 列。mov[gs:((80 * 17 + 2) * 2)], ax; 屏幕第 17 行, 第 2 列。ret ;因为上面只改变了EIP,calleaxLenFooequ$ - foobar: ;00501000hOffsetBarequbar - $$ ;相对于当前代码段的偏移movah, 0Ch; 0000: 黑底    1100: 红字moval, 'B'mov[gs:((80 * 18 + 0) * 2)], ax; 屏幕第 18 行, 第 0 列。moval, 'a'mov[gs:((80 * 18 + 1) * 2)], ax; 屏幕第 18 行, 第 1 列。moval, 'r'mov[gs:((80 * 18 + 2) * 2)], ax; 屏幕第 18 行, 第 2 列。retLenBarequ$ - bar%include"lib.inc"; 库函数SegCode32Lenequ$ - LABEL_SEG_CODE32; END of [SECTION .s32]; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式[SECTION .s16code]ALIGN32[BITS16]LABEL_SEG_CODE16:; 跳回实模式:movax, SelectorNormalmovds, axmoves, axmovfs, axmovgs, axmovss, axmoveax, cr0and     eax, 7FFFFFFEh          ; PE=0, PG=0movcr0, eaxLABEL_GO_BACK_TO_REAL:jmp0:LABEL_REAL_ENTRY; 段地址会在程序开始处被设置成正确的值Code16Lenequ$ - LABEL_SEG_CODE16; END of [SECTION .s16code]