x86保护模式 实模式与保护模式切换实例

x86保护模式     实模式与保护模式切换实例

实例一
逻辑功能   以十六进制数的形式显示从内存地址110000h开始的256个字节的值   
实现步骤:
1  切换保护方式的准备
2. 切换到保护方式
3. 把指定内存区域的内容传送到位于常规内存的缓冲区中
4. 切换回实模式
5. 显示缓冲区内容
 
代码:
386保护模式汇编语言程序用到的包含文件如下所示,该包含文件在后面的程序中还要用到。
;名称:386SCD.INC
;功能:符号常量等的定义
;----------------------------------------------------------------------------
;IFNDEF         __386SCD_INC     //宏定义
;__386SCD_INC   EQU     1
;----------------------------------------------------------------------------
.386P
;----------------------------------------------------------------------------
;打开A20地址线     宏定义
;----------------------------------------------------------------------------
EnableA20       MACRO        					// 使用32为地址线
                push    ax		//ax压栈保存
                in      al,92h		//读端口指令   92h端口的值送到al寄存器
                or      al,00000010b	//处理al的值  将位1变为1
                out     92h,al        //写端口指令  将al的值送到92h   此时就是打开a20地址线
                pop     ax		//弹出栈中的ax值
                ENDM
;----------------------------------------------------------------------------
;关闭A20地址线
;----------------------------------------------------------------------------
DisableA20      MACRO
                push    ax
                in      al,92h
                and     al,11111101b		//将位1置为0   就是关闭a20地址线
                out     92h,al
                pop     ax
                ENDM
;----------------------------------------------------------------------------
;16位偏移的段间直接转移指令的宏定义(在16位代码段中使用)
;----------------------------------------------------------------------------
JUMP16          MACRO   Selector,Offset   	//宏定义:  	 【宏指令名】 MACRO 【形式参数,......】
                DB      0eah     ;操作码		//宏使用格式      【宏指令名】【实在参数】
                DW      Offset   ;16位偏移量
                DW      Selector ;段值或段选择子
                ENDM
;----------------------------------------------------------------------------
;32位偏移的段间直接转移指令的宏定义(在32位代码段中使用)
;----------------------------------------------------------------------------
COMMENT <JUMP32>
JUMP32          MACRO   Selector,Offset			//宏定义
                DB      0eah     ;操作码		//根据x86操作码结构和指令表   0eah的指令为jmpf
                DD      OFFSET				//此时偏移为32位     选择子为16位
                DW      Selector ;段值或段选择子
                ENDM
<JUMP32>
;-------------------------------------------------
JUMP32          MACRO   Selector,Offset			
                DB      0eah     ;操作码			
                DW      OFFSET				//此时偏移为16位
                DW      0				//?高位是0    还是低位是0
                DW      Selector ;段值或段选择子		//选择子为16位	
                ENDM
;----------------------------------------------------------------------------
;16位偏移的段间调用指令的宏定义(在16位代码段中使用)
;----------------------------------------------------------------------------
CALL16          MACRO   Selector,Offset      		//16位调用    宏定义
                DB      9ah      ;操作码			//操作码9ah  指令为callf
                DW      Offset   ;16位偏移量
                DW      Selector ;段值或段选择子
                ENDM
;----------------------------------------------------------------------------
;32位偏移的段间调用指令的宏定义(在32位代码段中使用)
;----------------------------------------------------------------------------
COMMENT <CALL32>
CALL32          MACRO   Selector,Offset
                DB      9ah      ;操作码
                DD      Offset			//偏移为32位
                DW      Selector ;段值或段选择子
                ENDM
<CALL32>
;-------------------------------------------------
CALL32          MACRO   Selector,Offset
                DB      9ah      ;操作码
                DW      Offset			//偏移为16位
                DW      0
                DW      Selector ;段值或段选择子
                ENDM
