1 - GDT作用

GDT全称Global Descriptor Table,是x86保护模式下的一个重要数据结构,在保护模式下,GDT在内存中有且只有一个。GDT的数据结构是一个描述符数组,每个描述符8个字节,可以存放在内存当中任意位置:

其中,addr相当于GDT的基地址,GDT的总长度(单位字节)为GDT界限。

在实模式中,CPU通过段地址和段偏移量寻址。其中段地址保存到段寄存器,包含:CS、SS、DS、ES、FS、GS。段偏移量可以保存到IP、BX、SI、DI寄存器。在汇编代码mov ds:[si], ax中,会将AX寄存器的数据写入到物理内存地址DS * 16 + SI中。

而在保护模式下,也是通过段寄存器和段偏移量寻址,但是此时段寄存器保存的数据意义不同了。
此时的CS和SS寄存器后13位相当于GDT表中某个描述符的索引,即段选择子。第2位存储了TI值(0代表GDT,1代表LDT),第0、1位存储了当前的特权级(CPL)。

例如在保护模式下执行汇编代码mov ds:[si], ax的大致步骤如下:

  1. 首先CPU需要查找GDT在内存中位置,GDT的位置从GDTR寄存器中直接获取
  2. 然后根据DS寄存器得到目标段描述符的物理地址
  3. 计算出描述符中的段基址的值加上SI寄存器存储的偏移量的结果,该结果为目标物理地址
  4. 将AX寄存器中的数据写入到目标物理地址

2 - GDTR寄存器

CPU切换到保护模式前,需要准备好GDT数据结构,并执行LGDT指令,将GDT基地址和界限传入到GDTR寄存器。

