(第三章 5)保护模式下,读写大地址内存 & 从32位保护模式跳回16位实模式(一)

一、主要功能

        在本章最基本代码(P25、chapter3/a/)的基础上实现大地址(超过1M)的读写。在前面程序的基础上新建一个段,这个段以5MB为基址,远远超过1MB的界限。先读出开始处8字节的内容,然后写入一个字符串,再从中读出8字节。
        如果读写成功的话,两次读出的内容应该不同,而且第二次读出的内容应该就是我们写进的字符串。字符串是保存在数据段中的,也是新增加的。

************************************************************程序流程************************************************************


************************************************************程序流程************************************************************

注意:

1. 几个段的解释

扫描二维码关注公众号,回复: 1360616 查看本文章

        数据段(选择子为SelectorData)访问相关代码标记为红色(PMMessage将在保护模式下直接显示;StrTest则在保护模式下先被拷贝到SelectorTest对应的段,然后取出后显示);

        测试段(择子为SelectoTest)访问相关代码标记绿色(这个段的段基址设置很大,只是为了展示保护模式访问大地址的能力。它的描述符中段基址不用在[SECTION .s16]中精确设置,只需要设置为一个很大的值即可);

        Normal(选择子为SelectorNormal)段表示为紫色,这个选择子在代码最后准备跳回实模式的段[SECTION .s16code]中有用到。

        其中,我认为关键的一点是实模式和保护模式内存访问方式的区别:

        同样采用[ds:esi]这样的形式——两者的esi均表示偏移量(当然,实模式中应该为si);ds含义不同。在实模式中,ds表示段基值;而在保护模式中,ds会在32位代码段开始处被设置为段对应的选择子设置方法如下。

[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS	32]
LABEL_SEG_CODE32:
mov	ax, SelectorData
mov	ds, ax	 ; 数据段选择子
mov	ax, SelectorTest
mov	es, ax	 ; 测试段选择子
... ...

2. 代码段访问到>1MB内存地址,并不表示代码段中的指令本身在>1MB的地址

从这个程序可以看到,保护模式下可以访问超过1MB内存,但保护模式([SECTION .32])的代码仍然在1MB以内

3. 如何证明保护模式下可以访问超过1MB的地址:

进入32位代码段后,在保护模式下调用子例程"call TestRead --> call TestWrite --> call TestRead",在这些子例程中均出现访问大地址的代码,如下:

;                            段基址,        段界限 , 属性
LABEL_DESC_TEST:   Descriptor 0500000h,   0ffffh, DA_DRW
...

[SECTION  .s32]	;进入这个32段前设置了cr0[0]=1,即进入本段就在“保护模式”下了
[BITS	32]
	...
	mov	ax,	SelectorTest	;注意到测试段(Test段)的段基址为0500000h远大于1M
	mov	es,	ax

TestRead:
	...
	mov	al,	[es:esi]
	...
TestWrite:
	...
	mov	[es:edi],	al
	...

二、代码

%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 描述符,段基址在此设置为0(后面没有再重设)
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 结束

; 注意:
;    16位实模式段基址是cs<<4+LABEL_SEG_CODE16
;    32位保护模式段基址是cs<<4+LABEL_SEG_CODE32
;    数据段基址是ds<<4+LABEL_DATA
;          堆栈段基址是ds<<4+LABEL_STACK
; 它们都将在16位实模式段[SECTION .s16]开始被设置

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

; 初始化数据段描述符

; 仍然采用16位计算物理地址的方法(16位段基值左移4位,再加偏移量),但以前是加法器帮我们做的。

; 现在我们自己来计算这个物理地址(20bit),就需要用到32bit寄存器eax

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 ; P35讲解为何要用dword

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax

mov sp, [SPValueInRealMode]   //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
xor edi, edi
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。(屏幕为25行80列,一个位置对应两个字节)
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


; 到此停止,下面跳到的是最后一个段[SECTION .s16code],这个段准备跳回实模式
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和.2的效果实际上就是将[ds:esi]若干字符放到[es:edi]若干位置中

;  注意到ds在32位代码段开始时被置为SelectorData;es在32位代码段开始时被置为SelectorTest
.1:
lodsb                          

lodsb 指令:从esi 指向的源地址中逐一读取一个字符(默认数据段用SelectorData作选择子),送入AL 中; (见本博客《汇编

; (NASM)》部分)。注意到前面已将SelectorData放入ds中了。所以实际上lodsb就是“ mov    al,    [ds:esi] ”

;mov ax, SelectorData
;mov ds, ax ; 数据段选择子


test al, al                     ; 检测到最后一个字符00h,zf=0,就会跳转到.2
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:
; 回忆其描述符的定义如下,

; LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW   ; Normal 描述符,段基址在此设置为0(后面没有再重设)

 

; 书上P43最上面:在准备结束保护模式回到实模式之前,需要加载一个合适的描述符选择子(SelectorNormal==>(1)段基址:0 (2)段界限:0ffffh (3)属性:DA_DRW即可读写数据段)到有关段寄存器(ds,es,fs,gs,ss)。


mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, a
x
mov ss, ax


mov eax, cr0
and al, 11111110b
mov cr0, eax

LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY    ;段间直接转移指令(far jump),跳回到16位实模式段[SECTION .s16]

; 这里使用“段间直接转移指令”,也称为“远转移far jump”。
; A. “直接”:目标地址像立即数一样,“直接”在指令的机器代码中就是直接寻址方式;
; B. “段间”:从当前代码段跳转到另一个代码段,此时需要更改CS段地址和IP偏移地址。

; LABEL_GO_BACK_TO_REAL:
; jmp 0:LABEL_REAL_ENTRY

; 第二行代码在这里指定了IP偏移是LABEL_REAL_ENTRY(这是跳回16位实模式段[SECTION .s16]的一个偏移);
; 第二行代码并没有指定CS段地址(直接复0),但是在前面的16位实模式段[SECTION .s16]中实际上直接操作了机器码,设置其CS段地址为cs的值(如下所示)

; (1) 实模式下长跳转指令:

;    BYTE1        |         BYTE2                 BYTE3         |        BYTE4                 BYTE5

;     OEAh         |                        Offset                          |                     Segment 

 

; (2) 在跳入32位代码段前的那个16位代码段对这里远跳转的段基址进行设置

; [SECTION .s16]

; [BITS16]

; LABEL_BEGIN:

; movax, cs

; movds, ax

; moves, ax

; movss, ax

; movsp, 0100h

; mov[LABEL_GO_BACK_TO_REAL+3], ax

Code16Len equ $ - LABEL_SEG_CODE16

; END of [SECTION .s16code]

猜你喜欢

转载自chuanwang66.iteye.com/blog/1070536