x86保护模式——全局描述符表GDT详解
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
的大致步骤如下:
- 首先CPU需要查找GDT在内存中位置,GDT的位置从GDTR寄存器中直接获取
- 然后根据DS寄存器得到目标段描述符的物理地址
- 计算出描述符中的段基址的值加上SI寄存器存储的偏移量的结果,该结果为目标物理地址
- 将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属性。如果设置了该属性,那么在执行堆栈访问指令(例如PUSH
、POP
指令)时采用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位)
然后是实模式下的初始化代码,主要完成三件事:
- 初始化段描述符
- 初始化GDT的基址,并存放到GDTR寄存器
- 切换到保护模式(打开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详解相关推荐
- NASM汇编语言与计算机系统16-保护模式-全局描述符表GDT
全局描述符表Global Descriptor Table 表中每个元素8个字节,每个元素表示一个段(代码段,数据段,栈段)的信息,且GDT在进入保护模式之前必须存在,所以它必须位于1MB以下(实模式 ...
- CPU实模式和保护模式、全局描述符表GDT、Linux内核中GDT和IDT的结构定义
一 计算机实模式和保护模式 实模式 在实模式下,内存被限制为仅有1M字节(220 字节).有效的地址从00000到FFFFF (十六进制). 这些地址需要用20位的数来表示.一个20位的数不适合任何一 ...
- X86保护模式(一)· 何为保护模式
导航 保护模式的诞生背景 全局描述符表GDT 全局描述符表寄存器GDTR 描述符的分类 存储器段描述符的结构 TYPE域 DPL域 G位 P位 L位 D/B位 AVL位 向上/向下扩展时的段界限 进入 ...
- x86保护模式 任务状态段和控制门
x86保护模式 任务状态段和控制门 每个任务都有一个任务状态段TSS 用于保存任务的有关信息 在任务内权变和任务切换时 需要用到这些信息 任务内权变的转移和任务切换 一 ...
- gdt描述_全局描述符表(GDT)局部描述符表(LDT)
这三个表是在内存中由操作系统或系统程序员所建,并不是固化在哪里,所以从理论上是可以被读写的.这三个表都是描述符表.描述符表是由若干个描述符组成,每个描述符占用8个字节的内存空间,每个描述符表内最多可以 ...
- linux内核gdt,linux内核学习之全局描述符表(GDT)(二)
在进入保护模式之前,我们先要学习一些基础知识.今天我们看一下全局描述符表(Global Descriptor Table, 简称GDT). 同实模式一样,在保护模式下,对内存的访问仍然使用段地址加偏移 ...
- 深入剖析Redis系列(三) - Redis集群模式搭建与原理详解
前言 在 Redis 3.0 之前,使用 哨兵(sentinel)机制来监控各个节点之间的状态.Redis Cluster 是 Redis 的 分布式解决方案,在 3.0 版本正式推出,有效地解决了 ...
- X86保护模式下的内存寻址
段选择器 :32位汇编中16位段寄存器(CS.DS.ES.SS.FS.GS)中不再存放段基址,而是段描述符在段描述符表中的索引值,D3-D15位是索引值,D0-D1位是优先级(RPL)用于特权检查,D ...
- 关于隐私保护通话 - 隐私号关系详解
在某种程度上,隐私号业务的多样性和复杂性主要体现在号码绑定关系上. 对应各种业务场景,有各种不同的号码绑定关系模式: 基本绑定模式:AXB,AX(AXN,XB).AXE.AXYB: 复合绑定模式:AX ...
最新文章
- MySQL—04—MySQL的其他对象
- Zookeeper Api(java)入门与应用
- Machine Learning week 8 quiz: Principal Component Analysis
- linq查询不包含某个值的记录_MySQL行(记录)的详细操作
- 详细介绍nagios基本配置
- JavaFx 实现画图工具
- Linux服务器备份mySQL数据库_远程linux服务器mysql数据库定期备份和删除
- jsp的include两种使用方法
- javascript高级程序设计第四版(javascript高级程序设计第四版)
- 三行代码,教你在python中将MP4转化为GIF,这不完胜迅捷视频转化器?
- SonicWall远程命令执行漏洞
- RHEL7挂载本地yum源
- E1--千兆以太网接口测试应用2022-09-07
- 自定义数据集算子数据结构
- 频谱分析仪是什么东西 怎么去选择----TFN FMT350(3.1gHz)/FMT450(4.4gHz)/FMT650(6gHz) 系列频谱仪
- 软件工程第一章绪论————(2019.12.27学习笔记)
- 外置存储权限在哪打开_安卓手机外置sd卡的权限怎么打开?
- 教学生用计算机画画,教师资格证美术面试真题《用电脑画画》
- UVA 几道dp题总结
- SLF4j的介绍与使用+SpringBoot日志配置
热门文章
- 遇到 腾讯云 由于连接云服务器超时 的问题
- 更简单的非递归遍历二叉树
- 企业项目管理人才培养体系建设及创新思路
- ORACLE中dual的详解及其故障恢复
- 微信小程序的项目缓存路径
- vue中directives的用法
- hive 启动报错java.net.URISyntaxException: Relative path in absolute URI: ${system:java.io.tmpdir%7D/$%..
- word使用计算机题,巧用Word编题库
- @Value(“${}“)获取不到配置文件值的原因
- 【web编程技术学习笔记】因特网与万维网简介