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

来源:互联网 发布:多源数据融合技术 编辑:程序博客网 时间:2024/05/21 19:26

【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 [段索引]:[段基址偏移]

 

呵呵 接下来继续学习啊!!!

原创粉丝点击