在前面的代码上进行修改后,测试读写大地址内存(实模式下的1MB限制),而且从保护模式再调回实模式.
上代码分析(省略前面重复的代码部分):
; ==========================================
; pmtest2.asm
; 编译方法:nasm pmtest2.asm -o pmtest2.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 0100h ;参看DOS系统中.EXE文件的加载过程
jmp 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 ;5M基址
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
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] ; 32位数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0 ;保存实模式下的SP
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 在保护模式中显示
OffsetPMMessage equ PMMessage - $$ ;PMMessage在LABEL_DATA节中的偏移
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$ ;StrTest在LABEL_DATA节中中的偏移
DataLen equ $ - LABEL_DATA ;LABEL_DATA节的长度
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0 ;512字节的堆栈段
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 ;保存实模式下的CS
mov [SPValueInRealMode], sp ;保存实模式下的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, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs ;jmp 0:LABEL_REAL_ENTRY 跳转到此处,恢复cs
mov ds, ax ;使用cs恢复ds,es,ss
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode] ;恢复实模式下的sp
in al, 92h ; `.
and al, 11111101b ; | 关闭 A20 地址线
out 92h, al ; /
sti ; 开中断
mov ax, 4c00h ; `.
int 21h ; / 回到 DOS
; END of [SECTION .s16]
[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 ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi ; esi重置
xor edi, edi ; edi重置
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb ;逐个加载ds:esi对应的字符到al中,其中DS中的选择子SelectData指向LABEL_DATA
test al, al ;判断al字符是否为0(只是为了测试,请看PMMeesage的定义最后是0)
jz .2 ;为0跳转.2显示完毕
mov [gs:edi], ax ;非0将ax中的字符移到视频选择子对应的区域gs:edi
add edi, 2 ;设置gs:edi指向下个字符,edi加2(ax为16位)
jmp .1 ;跳转到.1处继续
.2: ; 显示完毕
call DispReturn ;显示完毕后,换行显示
call TestRead ;读5M地址内容
call TestWrite ;写5M地址内容
call TestRead ;在此读5M地址荣,测试写5M地址内容是否成功
; 到此停止
jmp SelectorCode16:0 ;跳转到LABEL_SEG_CODE16处执行(调回实模式)
; ------------------------------------------------------------------------
TestRead:
xor esi, esi ;esi重置
mov ecx, 8 ;设置读取字节数位8
.loop:
mov al, [es:esi] ;将es:esi中的字符移到al中(5M地址)
call DispAL ;显示al中的值
inc esi ;增加esi的值,指向下个es:esi的下个字符
loop .loop ;继续读取
call DispReturn ;读取完毕显示回车
ret ;返回调用处
; TestRead 结束-----------------------------------------------------------
; ------------------------------------------------------------------------
TestWrite:
push esi ;esi入栈
push edi ;edi入栈
xor esi, esi ;esi重置
xor edi, edi ;edi重置
mov esi, OffsetStrTest ;源数据偏移
cld
.1:
lodsb ;将OffsetStrTest对应的字符串逐个加载到al中
test al, al ;是否为0
jz .2 ;为0,跳转.2读取完毕
mov [es:edi], al ;将al中的值写入测试大地址(5M)
inc edi ;增加EDI,下个字符的位置
jmp .1 ;继续读取
.2:
pop edi ;恢复edi,esi
pop esi
ret
; TestWrite 结束----------------------------------------------------------
; ------------------------------------------------------------------------
; 显示 AL 中的数字(将二进制转换为16进制显示)
; 默认地:
; 数字已经存在 AL 中
; edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
; ax, edi
; ------------------------------------------------------------------------
DispAL:
push ecx ;ecx,edx入栈
push edx
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov dl, al ; al暂存入dl中
shr al, 4 ; al右移4位
mov ecx, 2 ;
.begin:
and al, 01111b ; 将al中的高4位清0,保留低四位(原高四位)
cmp al, 9 ; 是否大于9
ja .1 ; 大于则跳转.1
add al, '0' ;
jmp .2
.1:
sub al, 0Ah ;大于9将数字扣除0Ah(10)
add al, 'A' ;加上'A'(65)转换为A到F 例如11 – 10 + 65 = 66 = 'B'
.2:
mov [gs:edi], ax ;移入显存区域
add edi, 2 ;edi+2指向下个位置
mov al, dl ;dl移入al中,进行低4位的16进制转换
loop .begin ;跳转.begin
add edi, 2 ;edi加2
pop edx ;恢复edx,ecx
pop ecx
ret
; DispAL 结束-------------------------------------------------------------
; ------------------------------------------------------------------------
DispReturn:
push eax ;eax,ebx入栈
push ebx
mov eax, edi ;edi移入eax
mov bl, 160 ;bl设置为160(设定每行显示为(80*10 + 0)*2)
div bl ;al/bl = edi/bl al位商 ah为余数
and eax, 0FFh ;清除eax的高16位,
inc eax ;eax+1
mov bl, 160 ;
mul bl ;ah = al*160=(edi/160的商+1)*160-----达到换行的效果
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:
; 跳回实模式:
mov ax, SelectorNormal ;将SelectorNormal加载到ds,es,fs,gs,ss,去除在32保护模式中的段属性
;使描述符寄存器含有合适的段界限和属性(实模式下32段属性不符合实模式的要求)
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0 ;设置cr0的PE为0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
;jmp segment:offset 根据段间转移(长转移),汇编指令为:
;0EAh Offset(2字节) Segment(2字节)
;这就是代码mov [LABEL_GO_BACK_TO_REAL+3], ax的作用
;将实模式下的cs值,写入LABEL_GO_BACK_TO_REAL地址偏移+3的位置,
;刚好就是jmp指令EA的Segment处,请参看实模式下的长跳转指令图示
;所以此的指令为JPM cs(实模式):LABEL_REAL_ENRY
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
相关:org 0100h 请参看DOS加载.EXE过程《Orange’s 一个操作系统的实现》4.保护模式3----DOS加载.EXE过程