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 第 列。
 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

转载于:https://www.cnblogs.com/wanghj-dz/archive/2011/04/21/2024367.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值