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系统,对应的是英语(美国):

时间: 2024-10-15 16:47:21

IRQL和内核字符串处理函数的相关文章

Windows内核函数(1) - 字符串处理函数

1.ASCII字符串和宽字符串 打印一个ASCII字符串: CHAR* string = “Hello”; KdPrint((“%s\n”, string));        //s为小写 打印一个宽字符字符串 WCHAR* string = L”Hello”; KdPrint((“%S\n”,string));         //s为大写 2.ANSI_STRING字符串与UNICODE_STRING字符串 ANSI_STRING: typedef struct _STRING {  USH

【转】内核安全字符函数

原帖 驱动开发中使用安全字符串函数 一.前言 大量的系统安全问题是由于薄弱的缓冲处理以及由此产生的缓冲区溢出造成的,而薄弱的缓冲区处理常常与字符串操作相关.c/c++语言运行库提供的标准字符串操作函数(strcpy, strcat, sprintf等)不能阻止在超出字符串尾端的写入. 基于Windows XP SP1以及随后的操作系统的Windows DDK版本提供了安全字符串函数(safe string functions).这类函数被设计的目的是用来取代相同功能的c/c++标准函数和其它微软

数据结构——算法之(012)( linux C 所有字符串操作函数实现)

题目:实现linux C下常用的字符串操作函数 题目分析: 一.面试中可能经常遇到这样的问题:比如strcpy.memcpy.strstr 二.参考了linux 内核代码,对linux大神表示感谢,代码写得相当精致,这里拿来与大家分享吧 算法实现: /* * linux/lib/string.c * * Copyright (C) 1991, 1992 Linus Torvalds */ /* * stupid library routines.. The optimized versions

linux中字符串转换函数 simple_strtoul

Linux内核中提供的一些字符串转换函数: lib/vsprintf.c [html] view plain copy print? 1. unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base) 2. unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base) 3. long simple_st

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

面试题之java 编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 要求不能出现截半的情况

题目:10. 编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串. 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”. 一.需要分析 1.输入为一个字符串和字节数,输出为按字节截取的字符串-------------->按照字节[byte]截取操作字符串,先将String转换成byte类型 .2.汉字不可以截半----------------------------------

PHP内置的字符串处理函数

字符串的特点    1.其他类型的数据用在字符串类型处理函数中,会自动将其转化成字符串后,在处理 <?php echo substr("abcdefghijklmn",2,4),"<br>"; //cdef //使用数字会自动转化为字符串 echo substr(123456,2,4); //3456 ?> 2.可以将字符串视为数组,当做字符集合来看待 <?php $str="abcdefg"; //下面这两种方法都

Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938396.html 在基本分析完内核启动流程的之后,还有一个比较重要的初始化函数没有分析,那就是do_basic_setup.在内核init线程中调用了do_basic_setup,这个函数也做了很多内核和驱动的初始化工作,详解

Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938393.html 在分析start_kernel函数的时候,其中有构架相关的初始化函数setup_arch. 此函数根据构架而异,对于ARM构架的详细分析如下: void __init setup_arch(char **cmdlin