2. 进入保护模式

进入保护模式

进入保护模式2个例子

第1个例子

;进入保护模式 ,建立段描述符

;在保护模式每一个需要使用的段,都需要建立一个段描述符, 一个段描述符8个字节
;存放段描述符的地方称为全局描述符表GDT
;GDTR 全局描述符寄存器 , 用于指明 GDT 在哪个地址.
    ;此寄存器共48位, 高32位:GDT起始地址, 低16位:GDT的边界,边界的意思是总字节数-1
    ;边界的大小:2^16=64k, 每个段描述符8个字节, 意味着最多可以存放8192个段描述符
;lgdt 用于加载GDT, 让CPU知道此处是GDT的起始位置
;lgdt 后需要一个48位的内存单元, 16位GDT界限,高32位GDT起始地址


;段描述符(8字节 64位):
;高32位:
;31 ~ 24        23  22  21  20   19 ~ 16         15  14~13  12  11~8   7 ~ 0
;段地址(31~24)   G  D/B  L  AVL  段界限(19~16)    P    DPL   S   TYPE   段地址(23~16)
;低32位
; 31 ~ 16           15 ~ 0
;段地址(15-0)        段界限(15 ~ 0)

;G : 段界限粒度.  0 以1字节为单位 , 1 以4K为单位
;S : 描述符类型.  1 代码段/数据段/栈段 ,  0 : 系统段
;DPL: 特权 (0,1,2,3) 
;P: 此描述符对应的段是否存在于内存中, 有时候对应的段可能会被交换到硬盘上
;D/B: 默认操作数大小,对应栈段和代码段, 0:表示使用16位, 1:使用32位操作数
     ;对于代码段如果此位是0, 则使用ip, 对于栈,使用sp
     ;如果是1 , 则使用eip, esp
;L: 64位使用,当前默认0
;TYPE:类别.  给代码段/数据段设置额外属性(读写执行权限)
;AVL:cpu本身不使用

;与实模式不同,描述符的段地址是线性地址,而非逻辑段地址
;段地址: 段的起始地址,实际物理地址(线性地址) 
;段界限: 是一个数量单位, 其单位是根据G位来决定,如果G=0,则乘1字节. G=1,则乘4K
       ;实际的段界限值: (段界限+1)*( 1 或者 4k ) -1;
       ;1.段界限从0开始计算, 所以+1 得出数量
       ;2.然后乘以单位 . 根据G位 *1 或 * 4k
       ;3.由于地址是以0开始, 因此最后-1 得出实际最大偏移值
       ;总体来说:  N个以 (1字节为单位 / 4K为单位) 空间的最大值, 一旦超过即是非法访问
        

;在保护模式中不再把段地址直接赋值给段寄存器 比如 mov ax, 0x0000 mov ds,ax 这样是不行的
;而是通过段选择子来赋值.
;段选择子是 2个字节长 : 位 15 ~ 3   2        1~0
                        ;  索引    ti       RPL ;  ti 用于指定是GDT还是LDT , RPL 请求特权
;就目前来说, 只关注索引, 索引: 段描述符在GDT中的位置(索引号)
;由于一个段描述符占8字节, 因此 -> 索引号* 8 = 此段描述符在GDT中的位置(偏移地址)
;具体来说 : 先获取GDTR中高32位的起始地址 + 索引号*8 获取段描述符
;比如目前想把 (伪指令 实模式) mov ds,0xb800 
    ;下面的代码中 0xb8000 是第三个段描述符 , 索引为2, 对应 0x0010
    ;mov ax,0x0010
    ;mov ds,ax 就可以了

;[bits 16] (默认值)  以16位来编译机器指令   [bits 32] 32位编译机器指令

MBR_START_ADDR EQU 0x7C00