GDTR寄存器长度为6字节(48位),前两个字节为GDT界限,后4个字节为GDT表的基地址。所以说,GDT最多只能拥有8192个描述符(65536 / 8)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AXzy19kY-1603970086921)(#pic_center)]

一旦切换到保护模式,一般不会更改GDTR寄存器的内容。


3 - GDT段描述符结构

一个GDT段描述符占用8个字节,包含三个部分:

  • 段基址(32位),占据描述符的第16~39位和第55位~63位,前者存储低16位,后者存储高16位
  • 段界限(20位),占据描述符的第0~15位和第48~51位,前者存储低16位,后者存储高4位。
  • 段属性(12位),占据描述符的第39~47位和第49~55位,段属性可以细分为8种:TYPE属性、S属性、DPL属性、P属性、AVL属性、L属性、D/B属性和G属性。

下面介绍各个属性的作用:

S属性

S属性存储了描述符的类型

  • S = 0 S=0 S=0 时,该描述符对应的段是系统段(System Segment)。
  • S = 1 S=1 S=1 时,该描述符对应的段是数据段(Data Segment)或者代码段(Code Segment)
TYPE属性

TYPE属性存储段的类型信息,该属性的意义随着S属性不同而不同。
当 S = 1 S=1 S=1 (该段为数据段或代码段)时,需要分为两种情况:

  • 当TYPE属性第三位为0时,代表该段为数据段,第0~2位的作用为:

    作用 值为0时 值为1时
    2 段的增长方向 向上增长 向下增长(例如栈段)
    1 段的写权限 只读 可读可写
    0 段的访问标记 该段未被访问过 该段已被访问过

    (第0位对应描述符的第43位,第1位对应第42位,以此类推)

  • 当TYPE属性第三位为1时,代表该段为代码段,第0~2位的作用为:
    作用 值为0时 值为1时
    2 一致代码段标记 不是一致代码段 是一致代码段
    1 段的读权限 只能执行 可读、可执行
    0 段的访问标记 该段未被访问过 该段已被访问过

    一致代码段的“一致”意思是:当CPU执行jmp等指令将CS寄存器指向该代码段时,如果当前的特权级低于该代码段的特权级,那么当前的特权级会被延续下去(简单的说就是可以被低特权级的用户直接访问的代码),如果当前的特权级高于该代码段的特权级,那么会触发常规保护错误(可以理解为内核态下不允许直接执行用户态的代码)。
    如果不是一致代码段并且该代码段的特权级不等于(高于和低于都不行)当前的特权级,那么会引发常规保护错误。

当 S = 0 S=0 S=0 (该段为系统段)时:

TYPE的值(16进制) TYPE的值(二进制) 解释
0x1 0 0 0 1 可用286TSS
0x2 0 0 1 0 该段存储了局部描述符表(LDT)
0x3 0 0 1 1 忙的286TSS
0x4 0 1 0 0 286调用门
0x5 0 1 0 1 任务门
0x6 0 1 1 0 286中断门
0x7 0 1 1 1 286陷阱门
0x9 1 0 0 1 可用386TSS
0xB 1 0 1 1 忙的386TSS
0xC 1 1 0 0 386调用门
0xE 1 1 1 0 386中断门
0xF 1 1 1 1 386陷阱门

(其余值均为未定义)

DPL属性

DPL属性占2个比特,记录了访问段所需要的特权级,特权级范围为0~3,越小特权级越高。

P属性

P属性标记了该段是否存在:

  • P = 0 P=0 P=0 时,该段在内存中不存在
  • P = 1 P=1 P=1 时,该段在内存中存在

尝试访问一个在内存中不存在的段会触发段不存在错误(#NP)

AVL属性

AVL属性占1个比特,该属性的意义可由操作系统、应用程序自行定义。
Intel保证该位不会被占用作为其他用途。

L属性

该属性仅在IA-32e模式下有意义,它标记了该段是否为64位代码段。
当 L = 1 L=1 L=1 时,表示该段是64位代码段。
如果设置了L属性为1,则必须保证D属性为0。

D/B属性

D/B属性中的D/B全称 Default operation size / Default stack pointer size / Upper bound。
该属性的意义随着段描述符是代码段(Code Segment)、向下扩展数据段(Expand-down Data Segment)还是栈段(Stack Segment)而有所不同。

  • 代码段(S属性为1,TYPE属性第三位为1)
    如果对应的是代码段,那么该位称之为D属性(D flag)。如果设置了该属性,那么会被视为32位代码段执行;如果没有设置,那么会被视为16位代码段执行。

  • 栈段(被SS寄存器指向的数据段)
    该情况下称之为B属性。如果设置了该属性,那么在执行堆栈访问指令(例如PUSHPOP指令)时采用32位堆栈指针寄存器(ESP寄存器),如果没有设置,那么采用16位堆栈指针寄存器(SP寄存器)。

  • 向下扩展的数据段
    该情况下称之为B属性。如果设置了该属性,段的上界为4GB,否则为64KB。

G属性

G属性记录了段界限的粒度:

  • G = 0 G=0 G=0 时,段界限的粒度为字节
  • G = 1 G=1 G=1 时,段界限的粒度为4KB

例如,当 G = 0 G=0 G=0 并且描述符中的段界限值为 10000 10000 10000,那么该段的界限为10000字节,如果 G = 1 G=1 G=1,那么该段的界限值为40000KB。

所以说,当 G = 0 G=0 G=0 时,一个段的最大界限值为1MB(因为段界限只能用20位表示, 2 20 = 1048576 2^{20}=1048576 220=1048576),最小为1字节(段的大小等于段界限值加1)。
当 G = 1 G=1 G=1 时,最大界限值为4GB,最小为4KB。

在访问段(除栈段)时,如果超出了段的界限,那么会触发常规保护错误(#GP)
如果访问栈段超出了界限,那么会产生堆栈错误(#SS)


案例学习

实现保护模式下打印红色的Hello, world(不依赖操作系统和BIOS中断服务)。

首先定义单个描述符的数据结构,用NASM汇编宏可以表示为

; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3dw   %2 & 0FFFFh             ; 段界限 1             (2 字节)dw    %1 & 0FFFFh             ; 段基址 1             (2 字节)db    (%1 >> 16) & 0FFh         ; 段基址 2             (1 字节)dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ; 属性 1 + 段界限 2 + 属性 2     (2 字节)db    (%1 >> 24) & 0FFh         ; 段基址 3             (1 字节)
%endmacro

定义段属性常量:

DA_DR        EQU 90h ; 存在的只读数据段类型值
DA_DRW      EQU 92h ; 存在的可读写数据段属性值
DA_DRWA     EQU 93h ; 存在的已访问可读写数据段类型值
DA_C        EQU 98h ; 存在的只执行代码段属性值
DA_CR       EQU 9Ah ; 存在的可执行可读代码段属性值
DA_CCO      EQU 9Ch ; 存在的只执行一致代码段属性值
DA_CCOR     EQU 9Eh ; 存在的可执行可读一致代码段属性值

定义三个描述符:

  • 负责打印字符串的代码段

    DESC_CODE: Descriptor 0, CODE32_LEN - 1, DA_C + DA_32
    

    此处段基址暂时设置为0,在实模式下动态设置

  • 存放需要打印的字符串(Hello, world字符串)的数据段
    DESC_DATA: Descriptor 0, STRING_LEN - 1, DA_DR
    

    同样设置为0,在实模式下动态设置

  • 显存映射到内存的数据段(固定在0xB8000,如果不懂可以百度),并且该段要设置成可写
    DESC_VIDEO: Descriptor 0xB8000, 0xFFFF, DA_DRW
    

定义GDTR寄存器的数据:

GdtLen equ $ - DESC_GDT  ; GDT表的长度
GdtPtr dw GdtLen  ; GDT界限dd 0       ; GDT基址,暂时设置为0,需要动态设置

定义段选择子:

DataSelector equ DESC_DATA - DESC_GDT
CodeSelector equ DESC_CODE - DESC_GDT
VideoSelector equ DESC_VIDEO - DESC_GDT

因为TI和DPL都为0,且正好占据3位,所以只需要将描述符的地址减去基址就可以得到索引了(等同于左移3位)

然后是实模式下的初始化代码,主要完成三件事:

  1. 初始化段描述符
  2. 初始化GDT的基址,并存放到GDTR寄存器
  3. 切换到保护模式(打开A20地址线,将CR0寄存器第0位设置为1),并跳转到负责打印字符串的代码段。

代码如下:

%include "pm.inc"org 0x7C00
jmp _main_16[SECTION .gdt]
DESC_GDT: Descriptor 0, 0, 0   ;该描述符仅用来计算下面三个描述符的地址
DESC_VIDEO: Descriptor 0xB8000, 0xFFFF, DA_DRW
DESC_DATA: Descriptor 0, STRING_LEN - 1, DA_DR
DESC_CODE: Descriptor 0, CODE32_LEN - 1, DA_C + DA_32GdtLen equ $ - DESC_GDT
GdtPtr dw GdtLendd 0DataSelector equ DESC_DATA - DESC_GDT
CodeSelector equ DESC_CODE - DESC_GDT
VideoSelector equ DESC_VIDEO - DESC_GDT[SECTION .s16]
[BITS 16]
_main_16:; 初始化DS/ES/SS/SP寄存器mov ax, csmov ds, axmov es, axmov ss, axmov sp, 0x7C00; 初始化段描述符call near _init_desc; 初始化GDT基址xor eax, eaxmov ax, dsshl eax, 4add eax, DESC_GDTmov dword [GdtPtr + 2], eaxlgdt [GdtPtr]cliin al, 0x92or al, 00000010bout 0x92, almov eax, cr0or eax, 1mov cr0, eax; 跳转到代码段jmp dword CodeSelector:0_init_desc:xor eax, eaxmov ax, csshl eax, 4add eax, _main_32mov di, DESC_CODEcall near _init_desc_base_addressxor eax, eaxmov ax, csshl eax, 4add eax, STRINGmov di, DESC_DATAcall near _init_desc_base_addressret_init_desc_base_address:mov word [di + 2], axshr eax, 16mov byte [di + 4], almov byte [di + 7], ahret

最后是打印字符串的32位代码段:

[SECTION .s32]
[BITS 32]
_main_32:mov ax, VideoSelectormov gs, axmov esi, 0xA0mov ax, DataSelectormov ds, axmov edi, 0mov ecx, STRING_LENprint_loop:mov al, ds:[edi]mov ah, 0xCmov word gs:[esi], axadd esi, 2inc ediloop print_loopjmp $CODE32_LEN equ $ - _main_32[SECTION .s32]
[BITS 32]
STRING: db 'Hello, world'
STRING_LEN equ $ - STRING

完整代码:

pm.inc

DA_DR        EQU 90h ; 存在的只读数据段类型值
DA_DRW      EQU 92h ; 存在的可读写数据段属性值
DA_DRWA     EQU 93h ; 存在的已访问可读写数据段类型值
DA_C        EQU 98h ; 存在的只执行代码段属性值
DA_CR       EQU 9Ah ; 存在的可执行可读代码段属性值
DA_CCO      EQU 9Ch ; 存在的只执行一致代码段属性值
DA_CCOR     EQU 9Eh ; 存在的可执行可读一致代码段属性值; 宏 ------------------------------------------------------------------------------------------------------
;
; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3dw   %2 & 0FFFFh             ; 段界限 1             (2 字节)dw    %1 & 0FFFFh             ; 段基址 1             (2 字节)db    (%1 >> 16) & 0FFh         ; 段基址 2             (1 字节)dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ; 属性 1 + 段界限 2 + 属性 2     (2 字节)db    (%1 >> 24) & 0FFh         ; 段基址 3             (1 字节)
%endmacro

boot.asm

%include "pm.inc"org 0x7C00
jmp _main_16[SECTION .gdt]
DESC_GDT: Descriptor 0, 0, 0
DESC_VIDEO: Descriptor 0xB8000, 0xFFFF, DA_DRW
DESC_DATA: Descriptor 0, STRING_LEN - 1, DA_DR
DESC_CODE: Descriptor 0, CODE32_LEN - 1, DA_C + DA_32GdtLen equ $ - DESC_GDT
GdtPtr dw GdtLendd 0DataSelector equ DESC_DATA - DESC_GDT
CodeSelector equ DESC_CODE - DESC_GDT
VideoSelector equ DESC_VIDEO - DESC_GDT[SECTION .s16]
[BITS 16]
_main_16:mov ax, csmov ds, axmov es, axmov ss, axmov sp, 0x7C00call near _init_desc; 设置段基址xor eax, eaxmov ax, dsshl eax, 4add eax, DESC_GDTmov dword [GdtPtr + 2], eaxlgdt [GdtPtr]cliin al, 0x92or al, 00000010bout 0x92, almov eax, cr0or eax, 1mov cr0, eaxjmp dword CodeSelector:0_init_desc:xor eax, eaxmov ax, csshl eax, 4add eax, _main_32mov di, DESC_CODEcall near _init_desc_base_addressxor eax, eaxmov ax, csshl eax, 4add eax, STRINGmov di, DESC_DATAcall near _init_desc_base_addressret_init_desc_base_address:mov word [di + 2], axshr eax, 16mov byte [di + 4], almov byte [di + 7], ahret[SECTION .s32]
[BITS 32]
_main_32:mov ax, VideoSelectormov gs, axmov esi, 0xA0mov ax, DataSelectormov ds, axmov edi, 0mov ecx, STRING_LENprint_loop:mov al, ds:[edi]mov ah, 0xC  ;设置成红色mov word gs:[esi], axadd esi, 2inc ediloop print_loopjmp $CODE32_LEN equ $ - _main_32[SECTION .s32]
[BITS 32]
STRING: db 'Hello, world'
STRING_LEN equ $ - STRINGtimes 290 db 0
dw 0xAA55

编译:

NASM -f bin -o boot.com boot.asm

生成IMG软盘文件镜像:

dd if=boot.com of=boot.img bs=512 count=1
dd if=/dev/zero of=/tmp/empty.img bs=512 count=2880
dd if=/tmp/empty.img of=boot.img skip=1 seek=1 bs=512 count=2879

使用VMWare虚拟机,添加软盘设备并启动,运行结果:

x86保护模式——全局描述符表GDT详解相关推荐

  1. NASM汇编语言与计算机系统16-保护模式-全局描述符表GDT

    全局描述符表Global Descriptor Table 表中每个元素8个字节,每个元素表示一个段(代码段,数据段,栈段)的信息,且GDT在进入保护模式之前必须存在,所以它必须位于1MB以下(实模式 ...

  2. CPU实模式和保护模式、全局描述符表GDT、Linux内核中GDT和IDT的结构定义

    一 计算机实模式和保护模式 实模式 在实模式下,内存被限制为仅有1M字节(220 字节).有效的地址从00000到FFFFF (十六进制). 这些地址需要用20位的数来表示.一个20位的数不适合任何一 ...

  3. X86保护模式(一)· 何为保护模式

    导航 保护模式的诞生背景 全局描述符表GDT 全局描述符表寄存器GDTR 描述符的分类 存储器段描述符的结构 TYPE域 DPL域 G位 P位 L位 D/B位 AVL位 向上/向下扩展时的段界限 进入 ...

  4. x86保护模式 任务状态段和控制门

    x86保护模式    任务状态段和控制门 每个任务都有一个任务状态段TSS     用于保存任务的有关信息     在任务内权变和任务切换时  需要用到这些信息    任务内权变的转移和任务切换  一 ...

  5. gdt描述_全局描述符表(GDT)局部描述符表(LDT)

    这三个表是在内存中由操作系统或系统程序员所建,并不是固化在哪里,所以从理论上是可以被读写的.这三个表都是描述符表.描述符表是由若干个描述符组成,每个描述符占用8个字节的内存空间,每个描述符表内最多可以 ...

  6. linux内核gdt,linux内核学习之全局描述符表(GDT)(二)

    在进入保护模式之前,我们先要学习一些基础知识.今天我们看一下全局描述符表(Global Descriptor Table, 简称GDT). 同实模式一样,在保护模式下,对内存的访问仍然使用段地址加偏移 ...

  7. 深入剖析Redis系列(三) - Redis集群模式搭建与原理详解

    前言 在 Redis 3.0 之前,使用 哨兵(sentinel)机制来监控各个节点之间的状态.Redis Cluster 是 Redis 的 分布式解决方案,在 3.0 版本正式推出,有效地解决了 ...

  8. X86保护模式下的内存寻址

    段选择器 :32位汇编中16位段寄存器(CS.DS.ES.SS.FS.GS)中不再存放段基址,而是段描述符在段描述符表中的索引值,D3-D15位是索引值,D0-D1位是优先级(RPL)用于特权检查,D ...

  9. 关于隐私保护通话 - 隐私号关系详解

    在某种程度上,隐私号业务的多样性和复杂性主要体现在号码绑定关系上. 对应各种业务场景,有各种不同的号码绑定关系模式: 基本绑定模式:AXB,AX(AXN,XB).AXE.AXYB: 复合绑定模式:AX ...

最新文章

  1. MySQL—04—MySQL的其他对象
  2. Zookeeper Api(java)入门与应用
  3. Machine Learning week 8 quiz: Principal Component Analysis
  4. linq查询不包含某个值的记录_MySQL行(记录)的详细操作
  5. 详细介绍nagios基本配置
  6. JavaFx 实现画图工具
  7. Linux服务器备份mySQL数据库_远程linux服务器mysql数据库定期备份和删除
  8. jsp的include两种使用方法
  9. javascript高级程序设计第四版(javascript高级程序设计第四版)
  10. 三行代码,教你在python中将MP4转化为GIF,这不完胜迅捷视频转化器?
  11. SonicWall远程命令执行漏洞
  12. RHEL7挂载本地yum源
  13. E1--千兆以太网接口测试应用2022-09-07
  14. 自定义数据集算子数据结构
  15. 频谱分析仪是什么东西 怎么去选择----TFN FMT350(3.1gHz)/FMT450(4.4gHz)/FMT650(6gHz) 系列频谱仪
  16. 软件工程第一章绪论————(2019.12.27学习笔记)
  17. 外置存储权限在哪打开_安卓手机外置sd卡的权限怎么打开?
  18. 教学生用计算机画画,教师资格证美术面试真题《用电脑画画》
  19. UVA 几道dp题总结
  20. SLF4j的介绍与使用+SpringBoot日志配置

热门文章

  1. 遇到 腾讯云 由于连接云服务器超时 的问题
  2. 更简单的非递归遍历二叉树
  3. 企业项目管理人才培养体系建设及创新思路
  4. ORACLE中dual的详解及其故障恢复
  5. 微信小程序的项目缓存路径
  6. vue中directives的用法
  7. hive 启动报错java.net.URISyntaxException: Relative path in absolute URI: ${system:java.io.tmpdir%7D/$%..
  8. word使用计算机题,巧用Word编题库
  9. @Value(“${}“)获取不到配置文件值的原因
  10. 【web编程技术学习笔记】因特网与万维网简介