随便说说:
保护模式下的内存访问模式、方法,而这个保护模式下的内存访问模式在实模式与保护模式已经介绍过是什么,这次来具体点看。
访问内存
1.首先确定好哪些是属于访问内存?
1.1最容易确定莫过于直接对内存空间进行读写的指令
例如:
[bits 32]
mov [ds:esi],123 ;write
mov eax,[ds:esi] ;read
add [ds:esi],123 ;read/write
sub [ds:esi],123 ;read/write
shr [ds:esi],8 ;read/write
...
1.2代码执行类的指令(jmp,call…)
不仅仅是数据读写,包括代码的执行实际上也是对内存的读取,因为jmp、call这类跳转指令,终归就是读取跳转目的地的内容,再将其作为指令执行。
2.读写内存过程
在实模式与保护模式(里面讲到描述符)提到过会先判断访问是否合法,合法在进行读写。
先来看这两个表:
对于数据段分别为(X,E,W,A)
X | E | W | A | 描述符类别 | 含义 |
---|---|---|---|---|---|
0 | 0 | 0 | x | 数据 | 只读 |
0 | 0 | 1 | x | 数据 | 读、写 |
0 | 1 | 0 | x | 数据 | 只读,向下扩展 |
0 | 1 | 1 | x | 数据 | 读、写,向下扩展 |
对于代码段分别为(X,C,R,A)
X | C | R | A | 描述符类别 | 含义 |
---|---|---|---|---|---|
1 | 0 | 0 | x | 代码 | 只执行 |
1 | 0 | 1 | x | 代码 | 执行、读 |
1 | 1 | 0 | x | 代码 | 只执行,依从的代码段 |
1 | 1 | 1 | x | 代码 | 执行、读,依从的代码段 |
合法简单来说就是:
数据段:
1.只读数据段只能读,不能写
2.读写的位置不能超过描述符中的界限
3.特权级符合要求
代码段:
1.代码段均不能写
2.只执行的代码不能读
3.特权级符合要求
P.S.关于特权级的内容可以看特权级保护,其实特权级和其它属性的作用也相差无几
实践:
合法的话当然按照预想中一样成功进行,先来我们来看看不合法情况下的结果(其实这个才是这文章的关键。。。)
错误代码
;片1
data_selector equ 0x8
code_selector equ 0x10
mov es,ax
mov esi,[cs:gdt_address + 0x7c00 + 2]
;data descriptor 0~4GB
add esi,8
mov dword [es:esi],0x0000ffff
mov dword [es:esi + 4],1100_0000_10010010_00000000B
;code descriptor
add esi,8
mov dword [es:esi],0x7c0001ff
mov dword [es:esi + 4],0100_0000_10011010_00000000B
;set the size of GDT
mov word [cs:gdt_address + 0x7c00],23
lgdt [cs:gdt_address + 0x7c00]
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
cli ;中断机制尚未工作
mov eax,cr0
or eax,1
mov cr0,eax ;设置PE位
;以下进入保护模式... ...
jmp dword 0x0010:flush ;16位的描述符选择子:32位偏移
;清流水线并串行化处理器
;片2
;======================================
[bits 32]
;======================================
flush:
mov eax,data_selector
mov ds,ax
@a: mov eax,[cs:message]
@b: mov [ds:0x40000],eax
@c: mov ebx,[ds:0x40000]
@d: mov dword [cs:message],0x123456
;片3
message: dd 0xabcdef
gdt_address dw 0
dd 0x7e00
;---------------------------
;主引导扇区要求
times 510 - ($ -$$) db 0
db 0x55
db 0xaa
分析:
片1:
里面创建了2个全局描述符:
1.数据段描述符:可读写,基址:0x0,界限:4GB,也就是可以访问整个内存空间
2.代码段描述符:可读、可执行,基址:0x7c00,界限:512B
其中描述符表从0x7e00开始
片2:
有如下几个操作:
1.读代码段->@a
2.写数据段->@b:
3.读数据段->@c:
4.写代码段->@d:
安装完2个描述符后:
从图中可看到有3个描述符,第一个是空描述符(处理器要求…)
第二个就是我们安装的数据段描述符,从中可以看到其特性它是一个基址为0x0,界限4GB,可读写的数据段
第三个是我们安装的代码段描述符,基址为0x7c00,界限为512B,可执行、可读的32位代码
上面显示的信息与我们代码中的信息一致,描述符安装成功
图1:
图2:
jmp dword 0x0010:flush
上面的图1是执行上面行代码之前的,下图是执行了这行代码之后,关键是看图中红框部分,从实模式与保护模式中知道,改行代码中的0x0010就是段选择子,jmp指令,使得cs保存新的段,也就是正式进入了保护模式。
接下来看代码中读写内存的操作:
1.读代码段->@a
成功读取
2.写数据段->@b:
成功写入
3.读数据段->@c:
成功读取
4.写代码段->@d:
因为不合法,写失败,这时会自动跳到0xf000:e05b的位置(我也不知道为什么,不过当执行不合法的访问时都会自动跳转到该处)
3.总结
容易出现内存访问不合法地方:
1.没有初始化段选择器
特别是刚进入32位模式时,几个重要的段选择器必须初始化好,如cs,ds,ss,还有它们的偏移地址,特别是sp
2.使用jmp、call之前没有给正确的段选择子
这里简单提提jmp的几个用法:
1.直接访问
code_selector equ 0x8
...
section code vstart=0
start:
...
jmp code_selector:start
;or
jmp 0x8:start
start为地址(偏移地址)
2.间接访问
section data vstart=0
entry dd offset
dw selector
...
mov eax,data_selector
mov ds,ax
jmp far [ds:entry]
上面的jmp,先读取该地址起始的6个字节,其中前4个字节作为偏移地址,后2个字节为段选择子(由于是段间跳转,所以用jmp far)
3.代码段描述符初始化为只执行,但后期出现对代码段的读写
4.在保护模式中调用实模式的代码
wrong:
...
ret
[bits 32]
call wrong
就如上面代码中的用法,会出现和你预想完全不同的内存访问(偏移地址的完全不同),因此哪里使用call、jmp就调用哪里的方法。
5.段间的call,一定要配合上retf使用
段间的远距离call,是要压入选择子以及偏移地址,就是总共压入8个字节,而普通的ret最后只会弹回4个字节。。。
+
+
+
+
+
+
+
+
+
P.S.这5个看上去也不是很大的问题,不过加上汇编,一旦出现了,还真要调试一大轮才找出问题。。。而且,汇编的调试真的真的超级麻烦。。。。。