;----------------------------------------------------------------------------
;存储段描述符结构类型定义
;----------------------------------------------------------------------------
Desc            STRUC
LimitL          DW      0 ;段界限(BIT0-15)
BaseL           DW      0 ;段基地址(BIT0-15)
BaseM           DB      0 ;段基地址(BIT16-23)
Attributes      DB      0 ;段属性
LimitH          DB      0 ;段界限(BIT16-19)(含段属性的高4位)
BaseH           DB      0 ;段基地址(BIT24-31)
Desc            ENDS
;----------------------------------------------------------------------------
;门描述符结构类型定义
;----------------------------------------------------------------------------
Gate            STRUC
OffsetL         DW      0 ;32位偏移的低16位
Selector        DW      0 ;选择子
DCount          DB      0 ;双字计数       //参数 个数
GType           DB      0 ;类型
OffsetH         DW      0 ;32位偏移的高16位
Gate            ENDS
;----------------------------------------------------------------------------
;伪描述符结构类型定义(用于装入全局或中断描述符表寄存器)
;----------------------------------------------------------------------------
PDesc           STRUC			//gdtr寄存器   idtr寄存器为48位
Limit           DW      0 ;16位界限     //规定gdtr表的大小    项目
Base            DD      0 ;32位基地址	//gdtr表的存储位置
PDesc           ENDS
;----------------------------------------------------------------------------
;任务状态段结构类型定义
;----------------------------------------------------------------------------
TSS             STRUC
TRLink          DW      0      ;链接字段//是否被其他任务使用  
                DW      0      ;不使用,置为0
TRESP0          DD      0      ;0级堆栈指针     //不同级别的堆栈
TRSS0           DW      0      ;0级堆栈段寄存器
                DW      0      ;不使用,置为0
TRESP1          DD      0      ;1级堆栈指针
TRSS1           DW      0      ;1级堆栈段寄存器
                DW      0      ;不使用,置为0
TRESP2          DD      0      ;2级堆栈指针
TRSS2           DW      0      ;2级堆栈段寄存器
                DW      0      ;不使用,置为0
TRCR3           DD      0      ;CR3	//cr3寄存器  分页机制使用
TREIP           DD      0      ;EIP
TREFlag         DD      0      ;EFLAGS
TREAX           DD      0      ;EAX
TRECX           DD      0      ;ECX
TREDX           DD      0      ;EDX
TREBX           DD      0      ;EBX
TRESP           DD      0      ;ESP
TREBP           DD      0      ;EBP
TRESI           DD      0      ;ESI
TREDI           DD      0      ;EDI
TRES            DW      0      ;ES
                DW      0      ;不使用,置为0
TRCS            DW      0      ;CS
                DW      0      ;不使用,置为0
TRSS            DW      0      ;SS
                DW      0      ;不使用,置为0
TRDS            DW      0      ;DS
                DW      0      ;不使用,置为0
TRFS            DW      0      ;FS
                DW      0      ;不使用,置为0
TRGS            DW      0      ;GS
                DW      0      ;不使用,置为0
TRLDTR          DW      0      ;LDTR
                DW      0      ;不使用,置为0
