《天书夜读:从汇编语言到windows内核编程》七 内核字符串与内存

时间:2021-09-16 01:14:25

1)驱动中的字符串使用如下结构:

1 typedef    struct _UNICODE_STRING{
2 USHORT Length; //字符串的长度(字节数)
3 USHORT MaximumLength; //字符串缓冲区长度(字节数)
4 PWSTR Buffer; //字符串缓冲区
5 }UNICODE_STRING,*PUNICODE_STRING;

         这个为UNICODE字符串,一个字符占用双字节。对应的有Ansi字符串:

1 typedef    struct  _STRING{
2 USHORT Length; //字符串的长度(字节数)
3 USHORT MaximumLength; //字符串缓冲区长度(字节数)
4 PSTR Buffer; //字符串缓冲区
5 }ANSI_STRING,*PANSI_STRING;

  内核程序是Unicode编码的,窄字符只在十分罕见的特殊场合使用。UNICODE_STRING不是空结束的,初始化和拷贝要使用指定的函数处理。

 

2)字符串初始化:UNICODE_STRING中的Buffer只是一个未分配内存的指针。定义常量字符串使用ntdef.h中的一个宏如下:

1 #include <ntdef.h>
2 UNICODE_STRING str;
3 RtlInitUnicodeString(&str,L”my first string!”);

  这种方式初始化字符串并不分配内存,不用当心内存释放问题。

 

3)拷贝字符串:使用RtlCopyUnicodeString进行拷贝,但是目的串必须有足够的缓冲区,否则只能部分拷贝。如:

1 UNICODE_STRING dst;            //目的字符串
2 WCHAR dst_buf[256]; //定义缓冲区
3 UNICODE_STRING src = RTL_CONSTANT_STRING(L”My source string!”);
4
5 //把目的字符串初始化为拥有缓冲区长度为256的UNICODE_STRING空串,如果不这样初始化,则dst默认长度为0
6 RtlInitEmptyString(dst,dst_buf,256*sizeof(WCHAR));
7 RtlCopyUnicodeString(&dst,&src); //字符串拷贝

 

4)链接字符串:RtlAppendUnicodeToString(UNICODE_STRING连接宽字节字符串)或者RtlAppendUnicodeStringToString(UNICODE_STRING连接UNICODE_STRING),如:

1 NTSTATUS status = RtlAppendUnicodeToString(&dst,L”my second string!”);
2 if ( status != STATUS_SUCCESS )
3 {
4 //链接失败
5 }

  NTSTATUS是常见返回值,STATUS_SUCCESS代表返回成功;否则返回一个错误码,这个连接函数在目标字符串空间不足时依然可以连接,只不过返回一个警告STATUS_BUFFER_TOO_SMALL。

 

5)字符串格式化输出:使用RtlStringCbPrintfW(头文件ntstrsafe.h,库文件ntstrsafe.lib)

1 #include <ntstrsafe.h>
2 WCHAR dst_buf[512] = {0};//任何时候都动态分配内存,这里还没讲,暂时用固定长度的缓冲区
3 UNICODE_STRING des;
4 RtlInitEmptyString(dst,dst_buf,512*sizeof(WCHAR));
5 NTSTATUS status = RtlStringCbPrintfW(
6 dst->Buffer,512*sizeof(WCHAR),L”file path = %wZ file size = %d\r\n”,&file_path,file_size);
7 dst->Length = wcslen(dst->Buffer)*sizeof(WCHAR);// RtlStringCbPrintfW以空结束,所以这里可以使用wcslen

  这个函数在目标缓冲区不足时返回STATUS_BUFFER_OVERFLOW,不能确定缓冲区要多长时,一般采用倍增尝试,直到返回STATUS_SUCCESS为止。

  UNICODE_STRING类型的指针,用%wZ可以格式化,但是在不能保证字符串以空结束时,必须避免使用%ws或者%s。其它的格式化形式同C语言。

  使用KdPrint输出格式化的调试信息时要使用下面的形式:

1 KdPrint((L”file path = %wZ file size = %d\r\n”,&file_path,file_size));

 

6)内存分配和释放:ExAllocatePoolWithTag(分配)、ExFreePool(释放)

 1 //定义一个内存分配标记
