何谓双误
双误就是CPU是报告一个比较严重的异常后,在异常处理过程中,又发生了一个严重的异常(CPU无法处理)。
蛛丝马迹
利用!process
查看当前进程的信息,其中有一项为DirBase(页目录基地址)
。
利用r CR3
命令来查看CR3寄存器内容,CR寄存器一共存在四个:CR0-CR3。保存着全局性和任务无关性。其中CR3保存的内容是进程的页目录物理地址。理论上CR3的值和DirBase是一致的,在切换任务时,CR3寄存器的内容随之改变。
初现端倪
我们在接到警告的时候,发现CR3寄存器的值和通过!process
查看的DirBase的值不一样,在理论上这是不应该发生的。但是怎么就发生了呢?
在发生异常之后,CPU在内部,使用硬件相关的线程切换技术,切换CR3寄存器,但是系统却没有来得及及时更新已经发生改变的页目录基地址。造成CR3和DirBase数值不同。
在CPU控制区(FS指向的段),保存着每个CPU的当前线程结构。在那个线程结构中,保存的是所属进程的进程信息。利用如下方法获取当前进程信息
时光倒流
任务门,就是登记在IDT表中的一种特殊地址。CPU可以根据这个门所指向的任务状态段(TSS)来切换指定的任务线程。例如IDT中的8号表项处的任务门来切换处理双误的新线程。利用!pcr命令
得到当前线程的TSS,观察其内容。
Backlink成员是前一个任务的段选择子。利用.tts [序号]
可以切换到前一个线程了。
|
|
宝贵的内核态栈
多数线程都有两个栈,一个用户态栈,一个内核态栈,用户态栈大小为1M,一般不会造成溢出,但是内核态栈只有十几千字节到几十千字节,相对来说也是比较容易造成溢出的。
课后实验
是一个dump文件,我们使用命令!analyse -v
分析一下蓝屏产生的原因。可见注释的地方,产生了一个停止码。而带来的异常是double_fault。
由于windbg给出的警告说明了CR3和DirBase值不相同,利用r CR3
和!prcess 0 0
查看对应的数据。发现两者数据确实不同。原因见<初见端倪>一节。
当使用!process 0 0
遍历系统所有进程,发现system进程的Dirbase数据为00185000。这时候,我们大致了解到了事情的经过,程序ImBuggy.exe是发生双误异常后,CPU内部利用硬件机制将页目录基地址改变为00185000,但是OS并没有及时作出调整,产生了error,但是双误是如何产生的呢?
先使用.sympath c:\gedu\xxx
设置符号路径,然后加载符号(提前需要设置系统符号命令)。
使用knvL
命令回溯栈。n:显示行号,v:显示调用约定,L:省略源代码。0号栈是产生蓝屏的系统函数,1号栈是处理双误异常的异常处理函数KiTrap08(陷阱处理函数,08代表双误异常)。剩下的是RealBug!StackOverflow是产生双误的函数。
但是,windbg并没有显示完全栈回溯,使用kvnL 1000
回溯1000次调用。
在上面的栈回溯,可以得出,1.#3d2的返回地址接近用户态。2.根据栈回溯:程序递归调用了StackOverflow。
在栈回溯,可以看到双误处理函数的TSS:28,TSS:28表示的是前一个线程的TSS段选择子是28,利用.tss 28
可以查看蓝屏前的线程信息。可以发生是StackOverflow函数发生了双误异常。接下来,我们看一下发生异常的原因是什么。
如下图,在StackOverflow中递归调用了StackOverflow函数,会开辟处12个字节的栈空间。
根据栈回溯信息,发现从#002到#3CB一共调用了969次。产生了969*12=11628个字节大小栈空间。
利用!thread
查看线程的栈空间信息。最后一行:栈空间大小:cf6d1000-cfce000=3000,所需空间不够,在看current:cf6d0d8c明显不在cf6d1000-cfce000区间内,判断是栈溢出。