您的位置:首页 > 其它

学习笔记--保护模式进阶 访问大内存

2016-09-03 11:35 260 查看
我们虽然成功进入了保护模式,但是并没有体验到保护模式给我们带来的便利,上一个例子中打印了一个红色的p,这在实模式中也很容易做到,但是保护模式能做的远不止如此,上文我们提到在保护模式下的寻址空间可以到达4G,首先实验一下读大地址内存,在前面程序的基础上,新建一个段,这个段是以5MB为基址,远远超过了实模式下1MB的限制,我们先读出开始处8字节的内容,然后写入一个字符串,再从中读出8个字节的内容,如果读写成功的话,两次读出的结果是不一样的,而且第二次读出的结果应该是我们写的字符串,下面我们先来看一下源代码,然后在来慢慢分析。

[cpp]
view plain
copy

; ==========================================
; 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 -

,以StrTest−
表示字符串StrTest相对于本节的开始处(即LABEL_DATA处)的偏移,看看初始化数据段描述符的代码,容易发现数据段的基地址就是便是LABEL_DATA的物理地址,于是offsetStrTest既是字符串相对于LABEL_DATA的偏移,也是其在数据段中的偏移,我们在保护模式下需要用的正是这个偏移,而不再是实模式下的地址,另外,由于在保护模式下用到了堆栈,我们也建立了堆栈段,现在我们来解释一下回到实模式的代码,我们从实模式进入保护模式直接用一个跳转就可以了但是返回的时候却要稍微复杂一些,因为在准备结束保护模式回到实模式之前,需要加载一个合适的描述符选择子到相关段寄存器,以使对应段描述符高速缓存寄存器中含有合适的段界限和属性。而且,我们不能从32位代码段返回实模式,只能从16位代码段中返回。这是因为无法实现从32位代码段返回时cs高速缓冲寄存器中的属性符合实模式的要求(实模式不能改变段属性)。
所以,在这里,我们新增一个Normal描述符,在返回实模式之前把对应选择子SelectorNormal加载到ds,es,和ss,就是上面所说的这个原因。

[cpp]
view plain
copy

; 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]

这个段由[SECTION.s32]中的jmp SelectorCode16:0跳进来的,在后面的跳转中看上去好像不太对,因为段地址为0,其实这里只是暂时这样写罢了,在程序的一开始处就可以看到下面的代码:

[cpp]
view plain
copy

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,方便调用了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