上节内容是从实模式进入到保护模式,只是进入保护模式打印了一个字母P
。但是没有体现出保护模式的优势,也没有从保护模式中返回。这节就是要体验保护模式下读写大地址内存的能力和从保护模式返回到实模式。
这节要做的内容如下:首先在屏幕的第11行输出In Protect Mode now. ^-^。然后在屏幕第12行输出内存中起始地址为5MB的连续的8个字节。然后向这个以5MB开始的内存中写入ABCDEFGH
。再次在第13行输出这8个字节。结果演示如下:
源代码300多行,很长,分段讲述,主要讲新增的部分
首先是GDT段
[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 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]
相比于上一节,这里新增了下面几个描述符表项:
LABEL_DESC_NORMAL
:这个描述符用在从保护模式返回实模式的过程,为了让对应的段描述符告诉缓冲寄存器中含有合适的界限和属性。这儿有点不明白
LABEL_DESC_CODE16
:在保护模式返回到实模式的过程中用到。用来将SeletorNormal选择子赋给ds、es、fs、gs、ss,跟上面LABEL_DESC_NORMAL
合作实现返回实模式的准备工作。在书上43页
LABEL_DESC_DATA
:数据段描述符
LABEL_DESC_STACK
:栈段描述符
LABEL_DESC_TEST
:用来测试的大地址内存(5MB起始的内存)
自然地,需要增加了相应的选择子。
接下来新增了数据段。
[SECTION .data1] ; 数据段 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
:实模式中栈顶指针的值会保存在这里。干什么用?
ALIGN 32
:这是个伪指令,告诉编译器本伪指令下面的内存变量必须从下一个能被32整除的地址开始分配。
接下来你会发现定义的两个数据块PMMessage
和StrTest
下面都定义了另外一个符号Offset_PMMessage
和OffsetStrTest
,用来表示对应的上一个字符串相对于本节开始处(也就是LABEL_DATA处)的偏移。定义这两个符号是因为在保护模式下需要用到这个偏移来定位字符串的位置。而不再需要实模式下的地址。
你看到GDT中关于数据段的段基址并没有初始化,所以需要对数据段描述符进行初始化,在[SECTION .s16]
中,初始化代码如下:
; 初始化数据段描述符 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
回顾一下,保护模式下段值仍然是用16位的寄存器来存储。段值×16+偏移地址在此时它仅仅变成了一个索引,这个索引指向的就是GDT的一个表项,这个表项里面详细定义了段的起始地址、界限、属性等内容。在书的31页有详细介绍。
接下来定义了全局堆栈段,因为保护模式下用到了堆栈,这些都是需要我们自己定义的。因此需要在实模式下新建堆栈段。
定义堆栈段的源代码如下:
; 全局堆栈段 [SECTION .gs] ALIGN 32 [BITS 32] LABEL_STACK: times 512 db 0 TopOfStack equ $ - LABEL_STACK - 1 ; END of [SECTION .gs]
这里定义的栈的大小是512字节,栈顶是TopOfStack
。
但计算栈顶的时候减一是为什么呢?这儿我也没搞清楚。栈为空时,ss:esp指向的栈的最底部单元下面的单元,这个单元的偏移地址应该为栈最底部的双字(因为是32位的堆栈段)单元的偏移地址+4。但是这里我无论怎么计算都不是在所说的位置,好像栈空间小于512字节了,难道是为了避免访问越界,减1更有保障?
在[SECTION .s16]
有对应的堆栈段描述符如下:
; 初始化堆栈段描述符 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
再看看GDT中的关于堆栈的表项,属性是DA_DRWA+DA_32。DA_DRWA
表示存在的已访问可读写数据段类型值。DA_32
表示他是一个32位的堆栈段。
在[SECTION .s32]
有对应的堆栈初始化代码如下:
mov ax, SelectorStack mov ss, ax ; 堆栈段选择子 mov esp, TopOfStack
从上面可以看到:
数据段,栈段,代码段的定义是独立的;
数据段,栈段,32位代码段描述符的初始化是在16位代码段中定义的;
数据段,栈段相应寄存器的初始化要看它门在哪个代码段中使用,比如说这里的栈段是在32位代码段中使用的,所以ss,esp寄存器的初始化过程实在32位代码段中进行的。
这段用来对GDT进行初始化并进入保护模式,详细的介绍参考上一节文章。
[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 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, 并跳转到 Code32Selector:0 处
mov [LABEL_GO_BACK_TO_REAL+3], ax
、mov [SPValueInRealMode], sp
:这条指令是干什么的呢?先告诉你它是为了后面从保护模式返回到实模式用,具体为什么是这样在下面有详细的介绍
程序刚加载就会执行这个代码段,之后跳到32位代码段中运行,32位代码段如下。
相应的代码如下。虽然很长,但很简单,
首先,
是初始化相应段的寄存器。包括数据段
、测试段
、视频段
(屏幕)、堆栈段
然后,
显示字符串In Protect Mode now. ^-^
然后,
显示从内存地址5MB起始的8个字节,然后向内存5MB起始的地址中写入数据段中的字符串,然后再显示从内存5MB起始的8个字节。每次显示完成后都执行换行操作。
最后通过跳转指令jmp SelectorCode16:0
返回到实模式,到这里32位代码段就结束了。又回到了实模式。下面的一小节介绍跳转到的目的地做了什么工作。
先附上32位代码段,里面有详细的注释供参考
[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 xor edi, edi mov esi, OffsetPMMessage ; 源数据偏移 mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。 cld ; 置标志位DF的置为零 .1: lodsb ; al=((ds)*16+(esi))、(esi)=(esi)+1 test al, al ; al^al,结果不保存,只影响标志位 jz .2 ; 如果ZF标志位为1,转移到.2 mov [gs:edi], ax ; 否则,输出到屏幕 add edi, 2 ; 屏幕偏移寄存器加2 jmp .1 ; 跳转到.1 .2: ; 显示完毕 call DispReturn ; 换行 call TestRead ; 读出内存5MB起始的连续8字节内容 call TestWrite ; 向5MB起始的内存中写入字符串 call TestRead ; 再一次读出内存5MB起始的连续8字节内容 ; 如果正确,读出的内存与写入的内容相同 ; 到此停止 jmp SelectorCode16:0 ; 跳转到准备工作(返回实模式的准备工作代码) ; ------------------------------------------------------------------------ TestRead: ;输出到屏幕函数 xor esi, esi ;esi置0 mov ecx, 8 ; 向屏幕读出8个字节 .loop: mov al, [es:esi] ; 将内存字节内容传送到al中 call DispAL ; 以16进制的形式显示出来 inc esi ; 内存地址增加1 loop .loop ; 继续循环读出到屏幕 call DispReturn ; 调用换行函数 ret ; 函数返回 ; TestRead 结束----------------------------------------------------------- ; ------------------------------------------------------------------------ TestWrite: ; 写入内存函数 push esi ; 保存寄存器esi内容 push edi ; 保存寄存器edi内容 xor esi, esi ; esi置0 xor edi, edi ; edi置0 mov esi, OffsetStrTest ; 源数据偏移 cld ; DF标志位置0 .1: ; 因此内存-内存没有直接通路,所以下面需要al中转 lodsb ; al=((ds)*16+(esi))、(esi)=(esi)+1 test al, al ; al^al,结果不保存,只影响标志位 jz .2 ; 如果ZF标志位为0,跳转到.2 mov [es:edi], al ; 将字符写入内存 inc edi ; 内存偏移到下一位 jmp .1 ; 循环到写入的字符串为0时结束 .2: pop edi ; 还原子函数用到的寄存器edi、esi,注意顺序 pop esi ret ; TestWrite 结束---------------------------------------------------------- ; ------------------------------------------------------------------------ ; 以16进制显示 AL 中的数字 ; 默认地: ; 数字已经存在 AL 中 ; edi 始终指向要显示的下一个字符的位置 ; 被改变的寄存器: ; ax, edi ; ------------------------------------------------------------------------ DispAL: push ecx ; 保存子程序中用到的寄存器ecx、edx push edx mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov dl, al ; al高4位,低4位分开处理 shr al, 4 ; 先处理高四位 mov ecx, 2 ; 循环两次,第一次高四位,第二次低四位 .begin: and al, 01111b cmp al, 9 ; 如果大于9,要显示A~F的ASCii码 ja .1 add al, '0' ; 如果小于或等于9,显示0~9的ASCii码 jmp .2 .1: sub al, 0Ah add al, 'A' .2: mov [gs:edi], ax ; 输出到屏幕上 add edi, 2 ; 屏幕偏移指针+2 mov al, dl ; 处理低4位 loop .begin add edi, 2 ; edi要时刻指向要显示下一个字符的位置 pop edx ; 还原子函数用到的寄存器edx、ecx,注意顺序 pop ecx ret ; 子程序返回 ; DispAL 结束------------------------------------------------------------- ; ------------------------------------------------------------------------ DispReturn: ; 实现屏幕输出的换行 push eax ; 保存用到的eax、ebx push ebx mov eax, edi ; mov bl, 160 ; div bl ; 执行结束后,ax存放的是当前行的行号码 and eax, 0FFh ; 执行结束后,eax存放的是当前行的行号码 inc eax ; 让eax存放下一行的行号码 mov bl, 160 ; mul bl ; 执行结束后,eax存放的是下一行的行首偏移值 mov edi, eax ; 将下一行的行首偏移置传送到edi中 pop ebx ; 还原子函数用到的寄存器ebx、eax,注意顺序 pop eax ret ; 子程序返回 ; DispReturn 结束--------------------------------------------------------- SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32]
从保护模式返回到实模式有点复杂。因为在准备结束保护模式回到实模式之前,需要加载一个合适的描述符选择子到有关的寄存器,以使对应段描述符高速缓冲寄存器中含有合适的段界限和属性。而且,我们不能从32位的代码段返回实模式,只能从16位的代码段中返回。这是因为无法实现从32位代码段返回时cs告诉缓冲寄存器中的属性符合实模式的要求(实模式不能改变属性)。
所以,在这里,我们新增一个Normal描述符。在返回实模式之前把对应选择子SelectorNormal加载到ds、es和ss,就是上面所说的这个原因
下面就来看一下保护模式返回到实模式前用到的16位代码段:
; 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 ; 段地址会在程序开始处被设置成正确的值 Code16Len equ $ - LABEL_SEG_CODE16 ; END of [SECTION .s16code]
这段代码前面6条mov指令就是上面所说的目的:为了使对应段描述符告诉缓存寄存器中含有合适的段界限和属性,需要加载一个合适的描述符选择子到有关寄存器中。这边我现在也不太了解,先记住吧
接下来的3条指令实现将cr0
的PE位(第0位)置零,代表运行(将要运行)在保护模式下。
还记得上面提到的mov [LABEL_GO_BACK_TO_REAL+3], ax
指令吗?上面只是说它要用在从保护模式返回到实模式中,这里就详细说一下它为什么能够实现这样的功能:
你看这里的jmp指令的段地址是0,但是这是程序刚加载到内存中的时候,随着运行到mov [LABEL_GO_BACK_TO_REAL+3], ax
会发生什么呢?
首先看一下jmp 0:LABEL_REAL_ENTRY
的机器码:
BYTE1 BYTE2 BYTE3 BYTE4 BYTE5
0EAh | offset | Segment |
由上图可以看出,LABEL_GO_BACK_TO_REAL+3
恰好就是Segment的地址,而执行mov [LABEL_GO_BACK_TO_REAL+3], ax
之前ax的值已经是实模式下的cs(假设记为cs_real_mode)了,所以它这条mov指令将把cs保存到segment的位置,等到jmp指令执行时,它已经不再是:
jmp 0:LABEL_REAL_ENTRY
而是:
jmp cs_real_mode:LABEL_REAL_ENTRY
这条指令将会跳转到标号LABEL_REAL_ENTRY处。现在已经跳回到实模式了,接下来就是要重新设置各个寄存器的值,并回复sp的值,然后关闭A20,打开中断,重新回到原来的样子。LABEL_REAL_ENTRY的代码如下:
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]
这里我们又看到了SPValueInRealMode
,还记得上面没有详细说的指令mov [SPValueInRealMode], sp
吗?从这而很容易可以看出,它保存实模式下sp的值,也是为了现在回到实模式回复sp的值。
关闭A20地址先,开中断之后,通过int 21h
中断返回到DOS。
这样整个程序的运行过程就结束了哈哈。
通过nasm编译成.com
文件,这里面还有如何突破引导扇区512字节的限制。弄明白了再详细记录。
下面是主程序的完整源代码
; ========================================== ; pmtest2.asm ; 编译方法:nasm pmtest2.asm -o pmtest2.com ; ========================================== %include "pm.inc" ; 常量, 宏, 以及一些说明 org 0100h 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 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] ; 数据段 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] ; 全局堆栈段 [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 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, 并跳转到 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] [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 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 ; ------------------------------------------------------------------------ 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 mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov dl, al shr al, 4 mov ecx, 2 .begin: and al, 01111b 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 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: ; 跳回实模式: 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 ; 段地址会在程序开始处被设置成正确的值 Code16Len equ $ - LABEL_SEG_CODE16 ; END of [SECTION .s16code]