section mbr align=16 vstart=0
    

    ;GDT_BASE 根据实际物理地址计算逻辑段地址和偏移
    mov dx,[cs:GDT_BASE + MBR_START_ADDR + 2]
    mov ax,[cs:GDT_BASE + MBR_START_ADDR]
    mov cx,16
    div cx
    mov ds,ax       ;gdt_base 段地址
    mov bx,dx       ;gdt_base 偏移地址

    ;GDT中第一个描述符必须是空描述符
    ;!描述符8个字节
    mov dword [ds:bx],0
    mov dword [ds:bx + 0x04], 0

    ;创建一个代码段描述符
    ;此段从0x00007c00开始, 总共512字节, 也就是当前的引导程序
    ;低32位存放段地址,段界限 . 段地址(物理地址低16位):0x7c00 , 段界限: (0x01ff + 1) * 1 -1 = 最大偏移511
    mov dword [ds:bx + 0x08], 0x7c0001ff
    ;S:1 是代码/数据段
    ;D/B:1 使用32位操作数
    ;P:1 在内存中
    ;DPL:0 
    ;G:0 以字节为单位.
    ;TYPE: 1000 . 可执行,即为代码段
    mov dword [ds:bx + 0x0c],0x00409800


    ;创建数据段, 以便在显存里读写
    ;段地址:0x000b8000, 段界限:0x0ffff , G=0, 因此实际段界限: (0x0ffff+1)*1-1 = 最大偏移64k
    mov dword [ds:bx + 0x10],0x8000ffff
    ;G:0 以字节为单位
    ;D/B:1 32位操作数
    ;S:1 数据段
    ;DPL:0 
    ;TYPE:0010  可读写,向上扩展
    mov dword [ds:bx+0x14],0x0040920b

    ;创建栈段,向下扩展的段
    ;段地址:0x00000000, 段界限:0x07a00, G:0, 实际段界限: (0x07a00+1)*1-1+1= 最小偏移:0x7a01;
    ;栈的段界限指的是 : 最小偏移
    ;栈是从高地址往低地址, 因此栈顶不设限, esp=0xffff_ffff 可以为最大值
    ;   跟前2个段的段界限不同, 栈的界限最后需要+1. 原因是栈顶esp指向此栈最高地址的后一个地址上
    ;   假设目前: mov sp,0x7c00, push ax. 实际的数据存放在0x7BFE,0x7BFF 这2个地址上
    ;   因此由于初始化 esp 栈顶指向后一个字节上, 所以其界限最后需要 +1 作为最小偏移
    mov dword [bx+0x18],0x00007a00
    ;S:1
    ;D/B:1 使用32位操作数, 即使用 esp
    ;DPL:0
    ;TYPE:0110 可读写,向下扩展
    mov dword [bx+0x1c],0x00409600

    ;设置GDTR , 让CPU知道全局描述符在哪个位置
    ;设置GDT段界限值 , 界限值是所有段的最后一个字节的偏移 => 目前有4个段描述符,长度:8*4=32字节,最后一个字节:31
    mov word [cs:GDT_SIZE + MBR_START_ADDR], 31
    ;lgdt 读取6个字节. 低16位界限, 高32位GDT起始地址, 目前相当于 0x001f, 0x00007e00
    lgdt [cs:GDT_SIZE + MBR_START_ADDR]

    ;8086只有20根地址线
    ;打开第21根地址线(A20)
    ;端口0x92的位1置1即可打开
    ;0x92端口的位0可以用于重启计算机
    in al,0x92
    or al,0000_0010b    ;位1=1
    out 0x92,al         ;开启A20

    ;开始进入保护模式开启动作
    ;保护模式中bios中断不能再用
    cli

    ;cr0 (32位控制寄存器) 的最后一位PE位 用于开启保护模式
    ;下面使用了eax, 16位下也可以使用eax
    mov eax, cr0
    or eax,1        ;把PE位置1
    mov cr0,eax     ;开启保护模式
    ;由于当前还是16位模式中,  因此当前的状态是16位保护模式

    ; 当前还是在16位保护模式下, 需要跳转到32位执行环境中( 也就是对应的段描述符中的D/B位=1)
    ; 一旦开了保护模式, 所有的段寄存器 必须使用 段选择子
    ; dword 指明在 16位 运行环境中使用32位操作数 , 会在机器码前加前缀0x66->指明使用32位操作数
    ; 由于当前已是保护模式, 0x0008 是段选择子, 索引 0001_0000 对应第2个段描述符,其段地址(线性地址)0x7c00 
    ; cs = 根据 GDTR 提供的起始地址 + 索引(1) * 8 获取段描述符
    ; eip = into_protect_mode

    jmp dword 0x0008:into_protect_mode      ;dword 来修饰标号(使用4个字节), 否则由于当前是16位环境此标号是2个字节



