pmtest2.asm (转载)
转载于 http://blog.sina.com.cn/s/blog_6af1a64d0100kmg0.html
骑驴容易下驴难,从保护模式返回实模式就不这么容易了。 因为什么呢,先把代码拉出来看看吧。
; ==========================================
; pmtest2.asm
; 编译方法:nasm pmtest2.asm -o pmtest2.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 0100h
jmp LABEL_BEGIN //LABEL_BEGIN 程序代码运行时的入口处,是在实模式下,不需要选择子。
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位
LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT //这个选择子跳转到下面的16位保护模式代码段。因为selector选择子是用在保护模式下的, 即使是16位的保护模式。
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorTest equ LABEL_DESC_TEST - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0 用来保存实模式下sp,并在跳回实模式前重新赋值给sp
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 进入保护模式后显示此字符串
OffsetPMMessage equ PMMessage - $$
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
[SECTION .s16] //这个段不需要选择子的,因为它是在实模式下。在这里要初始化段描述符的段基址。
[BITS 16]
LABEL_BEGIN: //实模式下的代码
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax 改写跳回实模式前代码中的jmp 0:~这句中的0。0被实模式下的cs代替。
mov [SPValueInRealMode], sp
; 初始化 16 位代码段描述符
mov ax, cs
movzx eax, ax
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
; 初始化 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
; 初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
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
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 //执行这一句会把 SelectorCode32 装入 cs, 并跳转到32位代码保护模式处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ; ┓
and al, 11111101b ; ┣ 关闭 A20 地址线
out 92h, al ; ┛
sti ; 开中断
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16] //返回到实模式下完成回到DOS的功能
[SECTION .s32]; 32 位代码段. 由实模式跳入. //32位代码保护模式,需要选择子SelectorCode32
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorTest
mov es, ax ; 测试段选择子
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 显示完毕
call DispReturn
call TestRead
call TestWrite
call TestRead
; 到此停止
jmp SelectorCode16:0 //跳转到16位代码的保护模式,需要选择子。
//同时完成对CS高速缓冲寄存器的段属性和段界限的赋值,使之符合实模式要求
;-------------------------------------------------------------------------
; ------------------------------------------------------------------------以下为函数(子程序)定义
TestRead:
xor esi, esi
mov ecx, 8
.loop
mov al, [es:esi]
call DispAL
inc esi
loop .loop
call DispReturn
ret
; TestRead 结束-----------------------------------------------------------
; ------------------------------------------------------------------------
TestWrite:
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
; TestWrite 结束----------------------------------------------------------
; ------------------------------------------------------------------------
; 显示 AL 中的数字
; 默认地:
; 数字已经存在 AL 中
; edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
; ax, edi
; ------------------------------------------------------------------------
DispAL:
push ecx
push edx //push主要看看那些要用到,那些要xor,循环用的ecx一般都要push
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov dl, al
shr al, 4 //先对al的高4位处理
mov ecx, 2
.begin:
and al, 01111b //第二次循环处理al的低四位
cmp al, 9
ja .1
add al, '0'
jmp .2
.1:
sub al, 0Ah
add al, 'A'
.2:
mov [gs:edi], ax
add edi, 2
mov al, dl //要处理al的低四位
loop .begin
add edi, 2
pop edx
pop ecx
ret
; DispAL 结束-------------------------------------------------------------
; ------------------------------------------------------------------------
DispReturn:
push eax
push ebx
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop ebx
pop eax
ret
; DispReturn 结束---------------------------------------------------------
;-------------------------------------------------------------函数定义结束
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16: //16位代码保护模式,需要选择子SelectorCode16跳转到这儿,在这里主要是从新跳回到实模式。
; 跳回实模式:
mov ax, SelectorNormal //通过符合实模式段属性,段界限的选择子SelectorNormal,对个寄存器的高速缓存重新赋值,使之符合实模式的状态
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值 //通过实模式下的跳转,完成对CS的赋值
Code16Len equ $ - LABEL_SEG_CODE16 //对上句应由LABEL_REAL_ENTRY这个门牌号,推测到那个街道号LABEL_BEGIN。街道号,门牌号好
; END of [SECTION .s16code] //发现保护模式下的编程很清楚,大量运用[section .!!!]来间隔代码,通过选择子完成各section之间的跳转。
;注:这个代码是建立在原来代码基础上的,原本没这么长。
首先我不得不先强调一下保护模式的实质:在保护模式中,任何地址都要换算成基址+偏移的形式,也即任何地址的确定都要依靠该地址与指定段基址之间的差值,之后再将该基地址通过描述符映射为更广区域的物理地址。如果已知我们已经在单纯的模式下,那么我们可以直接通过诸如mov ax,op的形式传递op的地址,因为在单纯的32位或16位下的地址不会引起误会;
但是如果我们处于16位代码中却不得不要对32位地址进行操作时,那么我们只能通过基址+偏移的形式,即先将op的地址与它所在的段的基址做差,然后将基址传入描述符,以后如果要访问op的真实地制只需要通过上面的差值(偏移)就行了。
我们还是先来看看代码多了什么内容:
1 SECTION .gdt段语法结构很容易理解,只是多了几个描述符:
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位
LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
描述符的多少和种类是与执行的任务有关的,我们先放一放,回头再说。
2.下面是一个数据段,装载次程序设计到的所有数据:
[SECTION .data1] ; 数据段
ALIGN 32 ;ALIGN为定位之意,意思是下面为32位代码
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 进入保护模式后显示此字符串
OffsetPMMessage equ PMMessage - $$
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
SPValueInRealMode意为在实模式下的sp(堆栈指针值),PMMessage为进入保护模式后显示的字符串,StrTest为进入保护模式后的测试字符串。
3.然后是一个堆栈段
全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
4.之后正式进入代码:(实模式编程)
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs ;cs为代码段起始地址
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax ;
LABEL_GO_BACK_TO_REAL位置为后面的:LABEL_GO_BACK_TO_REAL:jmp 0:LABEL_REAL_ENTRY
即32位跳回到16位指令处,这句放到后面和另外几句统一讲。
mov [SPValueInRealMode], sp;保存实模式下堆栈指针到SPValueInRealMode,备以后还原时用
初始化 16 位代码段描述符,是描述符就要初始化,没啥好说的
mov ax, cs
movzx eax, ax
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
初始化 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
初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
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
为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
加载 GDTR
lgdt [GdtPtr]
关中断
cli
打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里,放在后面讲
mov ax, cs ;此时已经返回了实模式,必须先将各段寄存器赋值
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode];还原堆栈指针
in al, 92h ; ┓
and al, 11111101b ; ┣ 关闭 A20 地址线
out 92h, al ; ┛
sti ; 开中断
mov ax, 4c00h ; ┓
int 21h ; ┛回到 DOS
; END of [SECTION .s16]
5.[SECTION .s32]更没什么好说的了
6.最后讲讲[SECTION .s16code]:
16 位代码段(s16code,其实是保护模式下的16位代码). 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值.
在前面的实模式代码s16里有句:mov [label_go_back_to_real+3],ax
就是把实模式下的cs代替上面jmp中的0。从而实现在保护模式下把cs改造成和实模式一样。
Code16Len equ $ - LABEL_SEG_CODE16
这段代码和在实模式下构造保护模式的段一样,要在保护模式下构造实模式的段以便跳回实模式.
在由保护模式向实模式跳转时候,由于每个段寄存器都配有段描述符高速缓冲寄存器,其内容会由保护模式状态下带到实模式下,
但其中内容的"格式(其实就是实模式下的段属性,在实模式下不能手工修改)"是保护模式下的格式,与实模式不匹配,所以要做两件事:
1.加载一个16位代码的选择子,即SelectorNormal,并将这个段的这个段描述符向ds,es,fs,gs,ss复制,其实并在用到这些寄存
器的值,只是为了把这些寄存器的高速缓冲寄存器中的内容刷新成16位的实模式下的"格式".
因为CS寄存器不能直接填充,所以只能从保护模式的32位代码跳转到16位代码由CPU自动去刷新CS寄存器的高速缓冲寄存器.
jmp SelectorCode16:0的作用是jmp [cs:ip],所以SelectorCode16描述符被加载到CS段,完成对高速缓冲寄存器的刷新.
2.因为CS段属性已经正确,而开始时实模式下CS段的基址被保存在LABEL_GO_BACK_TO_REAL+3处,即jmp 0:LABEL_REAL_ENTRY这条指令的段地址分部,
所以jmp 0:LABEL_REAL_ENTRY 可以正确地跳到实模式的LABEL_REAL_ENTRY 处,并且将原来保存的实模式下的段地址带到LABEL_REAL_ENTRY 中.
这样完成了所有的寄存器的高速缓冲寄存器的内容的刷新成实模式的"格式"后,再跳入实模式的代码.所以从保护模式跳回实模式时
一定会借助一个Norma描述符和一个带有实模式的CS段地址的jmp指令.
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/axman/archive/2009/12/03/4932441.aspx
(详情参考:http://www.cnblogs.com/wanghj-dz/archive/2011/04/21/2024388.html)
posted on 2011-04-21 23:11 wanghj_dz 阅读(1702) 评论(0) 编辑 收藏 举报