- ; ==========================================
- ; pmtest2.asm
- ; 编译方法:nasm pmtest2.asm -o pmtest2.com
- ; ==========================================
- DA_32 EQU 4000h ; 32 位段
- DA_C EQU 98h ; 存在的只执行代码段属性值
- DA_DRW EQU 92h ; 存在的可读写数据段属性值
- DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
- ATCE32 EQU 4098h ;存在的只执行32代码段属性值
- ;下面是宏定义
- ; 有三个参数:段界限,段基址,段属性其中宏定义中的%1代表参数1,%2代表参数2,%3代表参数3
- %macro Descriptor 3
- dw %2 & 0FFFFh ; 段界限1(参数2的低16位)
- dw %1 & 0FFFFh ; 段基址1(参数1的低16位)
- db (%1 >> 16) & 0FFh ; 段基址2(参数1的16-23位)
- dw ((%2 >> 8) & 0F00h) | (%3 & 0F000h)| (%3 & 000FFh) ; 属性1(高4位) + 段界限2(高4位) + 属性2(低8位)
- db (%1 >> 24) & 0FFh ; 段基址3(参数1的24-31位)
- %endmacro ; 共 8 字节
- ;段界限共20位,段基地址30位,段属性共16位(含段界限高4位)
- 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 ; 在保护模式中显示(以0结尾)
- OffsetPMMessage equ PMMessage - $$ ;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 ;段值*16
- add eax, LABEL_SEG_CODE16 ;段值*16+偏移=16位代码段基地址
- mov word [LABEL_DESC_CODE16 + 2], ax ;基地址1
- shr eax, 16
- mov byte [LABEL_DESC_CODE16 + 4], al ;基地址2
- mov byte [LABEL_DESC_CODE16 + 7], ah ;基地址3
- ; 初始化 32 位代码段描述符
- xor eax, eax
- mov ax, cs
- shl eax, 4 ;段值*16
- add eax, LABEL_SEG_CODE32 ;段值*16+偏移=32位代码段基地址
- mov word [LABEL_DESC_CODE32 + 2], ax ;基地址1
- shr eax, 16
- mov byte [LABEL_DESC_CODE32 + 4], al ;基地址2
- mov byte [LABEL_DESC_CODE32 + 7], ah ;基地址3
- ; 初始化数据段描述符
- xor eax, eax
- mov ax, ds
- shl eax, 4 ;段值*16
- add eax, LABEL_DATA ;段值*16+偏移=数据段基地址
- mov word [LABEL_DESC_DATA + 2], ax ;基地址1
- shr eax, 16
- mov byte [LABEL_DESC_DATA + 4], al ;基地址2
- mov byte [LABEL_DESC_DATA + 7], ah ;基地址3
- ; 初始化堆栈段描述符
- xor eax, eax
- mov ax, ds
- shl eax, 4 ;段值*16
- add eax, LABEL_STACK ;段值*16+偏移=堆栈段基地址
- mov word [LABEL_DESC_STACK + 2], ax ;基地址1
- shr eax, 16
- mov byte [LABEL_DESC_STACK + 4], al ;基地址2
- mov byte [LABEL_DESC_STACK + 7], ah ;基地址3
- ; 为加载 GDTR 作准备
- xor eax, eax
- mov ax, ds
- shl eax, 4 ;段值*16
- 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 ;设置cr0的0位(PE位,PE=1准备进入保护模式)
- mov cr0, eax ;更新cr0
- ; 真正进入保护模式
- 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(调用dos中断号,结束程序,返回到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 ;从[ds:esi]中去一个字符送入al
- test al, al ;判断是否到字符串末尾(以0结尾)
- jz .2 ;是->跳转
- mov [gs:edi], ax ;否->输入到显存
- add edi, 2 ;指针加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] ;从指定内存中都一个字符送到al中
- call DispAL2 ;显示该字符
- 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 ;从[ds:esi]中读一个字符
- 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 ;暂存al
- shr al, 4 ;取数字的高4位(十位数字)
- mov ecx, 2 ;十位和个位共两位需要输出
- .begin:
- and al, 01111b ;保留低4位(十位或个位)
- cmp al, 9 ;大于数字9,则转换成相应的16进制数对应的ASCII
- ja .1
- add al, '0' ;+30转换成ASCII
- jmp .2
- .1:
- sub al, 0Ah
- add al, 'A'
- .2:
- mov byte[gs:edi], al ;改变了书上的写法
- add edi, 2 ;移动指针
- mov al, dl
- loop .begin
- add edi, 2 ;空格
- pop edx
- pop ecx
- ret
- ; DispAL 结束-------------------------------------------------------------
- DispAL2:mov [gs:edi], al ;显示字符
- add edi, 2
- ret
- ; ------------------------------------------------------------------------
- ;换行函数
- DispReturn:
- push eax
- push ebx
- mov eax, edi ;获取当前光标的显存偏移
- mov bl, 160 ;每行80字符,每个字符占两个字节(字符ASCII码+字符属性),所以一行共80*2=160个字节
- div bl ;获取当前行数(在显存中行数从0开始编号的)
- and eax, 0FFh;取低8位(在当前页,否则有可能输出到其它页了,显示器不会显示的)
- inc eax ;下一行
- mov bl, 160
- mul bl ;确定下一行开始的字节数
- mov edi, eax ;更新edi寄存器
- 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 ;设置cr0的0位(PE位,PE=0准备进入实模式)
- mov cr0, eax ;更新cr0
- LABEL_GO_BACK_TO_REAL:
- jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
- Code16Len equ $ - LABEL_SEG_CODE16
- ; END of [SECTION .s16code]
看这个之前必须得明白上一节的代码,在这里对重复的内容不多解释,如果不清楚,可以参考:
《Orange's 一个操作系统的实现》学习笔记--实践认识保护模式
代码中很多我都补充了注释,如果有汇编基础的,应该没有什么难度了,下面是书中的话:
段[SECTION.s32]在开始初始化了ds,es,gs,让ds指向了新增的数据段,es指向了新增的5MB内存的段,gs指向显存,DisAL将al中的字节用十六进制形式显示出来,DispReturn模拟一个回车的显示,实际上让下一个字符显示在下一行的开头处,在TestWrite中用到了一个常量offsetStrTest,注意:我们用到这个字符串的时候并没有用直接标号,StrTest,而是
又定义了一个符号offsetStrTest,它等于StrTest -
所以,在这里,我们新增一个Normal描述符,在返回实模式之前把对应选择子SelectorNormal加载到ds,es,和ss,就是上面所说的这个原因。
- ; 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 ;设置cr0的0位(PE位,PE=0准备进入实模式)
- mov cr0, eax ;更新cr0
- LABEL_GO_BACK_TO_REAL:
- jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
- Code16Len equ $ - LABEL_SEG_CODE16
- ; END of [SECTION .s16code]
- 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 ;保存实模式下的栈顶指针
mov[LABEL_GO_BACK_TO_REAL+3], ax 的作用就是为回到实模式的这个跳转指令指定正确的段地址,这条指令的机器码如下:
这个图告诉我们,LABEL_GO_BACK_TO_REAL+3恰好就是Segment的地址,而在这条指令mov[LABEL_GO_BACK_TO_REAL+3], ax执行之前ax已经是实模式下的cs,(我们记做cs_real_model)了,所以它将把cs保存到Segment的位置,等到jmp指令执行时,它已经
不再是jmp 0:LABEL_REAL_ENTRY,而变成了:
jmp cs_real_model :LABEL_REAL_ENTRY它将跳转到标号LABEL_REAL_ENTRY处。
在跳回实模式之后,程序重新设置各个段寄存器的值,恢复sp的值,然后关闭A20,打开中断,重新回到原来的样子。
下面是书上代码运行图:
下面是我修改了程序的图调用的是DisAL2直接输出字符
但是怎么读出的数据没有呢,其实呀显示的直接是ASCII,而ASCII为0的就是什么也没有,可以再次运行,这样上下就都是相同的字符了
再次运行后的图:
注:因为是.com文件,我没有按照书上的做法了弄,这些弄了一个dos虚拟机,上面是在里面运行的结果,当然还可以调用debug,方便调用了