您的位置:首页 > 编程语言

【学习】【保护模式编程、二】

2009-12-27 14:48 253 查看
【80386保护模式编程】

8086到80386的跳转,80386与8086在硬件上的区别在这就不说了!!

那么80386与8086在软件逻辑上面的区别就是:

8086是实模式,而80386 不仅包括实模式,而且还可以进入保护模式!!!

保护模式不仅不受64KB内存寻址的限制,而且还拥有4GB的寻址空间。这是因为386扩展了20地址线,将它扩展成32位了(32位能表达的字节数就是4GB).

此时的段寄存器不再是段基地了,而被叫做是选择子 ,存放的是一个段描述符的索引值.

而我们的通用寄存器与EIP 也是32位的,可以表达4GB地址!

不过计算机开机后,CPU默认是实模式。这就需要我们编程手动转换到386.

那么我们该怎么去做呢:

一、【准备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 结构体就这样成形了.

二、【编写程序跳转到保护模式】

%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

【总结】

编写一个386 程序主要用的步骤

1、准备GDT描述符集合结构体

2、用lgdt [gdtPtr] 载入 gdtPtr 这6个字节的结构体,,低字是描述符集合的界限 也就是集合总长度,高双字是描述符集合的基地址.

3、打开A20地址线

有一种方法是向键盘端口 IO,

4、置CR0的PE位 即0位为1

5、JMP [段索引]:[段基址偏移]

呵呵 接下来继续学习啊!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: