目录

1. 进入32位保护模式

1.1 创建GDT表

1.1.1 空描述符

1.1.2 数据段描述符

1.1.3 代码段描述符

1.1.4 代码段别名描述符

1.1.5 栈段描述符

1.2 mov ds, eax说明

1.2.1 增加尺寸反转前缀的代价

1.2.2 通过mov ds, eax进行优化

2. 修改段寄存器时的保护

2.1 修改段寄存器的步骤

2.2 修改合法性检查

2.2.1 段选择符合法性检查

2.2.2 段描述符合法性检查

3. 地址变换时的保护

3.1 代码段执行时的保护

3.1.1 实际使用的段界限

3.1.2 代码段界限检查

3.2 栈操作时的保护

3.2.1 内存线性地址的回绕特性

3.2.2 使用向下扩展的段作为栈段

3.2.3 向下扩展的段用作栈段的保护

3.2.4 使用向上扩展的段作为栈段

3.2.5 向上扩展的段用作栈段的保护

3.3 数据访问时的保护

4. 使用别名访问代码段对字符排序

4.1 通过别名实现段的共享

4.2 冒泡排序法基本原理

4.3 排序代码分析


1. 进入32位保护模式

1.1 创建GDT表

本章示例程序中,共创建如下5个段描述符,

1.1.1 空描述符

① 空描述符的存在是处理器的要求

② 空描述符在GDT表中的索引为0,处理器不允许访问这个描述符。任何时候,使用索引字段为0的选择子来访问该描述符,都会被处理器阻止,并触发异常

1.1.2 数据段描述符

① 该段基地址为0,段界限为0xfffff,粒度为4KB,向上扩展,因此是一个从0地址开始,长度为4GB的段

② 对于32位处理器,这是地址范围最大的段,使用该段可以访问0到4GB空间内的任意一个单元

1.1.3 代码段描述符

① 该段基地址为0x00007c00,段界限为0x1ff,粒度为字节,向上扩展,因此是一个512B的代码段

② 该段实际上就是当前程序所在的段,也就是主引导程序所在的区域

③ 该段D/B = 1,是一个运行时32位的段。但是在编译时,该段既包含16位代码,也包含32位代码

只不过在该描述符生效时,处理器的执行流已经位于32位代码中,且之前的16位代码也不会再运行

1.1.4 代码段别名描述符

① 该段范围与代码段范围一致,但是属性为可写的数据段

② 在保护模式下,代码段是不可写入的,即通过代码段描述符访问相应内存区域时,处理器不允许向其中写入数据(e.g. 以CS:作为段超越前缀修改内存)

但是在某些情况下,又需要对代码段进行修改(e.g. 在调试程序时,加入断点指令int3)。此时可以通过重新为代码段安装一个可读写的数据段描述符,并通过该描述符修改相应的内存区域

③ 当两个以上的描述符都描述和指向同一个段时,将另外的描述符称为别名(alias)

说明:别名技术不仅可以用于修改代码段,如果两个程序想共享同一个内存区域,可以分别为每个程序都创建一个描述符,并指向同一个内存区域

1.1.5 栈段描述符

① 该段基地址为0x00007c00,段界限为0xffffe,粒度为4KB,向下扩展,因此被用作栈段

② 该段基地址虽然与代码段相同,但是由于是向下扩展的,因此不会造成冲突

③ 对于该段段界限的解释,见下文分析

说明:设置完GDT表之后的内存布局如下图所示,

1.2 mov ds, eax说明

1.2.1 增加尺寸反转前缀的代价

① 如上一章节《指令的格式及操作尺寸》笔记所述,处理器可以通过操作数尺寸反转前缀和有效地址尺寸反转前缀来正确处理操作尺寸

② 但是增加前缀是有代价的,就是会增加执行指令的时钟周期。因此应该在16位模块中尽量使用16位指令,在32位模块中尽量使用32位指令,这样可以避免操作尺寸反转前缀

1.2.2 通过mov ds, eax进行优化

在本章示例程序中,使用如下方式修改段寄存器

乍一看,这里源操作数(32位通用寄存器)和目的操作数(16位段寄存器)的尺寸是不匹配的,实际上这是Intel处理器提供的一种优化方式

如果此处改为使用ax寄存器,操作数的尺寸匹配了,但是在某些老式编译器中,会增加操作数尺寸反转前缀,也就会在执行时多消耗一个时钟周期

但实际上,这样的指令经过特殊设计,没有前缀也能正常工作,因此这里增加的前缀是不必要的。因此Intel处理器允许在此处使用eax寄存器,避免编译器添加前缀

说明1:因为加载或切换程序时重新设置段寄存器是高频操作,所以提供了这种极致优化的手段。这也体现出CISC指令集确实是"复杂"

说明2:本课程使用的NASM编译器比较优秀,在16 & 32位模式下均不会添加不必要的反转前缀,生成的机器码均相同

2. 修改段寄存器时的保护

2.1 修改段寄存器的步骤

在保护模式下,对段寄存器的修改分为2步,

① 将一个段选择符载入段选择器,此时要检查代入值的合法性

② 用段选择符选择一个段描述符,并将其传送到段描述符高速缓存器,此时要检查段描述符的合法性

2.2 修改合法性检查

2.2.1 段选择符合法性检查

这里主要是检查段选择符是否在GDT或LDT表的范围内(以使用GDT表为例)

GDT表有自己的大小和边界,处理器不允许超过边界访问描述符。每个描述符8B,他的每个字节都必须位于GDT表的边界范围内

如果越界访问,处理器终止处理,并产生13号异常(General Protection),同时段寄存器保持原来的值不变

2.2.2 段描述符合法性检查

段描述符合法性检查又包含如下步骤,

2.2.2.1 段描述符类型正确性检查

检查段描述符的类型字段是否有效,例如0b0000就是一个无效类型

2.2.2.2 段描述符与段寄存器匹配性检查

检查段描述符的类型与要加载的段寄存器是否匹配,规则如下表所示,

说明1:可见代码段只有可读时,才能加载到其他段寄存器;而SS段只能加载可写的数据段

说明2:对于DS、ES、FS和GS,可以向其中加载数值为0的段选择符(CS和SS不允许),即可以是

mov eax, 0
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax

尽管在加载时不会有问题,但是在该段基础上访问内存时,就会导致异常。处理器使用该特性来保证系统安全,具体见后续笔记

2.2.2.3 段描述符存在性检查

检查段描述符的P位,如果P = 0,说明虽然定义了段描述符,但是该段目前不在物理内存中。此时处理器中止处理,产生11号异常(Segment Not Present)

说明:P位为0的异常一般用于实现虚拟内存管理,在11号异常的处理函数中,将该描述符所对应的段从硬盘加载到内存,然后设置P位。异常返回时,处理器将再次尝试加载段描述符的操作

3. 地址变换时的保护

说明:本章讨论的地址变换时的保护,只涉及访问界限的检查,特权级相关的保护将在后续笔记中介绍

3.1 代码段执行时的保护

在保护模式下,指令的线性地址 = CS段描述符高速缓存器中的基地址 + EIP中的偏移地址,在指令实际开始执行之前,处理器必须检验其存放地址的有效性,以防止执行超出允许范围之外的指令

3.1.1 实际使用的段界限

代码段均为向上扩展的,因此段界限是段内最后一个允许访问的偏移地址

① 粒度G = 0

实际使用的段界限就是描述符中记载的段界限,在本章示例中就是0x1FF,也就是段内最大偏移为511B(因此段长度为512B)

② 粒度G = 1

段界限以4KB为单位,实际使用的段界限可以按如下方式计算,

描述符中的段界限 * 0x1000 + 0xFFF

3.1.2 代码段界限检查

要执行的指令必须完全在段界限之内,必须满足如下条件,否则引发处理器异常

0 <= (EIP + 指令长度 - 1) <= 实际使用的段界限

上述检查条件图示如下,

3.2 栈操作时的保护

说明:在保护模式下,我们只讨论32位的栈段,即D/B = 1的栈段。处理器在这样的栈上执行压栈和出栈操作时,默认使用ESP寄存器

3.2.1 内存线性地址的回绕特性

3.2.1.1 线性地址范围与偏移量范围

① 32位处理器的线性地址范围为0x00000000 ~ 0xFFFFFFFF

② 32位处理器可以使用16位偏移量,也可以使用32位偏移量,

如果使用16位偏移量,偏移量范围为0x0000 ~ 0xFFFF

如果使用32位偏移量,偏移量范围为0x00000000 ~ 0xFFFFFFFF

