内核呓语系列6 —— 中断和异常

2014-1-21 Nie.Meining Coding

前面讲了系统调用对于一些不支持快速系统调用的情况,实际上就是通过中断进入内核的。linux是int 80h,windows是int 2eh。然后通过IDT中指定的中断服务例程(例如KiSystemService)进行系统服务分发。

中断和异常,一个主要是异步触发(如硬件I/O中断,当然也不排斥int 2eh这种软件中断),一个主要是同步触发(如除0异常),但由于系统的处理方式是相同的,所以经常被看作是一个东西。通常32及以上的中断号留给了中断,32以下的除了2号是NMI不可屏蔽中断以外,都留给了异常。这一篇不对中断的硬件原理做说明,仅围绕内核相关的东西展开介绍。

首先需要说明的是,对于多核处理器,每个核都有自己的IDT,因此无论是添加新的中断还是做IDT hook都得注意这个问题。此外还需要注意由于中断的优先级很高,不少操作是受限的。这里就涉及到IRQL的问题了。相信很多写驱动的朋友都碰到过IRQL_NOT_LESS_OR_EQUAL蓝屏的问题……

Windows将APIC的中断优先级映射到了自己的一套IRQL中断请求级上。任何时刻CPU都必然处于某个级别:

nhyy6_1.png

IRQL数值越大优先级越高,处理器始终只能被高级别的IRQL中断打断。其中一些比较特殊的IRQL有:

0:PASSIVE_LEVEL,优先级最低也是最安全的运行模式,普通线程,包括所有用户模式代码都运行在该IRQL上,因此能被任何高IRQL的事情打断;

1:APC_LEVEL仅比PASSIVE高,因此插APC可打断目标线程执行(APC留到下一章讲);

2:DISPATCH_LEVEL,比APC优先级高,但比所有的硬件中断优先级低,因此适合用于中断后端(即DPC,同样留到下一章讲)。此外线程调度器也是在该IRQL上执行,因此在一些内核Hook的代码中,经常可以看到在hook前后改变IRQL来防止竞争。这个方案比暴力关中断要好,原因后文会提到。不过需要注意,多核处理器需要提高每个核心的IRQL。可参考本系列上一篇文章《系统调用hook》。

3 —— 31:映射到硬件中断上

正因为线程调度代码运行在DISPATCH_LEVEL上,因此在高IRQL代码中,比如中断例程中,不能做任何与低IRQL代码同步的操作,即不能做任何等待动作(因为无法交出处理器)。间接的结果是,不能访问任何换页区内存,因为换页动作属于磁盘I/O,一定会有等待动作。这也是著名的IRQL_NOT_LESS_OR_EQUAL蓝屏的主要原因。

IRQL只是Windows中的概念,linux内核相对简单粗暴,只区分“能睡”或“不能睡”。运行在进程上下文中就能睡,中断上下文中就不能睡。中断上下文又称“原子上下文”(因为不能睡),包括中断处理例程和中断后端,中断后端留到下一章讲。能睡的时候就能使用大部分内核服务,包括等待操作和让出处理器(类似Windows的DISPATCH_LEVEL以下),不能睡则……真的不能睡(类似Windows中的DISPATCH_LEVEL)。Windows中可以通过KeGetCurrentIrql函数检测当前IRQL,或者直接从KPCR里读出;Linux可以通过in_interrupt()检测当前模式是否在中断上下文中。

另外,Windows和Linux一样,由于中断是异步产生的,因此执行中断例程时的当前进程可能是任意进程,因此内核栈不一定够用,这个在写代码时需要特别注意……对Linux来讲,编译内核时打开一个选项后,每个cpu核会有一个专门的中断专用栈,这样极大减小了内核栈的压力。

在扩展性方面,Windows内核通过“中断对象”管理中断,中断对象封装了中断处理历程,可以连接到中断向量上,因此方便一个中断向量连接多个中断对象实现串行多个中断处理历程,当然也方便了部分rootkit做中断过滤。Linux也提供了内核函数注册中断例程,同样可以实现多个中断处理例程共享一个中断号,并顺序串行执行。

最后再讲讲中断开关指令sti和cli的问题。不少驱动中,为了禁止线程调度会简单地通过cli指令关闭中断,并在完成工作后使用sti指令重新开启中断。这样做其实并不安全,除了之前提到的多核问题外,更重要的是,cli之后并不一定真的需要sti,万一cli之前本身就是cli的状态呢?针对这个问题,linux的解决方案是:local_irq_save(flags)/local_irq_restore(flags)。

针对多核的问题,如果要禁用/启用所有处理器核心的中断,Linux曾经提供过一组内核函数li()/sti()。由于这两个内核函数非常方便,而且还能当锁用,于是在驱动代码中泛滥了……因此接下来被取缔了(Linux不是商业系统,不太担心向后兼容的问题,因此强迫症比较严重)。不过要禁用/启用所有处理器的某个特定中断还是有办法的:void disable_irq(unsigned int irq)/void enable_irq(unsigned int irq)。不过这种手段同样比较野蛮,不提倡。

讲完中断简单讲讲异常。异常是调试的基础,Windows内核实现了一套两次机会的异常分发机制,极大的方便了软件调试(以及软件逆向……)。两次机会的具体过程就不细讲了,网上到处都是相关资料。这里只讲两个比较有意思的地方:

1. 用户模式异常两次机会借助了进程的调试端口和异常端口:

nhyy6_2.png

这实际上是一个LPC机制,LPC留到以后讲进程间通信的时候再细讲吧;

2. 对于Windows来讲,内核模式的异常是必须被处理的,否则进入KeBugCheckEx,绘制蓝屏……而Linux不太一样。我第一次测试Linux一个内核漏洞的时候,看到桌面右下角弹个框,告诉我内核崩溃了……当时对Linux还很不了解,觉得这货太牛了。后来了解到,其实当内核代码出现异常时,只要异常不在关键进程中(不在init和idle进程中),Linux内核会杀掉该进程,并继续执行……不过我严重怀疑这个时候内核已经不稳定了,还是Windows的蓝屏来得实在。

今天先到这里了,改天再接着讲中断后端(softirq、DPC一类)和APC。

评论:

EmazuelSab
2017-09-05 18:45
viagra effects on women
http://viagra-withoutadoctor.net - viagra without a doctor prescription
  viagra spam filter ciallis cia11is
<a href="http://viagra-withoutadoctor.net">viagra without prescription
</a> - indian generic viagra
viagra buy last post
你这个
2014-11-06 11:19
给套源码研究下可以不
Nie.Meining
2014-11-07 09:48
@你这个:linux内核源码可以直接下载到,windows的话就WRK + IDA + WinDbg
你这个
2014-11-06 11:19
给个源码研究一下可以不
竹萃集
2014-08-24 13:05
我是学C 出身的
Nie.Meining
2014-08-31 20:54
@竹萃集:然后转行卖化妆品么……
mlyknown
2014-11-12 23:51
@Nie.Meining:哈哈,博主写的好好,希望多写写啦。
Nie.Meining
2014-11-19 10:59
@mlyknown:多谢多谢:)

发表评论:

Powered by emlog