TRTrip          DW      0      ;调试陷阱标志(只用位0)
TRIOMap         DW      $+2    ;指向I/O许可位图区的段内偏移
TSS             ENDS
;----------------------------------------------------------------------------
;存储段描述符类型值说明   数据段和代码段
;----------------------------------------------------------------------------
ATDR            EQU     90h ;存在的只读数据段类型值
ATDW            EQU     92h ;存在的可读写数据段属性值
ATDWA           EQU     93h ;存在的已访问可读写数据段类型值
ATCE            EQU     98h ;存在的只执行代码段属性值
ATCER           EQU     9ah ;存在的可执行可读代码段属性值
ATCCO           EQU     9ch ;存在的只执行一致代码段属性值
ATCCOR          EQU     9eh ;存在的可执行可读一致代码段属性值
;----------------------------------------------------------------------------
;系统段描述符类型值说明 		门描述符    ldt    tss  三种
;----------------------------------------------------------------------------
ATLDT           EQU     82h ;局部描述符表段类型值
ATTaskGate      EQU     85h ;任务门类型值
AT386TSS        EQU     89h ;可用386任务状态段类型值
AT386CGate      EQU     8ch ;386调用门类型值
AT386IGate      EQU     8eh ;386中断门类型值
AT386TGate      EQU     8fh ;386陷阱门类型值
;----------------------------------------------------------------------------
;DPL值说明    当前段的访问权限级别
;----------------------------------------------------------------------------
DPL0            EQU     00h ;DPL=0
DPL1            EQU     20h ;DPL=1
DPL2            EQU     40h ;DPL=2
DPL3            EQU     60h ;DPL=3
;----------------------------------------------------------------------------
;RPL值说明	
;----------------------------------------------------------------------------
RPL0            EQU     00h ;RPL=0
RPL1            EQU     01h ;RPL=1
RPL2            EQU     02h ;RPL=2
RPL3            EQU     03h ;RPL=3
;----------------------------------------------------------------------------
;IOPL值说明
;----------------------------------------------------------------------------
IOPL0           EQU     0000h ;IOPL=0
IOPL1           EQU     1000h ;IOPL=1
IOPL2           EQU     2000h ;IOPL=2
IOPL3           EQU     3000h ;IOPL=3
;----------------------------------------------------------------------------
;其它常量值说明
;----------------------------------------------------------------------------
D32             EQU     40h       ;32位代码段标志
GL              EQU     80h       ;段界限以4K为单位标志
TIL             EQU     04h       ;TI=1(局部描述符表标志)
VMFL            EQU     00020000h ;VMF=1
VMFLW           EQU     0002h
IFL             EQU     00000200h ;IF=1
RFL             EQU     00010000h ;RF=1(重启动标志,为1表示忽略调试故障)
RFLW            EQU     0001h
NTL             EQU     00004000h ;NT=1
;----------------------------------------------------------------------------
;分页机制使用的常量说明
;----------------------------------------------------------------------------
PL              EQU     1     ;页存在属性位
RWR             EQU     0     ;R/W属性位值,读/执行
RWW             EQU     2     ;R/W属性位值,读/写/执行
USS             EQU     0     ;U/S属性位值,系统级
USU             EQU     4     ;U/S属性位值,用户级
;----------------------------------------------------------------------------
;ENDIF

2.实例源程序

实例一的源程序如下所示:
;名称:ASM1.ASM
;功能:演示实方式和保护方式切换(切换到16位代码段)
;----------------------------------------------------------------------------
INCLUDE         386SCD.INC    //包含上面的定义的文件
;----------------------------------------------------------------------------
;字符显示宏指令的定义
;----------------------------------------------------------------------------
EchoCh          MACRO   ascii     	//dos系统功能调用int21h    ah=02 表示显示输出
                mov     ah,2		//dl=输出字符
                mov     dl,ascii
                int     21h
                ENDM
;----------------------------------------------------------------------------
DSEG            SEGMENT USE16                 ;16位数据段
;----------------------------------------------------------------------------
GDT             LABEL   BYTE                  ;全局描述符表
DUMMY           Desc    <>                    ;空描述符
Code            Desc    <0ffffh,,,ATCE,,>     ;代码段描述符    以下分别为8字节长  空为全0
DataS           Desc    <0ffffh,0,11h,ATDW,,> ;源数据段描述符
DataD           Desc    <0ffffh,,,ATDW,,>     ;目标数据段描述符
;----------------------------------------------------------------------------
GDTLen          =       $-GDT                 ;全局描述符表长度    当前值-gdt首地址   为字节长度
VGDTR           PDesc   <GDTLen-1,>           ;伪描述符   GDtr寄存器值 16位的界限值定义   基地址为空
;----------------------------------------------------------------------------
Code_Sel        =       Code-GDT              ;代码段选择子   以下为定义16为选择子相对于与表首的偏移值
DataS_Sel       =       Datas-GDT             ;源数据段选择子
DataD_Sel       =       DataD-GDT             ;目标数据段选择子
;----------------------------------------------------------------------------
BufLen          =       256                   ;缓冲区字节长度
Buffer          DB      BufLen DUP(0)         ;缓冲区
;----------------------------------------------------------------------------
DSEG            ENDS                          ;数据段定义结束

;----------------------------------------------------------------------------
CSEG            SEGMENT USE16                 ;16位代码段
                ASSUME  CS:CSEG,DS:DSEG