;* 一个段使用32位还是16位操作数, 具体是由段描述符中的D/B位指定的

;由于段选择子:0x0008 对应的段描述符中D/B=1, 指明此段使用32位操作数
;bits 32 以32位环境编译的机器指令
;以下代码在32位环境中运行,  16位和32位机器码有时候或许一样,但解释不同

;注意 bits 32 是编译环境, 运行环境由 段描述符中的D/B位指定
;如果此段描述符的D/B位如果是0 , 那么将使用16位运行环境 解释 32位机器指令
[bits 32]
    into_protect_mode:
    ;对应的索引号为2, 00000000_0001_0000 
    ;对应上面GDT中第3个段描述符, 此描述符的段地址:0xb8000 指向显存
    mov ax,0x0010   ;指向显存 , index = 2 , ti = 0, rpl=0
    mov ds,ax       ;根据gdtr提供的段起始地址 + (索引) 2 * 8 找到对应描述符并加载到高速缓冲区中

    xor ebx,ebx
    mov byte [ds:ebx], 'f'
    mov byte [ds:ebx + 2], 'u'
    mov byte [ds:ebx + 4], 'c'
    mov byte [ds:ebx + 6], 'k'


    ;初始化栈
    mov ax, 0x18 ; 11_000B , 索引3 对应上面的栈段描述符
    mov ss, ax      ;段地址:0x0, 段界限:0x7a01
    mov esp, 0x7c00 ;栈顶0x7c00

    ;当前代码段的D位是1 , 因此默认是32位操作数
    ;这里使用 byte 只是编译使用, 实际push时还是32位
    ;栈段中的D/B位也是1, 因此将使用esp-4
    push byte 'e'
    push byte 'm'
    pop eax
    mov byte [ds:ebx + 8],al
    pop eax
    mov byte [ds:ebx + 10],al


    

    hlt


;这2个字段给GDTR(全局描述符表寄存器[48位, 高32位是其起始地址, 低16位 边界]使用)
GDT_SIZE dw 0        ;GDT边界
GDT_BASE dd 0x7e00   ;GDT实际物理地址, mbr起始0x7c00 + 512字节 = 0x7e00

times 510-($-$$) db 0
db 0x55,0xaa

第2个例子

;进入保护模式2
;任何一个段描述符都规定了其界限, 界限本身是一个数量单位, 需根据G位(粒度)来确定其范围
;段界限的计算方式: ( 段界限 + 1 ) * 粒度(1 或者 4k) -1  => 实际段界限
;最后还要加上段基址 : 段基址 + 实际段界限 => 段最大偏移
;代码段和数据段的方向向上扩展. 因此 段基址 + eip <= 段最大偏移 

;栈段往下扩展,栈顶不设限最大可以是0xffffffff
;因此栈的最高地址可以是 : 栈基址 + 0xffffffff
;栈的段界限用于限制最小偏移, 实际段界限: (段界限+1)*粒度(1或4k)-1+1 => 实际段界限
;最终:段基址+实际段界限=> 段最小偏移
;合法范围: 段最小偏移 <= (段基址 + esp - 2或4字节) <= 0xffffffff






MBR_START_ADDR EQU 0x7c00

