详细的过程说明参考:(1)  https://www.cnblogs.com/Philip-Tell-Truth/p/5211248.html    (2)x86汇编:从实模式到保护模式

这里简化一下说说要点:

1、 生成并加载GDT表

  实模式下任何进程可以无限制读写任何内存,甚至os的内存,毫无安全性可言;需要对用户进程读写内存的地址做严格限制,衍生出了保护模式;保护模式将内存分成不同的段,段基址、limit、各种属性存放在GDT表;用户程序读写段内存时需要先通过段寄存器的selector在GDT找到段描述符,查看是否有权限、偏移地址是否超过limit等。如果一切ok,可以继续读写段内数据;

  cs、ds、ss段的描述符:

注意点:(1)用户程序运行在3环,是没有权限更改GDT的,所以这种方式完全可以限制3环程序对内存的读写;windows要想改GDT,要么连接windbg,要么写驱动;

(2)lgdt [cs:gdt_size],从操作数指向的地址取6字节,高4字节作为gdt的基址,低2字节作为gdt中描述符的个数,如下:

      

      这6字节早在编译时就确定了:

      

     (3)GDT的机制是CPU定的,操作系统负责运维和使用各段的数据

2、打开A20

8086下,地址线有20位,寻址范围从0x00000~0xfffff,超过0xfffff的地址会被cpu重新从0x00000开始,相当于丢掉进位(或把地址对0xfffff取模)。80286以后,地址总线扩展到24位,为了访问0x100000~0x10FFEF之间的内存,而不是象8086/8088那样回绕到0,需要开启A20地址线,方法如下:

   mov dx,0x92                        ;南桥ICH芯片内的端口0x92
in al,dx
or al,0x02
out dx,al ;打开A20

3、开启保护模式

CPU提供的控制寄存器CR0~CR3用于控制CPU的运行模式。CR0第一位(0位)是保护模式允许位(Protection Enable,PE),如果把这个位置为1,那么处理器将会进入保护模式,核心代码如下:

    cli                                ;关闭中断,但后面一直没打开

    mov eax,cr0
or eax,0x01
mov cr0,eax ;设置PE位,处理器进入保护模式

注意:要先调用cli把if置0,关闭中断,避免给CR0赋值时被打断;

4、清空段寄存器中的缓存和旧流水线指令

 (1)段寄存器实际上有96位,保护模式下的汇编指令只能操作位于低16位的selector,剩余80位中部分作为缓存(可用sreg命令查看段寄存器dh、dl的值,这些都是描述符的缓存),存储了段基址。只要段不改变,缓存就不会更新;但保护模式下不能直接用实模式的地址(权限不够、位数不对等),需要清空;