;----------------------------------------------------------------------------
Start           PROC
                mov     ax,DSEG
                mov     ds,ax			//设置数据段寄存器  指向dseg定义处
                ;准备要加载到GDTR的伪描述符   //设置基地址 32位   界限已经定义过了
                mov     bx,16
                mul     bx			//mul是进行无符号乘法的指令 ax*bx,结果高16位存dx  低16位存ax 
											//相当于ax的值向左移4位			
                add     ax,OFFSET GDT          ;计算并设置基地址 实模式下段寄存器左移4位变为20位的段基地址再加偏移
                adc     dx,0                   ;界限已在定义时设置好    注意dx需要带进位  根据上一个操作
                mov     WORD PTR VGDTR.Base,ax//高16位
                mov     WORD PTR VGDTR.Base+2,dx//低16位   并且带进位   dx:ax共同组成32位的基地址
                ;设置代码段描述符
                mov     ax,cs
                mul     bx
                mov     WORD PTR Code.BaseL,ax ;代码段开始偏移为0
                mov     BYTE PTR Code.BaseM,dl ;代码段界限已在定义时设置好
                mov     BYTE PTR Code.BaseH,dh
                ;设置目标数据段描述符
                mov     ax,ds
                mul     bx                     ;计算并设置目标数据段基址
                add     ax,OFFSET Buffer
                adc     dx,0
                mov     WORD PTR DataD.BaseL,ax
                mov     BYTE PTR DataD.BaseM,dl
                mov     BYTE PTR DataD.BaseH,dh
                ;加载GDTR
                lgdt    QWORD PTR VGDTR
                cli                            ;关中断       开中断sti
                EnableA20                      ;打开地址线A20
                ;切换到保护方式
                mov     eax,cr0
                or      eax,1        //cr0中的位0置为1    
                mov     cr0,eax		//进入保护模式
                ;清指令预取队列,并真正进入保护方式
                JUMP16  Code_Sel,<OFFSET Virtual>    //宏调用   实参:选择子,偏移值
Virtual:        ;现在开始在保护方式下运行
                mov     ax,DataS_Sel
                mov     ds,ax                  ;加载源数据段描述符
                mov     ax,DataD_Sel
                mov     es,ax                  ;加载目标数据段描述符
                cld				//si   di  变化方向  加还是减
                xor     si,si			//si和di清零
                xor     di,di                  ;设置指针初值
                mov     cx,BufLen/4            ;设置4字节为单位的缓冲区长度  设置计数器
                repz    movsd                  ;传送双字
                ;切换回实模式
                mov     eax,cr0
                and     al,11111110b    		//最低位清0   进入实模式
                mov     cr0,eax
                ;清指令预取队列,进入实方式
                JUMP16  <SEG Real>,<OFFSET Real>
Real:           ;现在又回到实方式
                DisableA20			开中断
                sti				//开中断
                mov     ax,DSEG
                mov     ds,ax
                mov     si,OFFSET Buffer		//ds:si
                cld						
                mov     bp,BufLen/16		//bp 外循环次数     16行
NextLine:       mov     cx,16			//内循环次数		一行16个字符
NextCh:         lodsb				//目的地址的内容读到源地址  串操作    块读出指令
						// 即目标地址为es:di   源地址为ds:si 字节为单位传送
                push    ax
                shr     al,1			//右移1位
                call    ToASCII			//调用子程序 toascii
                EchoCh  al			//宏展开   echoch
                pop     ax
                call    ToASCII
                EchoCh  al
                EchoCh  ' '
                loop    NextCh 			//计数器为16    为0时跳出
                EchoCh  0dh			//实参传递给宏
                EchoCh  0ah
                dec     bp			//外循环的次数减1
                jnz     NextLine
                mov     ax,4c00h			//中断
                int     21h
Start           ENDP
;----------------------------------------------------------------------------
//子程序定义
ToASCII         PROC			//转换为ascii码
                and     al,0fh		//al低4位不变    高四位   变为0
                add     al,90h		//高4位变为9  低4位不变
                daa			//说明三
                adc     al,40h		//带进位加法   al变为对应的ascii码
                daa
                ret
ToASCII         ENDP
;----------------------------------------------------------------------------
CSEG            ENDS                           ;代码段定义结束
;----------------------------------------------------------------------------
                END     Start
说明:一mul指令
1将8位的操作数与al相乘 2将16位的操作数与ax相乘 3是将32位的操作数与eax相乘 乘积是乘数大小的2倍 三种格式都接受寄存器操作数和内存操作数 但是不接受立即数;
被乘数 乘数 积
al 8位 ax
ax 16位 dx:ax         dx存高16位      ax存低16位
eax 32位 edx:eax
 
