保护模式篇——中断与异常和控制寄存器

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?上一节教程学会了吗?上一节课的练习做了吗?没有的话就不要继续了。


🔒 华丽的分割线 🔒


练习及参考

本次答案均为参考,可以与我的答案不一致,但必须成功通过。

1️⃣ 在10-10-12分页模式下体会TLB的存在。要求:通过代码挂物理页,不能通过Windbg挂。原物理页挂完写入值读取,然后把地址换物理页继续读取,看看值是否发生变化。然后用INVLPG指令之后再看看值是否变化。

🔒 点击查看答案 🔒


  此题目一看就需要一个调用门实现提权,如果有所忘却请翻看前面的教程。

  首先构造一个调用门:eq 8003f098 0040EC0000081250,注意调用门和裸函数的地址一致。然后运行代码,可以得到下面的结果:

刷新缓存

不刷新缓存

  通过这个实验,看到TLB的作用了吧?


🔒 点击查看代码 🔒
#include "stdafx.h"
#include <iostream>
#include <windows.h>

int isinv=0;
int num1=0;
int num2=0;

void __declspec(naked) callgate()
{
    _asm
    {
        push 0x30;
        pop fs;

        pushad;
        pushfd;

        mov edi,0xC0000000;

        mov eax,0x10000;
        shr eax,10;
        add eax,edi;
        mov eax,dword ptr ds:[eax];
        mov dword ptr ds:[edi],eax;

        mov edx,dword ptr ds:[0];
        mov [num1],edx;

        mov eax,isinv ;
        test eax,eax;
        jz end;

        invlpg dword ptr ds:[0];

end:

        mov eax,0x20000;
        shr eax,10;
        add eax,edi;
        mov eax,dword ptr ds:[eax];
        mov dword ptr ds:[edi],eax;

        mov edx,dword ptr ds:[0];
        mov [num2],edx;

        popfd;
        popad;
        retf;
    }
}



