存储器的保护功能可以禁止程序的非法内存访问,比如像代码段写入数据、访问段界限之外的内存。处理器执行过程中会对内存进行段界限、属性检查。
由于EIP 永远指向下一条将要执行的指令,故在代码段内有如下等式:
0<= EIP -1 <= 段界限
EIP 满足该等式将是正常的内存访问,否则处理器将会引起处理器异常
栈段是向下扩展的,每当往栈中压入数据时,EIP的内容要减去操作数长度。和向高地址方向扩展的段相比,栈段段界限就是段内不允许访问的最低端偏移地址。至于最高端偏移地址,则没有限制,最大可以是0xffffffff。进行栈操作是必须符合以下规则:
段界限+1 <= (ESP-操作数长度) <= 0xffffffff
这里的数据段有别于栈段,是向上扩展的数据段。对于内存访问指令如:
mov [0x2000],edx
这条指令把edx 寄存器4字节内容写入当前段内偏移0x2000 (EA,有效地址)为基址的内存单元,操作数为4字节。处理器访问数据段时,按一下规则检测:
0<= (EA+操作数大小-1) <= 段界限
为已有的段添加新的段描述符,则通过该段描述符将具有新段描述符属性。代码段描述符不能修改段中的内容,但不意味着通过其他描述符做不到。
boot.s
org 0x7c00
[bits 16]
xor ax,ax
mov ss,ax
mov sp,0x7c00
;计算GDT所在的逻辑段地址
mov dx,[gdt_base+2]
mov ax,[gdt_base]
mov bx,16
div bx
mov ds,ax ;DS指向该段以进行操作
mov bx,dx ;段内起始偏移地址
;0索引位置的描述符必须是空描述符,这是处理器的要求
mov dword [bx],0x00
mov dword [bx+4],0x00
;1号索引位置描述符 --> 32位代码
mov dword [bx+0x08],0x7c0001ff
mov dword [bx+0x0c],0x00409800
;2号索引位置描述符 --> 堆栈
mov dword [bx+0x10],0x7c00fffe
mov dword [bx+0x10+4],0x00cf9600
;3号索引位置描述符 --> 数据段描述符,对应0~4GB的线性地址空间
;基地址为0,段界限为0xfffff
;粒度为4KB,存储器段描述符
mov dword [bx+0x18],0x0000ffff
mov dword [bx+0x18+4],0x00cf9200
;描述符寄存器GDTR界限
mov word [cs:gdt_size],31
lgdt [cs:gdt_size]
in al,0x92 ;南桥芯片内的端口
or al,0x02
out 0x92,al
cli ;保护模式下中断机制还未建立,故关闭中断
mov eax,cr0
or eax,0x01
mov cr0,eax ;设置PE位
;进入保护模式
jmp dword 8:code32-0x7c00
[bits 32]
code32:
;加载堆栈选择子
mov ax,0x10
mov ss,ax
xor esp,esp
;加载数据段选择子
mov ax,0x18
mov ds,ax
;使用冒泡排序算法排序字符串
mov ecx,@string_end-@string-1
@1:
push ecx
xor ebx,ebx
@2: mov ax,[@string+ebx]
cmp ah,al
jge @3
xchg ah,al
mov [@string+ebx],ax
@3:
inc ebx
loop @2
pop ecx
loop @1
;输出字符串
mov edi,0xb8000
mov ecx,@string_end-@string
xor ebx,ebx
@4: mov al,[@string+ebx]
mov [edi+ebx*2],al
inc ebx
loop @4
call io_hlt
jmp $
io_hlt:
hlt
ret
@string:
db "hjklasdf"
@string_end:
code32_end:
gdt_size:
dw 0
gdt_base:
dw 0x7e00
times 510 - ($-$$) db 0
db 0x55,0xaa