二 lodsb   lodsw      与stosb   stosw 分别对应
串操作指令    lodsb   lodsw是块读出指令   具体操作是把si指向的存储单元读入累加器   器中lodsb是写入al    lodsw写入
ax  ,然后si自动增加或减少1或2位   当方向df为0时   si自增 ;df为1则自减
stosb从al中读取      stosw从ax读取
三 daa   bcd码的加法调整指令
将al的内容调整为两位的组合型的二进制数   daa指令要分别考虑al的高4位和低4位
如果al的低4位大于9或af=1 ,则al的内容加06h   并将af置1;然后如果al的高4位大于9或cf=1  则al的内容加60h,且将cf置为1   如果两个都不满足   则将af,cf清零。
四切换到保护方式的准备工作
1.建立合适的全局描述符表  并使gdtr指向该gdt    在切换到保护方式时   至少要把代码段的选择子装载到cs  所以gdt中至少含有代码段的描述符
实例中各使用的存储段的描述符的界限都定义为0ffffh    根据属性可知三个段都是16位段
加载gdtr    
LGDT  QWORD  PTR   VGDTR
将存储器中的伪描述符VGDTR装入全局描述符表寄存器GDTR中   
2.由实模式切换到保护模式
cr0中的PE位置置为1即可
之后要马上把代码段的选择子存入cs   
jmp16 code_sel,<OFFSET VIRTUAL>
上面的段间转移指令在实模式下被预取   并在保护模式下被执行
3.由保护模式切换到实模式
cr0中的pe位为0   同时后面也要有一条段间转移指令    目的1为清除指令序列   目的2为将实模式下的代码段的段值送cs   此指令在保护方式下被预取  但是在实模式下执行
4.保护模式下的数据传送
源数据段和目的数据段的选择子装入ds    和es寄存器    这两个描述符已经在实模式下设置好   并把选择子装入段寄存器同时把描述符的信息装入到对应的高速缓冲寄存器      再设置si和di指针   cs计数器   ;根据段属性的值可以判断都为16位的段   串操作指令只是用16位的si和di 和cx寄存器   最后利用串操作指令实施传送
5.显示缓冲区的内容
缓冲区在常规内存中   即1m之内   所以需要在实模式下按要求以16进制数的形式显示其内容
五   内存映像
六特别说明
本实例简化程序   未定义中断描述符表      所以整个过程实在关中断的情况下运行的   
未定义ldt表   所以进入保护模式后默认的段选择子都位于gdt中
未定义保护模式下的堆栈段   gdt中没有堆栈描述符    所以程序不涉及堆栈的操作
各个描述符的特权级别均为0   dpl  rpl   cpl 均为0
未采用分页管理机制   cr0中的PG位为0   线性地址就是存储单元的物理地址
打开和关闭a20地址线      pc兼容机中的第21根地址线
系统中的一个门控制该地址线   是否有效   
为了访问地址在1m以上的存储段安源 应该先打开控制地址线a20的门   ;此设置与实模式只用1m内的空间有关  ,而与cpu是否工作在实模式还是保护模式无关   即使关闭a20地址线   也可以进入保护模式
 
 
实例二 32位代码段和16位代码段切换的实例
 
