专栏首页逆向技术保护模式 3讲-段寄存器GDT表与个人代码总结

保护模式 3讲-段寄存器GDT表与个人代码总结

一丶段描述符

1.1 GDT与LDT

1.1.1 段描述符之GDT表 与 LDT表的概述

  • GDT表 查询inter手册可以得知. 当我们在保护模式下. 进行内存访问的时候 所有的内存访问. 要么通过 全局描述符表(GDT) 要么就通过局部描述符表(LDT) 进行访问的. 而 这些描述符表中.记录的都是 段描述符 段描述符里面包含段的基地址 访问特权 类型用法信息. 每个段描述符都会有一个与之相关的段选择子,也叫做段选择符 段选择子 包含的是 GDT 与LDT(与它相关的的段描述符)里面的一个索引. 一个全局或者局部标志 决定了 段选择子 是指向GDT 还是 LDT. 想要访问GDT 或者LDT 要提供 段选择子以及偏移地址. 段选择子只是提供了一个索引.来进行访问GDT 或者LDT中的段描述符的. 而段描述符 包含了线性地址 空间的基地址 , 偏移量 确定了相对与基地址的字节地址. GDT表或者LDT表的线性地址都存储在 GDTR 寄存器与 LDTR寄存器 中. 通过上面我们可以得出几点概要
      1. 保护模式下.我们的内存访问其实都是查表. 查的是GDT 或者 LDT. 如何确定查询的是GDT 还是LDT 取决于段选择子的 全局或者局部的标志位 而查表其实就是 段选择当索引去GDT表中查询. 查到哪一项. 这一项保存的是 段描述符结构
      2. GDT或者LDT表中.保存的是段描述符结构 段描述符里面才真正的 描述了 段的基地址 访问特权 类型 和用法信息
      3. 访问GDT或者LDT 就要提供段选择子以及偏移地址.

    以上就是对GDT表或者 LDT表的描述 总结来说 GDT或者LDT 就是一块内存. 也可以看成一个数组. 数组的每一项其实保存的都是段描述符 段选择子就是下标 3.1.2 GDTR寄存器与GDT表了解. 根据Inter手册所属. GDTR寄存器 保存了 GDT的 32位基地址 和16位表界限 基地址指的就是GDT从0字节开始的线性地址.可以理解为就是数组首地址. 表界限.可以理解为就是数组的大小. 所以说GDTR 寄存器是一个48位寄存器 按照C语言结构来来表是就如下 struct GDTR { DWORD *GdtBase, SHORT limit; } LGDT 与SGDT 汇编指令 分别是用来获取保存 GDTR寄存器的. 电脑开机之后,通电之后.GDT就开始初始化了. 总结:

    • 1.GDTR是一个寄存器.保存的就是GDT的首地址以及一个长度. 根据长度可以确定一个GDT表示的内存有多大
    • 2.GDT是一个数组.数组里面保存的是段描述符结构 请不要搞混概念

在这里我们就可以用驱动程序来读取 GDT了.并且我们进行打印输出即可.

3.1.3 LDTR寄存器与LDT

LDTR寄存器 保存了 16位的段选择子 32位的基地址 16位的段界限(长度) LDT描述符的属性.

LLDT 和 SLDT 指令分别是用来 获取 和设置 LDTR寄存器的.

结构如下:

struct LDTR
{
  WORD select,
DWORD base,
  WORD  Limit,
WORD Attribute,
}

LDTR没有使用.所以简单了解下.

3.2 段选择子

3.2.1 段选择子介绍

上面了解了LDT表. GDT表. 以及分段的概念. 那么就该说一下如何查表. 段选择子是什么.

其实我们说了怎么多. 都是在了解 当汇编指令访问内存的时候 是怎么走的.

比如:

mov ebx,30h
mov fs,bx

官方的说法如下:

段选择子 是一个 16位 的段标识符. 他不是直接指向该段.而是作为一个索引.指向该

段的 段描述符 其实就是下标. 去GDT表查询. 查到段描述符 段描述符才描述了信息.

30就是段选择子. 这里的30赋值给了ebx中的bx位. bx赋值给fs. 可见部分是段选择子.所以会查表

看图了解

分为两个域 一个是 index(索引 3 - 15位表示) 一个是 标记以及特权位

