-
-
[原创][保护模式编程、三]
-
发表于: 2008-4-7 23:28 7812
-
【继续80386编程】
在一、到二、我们了解386基本寻址机制,没错就是这么简单!!!
接下来我们谈谈 对上一个386进行扩展:
大家在第二节已经知道了进入386的基本步骤了,那么我们来具体设计吧.
编程首先当然是【声明】与【定义】:
一、【声明】:
在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
二、【定义】:
;在定义模块之前我们首先要有个概念,那就是整体的雏形。
可以简单划分出来也就是2个主要步骤:
1、定义GDT数据段:
{
1、定义Descriptor即段描述符: (通常是以一个全为零的Descriptor开始)。
2、定义GdtPtr 信息结构体(再加载gdtr时候要用到)。
3、定义每个段描述符对应的索引位置(即定义选择子)。
}
2、定义如上描述的具体段:
{
1、实模式入口段 (这是个入口段:用于跳转到386的段,并不属于386段所以没有描述符)
2、剩下的段就全是386模式的段。
}
三、【具体编码】
还是要拿实实在在的能运行的代码来讲:
先说一下代码的主要功能:
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 "This is 5MB", 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,11
mov ah, 0Ch ; 0000: 黑底 1100: 红字
.loop:
mov al,[es:esi] ;这个是SelectorTest的段,也就是我们测试的大地址段
call DispAL ;显示从test段取出来的字符
inc esi
loop .loop ;cx--
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后 就可以转换成实模式寻址了!!!
接下来继续学习:
Day Day Up
在一、到二、我们了解386基本寻址机制,没错就是这么简单!!!
接下来我们谈谈 对上一个386进行扩展:
大家在第二节已经知道了进入386的基本步骤了,那么我们来具体设计吧.
编程首先当然是【声明】与【定义】:
一、【声明】:
在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
二、【定义】:
;在定义模块之前我们首先要有个概念,那就是整体的雏形。
可以简单划分出来也就是2个主要步骤:
1、定义GDT数据段:
{
1、定义Descriptor即段描述符: (通常是以一个全为零的Descriptor开始)。
2、定义GdtPtr 信息结构体(再加载gdtr时候要用到)。
3、定义每个段描述符对应的索引位置(即定义选择子)。
}
2、定义如上描述的具体段:
{
1、实模式入口段 (这是个入口段:用于跳转到386的段,并不属于386段所以没有描述符)
2、剩下的段就全是386模式的段。
}
三、【具体编码】
还是要拿实实在在的能运行的代码来讲:
先说一下代码的主要功能:
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 "This is 5MB", 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,11
mov ah, 0Ch ; 0000: 黑底 1100: 红字
.loop:
mov al,[es:esi] ;这个是SelectorTest的段,也就是我们测试的大地址段
call DispAL ;显示从test段取出来的字符
inc esi
loop .loop ;cx--
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后 就可以转换成实模式寻址了!!!
接下来继续学习:
补充一下.com要在纯DOS下运行,,,因为用到了很对特权指令!!!
Day Day Up
赞赏记录
参与人
雪币
留言
时间
Youlor
为你点赞~
2024-1-3 02:29
QinBeast
为你点赞~
2023-10-24 02:37
伟叔叔
为你点赞~
2023-10-18 01:09
一笑人间万事
为你点赞~
2023-7-27 00:11
shinratensei
为你点赞~
2023-7-3 00:25
心游尘世外
为你点赞~
2023-6-23 00:32
飘零丶
为你点赞~
2023-6-13 01:10
赞赏
他的文章
- [原创][保护模式编程、四] 6992
- [原创][保护模式编程、三] 7813
- [原创]386保护模式[学习一、二] 16002
- [原创]浅谈U盘防御文件夹!!! 13154
- [原创]Kmd(kernel mode Driver)简单入门之谈!! 13529
谁下载
bstzxy
Xacs
yhtchf
option
kenmark
qqeleven
HappyKL
fhwclq
archy
lijingli
davidfoxhu
ppanger
天涯过客
info
笑梅
basherone
Sacredfy
solidyjd
qqzhang
怒海潜将
独孤剑圣
xingchao
lly鹅
bonseedd
cooray
fourany
wexczz
freakie
hacktrace
firewing
dalada
freezhh
zhzhhach
caorujun
lxhgeorge
gpaul
yemailu
cdba
zhangzdzzd
正路人甲
liuwenbin
ConanAI
猎人猎枪
woaixj
asunjing
moonflow
龙飞雪
isgoodtim
vinkan
拎Q
pwttech
看原图
赞赏
雪币:
留言: