`
runfeel
  • 浏览: 936058 次
文章分类
社区版块
存档分类
最新评论

RayCommand操作系统的实现笔记3--GDT的介绍

 
阅读更多

GDT是X86上操作系统的一个最基础的问题。这个文章只在介绍GDT的基本知识。并没有任何一个RayCommand版本对应这一段东西。因为实在是太基础了,我也不想单独拿这个作为一个Milestone。但是,下文中介绍的任何实现,均在RayCommand的最新版本中/kernel/driver/x86arch/GDT中,有对应的实现。本文主体翻译自这里。但是有一些自己的改变。如果想看原文,请参考英文版。

在Intel X86架构上,有很多保护内存访问的方法,使得用户程序禁止访问内核程序的内存,或者其他程序的内存。其中一个重要的方法是使用全局描述符表(Global Descriptor Table), 也就是GDT。GDT定义了某一段特定内存的权限。我们可以使用GDT中的一个字段,定义某一段内存不能被出了内核程序之外的程序访问。现代操作系统使用的是"分页"技术实现这一点。使用分页技术会具有更大的灵活性。GDT基本上可以说是段式的内存,但是X86平台中,必须设置GDT,也算是一个历史遗留的问题。在GDT中还可以设置"任务状态段"(Task State Segments, Tss)。这个TSS段可以进行硬件的任务切换,但是不再本篇文章的讨论范围中。并且,TSS也不是唯一的多任务的方法。

值得注意的是Grub在加载系统的时候,已经载入了默认的GDT.但是,如果你对GRUB GDT的内存区域进行复写,会导致GDT的失效,引发一个'triple fault'异常。如果要解决这个问题,我们需要自己建立GDT,并将GDT放到一个可控的不会复写的内存中。再载入自己的GDT。载入后,再将cs,ds,es等段寄存器,设置成GDT中对应字段的偏移。例如,cs中为代码段的偏移。如果GDT中,描述代码段的内存属性,位于第0x10处偏移的话,则将cs设置成0x10.(抑或,某些书上翻译的叫做段选择子,但我实际上觉得,这个东西只是简单的偏移而已,说的那么复杂会让他人产生困惑)

GDT本身是一个数组。它内部的每一个元素都是一个64位长的字段(原文为Entry,但是我觉得字段的意思更明确,当然实际上字段应该是Field,Entry应该是入口点)。每一个字段设置了一段内存的属性,权限等等。一个通常的规范是,GDT的第0个字段,应该是个NULL字段,也就是全为0的字段。没有任何一个CS,ES这样的段寄存器应该设置为0。由于GDT设置了权限,在越权访问的情况下,CPU会产生一个"General Protection"异常。

GDT中的每一个字段,同时说明了这段内存运行在什么状态下。究竟是运行在内核空间(Ring 0)还是用户空间(Ring 3)。当然,X86系统还有其他的Ring,但是那些大多数情况都不会用到。在Ring 3中,程序被限制只能执行一些基础的命令。例如在用户状态下,就不可以关中断。这实际上是对操作系统内核程序的一种保护。

在每一个GDT的字段中,均有一些基地址,偏移地址,和一些属性为,他们不是依次排列的,其内存中状态如下图所示。

在每一个GDT的字段中,有几位说明了他的权限和访问情况。如下图所示。

上图中,每一个位代表的意思如下。

  • Pr: Present Bit,当前是否在内存中的标志位。对于任何一个有效的代码段,都必须是1.
  • Privl: Privilege, 特权位,两位。标志着这段的Ring等级。最高级为Ring0(内核状态),最低级为Ring3(用户状态)。
  • Ex: Executable,是否可执行位。如果为1,则该段内存为可执行的代码,即代码段。否则为数据段。
  • DC: Direction bit/Conforming bit, 方向或一致性位。
    • 如果该段为代码段,则位表示方向,如果是0,则该段是向上增长的,反之则是向下增长的。换句话说,向下增长意味着偏移地址要大于基地址。
    • 如果该段为数据段,则该位表示一致性。即地位的代码段是否能够方位该数据段。
  • RW: Readable/Writeable位。如果该段为1,则对应代码段的话为可读,对应的数据段可写。注意的是,代码段永远是不可写的,数据段永远是可读的。
  • Ac:Accessed bit. 当CPU访问过的时候,设置这位为1,当然,初始化的时候,我们需要将这位设置为0.
  • Gr:Granularity bit.粒度位。如果是0,则表示在这个GDT中,任何地址单位都为Byte。如果是1,则表示其单位为4KB
  • Sz:Operand Size bit. 如果为0,该段为16位的段,也就是IP每次会取16位指令。为1,则为32位的段。

下面是一些示例代码,在操作系统中载入三个GDT字段。为什么是3个呢?和开始说的一样,第0个为NULL的字段,再加上一个数据段一个代码段正好三段。当我们准备好这三个字段组成的数组后,我们需要一个新的数据结构去加载它,这个数据结构叫做GDT的指针,是个48位的地址,里面包括GDT的内存地址和GDT的长度。

在GDT.c中,我们定义了GDT的一些数据结构和数据。

/* Defines a GDT entry. We say packed, because it prevents the
*  compiler from doing things that it thinks is best: Prevent
*  compiler "optimization" by packing */
struct gdt_entry
{
    unsigned short limit_low;
    unsigned short base_low;
    unsigned char base_middle;
    unsigned char access;
    unsigned char granularity;
    unsigned char base_high;
} __attribute__((packed));

/* Special pointer which includes the limit: The max bytes
*  taken up by the GDT, minus 1. Again, this NEEDS to be packed */
struct gdt_ptr
{
    unsigned short limit;
    unsigned int base;
} __attribute__((packed));

/* Our GDT, with 3 entries, and finally our special GDT pointer */
struct gdt_entry gdt[3];
struct gdt_ptr gp;

/* This will be a function in start.asm. We use this to properly
*  reload the new segment registers */
extern void gdt_flush();

/* Setup a descriptor in the Global Descriptor Table */
void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
    /* Setup the descriptor base address */
    gdt[num].base_low = (base & 0xFFFF);
    gdt[num].base_middle = (base >> 16) & 0xFF;
    gdt[num].base_high = (base >> 24) & 0xFF;

    /* Setup the descriptor limits */
    gdt[num].limit_low = (limit & 0xFFFF);
    gdt[num].granularity = ((limit >> 16) & 0x0F);

    /* Finally, set up the granularity and access flags */
    gdt[num].granularity |= (gran & 0xF0);
    gdt[num].access = access;
}

/* Should be called by main. This will setup the special GDT
*  pointer, set up the first 3 entries in our GDT, and then
*  finally call gdt_flush() in our assembler file in order
*  to tell the processor where the new GDT is and update the
*  new segment registers */
void gdt_install()
{
    /* Setup the GDT pointer and limit */
    gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
    gp.base = &gdt;

    /* Our NULL descriptor */
    gdt_set_gate(0, 0, 0, 0, 0);

    /* The second entry is our Code Segment. The base address
    *  is 0, the limit is 4GBytes, it uses 4KByte granularity,
    *  uses 32-bit opcodes, and is a Code Segment descriptor.
    *  Please check the table above in the tutorial in order
    *  to see exactly what each value means */
    gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

    /* The third entry is our Data Segment. It's EXACTLY the
    *  same as our code segment, but the descriptor type in
    *  this entry's access byte says it's a Data Segment */
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);

    /* Flush out the old GDT and install the new changes! */
    gdt_flush();
}

在上述代码中,GDTInstall为安装GDT的过程,每个gdt_set_gate是设置GDT中每个字段的函数。具体的设置方法是根据GDT

; This will set up our new segment registers. We need to do
; something special in order to set CS. We do what is called a
; far jump. A jump that includes a segment as well as an offset.
; This is declared in C as 'extern void gdt_flush();'
global _gdt_flush     ; Allows the C code to link to this
extern _gp            ; Says that '_gp' is in another file
_gdt_flush:
    lgdt [_gp]        ; Load the GDT with our '_gp' which is a special pointer
    mov ax, 0x10      ; 0x10 is the offset in the GDT to our data segment
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    jmp 0x08:flush2   ; 0x08 is the offset to our code segment: Far jump!
flush2:
    ret               ; Returns back to the C code!

的数据结构设置。最开始设置了一个NULL字段,之后设置的是代码段,最后设置的是数据段。细心的朋友会发现,GDTFlush并没有定义,这个函数的目的,是将新设置好的GDT加载给CPU。这段函数使用汇编进行书写。单独文件GDT_ASM.S,汇编器为NASM。代码如下:


由此,我们加载完了新的GDT,在汇编中,将数据段(0x10,因为是GDT中第三个字段,每个字段长64bit,也就是长0x08。第三个为0x08*(3-2) = 0x10 )设置给了ds,es等等。又使用jmp,将代码段0x08设置给了cs。

现在,只要在Main函数中调用GDTInstall,即可完成GDT的设置。

分享到:
评论

相关推荐

    NX二次开发UF-DRF-create-gdt-symbol 函数介绍

    NX二次开发UF_DRF_create_gdt_symbol 函数介绍,Ufun提供了一系列丰富的 API 函数,可以帮助用户实现自动化、定制化和扩展 NX 软件的功能。无论您是从事机械设计、制造、模具设计、逆向工程、CAE 分析等领域的专业...

    NX二次开发UF-DRF-ask-gdt-symbol-info 函数介绍

    NX二次开发UF_DRF_ask_gdt_symbol_info 函数介绍,Ufun提供了一系列丰富的 API 函数,可以帮助用户实现自动化、定制化和扩展 NX 软件的功能。无论您是从事机械设计、制造、模具设计、逆向工程、CAE 分析等领域的专业...

    NX二次开发UF-DRF-create-gdt-symbol-with-multiple-leaders 函数介绍

    NX二次开发UF_DRF_create_gdt_symbol_with_multiple_leaders 函数介绍,Ufun提供了一系列丰富的 API 函数,可以帮助用户实现自动化、定制化和扩展 NX 软件的功能。无论您是从事机械设计、制造、模具设计、逆向工程、...

    操作系统篇-分段机制与GDT、LDT.docx

    ### 操作系统篇-分段机制与GDT、LDT #### 一、概述 本文主要探讨了操作系统中保护模式下的分段机制及其关键组成部分——全局描述符表(GDT, Global Descriptor Table)与局部描述符表(LDT, Local Descriptor ...

    操作系统 mit jos lab1--4代码

    通过参与JOS的Lab1至Lab4,学生能够逐步学习和实现操作系统的基本组件,包括中断处理、内存管理、进程调度以及I/O设备控制。 Lab1主要聚焦在初始化环境和基本的X86汇编语言编程。在这个阶段,你会学习如何用汇编...

    一个64位操作系统的设计与实现(运行效果图)-0_一个64位操作系统的设计与实现_

    64位操作系统的设计与实现是计算机科学中的一个重要领域,它涉及到计算机硬件、软件工程、编译原理等多个技术层面。在本项目中,我们看到的是一个实现了64位操作系统的源码,以及其运行效果的展示。这样的系统能够...

    一个简单的操作系统c++编写.rar

    3. **设备管理**:操作系统需要与硬件设备交互,设备驱动程序扮演了这个角色。在这个简易系统中,可能会有简单的输入/输出(I/O)操作,如键盘、显示器的模拟。 4. **文件系统**:尽管是简单的系统,文件管理仍是...

    转载 纯nasm实现中文操作系统.txt

    根据提供的文件信息,本文将对纯NASM实现中文操作系统的核心技术点进行详细的解析与总结。 ### 一、项目概述 该文件描述了一个基于NASM汇编语言开发的中文操作系统的初步实现。NASM (Netwide Assembler) 是一个...

    30天开发操作系统 第 4 天 -文字显示与IDT/GDT初始化

    第五天

    GDT2.0+MYSQL实现TCPIP网络聊天室

    该系统充分利用了GDT2.0的高效性以及MySQL的强大数据库支持能力,并结合Linux操作系统下的多线程技术和套接字编程技术,实现了用户之间的实时交流。 #### 关键技术及实现 ##### Linux平台 - **成本低廉**: Linux...

    VSCode-GDT-Extension:GDT文件的Visual Studio Code扩展

    gdt自述文件 这是扩展名“ gdt”的自述文件。 写下简短描述后,我们建议包括以下各节。 特征 描述扩展程序的特定功能,包括扩展程序的屏幕截图。 图像路径是相对于此README文件的。 例如,如果扩展项目工作空间下有...

    操作系统 课程设计 geekos project2

    3. **文件系统**:Project2 可能会涉及简单的文件系统设计,比如如何组织磁盘上的数据,实现文件的创建、读写和删除操作。理解 FAT(文件分配表)或其他简单的文件系统结构是必要的。 4. **设备管理**:操作系统...

    哈工大操作系统-L3操作系统启动1

    在这一阶段,操作系统获取硬件信息,如内存大小,这通常是通过调用INT 0x15中断来实现的。然后,操作系统会从加载位置移动到内存的0地址,这是因为0地址被认为是内存的起始位置,便于后续的管理和访问。接着,系统...

    GDT.rar_Table_gdt

    - 权限级别(Ring):x86系统中有四个环,从0到3,0代表最高权限(如操作系统),3代表最低权限(用户程序)。 - 段选择子中的特权级别必须匹配当前CPU的执行级别,否则访问会被禁止。 - 通过访问权限字段,限制...

    国科大操作系统高级教程-2018

    3. **加载操作系统引导扇区**:BIOS会将操作系统引导扇区加载到内存地址0x7C00处,并将控制权交给该扇区。 **为何BIOS仅加载一个扇区,后续扇区由bootsect代码加载?** BIOS仅加载第一个扇区(引导扇区),后续...

    操作系统实验-第二次实验报告---副本.docx

    这个实验报告涵盖了操作系统中的一些重要概念和技术,包括保护模式、GDT、LDT、调用门、Ring3、分页机制、内存管理、页表切换和中断处理。通过这些实验,我们可以更好地理解和掌握操作系统的原理和应用。

Global site tag (gtag.js) - Google Analytics