3.2.1.2 线性地址回绕示例

在保护模式下,线性地址 = 段基地址 + 偏移地址,如果计算的结果超过32位,线性地址将进行回绕

① 使用16位偏移量示例

如上图所示,段基地址指定为0xFFFFFF00,同时使用16位偏移量,当偏移量达到0x0100时,线性地址将发生绕回

基地址0xFFFFFF00 + 偏移量0x0000 = 线性地址0xFFFFFF00

基地址0xFFFFFF00 + 偏移量0x00FF = 线性地址0xFFFFFFFF

基地址0xFFFFFF00 + 偏移量0x0100 = 线性地址0x100000000,超过32位,保留低32位,即0x00000000,可见线性地址绕回

基地址0xFFFFFF00 + 偏移量0xFFFF = 线性地址0x10000FEFF,超过32位,保留低32位,线性地址 = 0x0000FEFF

因此,可以用如下方式标注偏移量,

② 使用32位偏移量示例

如上图所示,段基地址指定为0x20000000,同时使用32位偏移量,当偏移量达到0xE0000000时,线性地址将发生绕回

基地址0x20000000 + 偏移量0x00000000 = 线性地址0x20000000

基地址0x20000000 + 偏移量0xDFFFFFFF = 线性地址0xFFFFFFFF

基地址0x20000000 + 偏移量0xE0000000 = 线性地址0x100000000,超过32位,保留低32位,即0x00000000,可见线性地址绕回

基地址0x20000000 + 偏移量0xFFFFFFFF = 线性地址0x11FFFFFFF,超过32位,保留低32位,线性地址 = 0x1FFFFFFF

因此,可以用如下方式标注偏移量,

3.2.2 使用向下扩展的段作为栈段

3.2.2.1 实际使用的段界限

① 粒度G = 0

实际使用的段界限就是描述符中记载的段界限

② 粒度G = 1

段界限以4KB为单位,实际使用的段界限可以按如下方式计算,

描述符中的段界限 * 0x1000 + 0xFFF

示例程序中的栈段实际使用的段界限为0xFFFFEFFF

3.2.2.2 向下扩展栈段示例

本章示例程序定义的栈段描述符如下,

根据上节的线性地址回绕特性,该栈段的范围图示如下,

我们该如何解释这个向下扩展的栈段的范围呢,下节将会说明

3.2.3 向下扩展的段用作栈段的保护

由于使用的是向下扩展的段,段界限是段内偏移量所不允许的最小值,小于或等于该值就会触发异常

在32位向下扩展的栈段上进行栈操作时,必须符合如下规则,即入栈数据的全部字节在栈的范围内

实际使用的段界限 + 1 <= (ESP值 - 操作数的长度) <= 0xFFFFFFFF

图示如下,

代入本章示例中的设置值,规则如下,

0xFFFFEFFF + 1 <= (ESP值 - 操作数的长度) <= 0xFFFFFFFF
0xFFFFF000 <= (ESP值 - 操作数的长度) <= 0xFFFFFFFF

可见这是一个4KB大小的栈,对应的线性地址范围为0x00006C00 ~ 0x00007BFF,图示如下

说明1:段描述符中的D/B决定了向下扩展段的段内最大偏移量,如果是16位,最大偏移量为0xFFFF且默认栈操作使用SP;如果是32位,最大偏移量为0xFFFFFFFF且默认栈操作使用ESP

说明2:ESP初始值的设置

由于栈的推进方向向下,在使用向下扩展的段作为栈段时,应将ESP设置为最大偏移量 + 1,也就是0xFFFFFFFF + 1,该值绕回到0

这样当首次入栈操作时,ESP - 4 = 0xFFFFFFFC,该值符合判断规则的要求

说明3:栈大小的计算

栈大小 = (0xFFFFF - 栈段描述符段界限)* 粒度

对应到本节示例,

栈大小 = (0xFFFFF - 0xFFFFE)* 4KB = 4KB

3.2.4 使用向上扩展的段作为栈段

3.2.4.1 为何可以使用向上扩展的段作为栈段

在使用向上扩展的段作为栈段时,需要区分2个概念,

① 段的扩展方向

段的扩展方向并不是数据的读写方向,而是用来定义偏移量的范围,决定处理器如何对段的访问进行检查

如果是向上扩展的段,段的最小偏移量为0,最大偏移量由段描述符指定