section mbr vstart=0

    ;在实模式中, 32位CPU上也可以直接使用32位寄存器
    ;下面的代码基本跟前面的没区别, 除了使用32位寄存器
    mov eax,[cs:GDT_BASE + MBR_START_ADDR]
    mov ebx, 16
    xor edx,edx
    div ebx

    mov ds, eax     ;段地址
    mov ebx,edx     ;偏移地址

    ;创建0号描述符
    mov dword [ds:ebx],0
    mov dword [ds:ebx + 0x04],0

    ;1号描述符
    ;数据段
    ;段地址: 0x0, 段界限(数量):0xfffff, g=1(以4K为单位),type:0010 数据段
    ;实际段界限:(0xfffff+1)*4k-1 = 4G. 此数据段可以覆盖整个4G空间
    mov dword [ds:ebx + 0x08],0x0000ffff
    mov dword [ds:ebx + 0x0c],0x00cf9200

    ;2号描述符
    ;建立代码段
    ;段地址:0x7c00, 界限:0x01ff, g=0(以字节为单位)
    ;实际界限:(0x1ff + 1)*1-1 = 0x01ff 共512字节
    ;段属性D=1, 使用32位操作数
    ;该段指向当前引导程序, 即包含16位的代码也包含下面的32位代码
    mov dword [ds:ebx + 0x10],0x7c0001ff
    mov dword [ds:ebx + 0x14],0x00409800

    ;3号描述符
    ;数据段,此数据段对应上面的代码段描述符只修改TYPE
    ;段地址:0x7c00, 段界限:0x01ff,g=0,D=1

    ;这个段描述符的作用是可以动态的修改代码段中的代码
    ;由于无法通过代码段描述符写入数据, 因此可以创建一个对应的数据段描述符,例如调试器通过添加int3(0xCC)来断点
    mov dword [ds:ebx + 0x18],0x7c0001ff
    mov dword [ds:ebx + 0x1c],0x00409200    ;属性这里只修改了TYPE

    ;4号描述符
    ;栈段描述符
    ;栈段基地址: 0x7c00, 段界限:0xffffe, G=1,TYPE=0110
    ;实际段界限: (0xffffe+1)*4k-1 + 1 = 0xFFFFF000
    ;最终最低地址 : 段基址 + 实际段界限 = 0x7c00 + 0xFFFFF000 = 0x100006C00 最高位1被舍去 => 0x6c00
    ;最高地址: 由于esp最大可以为0XFFFF_FFFF,因此 段基址 + 0xffffffff = 0x100007BFF , 最高位舍去=>0x7bff
    ;因此栈段空间: 0x7bff - 0x6c00 = 4k
        ;每当push 2字节/4字节时
        ;都将检查:1. esp = push - (2或4)  
                 ;2. 段基址 + esp >= 段基址 + 实际段界限
                 ;3. 如果越界则有异常
        
    mov dword [ds:ebx + 0x20],0x7c00fffe
    mov dword [ds:ebx + 0x24],0x00cf9600

    ;设置GDTR
    mov word [cs:GDT_SIZE + MBR_START_ADDR],39  ;5*8-1
    lgdt [cs:GDT_SIZE + MBR_START_ADDR]

    ;打开A20(21号地址线)
    in al,0x92
    or al,0x02 ; 0010
    out 0x92,al

    ;屏蔽中断
    cli

    ;设置CR0的 PE位(最后1位)
    mov eax, cr0
    or eax,1
    mov cr0,eax
    ;-----当前正处于16位保护模式中----------------

    ;跳转进32位保护模式
    ;由于当前还处于16位保护模式,想进入32位,需要加载 段描述符并且其描述符的D位=1
    ;既然要加载段描述符,意味着需要修改段寄存器的值,在保护模式中需要使用段选择子(0x0010)
    ;dword 使用4字节修饰标号into_protect_mode 
    jmp dword 0x0010:into_protect_mode



