摘要:本节,我们将学习如何从实模式进入保护模式,剖析pmtest1.asm源码。详细讲解如何从保护模式进入实模式。
这一部分,你需要把一些关键物理器件和数据结构的布局记在脑子里
GDT描述符:32b段基地址(B2、B3、B4、B7)+20b段限长(B0、B1、B6的低4位)+12b段属性(B5、B6的高4位),详细布局见下图(针对代码段和数据段):
所以,我们定义了如下的存储段描述符结构体:
42;宏定义-----------------------------------
43;segment discripor
44;for code and data
45;usage Descripor Base, Limit, Attr
46; Base dd
47; Limit dd (Low 20b are available)
48; Attr dw(lower 4b of higher byte are 0)
49%macro Descriptor 3
50 dw %2&0FFFFh
51 dw %1&0FFFFh
52 db (%1 >> 16)&0FFh ;注意,运算以后超过8b,但是会被db截断
53 dw (%3&0F0FFh) | ((%2 >> 8)&F0FFh)
54 db (%1>> 24)&0FFh
55 %endmacro;8字节
57;for gate
58;usage Gate Selector Selector,Offset,Dcount,Attr
59; selector dw
60; offset dd
61; Dcount dw
62; Attr dw
63%macro Gate 4
64 dw (%2&FFFFh)
65 dw %1
66 dw (%3 & 1Fh) | ((%4<< 8) & FF00h)
67 dw (%2 >> 16) & FFFFh
68 %endmarco
段选择子:布局是2+(1+13)
相应的,我们根据IA-32的有关说明定义了如下的常量:
1;描述符类型说明:
2_LDT EQU 82h ; 局部描述符表段类型值
3DA_TaskGate EQU 85h ; 任务门类型值
4DA_386TSS EQU 89h ; 可用386任务状态段类型值
5DA_386CGate EQU 8Ch ; 386 调用门类型值
6DA_386IGate EQU 8Eh ; 386 中断门类型值
7DA_386TGate EQU 8Fh ; 386 陷阱门类型值
8;---------------------------------------------------
9;DA_32 EQU 4000h ;32b段
10DA_DPL0 EQU 00h ; DPL = 0
11DA_DPL1 EQU 20h ; DPL = 1
12DA_DPL2 EQU 40h ; DPL = 2
13DA_DPL3 EQU 60h ; DPL = 3
14
15;数据段和代码段描述符的低8位
16;---------------------------------------------------------------------------
17DA_DR EQU 90h ; 存在的只读数据段类型值
18DA_DRW EQU 92h ; 存在的可读写数据段属性值
19DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
20DA_C EQU 98h ; 存在的只执行代码段属性值
21DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值
22DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值
23DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值
24
25
26;选择子类型说明
27------------------------------------------
28;SA_selector attribute
29SA_RPL0 equ 0;
30SA_RPL1 equ 1;
31SA_RPL2 equ 2;
32SA_RPL3 equ 3;
33
34SA_TIG equ 0;
35SA_TIG equ 4;
注意,在这里,我们为什么用的是4,不是1,是为了方便以后用加法赋值。从中,我们也可以看到,尽量做到所有的数字,是字节对齐的.
写好上述代码以后,放入一个.inc文件之中,然后使用命令%include"pm.inc"将头文件引入
2.保护模式的运行环境
我们采用dos的引导程序,直接来书写启动内核。这样,可以解决引导扇区只有512B的限制。
需要在bochs环境中设置从软盘a启动,然后运行软盘b中的程序。
关于GDT的详细介绍,可以参考这里http://blog.csdn.net/trochiluses/article/details/8968750
代码解惑:
4.1[BITS32]等修饰,起到了什么作用?与BITS16有什么区别?
答:关于BITS(可能,你需要了解一下处理器字长和指令位数等:)
'BITS'指令指定NASM产生的代码是被设计运行在16位模式的处理器上还是运行在32位模式的处理器上。语法是'BITS16'或'BITS32'在大多数情况下,你可能不需要显式地指定'BITS'。
'a.out','elf'和'win32'目标文件格式都是被设计用在32位操作系统上的,它们会让NASM缺省选择32位模式。而'obj'目标文件格式允许你为每一个段指定'USE16'或'USE32',然后NASM就会按你的指定设定操作模式,所以多次使用'BITS'是没有必要的。
但是我们的Realinit.bin的开始部分代码,采用NASM编译,目标格式为BIN格式,即纯粹的二进制可执行文件,不带任何头文件。
最有可能使用'BITS'的场合是在一个纯二进制文件中使用32位代码;这是因为'bin'输出格式在作为DOS的'.COM'程序,DOS的'.SYS'设备驱动程序,或引导程序时,默认都是16位模式。如果你仅仅是为了在16位的DOS程序中使用32位指令,你不必指定'BITS32',如果你这样做了,汇编器反而会产生错误的代码,因为这样它会产生运行在16位模式下,却以32位平台为目标的代码。当NASM在'BITS16'状态下时,使用32位数据的指令可以加一个字节的前缀0x66,要使用32位的地址,可以加上0x67前缀。在'BITS32'状态下,相反的情况成立,32位指令不需要前缀,而使用16位数据的指令需要0x66前缀,使用16位地址的指令需要0x67前缀。'BITS'指令拥有一个等效的原始形式:[BITS16]和[BITS32]。而用户级的形式只是一个仅仅调用原始形式的宏。
4.2段基地址是如何计算的?LABEL_BIGIN不是段基地址?
我们要清楚,一个物理地址,如果没有开启分页机制,在保护模式和实模式下对应的地址是相同的,是不过寻址方式不同而已。
4.3实模式和保护模式的寻址区别在哪里?在代码中是如何体现出来的?
结合上一个问题,实模式下是segment*16+offset,注意,这个segment是cs等中的数值;保护模式也是CS:offset,不过此时的CS仅仅是一个选择符而已,还需要转换成段基地址。另外,代码中的LABEL-BEGIN等,表示的是段内偏移,所以采用(eax<<4)+ LABEL_BEGIN来表示段的首地址。
4.4不同长度的操作数是如何进行运算的?是截断还是补齐?为什么会有修饰符movbyte和jmpdword等?
分情况:movax,bl:寄存器之间,按照高位补0的原则;
mov al,ff0ffh:寄存器与操作树之间,按照截断和补充高位0的原则
mov [ax],03:立即数和内存,很显然,这个难办的地方在于双方都没有固定的长度,或者说只要有内存在内,就没有固定长度,此时需要指令操作数长度,例如movbyte [ax],03等。
同理,jmpword和dword在于对偏移量的处理,假如我们使用前者,只有16b寄存器,会发生截断;所以用后者才比较安全。所以,jmp指令是一个需要特殊对待的指令。
另外,不要将长操作数赋值给短的:movbyte [bx],ax这样是错误的;总之,进行相关操作的时候,尽量确保两个操作数长度的统一
4.5为什么进入保护模式之前,需要先关中断?需要打开A20?设置GDT?
这里,我们先挖坑,等以后回来再填补.
4.6实模式下的偏移和保护模式之下的offset有什么区别?实模式下,标号的地址表示实际的物理地址还是相当于段的offset呢?
答:offset本身的含义没有什么不同。label表示offset而不是实际的物理地址,之所以看起来像物理地址,是因为那时候cs的初始值是0000h。
那么,如果仅仅是offset,那么如何解释“mov word [LABEL_DESC_CODE32 + 2], ax”中内存数据的寻址呢?
我们来看一下,上面这条指令汇编以后的结果:
mov word ptr ds:0x010e, ax ; a30e01
看见了没有,对于内存寻址,ds被隐藏起来了,这就是我们之前为什么要初始化ds的原因!!!
也就是说,标号的地址是本身相对与段首地址的offset。
4.7org 0100h是什么意思?为什么要加载到这个地方?
答:要了解ORG 0100h,就必须先了解程序段前缀PSP(Program Segment Prefix)
程序段前缀是一个操作系统(DOS)概念。当输入一个外部命令或通过EXEC子功能(系统功能调用INT21h,子功能号为4Bh)加载一个程序时,COMMAND确定当前可用内存的最低端作为程序段的起点,也就是程序被加载到内存空间的起点。在程序所占用内存的前256(0100h)个字节中,DOS会为程序创建前缀(PSP)数据区。PSP结构与CP/M中的“控制区域”概念十分接近,这是因为DOS就是从CP/M演变而来的。
比如你有一个标号Test的偏移地址是0x0Bh,当编译器看见ORG 0x100h后,就会给这个偏移加上0x100h,编译完成的.com文件中,这个偏移就变成了0x10Bh。所以,你如果想在最开始设定断点,b 00h是不会生效的,因为00h被移动到了100h。
DOS下的.com程序肯定被加载的CS段+0x100h处,因为段的前0100h(256字节)是留给PSP的。这是操作系统DOS的概念,跟ORG没有关系,所以你写ORG 0200h
,也不会让程序加载到CS段的0200h处。另外,dos是一个运行在实模式之下的操作系统。
4.8关于equ?是什么时候计算的?
答:equ实际上相当于一个宏定义,所以它仅仅是一个替换作用,只有在程序实际运行的时候,才得到前面的值。
1)完成对段描述符的初始化
2)装填gdtptr
3)关中断
4)打开A20
5)设置cr0寄存器
6)jmp切换
观察各个寄存器和相关指令的执行和变化,得到的经验。
书写16进制数的时候可以写成ox0100,或者0010h,但是不能写成ox0100h