② 栈的推进方向

栈的推进方向始终是向下的

3.2.4.2 向上扩展栈段示例

下面给出一个向上扩展的段作为栈段的描述符示例,

mov dword [ebx + 0x18], 0x6c0007ff ;基地址为0x00006c00,向上扩展
mov dword [ebx + 0x1c], 0x00409200 ;段界限为0x007ff,粒度为字节

3.2.5 向上扩展的段用作栈段的保护

3.2.5.1 实际使用段界限

① 粒度G = 0

实际使用的段界限就是描述符中记载的段界限,在本章示例中就是0x7FF,也就是段内最大偏移为2047B(因此段长度为2KB)

② 粒度G = 1

段界限以4KB为单位,实际使用的段界限可以按如下方式计算,

描述符中的段界限 * 0x1000 + 0xFFF

3.2.5.2 向上扩展栈段界限检查

对于向上扩展的数据段,在实际访问之前,需要按一下规则进行检查,

0 <= 操作数有效地址 + 操作数大小 - 1 <= 实际使用的段界限

代入本章示例中的设置值,图示如下,

说明:ESP初始值的设置

mov esp, 0x800

由于栈的推进方向向下,在使用向上扩展的段作为栈段时,也应将ESP设置为最大偏移量 + 1,也就是0x000007FF + 1 = 0x00000800

3.3 数据访问时的保护

说明:这里所说的数据段,特指向上扩展的数据段,有别于栈和向下扩展的数据段

对数据段的保护,与使用向上扩展的段作为栈段场景相同

4. 使用别名访问代码段对字符排序

4.1 通过别名实现段的共享

要排序的字符串定义在代码段,而代码段是不可写的。因此创建了一个范围覆盖0 ~ 4GB的数据段,包含了代码段的范围。如果需要修改代码段的内容,就使用这个数据段的描述符

4.2 冒泡排序法基本原理

① 如果有N个元素,采用冒泡排序法进行两两比较排序时,需要遍历N - 1次(外层循环)

② 比较的过程是收敛的,每次遍历后,都会选出一个最大或最小元素,后面的每次遍历都对上一次剩余的元素进行两两比较(内层循环)

4.3 排序代码分析

说明1:外层循环分析

;开始冒泡排序
mov ecx, pgdt-string-1 ;遍历次数 = 串长度 - 1
@@1:push ecx;内部循环,从字符串开头进行两两比较pop ecxloop @@1

压栈保存ecx是因为内部循环也要用到ecx

说明2:内层循环分析

;开始冒泡排序
mov ecx, pgdt-string-1 ;遍历次数 = 串长度 - 1
@@1:push ecxxor bx, bx
@@2:;用bx中的有效地址访问字符串,依次对相邻的字符进行比较;必要时进行交换inc bxloop @@2pop ecxloop @@1

对于每一轮内层循环,外层循环的ecx值,正好是内层循环的循环次数

说明3:32位尺寸下的loop指令

如果CS段描述符高速缓存器中的D/B = 1(32位模式),则loop时使用ecx寄存器;如果D/B = 0(16位模式),则loop时使用cx寄存器

说明4:数据交换指令xchg

xchg指令格式如下,

xchg r/m, r/m

xchg指令将2个寄存器,或1个寄存器与1个内存单元中的操作数交换。这2个操作数不能同时为内存单元

