首页 > 代码库 > IRQL和内核字符串处理函数

IRQL和内核字符串处理函数

内核字符串处理函数和IRQL

--by 张佩

系统中断级(IRQL)

借助于IRQL机制,系统实现了任务抢占功能。高中断级任务可以任意抢占低中断级任务的系统执行权,而低中断级任务必须等待所有高中断级任务都完成后,才能获取执行机会和相应系统资源。在单核系统中,系统中断级还被用做实现系统同步机制的手段,因为一颗核心的CPU在同一时刻,仅能运行一个线程,所以只要把当前正在使用Critical资源的线程IRQL提高到更高Level上,就可以避免其他线程和抢占自己的可能性。

系统功能被细分为许多系统模块,所谓的系统模块,都由特定的系统线程来运行之。这样,每个系统模块也就拥有了他所在线程的IRQL属性,比如执行换页任务的系统线程,运行在DISPATCH_LEVEL(2),我们就可以说,负责换页的系统模块运行在DISPATCH_LEVEL。

对换页模块而言, 因为它运行在DISPATCH_LEVEL,使得凡是在<2的IRQL上发生的页错误,都能够被它及时地抢占处理。而对于>=2的IRQL上发生的页错误,它无法抢占执行权以处理,使得系统只能报之以BSOD蓝屏。

今天我们要讨论的内容,正与此相关。如果一个内核DDI使用了换页内存,则就存在内存已经换出而需要换入的可能性,为了确保这个内核DDI一定能够稳定运行,我们必须保证它只能在DISPATCH_LEVEL以下的IRQL上被调用。否则就可能发生蓝屏。

这使得内核驱动程序,绝不敢乱调用内核DDI。在WDK文档中每个DDI说明的最后,都会标有可运行的IRQL级别,我们应该严格照此编写代码。

 

内核运行时库

在内核中,大多数处理字符串的C运行时库(Run-Time Library)函数如strcat/strcpy等都可以直接被直接拿来使用的,但WDK并不推荐这种方法。Windows内核暴露了许多基于C运行时库而实现的字符串处理函数。这些函数一般以Rtl*String*的形式命名,比如RtlStringCbCat/ RtlStringCchCat/ RtlCopyString等。

原则上讲,字符串处理是最基本的功能函数,不管代码运行在什么IRQL上,都有使用的需求,所以原则上不应该有IRQL的限制。但现实情况并非如此,下面是两个典型的例子:

Index

函数

IRQL

1

RtlCopyString:

Any Level (if both source and destination are resident)

2

RtlCchPrintf/RtlCchCat:

Passive_level

 表格中的第一种情况很好理解,它可以运行在any level上,而具体的level则依赖于输入参数的情况。看一下它的声明:

 

VOID  RtlCopyString( IN OUTPSTRING  DestinationString,  IN const STRING  *SourceString );

源和目的buffer都是由调用者提供的,它的实现只是字符串的互拷(src[i] = dest[i]),所以如果这两个缓冲区都是固化在物理内存中的,就可以放心大胆地在任意IRQL上使用了。相反,如果某个缓冲区并非固化于物理内存中,而有被换出的可能,那么这个函数就只能在APC_LEVEL或以下执行之。所以这类函数的IRQL Level,完全取决于调用者以何种方式调用。

 

令人疑惑的是第二种情况,WDK文档死死地把大部分字符串处理函数的IRQL定在最低一级PASSIVE_LEVEL上。是什么会导致了这种现象呢?

由于所有这些只能运行在PASSIVE_LEVEL上的函数都是内联函数,可以在内核头文件ntstrsafe.h中找到它们的定义。我最终发现了它们的一个共同点,就是内部会调用RtlString*Printf系列函数,而这个系列函数,只要稍微反汇编就可以发现,最后都会调用CRT函数sprintf。问题就出在这个sprint函数身上。

字符串格式化和NLS(国际语言支持)

CRT函数Sprintf就是所谓的字符串格式化函数。系统为了支持发布版本的国际化,在内部维持一张表,称为国际语言支持表(NLS table)。NLS表中保存了和地区有关的信息,比如货币、时间、日期格式,以及本地字符的codePage等。

在字符串格式化函数被调用的时候,系统需要参照NLS表来确定其格式化结构,比如对于日期“12月12号”,欲表达的意思是一样的,但中文和英文中的表示法就很不一样,需要通过NLS表来确定表示形式。由于Windows所支持的语言版本特别多,需要的NLS表的数量就极庞大,最终导致这些NLS表只能保存在Paged换页内存池中,而非Non-Paged非换页内存池。从而使得凡是有可能引用到NLS表的字符串处理函数,都只能运行在最低的IRQL上。

至此,真相昭然。最后,请大家看一张示例NLS表,此表取自FreeDOS系统,对应的是英语(美国):

技术分享

 

IRQL和内核字符串处理函数