本文将从最简单的rtl例程函数讲解不同IRQL的真正原因,其他IRQL类似。
RtlCopyUnicodeString和RtlEqualUnicodeString看起来同属于rtl支持字符串例程,实际上它们可运行的IRQL还是不同的。这也许很不可思议,通过源代码解开其中的秘密。
二者IRQL不同
RtlCopyUnicodeString
RtlEqualUnicodeString
官方注解
NTSYSAPI VOID RtlCopyUnicodeString(
[in, out] PUNICODE_STRING DestinationString,
[in, optional] PCUNICODE_STRING SourceString
);
> 1. Src是optional, 如果为NULL, 那么只需要把Dest的Length设置为0即可。
2. 不管Src有多长,Dest的MaximumLength和Buffer都不会被改变。
3. 和普通String或Memory copy类似,拷贝长度不会超过Src和Dest的MaximumLength.
4. 最重要的一点,它指出,如果是DPC Level, Src和Dest都必须是非分页内存。当然,也 暗示了如果是DPC Level以下,Src和Dest可以是分页内存。
NTSYSAPI BOOLEAN RtlEqualUnicodeString(
[in] PCUNICODE_STRING String1,
[in] PCUNICODE_STRING String2,
[in] BOOLEAN CaseInSensitive
);
源代码解析
-
Src如果为空,直接设置Dest长度为0.
-
ASSERT_WELL_FORMED_UNICODE_STRING_IN检测字符长度是2的倍数。
-
判断Src的长度如果大于Dest的最大长度,选择较小的那个。
-
RtlCopyMemory执行真正的拷贝动作。
-
RtlCopyMemory是宏,不是函数。
-
-
此处memcpy是内核的,和C语言libc的实现类似,一般为汇编优化。
-
-
-
如果Dest的Length加上末尾的UNICODE_NULL不超过MaximumLength, 加上UNICODE_NULL.
-
最后ASSERT_WELL_FORMED_UNICODE_STRING_OUT再次判断Dest的MaximumLength是2的倍数且Length不超过MaximumLength.
总结一下,源代码的实现和如上官方文档完全对上。因为函数会直接操作Src和Dest内存,和官方解释第四条对上: 把责任推给了调用者。
RtlEqualUnicodeString 调用RTL_PAGED_CODE(PAGED_CODE)
此约束保证只能在PASSIVE_LEVEL执行。
看起来功能就是普通比较字符串或内存是否相等,并不复杂的样子,为什么内核要约束它只能在PASSIVE_LEVEL执行呢?
如果是大小写敏感,调用NLS_UPCASE.
Nls844UnicodeUpcaseTable变量被定义成分页内存的数据!! 这才是导致
RtlEqualUnicodeString为什么必须在PASSIVE_LEVEL的根本原因。
内核为什么要用不同IRQL?
对于ASCII码表,很小,几百字节,UNICODE表会大很多。内核不希望UNICODE表一直占用常驻内存,会降低可用内存,设计成可分页内存可节省内存开支。
当然,对于String操作例程,还有很多,都可以通过源代码了解官网对应内核API的解释。