;bits 32 以32位来编译以下代码
[bits 32]

    into_protect_mode:

    ;栈段
    ;每当push 2/4 字节 会检查时候越界: 段基址 + esp >= 段基址 + 实际段界限
    mov eax,0x0020  ;栈段选择子
    mov ss,eax      ;设置栈段
    xor esp,esp     ;esp=0, push 4字节后, esp = 0xffff_fffc, 实际地址: 段基址 + esp = 0x7c00 + 0xfffffffc

    mov eax,0x08        ;1号描述符,指向4G
    mov es,eax          

    ;通过1号描述符 在显存里输出字符
    mov byte [es:0xb8000],'f'
    mov byte [es:0xb8002],'u'
    mov byte [es:0xb8004],'c'
    mov byte [es:0xb8006],'k'

    ;通过1号描述符, 修改当前代码段里的数据
    ;把前4个字节和后4个字节交换
    mov eax,[es:MSG+ MBR_START_ADDR]
    mov ebx,[es:MSG+ MBR_START_ADDR + 4]
    xchg eax, ebx                               ;xchg 交换
    mov dword [es:MSG+MBR_START_ADDR] , eax
    mov dword [es:MSG + MBR_START_ADDR + 4], ebx

    ;输出代码段中的字符串
    mov edx,0xb8008 ;从这开始输出

    ;由于代码段的type:1000 , 不可读 . 但数据又在代码段中, 此时可用3号描述符的数据段
    ;3号描述符的段基址和段界限与 代码段一致
    mov eax,0x0018  ;3号描述符的段选择子
    mov ds,eax

    ;参数入栈,默认4字节
    push dword [ds:MSG_SZIE]    ;长度
    push es     ;目标段选择子
    push edx    ;目标偏移
    push ds     ;原段选择子
    push MSG    ;原偏移
    ;调用显示
    call .show_msg
    ;add esp, 20  这个不需要了, 在.show_msg中的ret 20 意味着: esp+20
    cmp esp,0
    jne .done       ;检查栈顶是否还原

    ;使用3号描述继续修改MSG处的数据
    mov eax,[ds:MSG] 
    xchg ah,al
    mov dword [ds:MSG], eax

    mov eax,dword [ds:MSG_SZIE] ;8
    add eax,eax                 ;16
    add edx,eax  ;目标偏移往后增加

    ;调用显示
    push dword [ds:MSG_SZIE]
    push es     
    push edx    
    push ds     
    push MSG    
    call .show_msg


    .done:
        hlt



    


;参数: 原偏移,原段选择子 ,目标偏移,目标段选择子,字符串长度
;这里由于0xb8000(显存并没有建立段描述符),因此0xb8000作为固定目标偏移来使用, 目标段选择子也固定是1号描述符
;ret 20 ; 20 指的是 参数字节数 * 个数 => 4 * 5 = 20
.show_msg:
    push ebp
    mov ebp , esp
    push es
    push ds
    push esi
    push edi
    push eax
    push ecx

    mov edi,[ebp + 16]  ;目标偏移
    mov eax,[ebp + 20]  ;目标段选择子
    mov es,eax

    mov eax,[ebp + 12]   ;原段选择子
    mov ds,eax
    mov esi,[ebp + 8]   ;原偏移

    mov ecx,[ebp + 24]  ;字符串长度

    .show_loop:
        mov al,[ds:esi]
        mov byte [es:edi],al
        mov byte [es:edi+1], 0x07
        add edi,2
        inc esi
        loop .show_loop
    


    pop ecx
    pop eax
    pop edi
    pop esi
    pop ds
    pop es

    mov esp,ebp
    pop ebp
    ret 20



;字符串长度
MSG_SZIE dd GDT_SIZE - MSG   
;将被修改的数据
MSG db 'fuckme!!'

GDT_SIZE dw 0
GDT_BASE dd 0x7e00      ;实际物理地址 

times 510-($-$$) db 0 
db 0x55,0xaa
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值