一、主要功能
在本章最基本代码(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]
相关推荐
在实模式下,CPU使用16位寄存器,而在保护模式下,它可以使用32位寄存器,提供更大的地址空间。为了进入保护模式,需要设置控制寄存器(如CR0),启用保护模式标志,并设置段描述符表(GDT)以定义内存段的属性。段...
《x86汇编语言:从实模式到保护模式》是一部深入探讨x86架构处理器在不同模式下工作原理的教程。在这个完整版中,它包含通常被其他资源遗漏的后三章,提供了全面的学习材料。由于文件体积较大,作者选择了通过网盘...
在实地址模式下,内存被看作一个连续的线性空间,而保护模式则将内存分为多个段,每个段都有自己的基地址和长度。这样,即使多个程序同时运行,它们也可以拥有独立的内存空间,减少了数据冲突的可能性。 分段机制是...
在实模式下,逻辑地址等于段地址乘以16加上偏移地址;而在保护模式下,逻辑地址通过段描述符在全局描述符表或局部描述符表中查找得到段地址,然后与偏移地址相加得到线性地址。 - 线性地址空间:这是处理器理解和...
首先,当计算机加电,硬件会自动进入实模式,这是一个最基础的操作模式,CPU和内存管理都是基于16位的。此时,控制权转移到BIOS(基本输入输出系统)的入口。BIOS的主要任务是进行硬件自检(POST)并初始化设备,...
第三,内存管理单元(Memory Management Unit, MMU)的作用至关重要。MMU负责将虚拟地址转换成物理地址,并且通常具备地址映射、权限检查和内存隔离等重要功能。在多核异构系统中,MMU的设计需要能够灵活地适应不同...
- **参数**:`ulUser0` 和 `ulUser1` 分别表示第一个和第二个用户自定义的 32 位值。 - **返回值**:操作状态,成功返回 0。 9. **`long FlashUserSave(void)`** - **功能**:保存用户自定义值。 - **返回值**...
3. **从16位到32位的过渡**:书中分析了从16位汇编向32位汇编过渡的过程,指出了其中的关键差异和注意事项,这对于理解和掌握32位汇编语言的使用非常有帮助。 4. **实战案例**:通过一系列具体的案例(如“Hello ...
很好用的U盘系统盘制作工具这次出的所谓MaxDOS 密码读取工具,其实只是读取了安装日志LOG里的设置值,并不是真正的MD5值被编译,本来不想保留这个东西的,但是如果不保留这个LOG文件的话,又无法完成自动卸载,怕有些人又...
接着,BIOS加载操作系统内核程序,为进入保护模式做准备,这是一个从16位实模式向32位保护模式的转变,为后续的高级功能如内存保护和多任务提供了基础。 第二章涉及设备环境初始化和激活进程0。在这个阶段,操作...
- **保护性读写操作**:对设置了保护位的数据块进行读写操作时,需先解锁才能进行。 #### 六、总结 T5557读写软件为用户提供了一个方便快捷的方式来管理和操作T5557射频卡。通过精心设计的硬件平台和软件功能,该...
此课件涵盖了从第一章到第六章的内容,主要聚焦于Intel架构的IA-16系列微处理器,如8086、8088、80186、80188和80286。这些处理器是早期个人计算机的核心组件,对现代计算机技术的发展有着深远的影响。 微处理器是...
### IA-32架构软件开发人员手册第3卷:系统编程指南知识点概览 #### 一、手册概览 **标题:** IA-32架构软件开发人员手册第3卷:系统编程指南(中文版) **描述:** 本手册为Intel官方发布的第三版系统编程指南,...
根据给定的文件信息,我们可以深入探讨SD卡SPI读写技术的关键知识点,这对于初学者尤其有益,因为它提供了从基础概念到具体操作的全面视角。 ### SD卡SPI读写概述 SD卡SPI读写是一种用于与SD卡进行通信的替代方法...
- 独立的用户模式地址空间和内核模式地址空间:用户模式和内核模式下有不同的内存访问权限,以保证操作系统的安全性。 - 灵活的程序闪存存储器分区:存储器可以根据需要灵活地划分,以优化存储空间的使用。 - 数据...
* DOS工作在实模式下,程序员可以寻址1M的内存,通过段寄存器来指定段的初始地址,每个段的大小为64K 4. Win32汇编语言: * Win32是指32位的Windows操作系统,进程有多种运行级别,操作系统工作在最高级别——0级...