为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

保护模式编程

2011-11-17 41页 pdf 168KB 37阅读

用户头像

is_747246

暂无简介

举报
保护模式编程 第1章 保护模式编程一 如果想更深、更亲近的了解电脑软件。那么学习 cpu是你的必选!! 386是 CPU史的一大 转折点,那 386做基础课是最好不过了。那么我们将开始进行学习之旅!!!大家跟我一块学 习吧,呵呵!!! 1.1 准备工作 l 1、NASM 编译环境(当然 Masm 也可以 但是用它来写 COM程序比较 麻烦) l 2、虚拟机 Virtual PC(Windows平台 ,执行比较快,即模拟又虚拟硬件)、 WMWarve(WIndows平台 虚拟硬件,)、 Bochs(支持Wi...
保护模式编程
第1章 保护模式编程一 如果想更深、更亲近的了解电脑软件。那么学习 cpu是你的必选!! 386是 CPU史的一大 转折点,那 386做基础课是最好不过了。那么我们将开始进行学习之旅!!!大家跟我一块学 习吧,呵呵!!! 1.1 准备工作 l 1、NASM 编译环境(当然 Masm 也可以 但是用它来写 COM程序比较 麻烦) l 2、虚拟机 Virtual PC(Windows平台 ,执行比较快,即模拟又虚拟硬件)、 WMWarve(WIndows平台 虚拟硬件,)、 Bochs(支持Windows平台、也支持在 Linux平台上运行 有 RPM版本的) 我们这些生长在Windows这棵大树下的朋友们,还是用 Virtual PC吧.。 l 3、写虚拟启动镜像文件的程序 :不知道我观察的对不对?用Nasm 编译一个 bin 然后将它转换为 img 镜像文件的时候。 只要文件大小符合软驱的就能启动。那么就代表 a.bin 与 a.img 文件的内容一模样就是 文件大小不一样!我是不太了解镜像文件格式.我用的是 Virtual PC。 1.2 开始接触引导程序 1.2.1 Com文件 Com 文件是纯二进制的文件,也是直接与 Cpu 交换的顺序指令文件。Com 文件的大小 是有限制的,不能超过 64KB.因为 8086时代的 CPU地址线是 20位的,20位能表达的数值也 就是 fffffh(1MB )。而寄存器最高也只是 16位,无法用 5个 F的形式来表达地址,所以用 CS(段 基地址)*16:IP(偏移地址)来寻址!80386后通用寄存器都得到了 32位扩展! 而 Cpu地址线也得 到了 32位的扩展。引导程序前期是需要进入实模式的,因为这是硬件上的限制是 IA32的限 制。386cpu只有两种模式: 实模式与保护模式!!!!,, 1.2.2 引导程序 引导程序也是有限制的,这个限制是靠 Bios处理的,开机后 Bios经过自检后,会从软驱或 者硬盘的 0 面 0 磁道 1 扇区搜寻一个程序文件。该文件的数据必需是等于 512Byte,并且以 aa55h结尾的(高高低低)。那么 bios会认为它是引导程序,这个时候就会把该 512byte 装载到 内存 7c00开始处。然后将主控权交给程序的第一行代码。那么这个时候程序脱离 Bios的控 制。Cpu将执行程序的代码. 1.2.3 写一个引导程序 引导程序可以说是非常简单: 1、boot.asm(nasm 的源文件如下) ;-----------------------欲编译,这里改成 100h就是 com程序 ------------------------------------------- %define _BOOT_DEBUG_ ;做调试的时候用 100h %ifdef _BOOT_DEBUG_ org 0100h %else org 07c00h ; 告诉编译器 以下代码段将从 07c00h内存地址处开始 %endif mov ax,cs ;让数据段与附加段寄存器跟代码段一样,因为 COM代码数据是混合. mov ds,ax mov es,ax call _HelloWorld ;让程序显示一个 HelloWorld jmp $ ;$表示当前地址 无限循环 _HelloWorld: mov ax,strHello ;取得字符串的地址 mov bp,ax ;给堆栈基寄存器 mov cx,strLen mov ax,1301H ;ah代表功能号 mov bx, 000ch ; 页号为 0(BH = 0) 黑底红字(BL = 0Ch,高亮 mov dx,0001h ;显示的行与列 int 10h ;bios 10h显示中断 ret strHello: db "Hello World" strLen equ $ - strHello times 510-($-$$) db 0 ; times重复定义 510-($-$$)个 $$ 表示段的起始地址 dw 0xAA55 那么引导程序完成了,用 nasm boot.asm -o a.com 就可以运行看效果.,如改成引导程序只需把%define _DEBUG_BOOT_注释 然后 nasm boot.asm -o a.bin然后用工具将 a.bin 转换成软驱大小的镜像文件 载入虚拟机启动就可以. 第2章 保护模式编程二 8086到 80386的跳转,80386与 8086在硬件上的区别在这就不说了!!那么 80386与 8086 在软件逻辑上面的区别就是:8086 是实模式,而 80386 不仅包括实模式,而且还可以进入保 护模式!!! 保护模式不仅不受 64KB 内存寻址的限制,而且还拥有 4GB 的寻址空间。这是因为 386 扩展了 20地址线,将它扩展成 32位了(32位能表达的字节数就是 4GB).此时的段寄存器不再 是段基地了,而被叫做是选择子 ,存放的是一个段描述符的索引值.而我们的通用寄存器与 EIP 也是 32位的,可以表达 4GB地址!不过计算机开机后,CPU默认是实模式。这就需要我们编 程手动转换到 386.那么我们该怎么去做呢: 2.1 【准备 GDT (全局描述符表)】 首先我们需要准备GDT结构体,它是 386保护模式必须的东西。全局描述符寄存器GDTR 指向的是所有段描述符表的信息.前面提到得段选择子索引,指得就是指向段描述符的索引. 段描述符是 8个字节的结构体、里面存放着段的段界限、段基址、段属性等信息.LDTR寄存 器是指向局部某一个段的描述符表。 段描述符表结构体用一个宏来表示(注意段 1 2表示同一个段描述内容被分开来放的): 【段基址】32位、表示物理地址 【段界限】20位、表示段的总长度 这里并不是地址,而是段的字节长度。 【段属性】12位. 系统、门、数据等属性 %macro Descriptor 3 ; 有三个:【段基址】、【段界限】、【段属性】 dw %2 & 0FFFFh ; 段界限 1 (2 字节) dw %1 & 0FFFFh ; 段基址 1 (2 字节) db (%1 >> 16) & 0FFh ; 段基址 1 (1 字节) dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) db (%1 >> 24) & 0FFh ; 段基址 2 (1 字节) %endmacro ; 共 8 字节 看似很简单的结构体 理解起来可不是那么简单! 【Descriptor结构体】有 8个字节。 1、【第 1、2字节】组合(word) 表示该段的[段界限①], dw %2 & 0FFFFh ;引用第二个参 数去掉高 16位 2、【第 3、4、5字节】组合表示该段的[段基址①],dw %1 & 0FFFFh ;先得到第一个参数 (段基址)低WORD。 3、接着把第 5个字节赋值,db (%1 >> 16) & 0FFh 去掉第 3第 4个字节的内容.再把剩下 的字节赋值 4、【第 6个字节】是与【第 7个字节】组合的内容可就更复杂了: 【第 6个字节】的内容: 【7(p) 6(DPL) 5(DPL) 4(S) 3(Type) 2(Type) 1(Type) 0(Type)】 0-3位表示:[段属性]、说明存储段描述符所描述的存储段的具体属性。 4位表示:说明描述符的类型, 对于存储段描述符而言,S=1表示是系统段描述符。 5-6位表示:DPL 该段的特权级别也就是 Ring 0-3; 7位表示:P: 存在(Present)位。 ; P=1 表示描述符对地址转换是有效的,即描述的段在内存当中. ; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异 常 【第 7个字节】的内容: 【7(G) 6(D) 5(0 ) 4(AVL) 3(段界限) 2(段界限) 1(段界限) 0(段界 限)】 0-3位表示:[段界限②] 4 位表示:软件可利用位。80386 对该位的使用未做规定,Intel 公司也保证今后开发生 产的处理器只要与 80386兼容,就不会对该位的使用做任何定义或规定。 5位表示:0 ;Intel资料也没表示 6位表示:是一个很特殊的位,在描述可执行段、向下扩展数据段或由 SS寄存器寻址的 段(通常是堆栈段)的三种描述符中的意义各不相同,通常置 1 7位表示: 段界限粒度(Granularity)位。 G=0 表示界限粒度为字节; G=1 表示界限粒度为 4K 字节。 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 那么这段宏 dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)表示: 取[段界限]参数除去低 16位取 高 4位,得到【段界限②】 取[段属性]参数的低 8位 12-15位(AVL属性等) 属性 1 + 段界限 2 + 属性 2 【第 8个字节】的内容: [段基址②] 、db (%1 >> 24) & 0FFh 取基地址参数的最高 8位 那么一个 Descriptor 结构体就这样成形了. 2.2 【编写程序跳转到保护模式】 %include "386.inc" ;是 Descriptor结构体宏 ;%define _DEBUG_BOOT_ %ifdef _DEBUG_BOOT_ org 0100h %else org 07c00h %endif jmp LABEL_BEGIN [SECTION .gdt] ;全局描述符数据段 ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ;空描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_CR | DA_32 ;代码段描述符 LABEL_DESC_DATA: Descriptor 0,SegDataLen - 1,DA_DRW ;数据段 LABEL_DESC_VIDEO: Descriptor 0B8000h,0FFFFh,DA_DRW ;显示器内存段 由于 DOS中断不能随 意使用了,,只能输出到显示缓冲区 ; GDT 结束 GdtLen equ $ - 1 GdtPtr dw GdtLen - 1 ;GDT 的段界限, dd 0 ;GDT基地址 ; GDT 选择子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT ;代码相对全局描述符起始地址的 EA值 SelectorData equ LABEL_DESC_DATA - LABEL_GDT ;数据段 SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ;显示数据段 [SECTION .s16] ;16位代码段 [BITS 16] ;BITS指出处理器的模式 是 16位 LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax ;初始化段寄存器 ;初始化数据 mov eax,strHello mov word[LABEL_DESC_DATA + 2],ax mov byte[LABEL_DESC_DATA + 4],al shr eax,16 mov byte[LABEL_DESC_DATA + 7],ah ; 初始化并把 32位段代码的基地址分配给段描述符 mov eax, LABEL_CODE32 ; mov word [LABEL_DESC_CODE32 + 2], ax ;ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ; 为加载 GDTR 作准备 mov eax, LABEL_GDT mov dword [GdtPtr + 2], eax ;得到 GDT基地址 ; 加载 全局描述符的信息结构体 到 GDTR lgdt [GdtPtr] CA20 ;利用键盘端口打开 A20地址线 ; 将 CRO的 PE位 也就是 0位 置 1 那么就进入 386模式了 mov eax, cr0 or eax, 1 mov cr0, eax jmp dword SelectorCode32:0 ; ;执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处 ;这个描述符集合是以一个空描述符开始得,现在 LABEL_DESC_CODE32描述符的索引值因该是 8, ;所以 SelectorCode32的值应该就是 LABEL_DESC_CODE32的索引值,Code32Selector:0当中的 0是指 LABEL_DESC_CODE32 的段基址+ 0 ;那么在打开 cr0的 PE位后,这个 JMP指令不再是直接跳到段地址去了; ;而是去 GDTR全局描述符寄存器当中去找这个当前 CS的索引,当前段基址+偏移 的内存地址了。 [SECTION .s32]; 32 位代码段. 由实模式跳入. [BITS 32] LABEL_CODE32: ;保护模式的死循环 mov ax, SelectorVideo mov gs, ax ; 视频段选择子(目的) mov edi, (80 * 10 + 9) * 2 ; 屏幕第 10 行, 第 0 列。 mov ah, 1Ch ; 0000: 黑底 1100: 红字 mov esi,0 mov ds,SelectData mov ecx,11 vi: lodsb mov [gs:edi], ax inc edi LOOPNZ vi ; 到此停止 jmp $ SegCode32Len equ $ - LABEL_CODE32 [SECTION .data] ;数据段 strHello: db "Hello World" SegDataLen equ $- strHello 2.3 【总结】 编写一个 386 程序主要用的步骤: 1、准备 GDT描述符集合结构体 2、用 lgdt [gdtPtr] 载入 gdtPtr 这 6个字节的结构体,,低字是描述符集合的界限 也就 是集合总长度,高双字是描述符集合的基地址. 3、打开 A20地址线。有一种方法是向键盘端口 IO, 4、置 CR0的 PE位 即 0位为 1 5、JMP [段索引]:[段基址偏移] 呵呵 接下来继续学习啊!!! 第3章 保护模式编程三 在一、到二、我们了解 386基本寻址机制,没错就是这么简单!!!接下来我们谈谈 对上一 个 386进行扩展: 大家在第二节已经知道了进入 386的基本步骤了,那么我们来具体设计吧. 编程首先当然是【声明】与【定义】: 3.1 【声明】: 在 386.inc 头文件里定义好需要的宏信息(好东西直接拿来用了呵呵) ;---------------------------------386.inc----------------------------------------------------- DA_32 EQU 4000h ;32位段 DA_DPL0 EQU 00h ; DPL = 0 DA_DPL1 EQU 20h ; DPL = 1 (表示描述符特权级 Ring0-Ring3级) DA_DPL2 EQU 40h ; DPL = 2 DA_DPL3 EQU 60h ; DPL = 3 ;---------------------------------------------------------------------------- ; 存储段描述符类型值说明 ;---------------------------------------------------------------------------- DA_DR EQU 90h ; 存在的只读数据段类型值 DA_DRW EQU 92h ; 存在的可读写数据段属性值 DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值 DA_C EQU 98h ; 存在的只执行代码段属性值 DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值 DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值 DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值 ;---------------------------------------------------------------------------- %macro Descriptor 3 ;3表示宏的参数有 3个 %1表示是第一个参数的标识 >>右移位 dw %2 & 0FFFFh ; 段界限 1 (2 字节) dw %1 & 0FFFFh ; 段基址 1 (2 字节) db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节) dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节) %endmacro ; 共 8 字节 %macro CA20 0 ; 打开地址线 A20 in al, 92h or al, 00000010b out 92h, al %endmacro %macro DA20 0 ;关地址线 in al,92h and al,11111101b out 92h,al %endmacro 3.2 【定义】: ;在定义模块之前我们首先要有个概念,那就是整体的雏形。可以简单划分出来也就是 2 个主要步骤: 1、定义 GDT数据段: { 1、定义 Descriptor即段描述符: (通常是以一个全为零的 Descriptor开始)。 2、定义 GdtPtr 信息结构体(再加载 gdtr时候要用到)。 3、定义每个段描述符对应的索引位置(即定义选择子)。 } 2、定义如上描述的具体段: { 1、实模式入口段 (这是个入口段:用于跳转到 386的段,并不属于 386段所以没有 描述符) 2、剩下的段就全是 386模式的段。 } 3.3 【具体编码】 还是要拿实实在在的能运行的代码来讲: 先说一下代码的主要功能: 1、从 8086跳到 386模式(Protect Mode) 2、在 386模式对大地址的寻址测试(超过 1MB) 3、测试完毕后回到 8086模式(Real Mode) 具体细节上,就看代码吧!!!在 386.asm 文件里实行具体模块的编写(代码比较多,刚开始 阅读有点复杂,因为是汇编可读性不是很好不过这是照上面的方法定义的,可以先从宏观入 手!!!): ;========================386.asm=============================== %include "386.inc" ; 常量, 宏, 以及一些说明 %define _DEBUG_B0OT_ %ifdef _DEBUG_B0OT_ org 0100h %else org 07c00h %endif jmp LABEL_BEGIN ;===========================;GDT全局描述符数据段============================== [SECTION .gdt] LABEL_GDT Descriptor 0,0,0 ;以空开头 ;这个段描述符描述的段有点特殊,因为在下面并没有实际的定义它。它的作用是从保护模式跳转到 8086时 要用到的,是用来初始化段寄存器的。本人估计是(保护模式与实模式段界限与段偏移是不同的, 而这个段描 述符的段基址是 0、段界限是 0FFFFH ,段属性是读加写,与 8086 的标准是一样,所以在回到 8086模式之前, CPU在对所有段寄存器进行实模式的转换就能正确安排界限与属性了,!!! ), LABEL_DESC_NORMAL Descriptor 0,0ffffh,DA_DRW LABEL_DESC_DATA Descriptor 0,SegDataLen - 1 ,DA_DRW | DA_32 ;段属性是非一致的 32位读写 数据段 LABEL_DESC_STACK Descriptor 0,TopOfStack - 1,DA_DRWA | DA_32 ;存在的已访问可读写的 32位 stack段 在保护模式下的 Call命令需要堆栈 LABEL_DESC_CODE32 Descriptor 0,SegCode32Len -1 ,DA_C | DA_32 ;Protect mode的 32位代码区 ;这个段是保护模式的段,但是它是以 16位形式存放的,它是用来从保护模式跳回到 8086模式。,。。。。 因为直接用 32位代码段跳转到 8086模式是不行,必需从 16位保护模式段跳转到 16位 8086模式。就像先 前说的Normal 描述符,它也是一个具有 8086属性的描述符。,所以CS段寄存器的状态也需要先转换成与 8086 模式相同段界限与段属性。这样才能正确的转换到 8086模式,由此可见:全部的段寄存器都需要对应 8086模 式的描述符状态。才能正常的进入 8086模式。 LABEL_DESC_CODE16 Descriptor 0,0ffffh ,DA_C ; ;以下两个段描述符是内存中的段不需要自己定义 LABEL_DESC_TEST Descriptor 0500000h,0ffffh,DA_DRW ;用于测试线性空间 LABEL_DESC_VIDEO Descriptor 0B8000h,0ffffh,DA_DRW ;显示器内存 GdtLen equ $ - LABEL_GDT GdtPtr dw GdtLen - 1 ;段界限与段基地址 dd 0 ;----------------选择子--------------------- SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT SelectorData equ LABEL_DESC_DATA - LABEL_GDT SelectorStack equ LABEL_DESC_STACK - LABEL_GDT SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT SelectorTest equ LABEL_DESC_TEST - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ;为了便于区分实模式代码与保护模式代码, 我就就把 16位的实模式段先写前面:(一般的编程下 数据段是写前面的) ;=============================-8086的 16位实模式起始段============================ [SECTION .s16] ;16位代码段 [BITS 16] ;BITS指出处理器的模式 是 16位 LABEL_BEGIN: ;从 100h处跳进来的 mov ax,cs mov ds,ax mov es,ax mov ss,ax mov sp,100h ;在实模式下并没有用到 sp,这个 100h 只是形象表示需要保存实模式的 SP值. mov [LABEL_GO_BACK_TO_REAL+3], ax ; 请看[LABEL_GO...]+3标号处的注释。是一个指令参数 mov [SPValueInRealMode],sp ;在这里保存实模式的 sp值 ;-----------全局数据段描述符初始化----------- xor eax,eax mov ax,ds shl eax,4 ;ds * 16 代表这 DS原来的基地址 add eax,LABEL_DATA1 ;得到物理地址 mov WORD [LABEL_DESC_DATA + 2],ax shr eax,16 mov BYTE [LABEL_DESC_DATA + 4],al mov BYTE [LABEL_DESC_DATA + 7],ah ;此时数据段描述符已经有基址了也就是可以访问了 ;-----------全局堆栈段描述符初始化----------- xor eax,eax mov ax,ds shl eax,4 add eax,LABEL_STACK mov WORD [LABEL_DESC_STACK + 2],ax shr eax,16 mov BYTE [LABEL_DESC_STACK + 4],al mov BYTE [LABEL_DESC_STACK + 7],ah;此时堆栈段描述符已经有基址了也就是可以访问了 ;-----------32位代码段描述符初始化----------- xor eax,eax mov ax,cs shl eax,4 add eax,LABEL_SEG_CODE32 mov WORD [LABEL_DESC_CODE32 + 2],ax shr eax,16 mov BYTE [LABEL_DESC_CODE32 + 4],al mov BYTE [LABEL_DESC_CODE32 + 7],ah ;进入保护模式后开始执行的代码段 ;-----------16位代码段描述符初始化----------- xor eax,eax mov ax,cs shl eax,4 add eax,LABEL_SEG_CODE16 mov WORD [LABEL_DESC_CODE16 + 2],ax shr eax,16 mov BYTE [LABEL_DESC_CODE16 + 4],al mov BYTE [LABEL_DESC_CODE16 + 7],ah ;用来跳转到实模式的 386代码段 ;----------GDTR Ready ----------- xor eax,eax mov ax,ds shl eax,4 add eax,LABEL_GDT mov [GdtPtr + 2],eax lgdt [GdtPtr] ;loader gdtr ;----------打开 A20-------- CA20 cli ;--------置 CR0 PE位----- mov eax,cr0 or eax,1 mov cr0,eax ;---------------跳到 386----------- jmp dword SelectorCode32:0 ;以上 3个步骤无需多讲了 ;这个是迎接保护模式跳回来的时候,执行的代码,,欢迎 386回来啊!!! LABEL_REAL_ENTRY: mov ax,cs mov ds,ax mov es,ax mov ss,ax mov sp,[SPValueInRealMode] ;恢复到 Real原来的堆栈 注意这里要用[标号] DA20 ;关闭 20地址线 sti mov ax,4c00h int 21h ;===================================386保护模式段================================== ;-------------------数据段-------------------------- [SECTION .data1] align 32 [BITS 32] LABEL_DATA1: SPValueInRealMode dw 0 ; 这个变量用来保存实模式跳入到保护模式前的 SP值 ;---------字符串------------- PMMessage: db "welcome to Protect Mode ", 0 ; 进入保护模式后显示此字符串 OffsetPMMessage equ PMMessage - $$ ;保护模式寻址方式是按段偏移 StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0 OffsetStrTest equ StrTest - $$ ;测试 5MB空间所用的字符串 SegDataLen equ $ - LABEL_DATA1 ;数据段长 ;-----------------------------------386全局堆栈段------------------------------------ [SECTION .stack32] align 32 [BITS 32] LABEL_STACK: times 512 db 0 ;堆栈大小是 512byte TopOfStack equ $ - LABEL_STACK - 1 ;栈顶的值 ;-----------------------------------进入保护模式后的起始代码段------------------------------------ [SECTION .s32]; 32 位代码段. 由实模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax,SelectorData mov ds,ax mov ax,SelectorTest mov es,ax mov ax,SelectorVideo mov gs,ax ;以上 3个也不用多说了吧,段的选择子也就是 GDT的索引 ;---堆栈-- mov ax,SelectorStack mov ss,ax mov esp,TopOfStack ;当然堆栈段也是段的选择子咯 ;-----------显示缓冲-------- mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov esi,OffsetPMMessage ;这个是字符串相对于它的段偏移值 mov edi,(80*10+0)*2 ;第 10行 cld .1: lodsb test al,al jz .2 mov [gs:edi],ax ;要用 ax做为参数传进缓冲区 add edi,2 jmp .1 .2: ;OffsetPMMessage 字符串显示完成: ;------------------------测试 5MB空间的读------------------ call DispReturn ;显示回车,也就是改变 edi的位置(edi 是显缓冲区段的偏移值) call ReadTest call WriteTest call ReadTest ;当在执行 Call命令的时候 会将 eip + 1压栈,然后跳转 jmp SelectorCode16:0 ;跳到最后那个 16位代码段去了 前面有 16位段描述符的讲解。。 ;--------------显示回车--------------- DispReturn: push ebx ;临时用 ebx eax 所以先保存一下 push eax mov bl,160 mov eax,edi div bl inc eax ;得到回车后的行数 mov bl,160 mul bl mov edi,eax ;取得当前的位置 pop eax pop ebx ret ;ret 指令会恢复 eip + 1 ;--------------读取我们定义的大地址段--------------- ReadTest: xor esi, esi mov ecx, 8 . loop: mov al, [es:esi] call DispAL inc esi loop .loop call DispReturn ret ;--------------写入我们定义的大地址段--------------- WriteTest: push esi ;借这两个寄存器来传字符串 push edi xor esi, esi xor edi, edi mov esi, OffsetStrTest ; 数据段的字符串 cld .1: lodsb test al, al jz .2 mov [es:edi], al ;把数据段的字符串传给测试段 inc edi jmp .1 .2: pop edi pop esi ret ;--------------显示 AL的内容--------------- DispAL: ;对传来的 Al字符进行处理 test al,al jnz .next mov al,'0' .next: mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov [gs:edi],ax add edi,2 ret SegCode32Len equ $ - LABEL_SEG_CODE32 ;-----------------------------------准备 8086模式的 16位段------------------------------------ [SECTION .code16] align 32 [BITS 16] LABEL_SEG_CODE16: mov ax,SelectorNormal ; 保护 8086标准段属性的描述符选择子 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax ;使所有的段选择子都达到 8086标准 mov eax,cr0 and al,0feh mov cr0,eax ;CR0 PE位为 0 回到 Real mode LABEL_GO_BACK_TO_REAL: jmp 0:LABEL_REAL_ENTRY ;还记得上面的[LABEL_GO..]+3吗,指的就是 0这个段值 Code16Len equ $ - LABEL_SEG_CODE16 呵呵、那么就完工了代码虽然比较繁杂,但是还不是特别复杂。有几个要注意的地方: 1、堆栈(新加了堆栈这个段,其寻址方式也是选择子。)在入口的时候是实模式,应该把当 前的 sp 值保存下来,因为在返回系统时我们需要基本恢复原来的样子。就在保护模式的全局 数据段定义一个变量用来保存 SP。在返回 DOS时候要记得恢复。 2、保护模式到实模式。实模式到保护模式 比较简单就几个固定步骤不需要考虑太多, 而从保护模式到实模式就比较复杂:首先需要定义两个 8086 标准属性的段描述符.它们分别 是 Normal数据段与 16位代码段描述符。在 16位代码段中 cs的属性跟 8086模式的 CS段界 限与段属性一致,并且把 Normal 的段属性分别付给全部的段寄存器.这样就让所有保护模式 的段寄存器全部跟 8086段界限与段属性一致!!!在 cr0 的 PE位为 0后 就可以用实模式 寻址了!!!接下来继续学习: 第4章 保护模式编程四--LDT描述符&&特权级&&门 4.1 LDT(局部描述符) GDT是全局描述符,是整个系统的描述符,描述符着所有的段!!!在前几章我们已经 熟悉了 GDT的一些基本功能,与运作机制。对 GDT描述符的定义与使用也就那么几项固定 的步骤,接下来再了解 LDT。 LDT是局部描述符。看字面 LDT与 GDT很相似.它们都是描述符。只不过 GDT是全局 描述符、而 LDT是局部描述符。那么 LDT该如何【定义】与【使用】呢?(与 GDT非常类 似,不过 LDT是归属于 GDT的): 4.1.1 【定义】: 1、在 GDT的描述集合中插入一条 LABEL_DESC_LDT Descriptor 0,LdtLen - 1,DA_LDT (这个段描述符描述的 LDT数据段:是一个局部描述符段的集合,结构类似 GDT.) 2、在 GDT 选择子集合中照样也插入一条 SelectorLdt equ ;LABEL_DESC_LDT - LABEL_GDT(看似跟普通的段描述符没啥两样哦,不过接下来就有点小区别了!) 3、建一个 LDT数据段,这个数据段里的结构与 GDT描述符段类似! { LABEL_LDT: ;这里与 GDT不同,LDT的段描述符一开始就是实际的段 LABEL_DESC_LDT_CODEA: Descriptor 0,LdtCodeALen - 1,DA_C | DA_32 LABEL_DESC_LDT_DATA: Descriptor 0,LdtDataLen - 1,DA_DRW LdtLen equ $ - LABEL_LDT; ;在加载 LDT的时候并不是靠一个绝对结构体,而是通过 GDT这个桥梁索引加载的。 ;LDT内部段的选择子 SelectorLdtCodeA equ LABEL_DESC_LDT_CODEA - LABEL_LDT | SA_TIL SelectorLdtData equ LABEL_DESC_LDT_DATA - LABEL_LDT | SA_TIL ;LDT内部的选择子多了一个 SA_TIL属性,他是段选择的 TI位也就是第 2位 如果是 1代表当前选择子 代表的段是 LDT的内部段。 因为选择子的 0-2位并不参与索引。所以选择子的索引都是 8的倍数或 0, } 4、接着完成 LDT 里面段描述符描述的段。这里只定义了一个代码段 : LABEL_DESC_LDT_CODEA(这个跟普通的代码段一样,有执行、32位等属性. 为了测试LDT 的效果那么我们还是加一个 Data段吧)那么 LDT的基本定义已经完成了!!! 4.1.2 使用 1、LDT 描述符与其内部的局部段描述符信息、,我们在进入保护模式前初始化!!!那么 我们的进入保护模式之前的段在哪还知道吗?当然就是最最开始了!在 16位段实模式入口那 里!!!我们在这里做完 GDT描述符的初始化工作后: ;--------------初始化 LDT描述符--------- xor eax,eax mov eax,ds shl eax,4 add eax,LABEL_LDT mov WORD [LABEL_DESC_LDT + 2],ax shr eax,16 mov BYTE [LABEL_DESC_LDT + 4],al mov BYTE [LABEL_DESC_LDT + 7],ah ;---------------初始化 LDT内部的数据段描述符---------- xor eax,eax mov eax,ds shl eax,4 add eax,LABEL_LDT_DATA mov WORD [LABEL_DESC_LDT_DATA + 2],ax shr eax,16 mov BYTE [LABEL_DESC_LDT_DATA + 4],al mov BYTE [LABEL_DESC_LDT_DATA + 7],ah ;---------------初始化 LDT内部的代码段描述符---------- xor e
/
本文档为【保护模式编程】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索