根据官方手册所说. 索引位. 来表示 GDT表中的8192个描述符的某一项. 由此可以得知.GDT表不会超过 8192索引值 * 8 来索引 段描述符 由此可以得知. 段描述符是8个字节大小 且GDT表中的每一项是8个字节

手册还是 GDIT 第一项是不可用的. 也就是段选择子为0. TI 位设置为0 则被视为空选择子

当我们查询GDT表的时候如果为索引为0 那么CPU不会产生异常的. 但是如果我们以空选择子来查询的时候得出的段描述符来进行内存访问的时候.就会产生异常. 就会产生一个 通用保护异常.(GP)

还说了 对CS 以及SS赋予一个空选择副是的时候也会产生GP异常.

  • TI标记位 进行内存访问的时候,为 0 表示查询GDT. 为1则访问LDT表.
  • RPL 位 特权级别位. 总共三个等级 0 - 3 0为最高特权级. 描述了 RPL 与 CPL 之间的关系. 以及 段选择子所指向的段描述符

看到这里我们可以进行总结了

  • 1.段段选择子其实也是一个结构.表达了 GDT表的索引. 一个标志.查询GDT还是LDT. 一个特权级别.

所以我们可以进行手工解析了.

  • 2.GDT表中的第一项为不可用的.也就是空选择子

比如我电脑上的段选择子. 也就是段的可见部分. 以DS段为例子 段选择子为 0x0023

拆分成二进制

0000 0000 0010 0011    第一次拆分
0000 0000 0000 0       索引 = 0
0                      查询的GDT表
11                     RPL 特权级别  3

总结一下, 查询的索引 = 0; 查询的是GDT表 . RPL 特权级别 = 3;

2. 段描述符

2.1 段描述符介绍

段描述符是一个结构. 占用了8个字节大小 是GDT表或者LDT表中的一个数据结构

其实上面也说了当进行内存访问的时候,段选择子 当索引 查询GDT表.来得出段描述符表.

段描述符表示了 段基地址 段的大小 访问权限. 以及状态等信息.

看一下这个结构体信息

2.2 段描述符属性详解.