2 #define MEM_TAG ‘MyTt’
3 //目标字符串
4 UNICODE_STRING dst = {0};
5 //根据源字符串长度分配空间给目标字符串
6 dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NopagedPool,src->Length,MEM_TAG);
7 if(dst.Buffer == NULL)
8 {
9 //分配失败
10 status = STATUS_INSUFFICIENT_RESOURCE;
11 ……
12 }
13 dst.Length = dst.MaximumLength = src->Length;
14 status = RtlCopyUnicodeString(&dst,&src);
15 ASSERT(status == STATUS_SUCCESS)

  ExAllocatePoolWithTag的第一个参数NopagedPool,src指定锁定在物理内存(不被置换到硬盘),第二个参数是要分配的内存的长度,第三个参数是所谓的“内存分配标记”---用于检测内存泄漏。分配的内存如果不释放,则永远泄漏,除非重启(卸载驱动也没用),释放内存如下:

1     ExFreePool(dst.Buffer);
2 dst.Buffer = NULL;
3 dst.Length = dst.MaximumLength = 0;

  ExFreePool不能释放栈空间(临时变量),否则系统立即崩溃,导致蓝屏

 

7)LIST_ENTRY:一个双向循环链表结构,由内核开发者们开发,内核开发经常使用这个结果。比如说要使用到这样一个结构:

1 typedef struct{
2 PFILE_OBJECT file_object;
3 UNICODE_STRING file_name;
4 LARGE_INTEGER file_length;
5 }MY_FILE_INFO,*PMY_FILE_INFO;

  使用LIST_ENTRY可以如下构造该结构:

1 typedef struct{
2 LIST_ENTRY list_entry;
3 PFILE_OBJECT file_object;
4 UNICODE_STRING file_name;
5 LARGE_INTEGER file_length;
6 }MY_FILE_INFO,*PMY_FILE_INFO;

        即使用LIST_ENTRY作为链表头,使用流程:

 1 LIST_ENTRY    my_list_head;//我们的链表头
2 InitializeListHead(&my_list_head);//链表头初始化
3 //以下为插入操作
4 PMY_FILE_INFO my_file_info = (PMY_FILE_INFO)ExAllocatePoolWithTag(PagedPool,sizeof(MY_FILE_INFO),MEM_TAG);//新建一个节点
5 if (my_file_info == NULL) return STATUS_INSUFFICIENT_RESOURES;//内存分配失败
6 my_file_info->file_object = file_object;
7 my_file_info->file_name = file_name;
8 my_file_info->file_length = file_length;//赋值,填写数据成员
9 InsertHeadList(&my_list_head,(PLIST_ENTRY)&my_file_info);//执行插入
10 //以下为遍历操作
11 for ( p = my_list_info.Flink ; p != &my_list_head.Flink ; p = p->Flink)
12 {
13 PMY_FILE_INFO elem = CONTAINING_RECORD(p , MY_FILE_INFO, list_entry)
14 }
15 /*其中,CONTAINING_RECORD宏是通过LIST_ENTRY结构的指针,找个指定节点目标结构的指针,该宏被定义为:
16 #define CONTAINING_RECORD(address,type,field) (\
17 (type*)((PCHAR)(adress)-(ULONG_PTR)(&((type*)0)->field)))
18 */

 

8)LARGE_INTEGER:长长整型数据,这是一个共用体:

 1 typedef union _LARGE_INTEGER{
2 struct {
3 ULONG LowPart;
4 LONG HighPart;
5 };
6 struct {
7 ULONG LowPart;
8 LONG HighPart;
9 }u;
10 LONGLONG QuadPart;
11 }

  它可以方便的得到高32位和低32位,一把用QuadPart即可。实际使用时大部分是PLARGE_INTEGER类型。

 

9)自旋锁:目的是为了实现多线程同步。使用自旋锁如下:

1 KSPIN_LOCK my_spin_lock;
2 KeInitializeSpinLock(&my_spin_lock);//初始化
3
4 KIRQL irql;//中断级
5 KeAcquireSpinLock(&my_spin_lock,&irql);//进入区
6 //to do something…//临界区
7 KeReleaseSplinLock(&my_spin_lock,irql);//退出区

  锁要被所有进程共享,所以必须定义为全局变量、静态变量、或者分配在堆中。LIST_ENTRY提供一系列对加锁时的操作,只需要为每个链表定义并且初始化一个锁,然后使用对应提供的函数即可:

1 LIST_ENTER my_list_head;//链表头
2 KSPIN_LOCK my_list_lock;//链表锁
3 InitializeListHead(&my_list_head);//链表头初始化
4 KeInitializeSpinLock(&my_list_head);//链表锁初始化
5 //加锁时的插入操作
6 ExInsertlockedInsertHeadList(&my_list_head,(PLIST_ENTRY)&my_file_info,&my_list_lock);
7 //加锁时的移除操作
8 my_file_info = ExInsertlockedRemoveHeadList(&my_list_head,&my_list_lock);