一、主要功能
在本章最基本代码(P25、chapter3/a/)的基础上实现大地址(超过1M)的读写。在前面程序的基础上新建一个段,这个段以5MB为基址,远远超过1MB的界限。先读出开始处8字节的内容,然后写入一个字符串,再从中读出8字节。
如果读写成功的话,两次读出的内容应该不同,而且第二次读出的内容应该就是我们写进的字符串。字符串是保存在数据段中的,也是新增加的。
************************************************************程序流程************************************************************
************************************************************程序流程************************************************************
注意:
1. 几个段的解释
数据段(选择子为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, ax
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]
相关推荐
很好用的U盘系统盘制作工具这次出的所谓MaxDOS 密码读取工具,其实只是读取了安装日志LOG里的设置值,并不是真正的MD5值被编译,本来不想保留这个东西的,但是如果不保留这个LOG文件的话,又无法完成自动卸载,怕有些人又...
笔者从事汇编编程已经有十几年的历史了,从8086时代的DOS汇编编程开始到当前的Win32汇编编程,从一个初学者到现在能利用Win32汇编来解决大部分编程需求,中间也经过了很长时间的摸索和大量的挫折,所以笔者很清楚...
答:分段部件形成的32位线性地址中高10位作为寻址页目录表的偏移量,与控制寄存器CR3中页目录表基地址共同形成一个32位的地址指向页表中的一个页项,即为一个页面描述符。该页面项中高20位作为页面基地址,线性地址...
Ring-3 16位保护模式(16位Win程序) 地址内容 使用INT 0x41 .DOT命令 理解从R-3到R-0的转变 第七章 使用断点 第八章 ----------------------------------------------------------- (...很累人那!今天就...
书中的实例操作系统采用IA32作为默认平台,所以保护模式也作为必备知识储备收入书中,而这是传统的操作系统实践书籍经常忽略的。总之,只要是开发自己的操作系统中需要的知识,书中都尽量涉及,以便于读者参考。 ...
书中的实例操作系统采用IA32作为默认平台,所以保护模式也作为必备知识储备收入书中,而这是传统的操作系统实践书籍经常忽略的。总之,只要是开发自己的操作系统中需要的知识,书中都尽量涉及,以便于读者参考。 ...
第3章高速缓存子系统 3.1SRAM的工作原理 3.1.1历史概况 3.1.2内核 3.1.3触发器的设计 3.1.4逻辑非元件(取反器)的设计 3.1.5SRAM阵列的设计 3.1.6封装接口的设计 3.1.7读写时序图 3.1.8静态存储器的类型 3.2高速缓存...
在32个通用工作寄存器中,有6个寄存器可以合并成为3个16位的,用于对数据存储器空间进行间接寻址的间接地址寄存器(存放地址指针),以实现高效的地址计算。这3个16位的间接地址寄存器称为:X寄存器,Y寄存器和Z...
第3章高速缓存子系统 3.1SRAM的工作原理 3.1.1历史概况 3.1.2内核 3.1.3触发器的设计 3.1.4逻辑非元件(取反器)的设计 3.1.5SRAM阵列的设计 3.1.6封装接口的设计 3.1.7读写时序图 3.1.8静态存储器的类型 3.2高速缓存...
第3章高速缓存子系统 3.1SRAM的工作原理 3.1.1历史概况 3.1.2内核 3.1.3触发器的设计 3.1.4逻辑非元件(取反器)的设计 3.1.5SRAM阵列的设计 3.1.6封装接口的设计 3.1.7读写时序图 3.1.8静态存储器的类型 3.2高速缓存...
保护模式篇章第三部分:直接访问硬件 1)修改iopl,ring3直接访问硬件 2)追加tss默认I/O许可位图区域 3)更改tss I/O许可位图指向 5。detour 修改函数执行路径,可用于对函数的控制流程进行重定路径。 1)...
保护模式篇章第三部分:直接访问硬件 1)修改iopl,ring3直接访问硬件 2)追加tss默认I/O许可位图区域 3)更改tss I/O许可位图指向 5。detour 修改函数执行路径,可用于对函数的控制流程进行重定路径。 1)...