2.2.1 段寄存器与段描述符 一一的对应关系
  • 段寄存器中的段属性 与 段描述符中的段属性的对应关系 段寄存器我们知道其结构为 struct set { WORD Selector, //段选择子 16位 WORD Attribute,//段属性 16位 DWORD Base, //段基地址 32位 DWORD Limit, //段限长 32位 } 那么段属性与段描述符 是一种怎么样的对应关系? 请不要把 段寄存器结构段描述符搞混 看下图

对应着段描述符的 高4个字节中的 第八位 到 第23位

  • 段寄存器中的 32位的基地址 与 段描述符中的基地址对应关系 段描述符的基地址由三部分组成. 原因是CPU实在16位上扩展的.要兼容16位.32位 64位.所以只能不断扩展 看下图:

分别是由 高4字节的 24 - 31位 4-7位 低四个字节的 16 - 31位来组成的. 所以我们通过位运算.可以拼接处地址. 并且赋值给段寄存器的基地址域

  • Limit对应 这个就不用说了.看上图就知道. 第四个字节的 0-15位来做成的limit 高地址的 16 - 19位 也是. 合计20个字节

2.2.2 段属性中的位详解
2.2.2.1P位 高四个字节的第15位

p = 1 代表这个段描述符是有效的

p = 0 代表这个段描述符是无效的

2.2.2.2G位 粒度位 高四个字节的第23位

G位 根据inter手册所说如下

G = 0: 代表段limit(限长)是以字节为单位的. 根据段描述符我们知道段限长 为20位组成.

也就是0xFFFFF 大小. 如果G = 0; 那么是以字节为单位. 在 0xFFFFF最大表示了0xFFFFF个大小

意思就是说以字节为单位. 字节 * limit 来表示一个 一个段界限值.

G = 1: 段限长就是表示4kb大小. 4096-1 = 4095 也就是0xFFF 大小. 也就是一个页大小 段限长可以表示为 4kb * limit 来表示一个limit有多大.

2.2.2.3 S位

在系统中段描述符有很多形式.一种是系统段描述符 一种是数据或者代码段描述符

S = 1 表示代码段或者数据段描述符

s = 0 表示系统段描述符

根据G位 DPL位 S位 可以枚举出什么是代码段.什么是数据段.

DPL要么都为1 要么都为0

所以如果是代码段 G DPL S 组合起来 十六进制要么为9 要么为F

  • S = 1 代码段与数据段中的Type域讲解

TYPE 域

TYPE域 跟S位相关联. 看下TYPE域的4个位表示

S = 1的时候Type如下.是针对 代码或者数据段表示的. S= 0那么则是针对内核.

TYpe 域中的第 11位 = 0 确定了这是数据段

11位 = 1 确定了这是代码段

意思就是说. S位只是确定了你是代码段还是数据段,但是肯定不会是系统段描述符.

而Type域则真正决定了.你是代码段还是数据段.

所以我们可以在确定代码段或者数据段的前提下.精确的遍历出那些是代码段.那些是数据段.

在段描述符的第6个十六进制位 可以看. 入如果 > 9 就是代码段. 否则就是数据段

对于数段而言 控制位如下:

控制位了. E W A

W 表示可写

A 访问位

E 扩展位

A 位表示了你这个段描述符是否访问过.如果访问过.那么就会置1 否则就是0

W 表示可写位. 如果针对这个段描述符写过.则W位置1. 否则如果为0.那么表示这个数据段是不可写的.

E扩展位

扩展位 分为向下扩展 和向上扩展. 具体意思是啥.

首先了解下堆栈段. 堆栈比如是可读写的数据段. 而且大小需要动态变化. 所以我们使用向下扩展.扩展方向 = 1 就是向下扩展的意思. 意思就是ss.base + limit 这块表示的空间. 其余的空间 是有效的. 比如我们读取堆栈.那么除了ss.base可以读.其余空间也是有效的.也是可以读的.

E = 0 向上扩展: 那么意思就是有效空间只有 ss.base + limt空间大小.

如下图:

图是以fs为原型. 第一个表示向上扩展. 有效空间只有 fs.base + limit E = 0

E = 1 向下扩展. 表示有效空间除了fs.base + limit 之外都是可以访问的.

对于代码段而言 控制位如下:

控制位: C R A

A = 访问位. 同数据段一样.

R = 可读位 表示当前这个段是否是可读的.

C = 一致位

C = 1 一致代码段

C = 0 非一致代码段
  • S = 0 Type域讲解 上面我们说过S = 1 表示代码段域数据段.而且Type域也不同. 那么S = 0则表示是系统段描述符.那么看下如果是系统段描述符 根据Inter手册所属 当S位 = 0; 这个描述符位系统描述符 处理可以识别以下类型的系统描述符 局部描述符 LDT 任务段描述符 TSS 调用们描述符 中断门描述符 陷阱门描述 任务们描述 这些描述又可以分为两大类 一类是系统段描述符 另一个是门描述符. 系统段(LDT TSS) 门就是本身 看下Type解析

如果是系统段.那么Type域就进行组合解析了. 比如序号为11的一项 二进制为 1011 那么它则表示这个描述符为 32位的TSS 并且处于繁忙状态(Busy)

其实也是第五个字节.可以直接看第五个字节的数据表示形式.图片如下

  • db位 db位主要是影响段寄存器的操作
    • CS代码段的影响 D = 1 那么我们的汇编就采用32位的寻址方式 D = 0 那么我们的汇编就采用16位的寻址方式.

    ​ D = 0 汇编伪代码 ``` mov esp,dword ptr [di - 18] 操作的di 也就是16位寻址 可以用硬编码0x67进行修改来实现这种寻址 ``` D = 1 mov esp,dword ptr [ebp - 18] 操作的都是32位的寄存器 ​ SS段的影响 ​ 堆栈段影响也是分为16位与32位. 32位下.我们 push pop call 都会影响ESP. 也就是D = 1的情况下 ​ D = 0的话. 那么影响的就是SP 而不是ESP ​ 向下扩展的数据段 ​ 如果D = 1 那么数据段的limit寻址可以到达4GB. 如果D = 0; 那么就只能到达64kb 其实就是 2^32 与 2^16影响. d = 1 那么都按照32位来算. 否则就是按照16位寻址来算.

总结与实验

总结1 GDT的操作

1.1 获取GDT数组表.输出所有段描述符

遍历获取GDT表 只是获取GDT表的首地址. 地址里面的内容则是段描述符

#include <ntddk.h>

//读取GDT表并且遍历解析,



void DriverUnLoad(PDRIVER_OBJECT pDriverObj)
{
	UNREFERENCED_PARAMETER(pDriverObj);

}

NTSTATUS DriverEntry(
	PDRIVER_OBJECT pDriverObj, 
	PUNICODE_STRING pReg)
{
	UNREFERENCED_PARAMETER(pDriverObj);
	UNREFERENCED_PARAMETER(pReg);
	pDriverObj->DriverUnload = DriverUnLoad;
	char  gdt[6];
	short index = 0;
	int* pBase = NULL;
	short limit = 0;
	__asm
	{
		sgdt gdt;
	}
	//遍历GDT表
	limit = *(short*)gdt;         //获取limit

	//获取base
	pBase = (int *)*(int *)((char *)gdt + 2);
	/*DbgPrint("base = %p\r\n", index, gdt.BaseAddress);*/
	for (index = 0; index < limit; index++)
	{
		//每个GDT表存储的是 8个字节的段描述符.这里不解析段描述符.只是遍历内存
		if (index == 5)
		{
			break;
		}
		DbgPrint("GDT Array %d base = %x\r\n", index,(char *)pBase + index * 8);
		//输出结果 GDT Array 0 base = 80b95000
		//输出结果 GDT Array 1 base = 80b95008
	}

	return STATUS_SUCCESS;
}

封装成函数如下
PVOID GetGdtBaseByIndex(ULONG uIndex)
{
	

	/*
	利用汇编 sgdt 读取GDTR内容. 并且进行解析
	*/
	char  gdt[6];
	short index = 0;
	int* pBase = NULL;
	short limit = 0;
	__asm
	{
		sgdt gdt;
	}

	limit = *(short*)gdt;         //获取limit

	//获取base
	pBase = (int*)*(int*)((char*)gdt + 2);

	for (index = 0; index < limit; index++)
	{
		//每个GDT表存储的是 8个字节的段描述符.这里不解析段描述符.只是遍历内存
		if (index == uIndex)
		{
			return  (PVOID)((char*)pBase + (index * 8));
		}
	}

	return NULL;
}

1.2解析GDT表中的段描述符

总结2

2.1输出所有有效的段描述符 (P位)

原理: 根据P位来判断段描述符表是否是有效还是无效.解析P位进行输出即可.

伪代码

//我们要获取P位.就要建立段描述符结构体.来表示GDT中的内容.这样就很方便获取值了.用结构体位域表示
typedef struct _Segmentdescriptor
{
    ULONG limit0_15 : 16;
    ULONG LowBase16_31 : 16;
    ULONG LowHighBase0_7 : 8;
    ULONG Type : 4;
    ULONG sBit : 1;
    ULONG DplBit : 2;
    ULONG pBit : 1;
    ULONG HightLimit16_19 : 4;
    ULONG Avl : 1;
    ULONG Resever : 1;
    ULONG DbBit : 1;
    ULONG Gbit : 1;
    ULONG HightBase : 8;
}SEGMENTDESCRIPTOR, * PSEGMENTDESCRIPTOR;

BOOLEAN CheckSegmentdescriptorEffective(PVOID GDTValue)
{
	/*
		根据段描述符来判断P位是否= 1 = 1则是有效位
	*/

	KdBreakPoint();
	PSEGMENTDESCRIPTOR pSecDescr = NULL;
	if (GDTValue == NULL)
	{
		return 0;
	}
	//解析GDTVALUE 进行解析
	pSecDescr = (PSEGMENTDESCRIPTOR)GDTValue;
	ULONG isEffective = 0;
	isEffective = pSecDescr->pBit;
	if (isEffective&0x00000001)
	{
		return TRUE;
	}
	return FALSE;
}

2.2 获取所有数据段描述符 并且输出其Type域表示 (S = 1,Type 1 = 0)

原理: 根据S位. 来解析对应的Type位. S= 1代表可能是数据段或者代码段 解析其Type域的高第一位.来肯定是代码段还是数据段 并且根据不同段.对type域不同的解释.来解释type域

这里只是获取特定的一个段来判断是否是数据段.其实可以进行遍历. 这里逻辑就不写了.贴出简单代码

总共实现三步

1.获取是s 判断是系统段. 还是代码以及数据段

2.获取Type值

3.解析Type值 确定是代码 还是数据段

4.输出

//获取s位
INT CheckSegmentdescriptorIsSystem(PVOID GDTValue)
{
	/*
		根据段描述符来判断S位是否= 1 = 1则是代码数据段 0就是系统段
	*/

	KdBreakPoint();
	PSEGMENTDESCRIPTOR pSecDescr = NULL;
	if (GDTValue == NULL)
	{
		return -1;
	}
	//解析GDTVALUE 进行解析
	pSecDescr = (PSEGMENTDESCRIPTOR)GDTValue;
	ULONG isSystemSegmentdescriptor = 0;
	isSystemSegmentdescriptor = pSecDescr->sBit;
	if (isSystemSegmentdescriptor & 0x00000001)
	{
		return TRUE;
	}
	return FALSE;
}

//根据GDT数组的内容(也就是段描述符) 来获取对应的Type域
INT GetSegmentdescriptorTypeValue(PVOID GDTValue)
{
	/*
		根据段描述符来获取Type域的值即可.
	*/

	KdBreakPoint();
	PSEGMENTDESCRIPTOR pSecDescr = NULL;
	if (GDTValue == NULL)
	{
		return -1;
	}
	//解析GDTVALUE 进行解析
	pSecDescr = (PSEGMENTDESCRIPTOR)GDTValue;
	int TypeValue = -1;
	TypeValue = pSecDescr->Type;
	
	return TypeValue;
}

//根据Type值进行校验 是数据段还是代码段
INT CheckCodeAnDataByTypeValue(INT TypeValue)
{
	//获取TypeValue的高位.来判断高位是否是1
	if (TypeValue & 0x00000008)
	{
		// 高位位1 是代码段
		return TRUE;
	}
	else if (TypeValue & 0x00000007)
	{
		return FALSE;
	}
	else
	{
		return -1;
	}
}
//输出
NTSTATUS PrintTypeBlock(INT isSystem, INT uTypeValue)
{
	if (isSystem == 0)
	{
		//Type直接按照系统段解析
		switch (uTypeValue)
		{
		case 0x0:
			DbgPrint("Inter公司保留未定义\r\n");
			break;
		case 0x1:
			DbgPrint("有效的286TSS\r\n");
			break;
		case 0x2:
			DbgPrint("LDT \r\n");
			break;
		case 0x3:
			DbgPrint("286TSS忙碌\r\n");

			break;
		case 0x4:
			DbgPrint("286调用门\r\n");

			break;
		case 0x5:
			DbgPrint("任务门\r\n");

			break;
		case 0x6:
			DbgPrint("286中断门\r\n");

			break;
		case 0x7:
			DbgPrint("286陷阱门\r\n");

			break;
		case 0x8:
			DbgPrint("未定义(Inter保留)\r\n");

			break;
		case 0x9:
			DbgPrint("有效的386TSS\r\n");

			break;
		case 0xA:
			DbgPrint("有效的386TSS忙\r\n");

			break;
		case 0xB:
			DbgPrint("未定义(Inter保留)\r\n");

			break;
		case 0xC:
			DbgPrint("386调用门\r\n");

			break;
		case 0xD:
			DbgPrint("未定义(Inter保留)\r\n");

			break;
		case 0xE:
			DbgPrint("386中断门\r\n");

			break;
		case 0xF:
			DbgPrint("386陷阱门\r\n");

			break;
		default:
			DbgPrint("解析Type未找到相关定义\r\n");
			break;
		}
	}
	else
	{
		//Type按照数据段 以及代码段进行解析
		switch (uTypeValue)
		{
		case 0x0:
			DbgPrint("Index = 0 DType = 0 Data Seg E = 0 W = 0 A = 0 Read-Only\r\n");
			break;
		case 0x1:
			DbgPrint("Index = 1 DType = 0 Data Seg E = 0 W = 0 A = 1 Read-Only,accessed\r\n");
			break;
		case 0x2:
			DbgPrint("Index = 2 DType = 0 Data Seg E = 0 W = 1 A = 0 Read/Write\r\n");
			break;
		case 0x3:
			DbgPrint("Index = 3 DType = 0 Data Seg E = 0 W = 1 A = 1 Read/Write,accessed\r\n");

			break;
		case 0x4:
			DbgPrint("Index = 4 DType = 0 Data Seg E = 1 W = 0 A = 0 Read-Only,expand-down\r\n");

			break;
		case 0x5:
			DbgPrint("Index = 5 DType = 0 Data Seg E = 1 W = 0 A = 1 Read-Only,expand-down,accessed\r\n");

			break;
		case 0x6:
			DbgPrint("Index = 6 DType = 0 Data Seg E = 1 W = 1 A = 0 Read/Write,expand-down\r\n");

			break;
		case 0x7:
			DbgPrint("Index = 7 DType = 0 Data Seg E = 1 W = 1 A = 1 Read/Write,expand-down,accessed\r\n");

			break;
		case 0x8:
			DbgPrint("Index = 8 CType = 1 Code Seg C = 0 R = 0 A = 0 Execute-Only\r\n");

			break;
		case 0x9:
			DbgPrint("Index = 9 CType = 1 Code Seg C = 0 R = 0 A = 1 Execute-Only,accessed\r\n");

			break;
		case 0xA:
			DbgPrint("Index = A CType = 1 Code Seg C = 0 R = 1 A = 0 Execute/Read\r\n");

			break;
		case 0xB:
			DbgPrint("Index = B CType = 1 Code Seg C = 0 R = 1 A = 1 Execute/Read,accessed\r\n");

			break;
		case 0xC:
			DbgPrint("Index = C CType = 1 Code Seg C = 1 R = 0 A = 0 Execute-Only,conforming\r\n");

			break;
		case 0xD:
			DbgPrint("Index = D CType = 1 Code Seg C = 1 R = 0 A = 1 Execute-Only,conforming,accessed\r\n");

			break;
		case 0xE:
			DbgPrint("Index = E CType = 1 Code Seg C = 1 R = 1 A = 0 Execute/Read-Only,conforming\r\n");

			break;
		case 0xF:
			DbgPrint("Index = F CType = 1 Code Seg C = 1 R = 1 A = 1 Execute/Read-Only,conforming,accessed\r\n");

			break;
		default:
			DbgPrint("解析Type未找到相关定义\r\n");
			break;
		}
	}
	return STATUS_SUCCESS;
}

//调用代码

GetGdtBaseByIndex 函数就是获取GDT表数组中记录的地址.6就是下标.表示要获取 GDT表中哪一项
uIsSytem = CheckSegmentdescriptorIsSystem(GetGdtBaseByIndex(6));
	if (uIsSytem != -1)
	{
		if (uIsSytem == FALSE)
		{
			DbgPrint("当前段描述符是系统段\r\n");
		}
		else
		{
			INT TypeVaue = -1;
			INT IsCodeAnDataSeg = -1;
			TypeVaue = GetSegmentdescriptorTypeValue(GetGdtBaseByIndex(6));
			if (TypeVaue == -1)
			{
				DbgPrint("Type类型数据获取错误");
				return STATUS_SUCCESS;
			}
			IsCodeAnDataSeg = CheckCodeAnDataByTypeValue(TypeVaue);
			if (IsCodeAnDataSeg != -1)
			{
				if (IsCodeAnDataSeg == TRUE)
				{
					DbgPrint("当前段描述符是代码段\r\n");
					PrintTypeBlock(uIsSytem, TypeVaue);
				}
				else
				{
					DbgPrint("当前段描述符是数据段\r\n");
					PrintTypeBlock(uIsSytem, TypeVaue);
				}
			}
		

		}
	}

2.3 获取所有代码段描述符 并且输出其Type域表示(S = 1,Type 1 = 1)

原理: 根据S位. 来解析对应的Type位. S= 1代表可能是数据段或者代码段 解析其Type域的高第一位.来肯定是代码段还是数据段 并且根据不同段.对type域不同的解释.来解释type域

同上.只要进行遍历即可.

2.4 获取所有系统段描述符,并且输出其Type域表示(S = 0)

原理: 根据S位. 来解析对应的Type位. S= 0代表是系统段. 然后直接解析Type域来看看表示的是什么.

同上.只要进行遍历即可.

2.5 代码工程下载

代码工程放到百度盘里面.也不大.可以直接下载下来学习.是针对32位的.

链接:https://pan.baidu.com/s/1tHLBuus91fgYkjKTjiZcsw 提取码:rfbv

2.6 总结

1.总结GDT 以及如何用代码获取GDT

2.根据段描述符建立对应结构体.来进行解析GDT中数组的内容. 其实定义好了怎么解析都可以了.就很简单了.

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:http://www.cnblogs.com/iBinary/复制
如有侵权,请联系 yunjia_community@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • Linux从头学09:x86 处理器如何进行-层层的内存保护?

    保护模式与实模式最本质的区别就是:保护模式使用了全局描述符表,用来保存每一个程序(bootloader,操作系统,应用程序)使用到的每个段信息:开始地址,长度,...

    IOT物联网小镇
  • ucoreOS_lab1 实验报告

    进入 /home/moocos/ucore_lab/labcodes_answer/lab1_result 目录下

    Angel_Kitty
  • 又又又被内存坑了!!

    很多小伙伴在学操作系统的时候,学习到内存管理的部分时,都会接触到分段内存管理、分页内存管理。

    终码一生
  • 保护模式-第4讲-段-段跨越段权限

    ​ 这一点主要是了解下. 我们很多时候都听别人说 ring3 ring0 其实就是 CPU的等级划分.

    IBinary
  • 保护模式第六讲-IDT表-中断门 陷阱门 任务门

    之前所说 GDT表 中存储了一些段描述符. 比如有调用门 段描述符. 代码段段描述符. 数据段段描述符 TSS段段描述符

    IBinary
  • GDT,LDT,GDTR,LDTR 详解

    PS:原先实模式下的各个段寄存器作为保护模式下的段选择器,80486中有6个(即CS,SS,DS,ES,FS,GS)80位的段寄存器。由选择器CS对应表示的段仍...

    战神伽罗
  • Windows程序设计学习笔记(一)Windows内存管理初步

    学习Windows程序设计也有一些时间了,为了记录自己的学习成果,以便以后查看,我希望自己能够坚持写下一系列的学习心得,对自己学习的内容进行总结,同时与大家交流...

    Masimaro
  • 80386的分段机制、分页机制和物理地址的形成

    注:本分类下文章大多整理自《深入分析linux内核源代码》一书,另有参考其他一些资料如《linux内核完全剖析》、《linux c 编程一站式学习》等,只是为了...

    s1mba
  • 80386的分段机制、分页机制和物理地址的形成

    注:本分类下文章大多整理自《深入分析linux内核源代码》一书,另有参考其他一些资料如《linux内核完全剖析》、《linux c 编程一站式学习》等,只是为...

    bear_fish
  • 保护模式-第五讲-门-调用门

    但是长跳转只限于 段间跳转. 也就是一个段中. 因为在一个段中. 最后的偏移 加 段描述符.base才能构成真正的跳转地址.

    IBinary
  • 《一个操作系统的实现》笔记(2)--保护模式

    felix
  • Linux从头学10:理解了这三个概念,才能彻底理解【任务管理】和【任务切换】

    x86 系统中的保护模式,给系统的安全性提供了很大的保障,但是在我们之前的文章中,一直都淡化了特权级别这个概念。

    IOT物联网小镇
  • 全局描述符表

    进入保护模式以后,数据段、代码段等内存段不再是通过段寄存器获得段基址就可以使用,我们需要把段定义好,并且登记好,全局描述符表便是用来记录这些段信息的数据结构。

    shysh95
  • 内存分段与分页机制

    8086CPU以后总线寻址和CPU位数趋于一致,操作系统结构向下兼容,线性地址基址置0:

    sofu456
  • Linux中的段

    Intel 微处理器的段机制是从8086 开始提出的, 那时引入的段机制解决了从CPU 内部 16 位地址到20 位实地址的转换。为了保持这种兼容性,386 仍...

    changan
  • Linux从头学10:三级跳过程详解-从 bootloader 到 操作系统,再到应用程序

    不论是在 x86 平台上,还是在嵌入式平台上,系统的启动一般都经历了 bootloader 到 操作系统,再到应用程序,这样的三级跳过程。

    IOT物联网小镇
  • 通过linux0.11源码理解进程的虚拟地址、线性地址、物理地址

    进程的地址有三种,分别是虚拟地址(逻辑地址)、线性地址、物理地址。在分析之前先讲一下进程执行的时候,地址的解析过程。在保护模式下,段寄存器保存的是段选择子,当进...

    theanarkh
  • 操作系统(3)实验相关原理——bootloader启动uCore

    CS部分后面又4个0,相当于是左移了4位。总之就是要让CS左移4位之后加上EIP来得到要跳转的地址。

    太阳影的社区

扫码关注云+社区

领取腾讯云代金券