低声飞过  同实例一的逻辑功能相同    
具体实现步骤:
1.切换保护方式准备
2.切换到保护方式的一个32位代码段
3.将指定内存区域的内容以字节为单位  转换成对应的十六进制数的ascii码  并填入显示缓冲区实现显示
4.再变换到保护方式下的一个16代码段
5.将指定内存区域的内容直接作为ascii码填入显示缓冲区中实现显示
6.切换到实模式
源程序如下:
1.实例二源程序
实例二的源程序如下所示:
;名称:ASM2.ASM
;功能:演示实方式和保护方式切换(切换到32位代码段)
;----------------------------------------------------------------------------
INCLUDE         386SCD.INC
;----------------------------------------------------------------------------
DSEG            SEGMENT USE16                     ;16位数据段定义
;----------------------------------------------------------------------------
GDT             LABEL   BYTE                      ;全局描述符表
DUMMY           Desc    <>                        ;空描述符
Normal          Desc    <0ffffh,,,ATDW,,>         ;规范段描述符
Code32          Desc    <C32Len-1,,,ATCE,D32,>    ;32位代码段描述符
Code16          Desc    <0ffffh,,,ATCE,,>         ;16位代码段描述符
DataS           Desc    <DataLen-1,0,10h,ATDR,,>  ;源数据段描述符
DataD           Desc    <3999,8000h,0bh,ATDW,,>   ;显示缓冲区描述符
Stacks          Desc    <StackLen-1,,,ATDW,,>     ;堆栈段描述符
;----------------------------------------------------------------------------
GDTLen          =       $-GDT                     ;全局描述符表长度
VGDTR           PDesc   <GDTLen-1,>               ;伪描述符
;----------------------------------------------------------------------------
SaveSP          DW      ?                         ;用于保存SP寄存器
SaveSS          DW      ?                         ;用于保存SS寄存器
;----------------------------------------------------------------------------
Normal_Sel      =       Normal-GDT                ;规范段描述符选择子   ?
Code32_Sel      =       Code32-GDT                ;32位代码段选择子
Code16_Sel      =       Code16-GDT                ;16位代码段选择子
DataS_Sel       =       Datas-GDT                 ;源数据段选择子
DataD_Sel       =       DataD-GDT                 ;目标数据段选择子
Stacks_Sel      =       Stacks-GDT                ;堆栈段描述符选择子
;----------------------------------------------------------------------------
DataLen         =       16				//?  需要显示的数据长度   
;----------------------------------------------------------------------------
DSEG            ENDS                              ;数据段定义结束
 
 
;----------------------------------------------------------------------------
StackSeg        SEGMENT PARA STACK USE16
StackLen        =       256
                DB      StackLen DUP(0)				//定义256个字节长度
StackSeg        ENDS
;----------------------------------------------------------------------------

CSEG1           SEGMENT USE16 'REAL'              ;16位代码段   貌似为实模式下调用
                ASSUME  CS:CSEG1,DS:DSEG
;----------------------------------------------------------------------------
Start           PROC
                mov     ax,DSEG
                mov     ds,ax
                ;准备要加载到GDTR的伪描述符
                mov     bx,16
                mul     bx
                add     ax,OFFSET GDT             ;计算并设置基地址
                adc     dx,0                      ;界限已在定义时设置好
                mov     WORD PTR VGDTR.Base,ax
                mov     WORD PTR VGDTR.Base+2,dx
                ;设置32位代码段描述符
                mov     ax,CSEG2     		// 代码段开始偏移为0   
                mul     bx
                mov     WORD PTR Code32.BaseL,ax
                mov     BYTE PTR Code32.BaseM,dl
                mov     BYTE PTR Code32.BaseH,dh
                ;设置16位代码段描述符
                mov     ax,CSEG3  
                mul     bx
                mov     WORD PTR Code16.BaseL,ax  ;代码段开始偏移为0
                mov     BYTE PTR Code16.BaseM,dl  ;代码段界限已在定义时设置好
                mov     BYTE PTR Code16.BaseH,dh
                ;设置堆栈段描述符
                mov     ax,ss
                mov     WORD PTR SaveSS,ax	//用来保存ss段寄存器中的值
                mov     WORD PTR SaveSP,sp	//用来保存sp段寄存器中的值
                movax,StackSeg		
mulbxmovWORDPTR Stacks.BaseL,axmovBYTEPTR Stacks.BaseM,dlmovBYTEPTR Stacks.BaseH,dh;加载GDTRlgdtQWORDPTR VGDTR			//加载伪描述符 到gdtr寄存器
                cli;关中断EnableA20;打开地址线A20;切换到保护方式moveax,cr0oral,1movcr0,eax;清指令预取队列,并真正进入保护方式JUMP16Code32_Sel,<OFFSET SPM32> 	//跳转指令  传给宏JUMP16 实参值ToReal:;现在又回到实方式     movax,DSEG
                movds,axmovsp,SaveSP
                movss,SaveSS
                DisableA20				//关闭a20地址线sti					//打开中断movax,4c00h			int21h
