分类:
2010-06-28 13:43:41
【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 [段索引]:[段基址偏移]
呵呵 接下来继续学习啊!!!