int main(int argc, char* argv[])
{

    LPVOID page1 = VirtualAlloc((LPVOID)0x10000,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    LPVOID page2 = VirtualAlloc((LPVOID)0x20000,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);

    if (!page1||!page2)
    {
        puts("分配内存失败!!!");
        VirtualFree(page1,0,MEM_FREE);
        VirtualFree(page2,0,MEM_FREE);
        system("pause");
        return 0;
    }

    *(int*)page1 = 0x12345;
    *(int*)page2 = 0x67890;

    puts("是否清理缓存?");
    scanf("%d",&isinv);


    const char buffer[6]={0,0,0,0,0x9B,0};

    _asm
    {
        push fs;
        call fword ptr [buffer];
        pop fs;
    }

    printf("第一次挂页的值:%x\n换页后的值:%x\n",num1,num2);

    VirtualFree(page1,0,MEM_FREE);
    VirtualFree(page2,0,MEM_FREE);

    system("pause");
    return 0;
}

中断

  中断通常是由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知CPU有事情需要处理,因此又叫中断请求,英文为Interrupt Request。中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程,中断处理程序由哪有IDT表决定。
  80x86有两条中断请求线:非屏蔽中断线,NMI,全称NonMaskable Interrupt和可屏蔽中断线,INTR,全称Interrupt Require

不可屏蔽中断

  什么是不可屏蔽中断CPUEFLAG之中有一个位,它是IF位。如果它被置0。如果有可屏蔽中断告诉CPU有中断来了,你能先执行我的代码呢?可是IF位是0,对不起,我听不见。左耳朵进,右耳朵出。反之,我会处理。常见的不可屏蔽中断有电脑长按关机、键盘输入等等。当非可屏蔽中断产生时,CPU在执行完当前指令后会里面进入中断处理程序,非可屏蔽中断不受那个位的影响,一旦发生,CPU必须处理。为了方便观看,给个EFLAG图解:

  那么CPU是如何处理我们的不可屏蔽中断呢?我们先来看如下表格:

(IDT表)中断号 NMI 说明
0x2 不可屏蔽中断 80x86 中固定为 0x2

  如果处理不可屏蔽中断,CPU会调用2号中断。涉及的IDT表和中断门的知识如果忘却请查看前面的教程。

可屏蔽中断

  什么是可屏蔽中断,我就不赘述了。在硬件级,可屏蔽中断是由一块专门的芯片来管理的,通常称为中断控制器。它负责分配中断资源和管理各个中断源发出的中断请求.为了便于标识各个中断请求,中断管理器通常用IRQ,全称为Interrupt Request,后面加上数字来表示不同的中断。
  在Windows中,怎么查看某个可屏蔽中断的IRQ。可以在计算机管理中查看。我以键盘的输入的IRQ为例,如下图所示,可以看到它的IRQ0x1

  那么CPU是如何处理我们的可屏蔽中断呢?我们先来看如下表格:

(IDT表)中断号 IRQ 说明
0x30 IRQ0 时钟中断
0x31-0x3F IRQ1-IRQ15 其他硬件设备的中断

  如果自己的程序执行时不希望CPU去处理这些中断,可以用CLI指令清空EFLAG寄存器中的IF位,用STI指令设置EFLAG寄存器中的IF位。
  硬件中断与IDT表中的对应关系并非固定不变的,可以参考白皮书的Chapter 10 Advanced Programmable Interrupt Controller(APIC)进行了解。

异常

  异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页等。中断与异常之间有一些相似之处,但它们是不一样的:中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的;而异常来自于CPU本身,是CPU主动产生的。INT N虽然被称为“软件中断”,但其本质是异常,EFLAGIF位对INT N是无效。

异常处理

  无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表。常见的异常处理程序如下表所示:

错误类型 (IDT表)中断号
页错误 0xE
段错误 0xD
除零错误 0x0
双重错误 0x8

  有些异常比较特别,我们在来略微讲解一下:

缺页异常

  缺页异常当PDE/PTE的P=0时或当PDE/PTE的属性为只读但程序试图写入的时就会触发。一旦发生缺页异常,CPU会执行IDT表中的0xE中断处理程序,由操作系统来接管。
  你或许不清楚操作系统来接管。一个程序要是使用内存,必须有一个物理页。但物理页被挂上之后,这个物理页也不一定是永久属于你的。如果不经常用,操作系统看到后,就操作PDE/PTE的P位为0,把它放到称之为虚拟内存交换文件之中。毕竟内存宝贵,不能养“闲人”嘛。当这个程序过了好长时间又想要它了,结果发现PDE/PTE不合法,触发缺页异常。然后操作系统过来,通过查看PDE/PTE看看是不是我自己裁的员(如下图所示),如果是的话我再招一个物理页,然后把数据写进入,然后重新挂上,然后告诉CPU我处理妥善,继续干活,然后程序就像没啥事情一样正常使用。可以说,缺页异常无时无刻发生着。

  那么我们如何查看虚拟内存交换文件呢?如何设置它的大小呢?看下面的示意图就知道了。

文件位置

大小设置

控制寄存器

  控制寄存器用于控制和确定CPU的操作模式。控制寄存器有Cr0Cr1Cr2Cr3Cr4Cr1被保留了,Cr3用于页目录表基址,其他的将继续详细讲解。

Cr0

  Cr0是一个十分重要的寄存器,可以说它是总开关的集合体。如下图所示:

  PE位是启用保护模式(Protection Enable)标志。若PE = 1是开启保护模式,反之为实地址模式。这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么PEPG标志都要置位。
  PG位是启用分页机制。在开启这个标志之前必须已经或者同时开启PE标志。PG = 0PE = 0,处理器工作在实地址模式下。PG = 0PE = 1,处理器工作在没有开启分页机制的保护模式下。PG = 1PE = 0,在PE没有开启的情况下无法开启PGPG = 1PE = 1,处理器工作在开启了分页机制的保护模式下。
  WP位对于Intel 80486或以上的CPU,是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作;当CPL < 3的时候,如果WP = 0可以读写任意用户级物理页,只要线性地址有效。如果WP = 1可以读取任意用户级物理页,但对于只读的物理页,则不能写。

Cr2

  当CPU访问某个无效页面时,会产生缺页异常,此时,CPU会将引起异常的线性地址存放在CR2中,如下图所示:

Cr4

  Cr4的结构如下图所示:

  VME用于虚拟8086模式。PAE用于确认是哪个分页,PAE = 1,是2-9-9-12分页,PAE = 010-10-12分页。PSE是大页是否开启的总开关,如果置0,就算PDE中设置了大页你也得是普通的页。

小节

  有些结构的位我并没有详细介绍,详情请查看白皮书的控制寄存器的篇章,如下图所示:

练习

本节的答案将会在下一节进行讲解,务必把本节练习做完后看下一个讲解内容。不要偷懒,实验是学习本教程的捷径。

  俗话说得好,光说不练假把式,如下是本节相关的练习。如果练习没做好,就不要看下一节教程了,越到后面,不做练习的话容易夹生了,开始还明白,后来就真的一点都不明白了。本节练习不多,请保质保量的完成。

1️⃣ 分析IDT表中0x2号中断的执行流程。
2️⃣ 分析IDT表中0x8号中断的执行流程。

下一篇

  保护模式篇——总结与提升

posted @ 2021-10-24 20:23  寂静的羽夏  阅读(1158)  评论(2编辑  收藏  举报