X86汇编语言从实模式到保护模式12:存储器的保护相关推荐

  1. x86汇编语言从实模式百度云_Intel x86 CPU 32位保护模式杂谈之任务切换 上

    目录: 什么是任务 任务由什么组成 任务门描述符是什么东东?有了TSS描述符为什么要有任务门描述符? 参考文献 什么是任务 任务(task)是处理器可以分配.执行.挂起的工作单位,笔者认为和我们操作系 ...

  2. 【OS修炼指南目录】----《X86汇编语言-从实模式到保护模式》读书笔记目录表

    学习交流加(可免费帮忙下载CSDN资源): 个人微信: liu1126137994 学习交流资源分享qq群1(已满): 962535112 学习交流资源分享qq群2: 780902027 本文是将个人 ...

  3. x86汇编语言从实模式百度云_x86汇编语言:从实模式到保护模式

    x86汇编语言:从实模式到保护模式2013年1月由电子工业出版社出版发行,总共6000行的源代码,全方位地向读者展现汇编语言程序设计之美.尽管汇编语言也是一种计算机语言,但却是与众不同的,与它的同类们 ...

  4. X86汇编语言从实模式到保护模式20:平坦模型

    1 引入平坦模型(Flat Model)的原因 1.1 内存管理模型变迁 1.1.1 分段模型 1.1.1.1 基本特点 1. 在程序中按结构组织为多个段 2. 在加载程序时,为程序中的每个段创建段描 ...

  5. X86汇编语言从实模式到保护模式13:保护模式程序的动态加载和执行

    目录 1. 引入保护模式对程序加载与执行的影响 1.1 对应用程序的影响 1.2 对操作系统的影响 1.3 本章程序总体结构 2. MBR加载内核过程分析 2.1 内核头部段分析 2.1.1 内核总长 ...

  6. 李忠 X86汇编语言 从实模式到保护模式-初学

    学习资料: 教学视频 网易云课堂 哔哩哔哩 原书网站 原书相关源码附件下载 网友帖子 除了后面没有图片之外很不错的笔记总结,写者很用心 留存待看,一片文章写了特点 很有特色总结的笔记 学习目标: 15 ...

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

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

  8. X86汇编语言从实模式到保护模式19:分页和动态页面分配

    目录 1. 段式内存管理机制 1.1 任务的全局部分和私有部分 1.2 任务的线性地址空间 1.3 段式内存管理 1.3.1 对物理内存的初步划分 1.3.2 段式内存管理策略 1.4 段式内存管理机 ...

  9. X86汇编语言从实模式到保护模式18:中断和异常的处理与抢占式多任务

    目录 1. 中断和异常概述 1.1 中断的分类 1.1.1 中断(Interrupt) 1.1.2 异常(Exception) 1.2 异常的分类 1.2.1 按异常的来源分类 1.2.2 按异常的性 ...

  10. X86汇编语言从实模式到保护模式17:协同式任务切换

    目录 1. 多任务和任务切换概述 1.1 多任务系统 1.2 任务切换含义 1.2.1 切换任务上下文 1.2.2 上下文是什么 1.3 任务切换方式概述 1.3.1 协同式任务切换 1.3.2 抢占 ...

最新文章

  1. 基于WinCE的I2C驱动程序设计
  2. 网易 for linux,NetEaseMusic
  3. OpenGL立方体的纹理
  4. 前后台传值乱码问题解决
  5. 一个excel文档里复制黏贴另外表单跟着变动_利用Excel连接Power BI,实现PPT报告自动输出...
  6. 证明AVL树的上界和下界
  7. lambda表达式可以用来声明_lambda表达式可以用来创建包含多个表达式的匿名函数...
  8. 串口如何接收数据_原创分享 | S71200通过串口服务器读取MODBUS RTU设备
  9. Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)
  10. JVM监控及诊断工具命令行篇之jinfo
  11. HuangTools 绿色软件包 (Version 1.0.0)
  12. 种草平台--持续更新
  13. 如何利用PyTorch实现一个Encoder-Decoder结构进行英法互译
  14. 宝塔linux取消登录,宝塔面板如何关闭安全入口
  15. 名侦探柯南之零的执行人
  16. HTML表单、表格制作个人简历
  17. MPCS-341 3A 光电耦合器 用于IGBT/MOSFET隔离栅极驱动 完美代替TLP5754
  18. 乘车码来了,地铁公交都可以刷微信了
  19. uniapp 模块权限配置 权限管理中英对照
  20. 华硕笔记本计算机名称,华硕NB是如何命名的?5招教你看清楚

热门文章

  1. 2020年市场最缺什么_2020年资本市场回顾
  2. 查询阜阳2021高考成绩,今年阜阳高考状元名单是谁,2021年阜阳高考状元多少分
  3. springMVC处理跨域问题
  4. Linux中Docker部署MySQL
  5. 吃鸡电脑配置清单_2020年5月份最佳组装电脑配置清单,吃鸡联盟总有满足你的...
  6. 【电脑帮助】解决Wind10系统没有本地用户和组的问题
  7. Mysql改写子查询SQL优化案例
  8. codeigniter index.php,CodeIgniter如何隐藏index.php | 学步园
  9. css未生效,css不生效是什么原因
  10. Apollo 对表名区分大小写 如何配置MYSQL不区分大小写呢