Start           ENDP					//程序的末尾;----------------------------------------------------------------------------
CSEG1           ENDS;代码段定义结束
;----------------------------------------------------------------------------
CSEG2           SEGMENTUSE32'PM32'   		//32位 代码段   保护模式下执行ASSUMECS:CSEG2
;----------------------------------------------------------------------------
SPM32           PROCmovax,Stacks_Sel
                movss,ax			//将堆栈的选择子装入到ss段寄存器movesp,StackLen		//esp指向栈顶
                movax,DataS_Sel		//将源数据段选择子装入ds段寄存器
                movds,ax			movax,DataD_Sel		//目的数据段选择子装入es寄存器
                moves,axxoresi,esi			//指针清零   用 ds:esixoredi,edi			//用es:edimovecx,DataLen		//计数器赋初值
                cldNext:lodsb				//块传送   si指向的字节内容写入al   算法过程看备注pushax			入栈CALL    ToASCII			//调用子程序  显示ascii码
                movah,7shleax,16			//左移 16位popaxshral,4			//右移4位CALL    ToASCII
                movah,7stosd				//将al的内容存入edi指向的内存单元中moval,20h			//空格stosw				//从ax读取出数据  存入edi指向的内存单元
						//如果使用的是stosd  则将eax的内容存入edi指向的内存单元
                loop    Next			//循环  ecx-1   直到ecx=0为止
                JUMP32   Code16_Sel,<OFFSET SPM16>   //跳转到16位代码段
SPM32           ENDP
;----------------------------------------------------------------------------
ToASCII         PROC
                and     al,00001111b		//高4位清0
                add     al,30h			//加30h    0+30h=30h  对应的ascii码为0
                cmp     al,39h			//比较低4位与9的大小
                jbe     Isdig			//小于等于 39h 跳转   说明是数字   判断是数字还是字母
                add     al,7			//大于39h的就是字母  对应的
IsDig:          ret
ToASCII         ENDP
;----------------------------------------------------------------------------
C32Len          =       $
;----------------------------------------------------------------------------
CSEG2           ENDS

;----------------------------------------------------------------------------
CSEG3           SEGMENT USE16 'PM16'	//16位代码段
                ASSUME  CS:CSEG3		
;----------------------------------------------------------------------------
SPM16           PROC
                xor     si,si
                mov     di,DataLen*3*2
                mov     ah,7
                mov     cx,DataLen
AGain:          lodsb		//从di指向的内存地址  取数据存入al
                stosw		//读ax数据   写到edi指向的内存单元  ah为7
                loop    AGain	//循环块传送  直到cx值为0
                mov     ax,Normal_sel	//规范段选择子赋值
                mov     ds,ax
                mov     es,ax
                mov     ss,ax
                mov     eax,cr0			//准备实模式
                and     al,11111110b
                mov     cr0,eax
                jmp     FAR PTR ToReal		//跳转到实模式
SPM16           ENDP
;----------------------------------------------------------------------------
CSEG3           ENDS
;----------------------------------------------------------------------------
                END     Start
注释
1.切换到保护模式的准备工作
建立全局描述符表,含有两个16位数据段的描述符  一个16位代码段的描述符和一个16位堆栈段描述符    
一个32位代码段描述符      
2.实模式切换到保护模式
JUMP32 CODE16_SEL,<OFFSET SPM16>
该转移指令含48位指针  其中高16位是选择子   低32位是16位代码段的入口偏移
3.显示指定内存区域的内容
直接写显示缓冲区的方法实现显示 
4.特别说明  在程序的结尾   给各个段寄存器传递一个normal的选择子
在分段管理机制中  每个段寄存器都有高速缓冲寄存器    这些寄存器在实模式下仍然有作用    仅仅是内容上与保护方式不同;段属性值在实模式下没有意义 实模式下不可设置;  而且段的基地址位数不同保护为32位  而实模式下位20位    
所以在准备结束保护模式回到实模式之前,要通过加载一个合适的描述符选择子到有关段寄存器   以使得对应段描述符高速缓冲寄存器中含有合适的段界限和属性值
需要注意的是不能从32位代码段返回到实模式    而是需要从16位代码段返回
在32位代码段中  缺省的操作数大小是32位    缺省的存储单元地址大小是32位 
原文地址:https://www.cnblogs.com/dongguolei/p/7896476.html