(2)cpu和内存的速度差异很大,为了提高效率,cpu会提前预测并执行某些分支指令(幽灵漏洞就是这么来的,细节可参考B站的一个科普视频:https://www.bilibili.com/video/BV1eW411i7ZM?from=search&seid=2138940898008952062,这就是所谓的乱序执行,进入保护模式后这些指令的逻辑结果可能是有问题的,也要马上清除;

清除各个段寄存器、乱序指令缓存的办法:马上调用jmp指令跳转,让cpu认为当前各种缓存已经失效,核心代码如下:

;保护模式
jmp 0x0008:flush-$$ ;现在是在16位保护模式下,0x0008依然是段的选择子,而flush则是偏移地址
[bits ]
flush: ;头部加了vstart=0x7c00,这里变成了0x7c85,所以上面的jmp中的偏移要减去开头的基址,得到偏移
mov cx,0x0010
mov ds,cx

5、完整代码:

;---------------------保护模式主引导扇区程序---------------------
SECTION protectModel vstart=0x7c00 align=
mov ax,0x00
mov ss,ax
mov sp,0x7c00 mov ax,[cs:gdt_base];ax=0x7e00
mov dx,[cs:gdt_base+0x02];dx=0x0000;
;mov ax,[cs:gdt_base+0x7c00];这段已经被加载到0x7c00,所以需要加上;ax=0x7e00
;mov dx,[cs:gdt_base+0x7c00+0x02];dx=0x0000;
mov bx,0x10
div bx mov ds,ax ;得到base基地址,ds=0x7e0
mov bx,dx ;得到偏移地址,这里是0x0000 ;---------------------安装描述符---------------------
;描述符0
mov dword [ebx+0x00],0x00 ;第一个描述符必须是0;这里默认是ds段,也就是gdt_base的基址
mov dword [ebx+0x04],0x00 ;ebx是gdt表的偏移,ds是gdt的基址 ;描述符1
mov dword [ebx+0x08],0x7c0001FF
mov dword [ebx+0x0c],0x00409800 ;基地址0x00007c00,段界限0x001FF,粒度是字节,
;长度是512字节,在内存中的32位段,特权级为0,只能执行的代码段
;描述符2
mov dword [ebx+0x10],0x8000FFFF
mov dword [ebx+0x14],0x0040920B ;基地址0x000B8000,段界限0x0FFFF,粒度是字节,
;长度是64KB,在内存中的32位段,特权级为0,可以读写的向上拓展的数据段
;描述符3
mov dword [ebx+0x18],0x00007A00
mov dword [ebx+0x1c],0x00409600 ;基地址0x00000000,段界限0x07A00,粒度是字节,
;在内存中的32位段,特权级为0,可以读写的向下拓展的栈段 mov word [cs:gdt_size],;写入GDT段界限,4个描述符是32个字节,所以界限就是31
lgdt [cs:gdt_size] ;load gdt
;mov word [cs:gdt_size+0x7c00],31;写入GDT段界限,4个描述符是32个字节,所以界限就是31
;lgdt [cs:gdt_size+0x7c00] ;load gdt mov dx,0x92 ;南桥ICH芯片内的端口0x92
in al,dx
or al,0x02
out dx,al ;打开A20 cli ;关闭中断,但后面一直没打开 mov eax,cr0
or eax,0x01
mov cr0,eax ;设置PE位,处理器进入保护模式 ;保护模式
jmp 0x0008:flush-$$ ;现在是在16位保护模式下,0x0008依然是段的选择子,而flush则是偏移地址
[bits ]
flush: ;头部加了vstart=0x7c00,这里变成了0x7c85,所以上面的jmp中的偏移要减去开头的基址,得到偏移
mov cx,0x0010
mov ds,cx ;以下在屏幕上显示"Protect mode OK."
mov byte [0x00],'P'
mov byte [0x02],'r'
mov byte [0x04],'o'
mov byte [0x06],'t'
mov byte [0x08],'e'
mov byte [0x0a],'c'
mov byte [0x0c],'t'
mov byte [0x0e],' '
mov byte [0x10],'m'
mov byte [0x12],'o'
mov byte [0x14],'d'
mov byte [0x16],'e'
mov byte [0x18],' '
mov byte [0x1a],'O'
mov byte [0x1c],'K' ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作
mov cx,00000000000_11_000B ;加载堆栈段选择子
mov ss,cx
mov esp,0x7c00 mov ebp,esp ;保存堆栈指针
push byte '.' ;压入立即数(字节) sub ebp,
cmp ebp,esp ;判断压入立即数时,ESP是否减4
jnz ghalt
pop eax
mov [0x1e],al ;显示句点 ghalt:
hlt ;已经禁止中断,将不会被唤醒 ;------------------------------------------------------------------------------- gdt_size dw
gdt_base dd 0x00007e00 ;GDT的物理地址,主引导扇区是512个字节,这个地址刚好在主引导扇区之后 times -($-$$) db
db 0x55,0xaa

说明:

(1)整段代码被加载到0x7c00处,所以在开头额外加vstart=0x7c00,让nasm从这个地址开始编译;

    紧接着这4行代码也要更改:去掉0x7c00,因为gdt_base和gdt_size已经相对0x7c00计算偏移

  mov ax,[cs:gdt_base+0x7c00]
mov dx,[cs:gdt_base+0x7c00+0x02]   mov word [cs:gdt_size+0x7c00],31;写入GDT段界限,4个描述符是32个字节,所以界限就是31
lgdt [cs:gdt_size+0x7c00] (2)flush也是相对0x7c00开始计算偏移的,具体偏移是0x7c85,远超代码段的limit,导致出错异常,又跳回biso启动处执行
解决办法也简单,直接改成jmp 0x0008:flush-$$ 即可,让后面的偏移值相对于段开始的地方,这次对了,如下:

 (3)81行代码:push byte '.'  ,但nasm还是编译成push 0x0000002e, 估计是为了内存对齐

(4)效果:在频幕上打印一行字

 

x86架构:从实模式进入保护模式的更多相关文章

  1. ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构

    ★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...

  2. IA32系统级架构总览(一) 实模式和保护模式

    应用程序的编写大部分的时候是不必关心系统级架构的,最多学习一下平台所给的API即可,也就是我们通常说的黑箱子.但是在学习操作系统的时候,系统级架构是要关心的. 系统级架构很难学习,其中一个很大的原因是 ...

  3. 存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20

    存储器的保护(三) 修改本章代码清单,使之可以检测1MB以上的内存空间(从地址0x0010_0000开始,不考虑高速缓存的影响).要求:对内存的读写按双字的长度进行,并在检测的同时显示已检测的内存数量 ...

  4. 存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18

    本文是原书第12章的学习笔记. 说句题外话,这篇博文是补写的,因为让我误删了,可恶的是CSDN的回收站里找不到! 好吧,那就再写一遍,我有坚强的意志.司马迁曰:“文王拘而演<周易>:仲尼厄 ...

  5. 进入保护模式(三)——《x86汇编语言:从实模式到保护模式》读书笔记17

    (十)保护模式下的栈 ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 mov cx,00000000000_11_000B ;加载堆栈段选择子 mov ss,cx mov esp,0x7c00 ...

  6. 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16

    一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...

  7. 进入保护模式(二)——《x86汇编语言:从实模式到保护模式》读书笔记14

    首先来段题外话:之前我发现我贴出的代码都没有行号,给讲解带来不便.所以从现在起,我要给代码加上行号.我写博客用的这个插入代码的插件,确实不支持自动插入行号.我真的没有找到什么好方法,无奈之下,只能按照 ...

  8. 32位x86处理器编程导入——《x86汇编语言:从实模式到保护模式》读书笔记08

    在说正题之前,我们先看2个概念. 1.指令集架构(ISA) ISA 的全称是 instruction set architecture,中文就是指令集架构,是指对程序员实际"可见" ...

  9. 《X86汇编语言:从实模式到保护模式》读书笔记之引言

    有幸结识了<X86汇编语言:从实模式到保护模式>一书.我觉得这本书非常好,语言活泼,通俗易懂,源码丰富,受益匪浅.读罢一遍,意犹未尽.于是打算再读一遍,并把自己的读书所学总结成笔记,一来给 ...

随机推荐

  1. HDU 4352 XHXJ's LIS HDU(数位DP)

    HDU 4352 XHXJ's LIS HDU 题目大意 给你L到R区间,和一个数字K,然后让你求L到R区间之内满足最长上升子序列长度为K的数字有多少个 solution 简洁明了的题意总是让人无从下 ...

  2. python入门008

    目录 一.for循环 作用:for循环是因为在循环取值(即遍历值)时for循环比while循环的使用更为简洁 1.for循环语法: 2.应用案例: 注意:break 与 continue也可以用于fo ...

  3. 源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性

    问题起源 使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便.最近却遇到了一个有点困惑的 ...

  4. day04 python入门(变量,基本数据类型)

    python入门学习 来自egon的学习套路 在每次遇到一个新事物的时候,要学三步: xxx是什么? 为什么要有xxx? ​ 大前提:python中所有出现的语法都是为了让计算机能够具有人的某一个功能 ...

  5. Typora上传图片设置

    Typora上传图片设置 问题 使用Typora写文档时上传图片的路径默认是本地磁盘的路径,这样就导致一个问题,当写好的文档上传到blog.csdn等博客网站时,会导致图片失效无法识别的问题. 解决方 ...

  6. selenium报错Element is not clickable at point及四种解决方法

    使用Selenium时,触发点击事件,经常报如下异常:Element is not clickable at point 1.未加载没加载出来就等待元素加载出来,再往下执行.可以使用python库ti ...

  7. Elasticsearch恢复备份的数据

    1.恢复备份好的snapshot 1.1恢复snapshot_1下的所有index POST _snapshot/my_backup/snapshot_1/_restore 1.2恢复snapshot ...

  8. HotSpot VM运行时

    HotSpot VM运行时系统为HotSpot JIT编译器和垃圾收集器提供服务和通用API,同时还为VM提供启动.线程管理.JNI(Java本地接口)等基本功能.HotSpot VM运行时环境担当许 ...

  9. C#中String与byte[]的相互转换

    从文件中读取字符串 string filePath = @"C:\Temp.xml"; string xmlString= File.ReadAllText(filePath); ...

  10. 肝了两天IntelliJ IDEA 2020,解锁11种新姿势, 真香!!!

    IDEA2020版本正式发布已经有3个月了,当时由于各方面原因(太懒)也没有去尝试新功能.于是这个周末特意去在另一个电脑上下载了最新版的IDEA,并尝试了一下.总的来说呢,体验上明显的提升. 作为一个 ...