标签:TE 数据 安全管理 run SM 硬件 处理器 $$ mem
保护模式强调的是保护,是在Intel 80286中首次出现.
实模式的特点:
为了克服这种不安全的内存管理.处理器厂商,开发出保护模式.物理内存不能直接被程序访问,程序内部的地址需要被转化位物理地址再去访问.这个过程对程序透明.地址转换需要由处理器和操作系统共同完成.处理器提供地址转换部件,操作系统提供转化过程中需要的页表.
32位cpu具有保护模式和实模式两种运行模式,可以兼容实模式下的程序,兼容实模式值得是能够正确处理实模下的程序.起寄存器还是32位.
32位cpu中:保护模式下通用寄存器都扩展位32位,其中低16位,为了兼容,abcd四个寄存器仍然可以再拆分使用,例如ax
拆分为al
和ah
.但是高16位,不能拆分.
段寄存器仍然是16位.
保护模式下,偏移地址和实模式下一样.为了增加对内存的安全管理.对段基址进行了改造,段基址不在对应物理地址(实模式下的段基址其实是对应的物理地址的一段的首地址),而是作为一个选择子(其实就是一个下标),在全局描述符表GDT中索引出一个表项,该表项中由选择子对应的段基址,还有该段的长度,以及读写属性.
全局描述符表和段选择子,存在两个问题:
因此,80286为了提高效率,对段寄存器使用了缓存技术,将短信息用一个寄存器缓存,段描述符缓冲寄存器,对程序员不可见,也不能够修改.cpu每次将全局段描述符整理以后,存入段描述符缓冲寄存器中,以后每次访问相同的段,就直接读取对应的段描述符缓冲寄存器.在实模式下,也用到了该缓存寄存器.只要向ds
寄存器中赋值,不管是否与之前一样还是不一样,段描述符缓冲寄存器都会被刷新.
80268 cpu是一个16位cpu,通用寄存器是16位的,但是地址总线却是24位.它能够访问全部的16\(MB\)的原因是:段描述符中,段基址是用24位.因此可以访问全部的\(16MB\).
实模式中,内存寻址中的基址寻址,变址寻址,基址变址寻址中基址寄存器只能使用bx
,bp
,变址寄存器只能使用si
,di
.
在保护模式下,基址寄存器,变址寄存器都可以是所有的32位通用寄存器(变址寄存器不能使esp)
cpu运行模式由实模式和保护模式两种.并且在实模式的时候,可以使用32位的寄存器的全部32位.
这两个模式下,相同的指令对应的机器码不同,因为保护模式是32位的,其地址是32位的.
当从实模式切换到保护模式的时候,需要显示的告诉编译器:[bits 16]
或是[bits 32]
表示,从该标签开始到下一个标签,或是结尾,编译为对应位的机器码
从保护模式进入实模式需要三个步骤:
cr0
的pe
位设为1跳
所有用到的内存,都需要在全局段描述符表中注册,当使用到了局部段描述表的时候,其所占的内存,也需要在全局段描述符表中,占一个表项.全局描述符表中的表项(可以理解为是一个数组,其中的每个元素)是段描述符,是一个8字节的数据结构.
cpu有一个专门的寄存器GDTR,来保存全局描述符表在内存中的地址,以及全局段描述符表的界限.界限的意思是:全局描述符表的地址加上界限是全局描述符表的最后一个字节.
专用的指令位:lgdt 48位数据
加载局部段描述符表的指令位:lldt 48位数据
由于GDT长度位16位,因此最大\(65536\)字节,每个描述符8字节,因此最多\(8192\)个段描述符.
32位cpu,保护模式下地址总线宽度是32位,段基址需要32位来表示.(段基址可以从任意字节开始,因此需要完整的32位),
段描述符中有一个段界限(全局段描述符表地址加上该值,是全局段描述符所在的区域.),数值上是一个20位的无符号数,其单位可以是字节,也可以是\(4KB\),当是\(4KB\)的时候,那么一个段界限就是\(4GB\),就可以访问完整的32位的内存.
其格式为:
0~15:段界限15~0:拆分的原因:历史遗留原因,80286是第一个intel有保护模式的cpu,是一个24位的.
16~31:基址15~0
32~39:段基址23~16
40~43:TYPE,段描述符的类型:
S为0时,系统段:
S为1时,数据段:第一位是否是代码段X.
一致性代码段的意思:后面解释
最后一位为A,已访问,当cpu访问过后,该位被设置为1
44:S,描述符是系统段0,还是数据段1.凡是硬件运行需要用到的都称之为系统,凡是软件(包括操作系统)运行需要的都是数据.因此,绝大多数都是数据段.属于系统段的有:门结构,他是硬件运行的东西,TSS段,局部段描述符表(需要现在全局段描述符中注册),
45~46:DPL,特权级,00特权级最大,11最小
47:P,是否存在与内存中,P字段由cpu检查,如果为0,cpu抛出异常,转到相应的异常处理程序,该异常处理程序是程序员来写的,异常处理程序要将P设位1.在当内存不足的时候,全局描述符表所在内存被交换到硬盘的时候,P被设为0.
48~51:段界限23~16
52:AVL,
53:L,是否是64位代码段,0为32位代码段
54:D/B,有效地址以及操作数的位数.当为代码段的时候:D为1表示是32位.当位数据段的时候,B为0表示32位
55:G:是段界限的单位:0的时候表示单位是字节,1的时候表示\(4KB\),因此当1的时候,一个段就可以完整的方位\(4GB\),
56~63:段基址31~24
注意:段描述符的特权级使用DPL来表示.
结构:
选择子索引部分是13位,因此也是8192个段.
按照cpu厂商的建议,一个任务对应一个LDT.
这三步都做完才会进入保护模式,而且,步骤任意
首先是构造GDT表项,这里需要构造4个:
0xb8000
code段和,data段使用平坦模式,因此段基址(GDT表项中的)是0,而段界限设为最大,单位为\(4KB\)
首先按照16位为一组,定义出每个位.最后采用相加和移位的形式构造出完整的64位
; ------------------------------------构造GDT需要的数据 ----------------------------------
; 16位为一组,最后通过位操作拼接.
GDT_48_G_4K equ 00000000_10000000b
GDT_48_D_32 equ 00000000_01000000b
GDT_48_L_32 equ 00000000_00000000b
GDT_48_AVL equ 00000000_00000000b
GDT_48_LEN_H equ 00000000_00001111b
GDT_32_P equ 10000000_00000000b
GDT_32_DPL_0 equ 00000000_00000000b
GDT_32_DPL_3 equ 01100000_00000000b
GDT_32_S_SYS equ 00000000_00000000b
GDT_32_S_USER equ 00010000_00000000b
GDT_32_TYPE_CODE equ 00001000_00000000b
GDT_32_TYPE_DATA equ 00000010_00000000b
; -----------------------------------构造GDT需要的数据 ----------------------------------
然后拼接高32位:
注意,汇编里面位操作<<
优先级是小于+
的,所以尽可能的多加括号吧.
; -----------------------------------三个段描述符标表项 ----------------------------------
GDT_CODE_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+GDT_48_LEN_H)<<16)+ (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_CODE+00000000b)
GDT_DATA_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+GDT_48_LEN_H)<<16)+ (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_DATA+00000000b)
GDT_VGA_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+00000000_00000000b)<<16)+ (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_DATA+00001011b)
这样,就构造出了,高32位.指向显卡的段.
然后拼接上低32位:
GDT_BASE equ 00000000b<<(24+32)
GDT_CODE equ (GDT_CODE_H32<<32)+0x0000FFFF
GDT_DATA equ (GDT_DATA_H32<<32)+0x0000FFFF
GDT_VGA equ (GDT_VGA_H32<<32 )+0x80000007
第0个全为0,是系统要求的,第1个和第2个在平坦模式下,因此段基址为0,段长度为1.
显卡段:显卡内存映射在:1011_1000000000000000
第16位,应该在GDT表项的低32位里.所以会有这样的形式.
显卡段的段长度:(0xbffff / 0xb8000) /4K = 0x07
,所以其低32位为:10000000000000000000000000000111
,也就是0x80000007
对应的选择子为:
; -----------------------------------构造选择子需要的数据 ----------------------------------
SELECT_RPL_0 equ 00b
SELECT_RPL_3 equ 11b
SELECT_TI_GDT equ 000b
SELECT_TI_LDT equ 100b
; -----------------------------------构造选择子需要的数据 ----------------------------------
实模式下寄存器都是16位的,地址总线是20位的,当超出\(1MB\)时,自动回绕会0地址,继续从0地址开始映射.超出的部分内存成为高端内存区HMA
in al,0x92
or al,00000010b
out 0x92,al
控制寄存器crx
系列,是cpu的窗口,用来展示cpu的内部状态,也可以控制cpu的运行机制.
cr0
寄存器的第0位PE位,是把哦哦胡模式的开关.打开的方式是.
mov eax,cr0
or eax,0x00000001
mov cr0,eax
目录结构:
└── bochs
├── 02.tar.gz
├── 03.tar.gz
├── 04
?? ├── boot.inc
?? ├── loader.asm
? ? ├── mbr.asm
?? └── start.sh
思路:在之前的基础上:
boot.inc
定义了一些构造gdt表项需要的一些数据,是宏loader.asm
中,构造4个GDT表项(物理上连续),然后构造GDTR寄存器需要的48位数值.并加载到GDTR中; -------------------------------------loader.bin -------------------------------------
; 将要加载在内存的位置,和在虚拟磁盘的扇区位置
LOADER_IN_MEM equ 0x900
LOADER_IN_DISK equ 2
; loader 执行的栈基址
LOADER_STACK_TOP equ LOADER_IN_MEM
; -------------------------------------loader.bin -------------------------------------
; ------------------------------------构造GDT需要的数据 ----------------------------------
; 16位为一组,最后通过位操作拼接.
GDT_48_G_4K equ 00000000_10000000b
GDT_48_D_32 equ 00000000_01000000b
GDT_48_L_32 equ 00000000_00000000b
GDT_48_AVL equ 00000000_00000000b
GDT_48_LEN_H equ 00000000_00001111b
GDT_32_P equ 10000000_00000000b
GDT_32_DPL_0 equ 00000000_00000000b
GDT_32_DPL_3 equ 01100000_00000000b
GDT_32_S_SYS equ 00000000_00000000b
GDT_32_S_USER equ 00010000_00000000b
GDT_32_TYPE_CODE equ 00001000_00000000b
GDT_32_TYPE_DATA equ 00000010_00000000b
; -----------------------------------构造GDT需要的数据 ----------------------------------
; -----------------------------------三个段描述符标表项 ----------------------------------
GDT_CODE_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+GDT_48_LEN_H)<<16)+ (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_CODE+00000000b)
GDT_DATA_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+GDT_48_LEN_H)<<16)+ (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_DATA+00000000b)
GDT_VGA_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+00000000_00000000b)<<16)+ (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_DATA+00001011b)
GDT_BASE equ 00000000b<<(24+32)
GDT_CODE equ (GDT_CODE_H32<<32)+0x0000FFFF
GDT_DATA equ (GDT_DATA_H32<<32)+0x0000FFFF
GDT_VGA equ (GDT_VGA_H32<<32 )+0x80000007
; -----------------------------------三个段描述符标表项 ----------------------------------
; -----------------------------------构造选择子需要的数据 ----------------------------------
SELECT_RPL_0 equ 00b
SELECT_RPL_3 equ 11b
SELECT_TI_GDT equ 000b
SELECT_TI_LDT equ 100b
; -----------------------------------构造选择子需要的数据 ----------------------------------
新加了很多代码.
其中LOADER_STACK_TOP equ LOADER_IN_MEM
这句的含义:程序在执行的时候就需要用到栈,我们将切换到保护模式后的栈顶设置为loader
在内存中的位置,也就是说:loader
被加载到0x900
以上的内存中,而0x900
一下的内存就被用作是程序的栈.因此在loader.asm
中为ss
寄存器赋的值也是data段
改动比较大,因为加的东西多.
%include "boot.inc"
SECTION loader vstart=LOADER_IN_MEM
; 上来就跳转
jmp 0:loader_start
gdt_base: dq GDT_BASE
gdt_code: dq GDT_CODE
gdt_data: dq GDT_DATA
gdt_vga: dq GDT_VGA
gdt_size equ $-gdt_base
; 这里预留出 60 个段描述符的位置
times 60 dq 0
; 界限,也就是全局段描述符表在内存中的地址位:gdt_base + 界限
; 因此界限=长度-1
gdt_ptr dw (gdt_size-1)
dd gdt_base
;构建选择子
select_code equ (0x1<<3)+SELECT_TI_GDT+SELECT_RPL_0
select_data equ (0x2<<3)+SELECT_TI_GDT+SELECT_RPL_0
select_vag equ (0x3<<3)+SELECT_TI_GDT+SELECT_RPL_0
loader_start:
mov ax,0xb800
mov gs,ax
mov byte [gs:1120],'l'
mov byte [gs:1121],00000111b
mov byte [gs:1122],'o'
mov byte [gs:1123],00000111b
mov byte [gs:1124],'a'
mov byte [gs:1125],00000111b
mov byte [gs:1126],'d'
mov byte [gs:1127],00000111b
mov byte [gs:1128],'e'
mov byte [gs:1129],00000111b
mov byte [gs:1130],'r'
mov byte [gs:1131],00000111b
mov byte [gs:1132],'!'
mov byte [gs:1133],00000111b
; 开启A20
in al,0x92
or al,000000010b
out 0x92,al
; 开启保护模式
mov eax,cr0
or eax,0x00000001
mov cr0,eax
; 加载GDT
lgdt [gdt_ptr]
; 流水线的原因,要强制刷新一次流水线
; 这里要用远跳转,因为进入了保护模式了,cs寄存器中存的不在是段基址,而是选择子
jmp select_code:p_mode
; 主动告诉编译器下面的代码按照32位机器码编译
[bits 32]
p_mode:
; 注意这里ss段寄存器,也被赋值为 select_data
mov ax,select_data
mov ds,ax
mov es,ax
mov ss,ax
mov esp,LOADER_STACK_TOP
mov ax,select_vag
mov gs,ax
mov byte [gs:1440],'p'
jmp $
jmp原因,后面讲.
改动较小,首先,因为loader.asm中有64个段描述符,因此\(64\times 8=512\)字节,超出了一个扇区的字节数(512),因此,在mbr.asm中,我们要将读取的磁盘数据,扩充为4个扇区,也就是\(4\times 512\)字节
%include "boot.inc"
; mbr.asm 主引导程序
SECTION MBR vstart=0x7c00
; ---------- 初始化各个寄存器 ----------
; 使用cs寄存器的值去初始化其他的段寄存器.
mov ax,cs
mov ds,ax
mov ss,ax
mov fs,ax
; 设置栈指针
mov sp,0x7c00
; ---------- 使用中断清除屏幕----------
; 清除屏幕
; INT 0x10 功能号0x06,AH中存功能号,AL中存上卷行数,BH中存上卷行属性
; ah:子功能号,al:0表示0x06子功能号,向上卷屏幕的行数,0表示全部
; bh:空白区域缺省属性
; cx,dx:上卷时候认为屏幕的大小,也就是只上卷cx,dx标识的矩形区域内的字符.
mov ax,0x600
mov bx,0x0700
mov cx,0x0
mov dx,0x184f
int 0x10
; 设置光标位置
; INT 0x10 子功能号0x02,AH中存子功能号
; bh:显示页码,dh:y,dl:x
mov ax,0x02
mov dx,0x0
int 0x10
; ---------- 使用中断在屏幕上打印字符串 ----------
; 显示字符串
; int 0x10 子功能号0x13
; es:bp:字符串起始地址,bh:页码,cx:字符串长度,al输出模式
mov ax,msg_start
mov bp,ax
mov cx,msg_end-msg_start
mov bx,0x0002
mov ax,0x1301
int 0x10
; ---------- 直接操作显卡内存,打印字符 ----------
; 直接读写显卡映射内存
mov ax,0xb800
mov gs,ax
mov byte [gs:320],' '
mov byte [gs:321],00001111b
mov byte [gs:322],'d'
mov byte [gs:323],00001111b
mov byte [gs:324],'i'
mov byte [gs:325],00001111b
mov byte [gs:326],'r'
mov byte [gs:327],00001111b
mov byte [gs:328],'e'
mov byte [gs:329],00001111b
mov byte [gs:330],'c'
mov byte [gs:331],00001111b
mov byte [gs:332],'t'
mov byte [gs:333],00001111b
; ---------- 加载 loader ----------
; 设置调用rd_disk_m_16时候的3个参数.
; eax 是要读取磁盘的LBA地址
; bx 是读取出来的数据,加载到内存的位置
; cx 是要读取的扇区数.
mov eax,LOADER_IN_DISK
mov bx,LOADER_IN_MEM
mov cx,4
mov byte [gs:640],'r'
mov byte [gs:641],00001111b
mov byte [gs:642],'e'
mov byte [gs:643],00001111b
mov byte [gs:644],'a'
mov byte [gs:645],00001111b
mov byte [gs:646],'d'
mov byte [gs:647],00001111b
mov byte [gs:648],' '
mov byte [gs:649],00001111b
mov byte [gs:650],'d'
mov byte [gs:651],00001111b
mov byte [gs:652],'i'
mov byte [gs:653],00001111b
mov byte [gs:654],'s'
mov byte [gs:655],00001111b
mov byte [gs:656],'k'
mov byte [gs:657],00001111b
; 开始读取
call rd_disk_m_16
mov byte [gs:800],'j'
mov byte [gs:801],00001111b
mov byte [gs:802],'m'
mov byte [gs:803],00001111b
mov byte [gs:804],'p'
mov byte [gs:805],00001111b
; 因为读取的是 内核加载器loader ,因此读取完成以后,直接跳转过去执行
jmp LOADER_IN_MEM
; ---------- 从磁盘中能够读取数据的函数 ----------
; 功能:读取硬盘n个扇区
; 参数:
; eax:开始读取的磁盘扇区
; cx:读取的扇区个数
; bx:数据送到内存中的起始位置
rd_disk_m_16:
; 这里要保存eax 的原因在与,下面section count 寄存器需要一个8位的寄存器
; 只有acbd这四个寄存器能够拆分为高低8位来使用,而dx作为寄存器号,被占用了
; 因此需要个abc三个寄存器中一个来用,这里选择了 ax
mov esi,eax
mov di,cx
; 0x1f2 寄存器:sector count ,读写的时候都表示要读写的扇区数目
; 该寄存器是8位的,因此送入的数据位 cl
mov dx,0x1f2
mov al,cl
out dx,al
; 恢复eax
mov eax,esi
; eax中存放的是要读取的扇区开始标号,是一个32位的值,因此 al 是低8位
; 0x1f3 存放0~7位的LBA地址,该寄存器是一个8位的
mov dx,0x1f3
out dx,al
; 下面的 0x1f4 和5 分别是8~15,16~23位LBA地址,这俩寄存器都是8位的
; 因此是用shr,将eax右移8位,然后每次都用al取eax中的低8位
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
shr eax,cl
mov dx,0x1f5
out dx,al
; 0x1f6 寄存器低4位存放 24~27位LBA地址,
; 0x1f6 寄存器是一个杂项,其第六位,1标识LBA地址模式,0标识CHS模式
; 上面使用的是LBA地址,因此第六位位1
shr eax,cl
and al,0x0f
or al,0xe0
mov dx,0x1f6
out dx,al
; 0x1f7 寄存器,读取该寄存器的时候,其中的数据是磁盘的状态
; 写到该寄存器的时候,写入的僵尸要执行的命令,写入以后,直接开始执行命令
; 因此需要在写该寄存器的时候,将所有参数设置号
; 0x20 表示读扇区,0x30写扇区
mov dx,0x1f7
mov al,0x20
out dx,al
.not_ready:
; 读 0x1f7 判断数据是否就绪,没就绪就循环等待.
nop
in al,dx
and al,0x88
cmp al,0x08
jnz .not_ready
; 到这一步表示数据就绪,设置各项数据,开始读取
; 一个扇区512字节,每次读2字节,因此读一个扇区需要256次从寄存器中读取数据
; di 中是最开始的cx也就是要读取的扇区数
; mul dx 是ax=ax * dx ,因此最终ax 中是要读取的次数
mov ax,di
mov dx,256
mul dx
mov cx,ax
; 0x1f0 寄存器是一个16位寄存器,读写的时候,都是数据.
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,2 ;ax 是 16位寄存器,读出的也是2字节,因此读一次 dx+2
loop .go_on_read
ret
msg_start db "2 mbr start!!!!!"
msg_end db 0
times 510-($-$$) db 0
db 0x55,0xaa
改动的只是,一个数而已
同上,因此loader.asm
最后编译出的文件大于512字节了,因此在dd
刻录的时候需要,多刻录几块,也改为4
#! /bin/bash
# 编译mbr.asm
echo "----------nasm mbr.asm----------"
if !(nasm -o mbr.bin mbr.asm);then
echo "nasm error"
exit
fi
# 刻录mbr.bin
echo "----------dd mbr.bin ----------"
if !(dd if=./mbr.bin of=../hd60m.img bs=512 count=1 conv=notrunc);then
echo "dd error"
exit
fi
# 编译 loader.asm
echo "----------nasm loader.asm----------"
if !(nasm -o loader.bin loader.asm -I include/);then
echo "nasm error"
exit
fi
# 刻录loader.bin
echo "----------dd loader.bin ----------"
if !(dd if=./loader.bin of=../hd60m.img bs=512 count=4 seek=2 conv=notrunc);then
echo "dd error"
exit
fi
# 删除临时文件
sleep 1s
rm -rf mbr.bin
rm -rf loader.bin
# 运行bochs
cd ..
bochs
改动也只是一个数而已
./start/sh
然后c
执行,ctrl+c
打断,info gdt
查看全局描述符表
Accessed表示被访问了,由cpu设置的.
保护模式的保护二字主要体现在段描述符的属性字段中.cpu用这些属性来检查指令的合法性,从而起到了保护作用.
当更换段寄存器中的值时,实际上是向段寄存器中加载选择子,为了避免内存非法引用,cpu会进行下面这些检查:
首先cpu必须保证选择子是正确的,判断选择子的索引值一定要小于等于描述符表中的描述符个数.
然后检查选择子的属性:
再然后,会检查段的P位,如果P位不为1,那么会自动专区执行相应的异常处理程序
对于代码段和数据段,cpu没访问一个地址,都要确认改地址不能超过其所在内存段的范围.段可以访问的区域为:段基址~段基址+段界限
段描述符中type中的E位标识段的扩展方向,栈向下扩展是push指令的作用,与E位无关.也就是说,数据段,本身就可以作为栈段.
标签:TE 数据 安全管理 run SM 硬件 处理器 $$ mem
原文地址:https://www.cnblogs.com/perfy576/p/9119000.html