6、Windows驱动开发技术详解笔记(2) 基本语法回顾
时间:2010-10-18 来源:edwardlewiswe
Unicode 字符串有一个结构体定义如下:
typedef struct _UNICODE_STRING {
USHORT Length; // 字符串的长度(字节数)
USHORT MaximumLength; // 字符串缓冲区的长度(字节数)
PWSTR Buffer; // 字符串缓冲区
} UNICODE_STRING, *PUNICODE_STRING;
需要注意的是,当我们定义了一个UNICODE_STRING 变量之后,它的Buffer 域还没有分配空间,因此我们不能直接赋值,好的做法是使用微软提供的Rtl 系列函数。
UNICODE_STRING str;
RtlInitUnicodeString(&str, L"my first string!");
或者如下所示:
#include <ntdef.h>
UNICODE_STRING str = RTL_CONSTANT_STRING(L"my first string!");
与ring3 不同,我们的UNICODE 字符串并不是以“\0”来表示字符串结束的,而是依靠UNICODE_STRING 的Length 域来确定。
1)字符串的很多操作都有相应的函数,例如字符串的复制可以使用RtlCopyUnicodeString函数,字符串的比较可以使用RtlCompareUnicodeString 函数,字符串转换成大写可以使用RtlUpcaseUnicodeString 函数(没有转换成小写的),字符串与整数数字互相转换分别可以使用RtlUnicodeStringToInteger 和RtlIntegerToUnicodeString 函数。
比如在输出日志记录的时候,我们往往同时涉及数字、字符等信息,在C语言中我们可以使用sprintf 和swprintf 函数来完成任务,这两个函数在驱动中仍然可以使用,但很不安全,因为有许多C 语言的运行时函数都是基于Win32 API 的,在驱动中绝对不能使用,如果我们不清楚哪些可以使用哪些不能使用,就都不要使用,而使用微软推荐的Rtl 系列函数。对应sprintf 的功能函数是RtlStringCbPrintfW,它需要包含头文件“ntstrsafe.h”和静态连接库“ntsafestr.lib”。
http://msdn.microsoft.com/en-us/library/ff561817%28VS.85%29.aspx
WCHAR buf[512] = { 0 };
UNICODE_STRING dst;
NTSTATUS status;
……
// 字符串初始化为空串。缓冲区长度为512*sizeof (WCHAR)
RtlInitEmptyString(dst,dst_buf,512*sizeof(WCHAR));
// 调用RtlStringCbPrintfW 来进行打印
status = RtlStringCbPrintfW(
dst->Buffer,L”file path = %wZ file size = %d \r\n”,
&file_path,file_size);
//这里调用wcslen 没问题,这是因为RtlStringCbPrintfW打印的字符串是以空结束的。
dst->Length = wcslen(dst->Buffer) * sizeof (WCHAR);
RtlStringCbPrintfW在目标缓冲区内存不足的时候依然可以打印,但是多余的部分被截
去了。返回的status值为STATUS_BUFFER_OVERFLOW。调用这个函数之前很难知道究竟需要多长的缓冲区。一般都采取倍增尝试。每次都传入一个为前次尝试长度为2 倍长度的新缓冲区,直到这个函数返回STATUS_SUCCESS为止。
值得注意的是UNICODE_STRING 类型的指针,通常用%wZ可以打印出字符串。在不
能保证字符串为空结束的时候,必须避免使用%ws或者%s。其他的打印格式字符串与传统
C 语言中的printf函数完全相同。可以尽情使用。
2)内核模式下各种开头函数的区别
函数开头含义
Cc |
Cache manager |
Cm |
Configuration manager |
Ex |
Executive support routines |
FsRtl |
File system driver run-time library |
Hal |
Hardware abstraction layer |
Io |
I/O manager |
Ke |
Kernel |
Lpc |
Local Procedure Call |
Lsa |
Local security authentication |
Mm |
Memory manager |
Nt |
Windows 2000 system services (most of which are exported as Win32 functions),例如NtCreateFile 往往导出为CreateFile |
Ob |
Object manager |
Po |
Power manager |
Pp |
PnP manager |
Ps |
Process support |
Rtl |
Run-time library |
Se |
Security |
Wmi |
Windows Management Instrumentation |
Zw |
Mirror entry point for system services (beginning with Nt) that sets previous access mode to kernel, which eliminates parametervalidation, since Nt system services validate parameters only if previous access mode is user see Inside Microsoft Windows 2000 |
3)大部分的Win32 API 都是通过Native API 实现的,Native API 函数一般都是Win32 API函数前面加上Nt 两个字符,例如CreateFile 函数对应着NtCreateFile 函数,这些Nt 函数都是在“ntdll.dll”实现的,而多数Win32 API 都是在“kernel.dll”导出的,也有少部分GDI或窗口相关的函数是在“gdi32.dll”和“user32.dll”导出的。
Native API 从用户模式穿越进入到内核模式调用系统服务,这个穿越过程是通过软中断的方式进入的。这个软中断的实现方法在不同版本的Windows 实现方式略有不同,在Win 2K下是通过“int 2eh”实现的,在Win XP 是通过“sysenter”指令完成的。
软中断会将Native API 的参数和系统服务号的参数一起传进内核模式,不同的NativeAPI 会对应不同的系统服务号,这个过程是由SSDT 辅助完成的。
系统服务函数一般和Native API 具有相同的名字,例如都是NtCreateFile,但它们的实现不同,系统服务调用是在“ntoskrnl.exe”导出的。
4)创建文件
NTSTATUS
CreateFileTest( IN PUNICODE_STRING FileName )
{
HANDLE hFile = NULL;
NTSTATUS status;
IO_STATUS_BLOCK Io_Status_Block;
// 初始化文件路径
OBJECT_ATTRIBUTES obj_attrib;
InitializeObjectAttributes( &obj_attrib,
FileName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL );
// 创建文件
status = ZwCreateFile( &hFile,
GENERIC_ALL,
&obj_attrib,
&Io_Status_Block,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_CREATE,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0 );
if (NT_SUCCESS(status))
{
status = STATUS_SUCCESS;
}
else
{
status = Io_Status_Block.Status;
}
KdPrint(("hFile = %08X", hFile));
// 关闭句柄
if (hFile)
{
ZwClose(hFile);
}
return status;
}
2、链表
1)内存的分配与释放
传统的C 语言中,分配内存常常使用的函数是malloc,但在驱动开发过程中这个函数不再有效。驱动中分配内存,最常用的是调用ExAllocatePoolWithTag 或ExAllocatePool。
// 定义一个内存分配标记
#define MEM_TAG ‘MyTt’
// 目标字符串,接下来它需要分配空间。
UNICODE_STRING dst = { 0 };
// 分配空间给目标字符串。根据源字符串的长度。
dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonpagedPool,src->Length,M EM_TAG);
if(dst.Buffer == NULL)
{
// 错误处理
status = STATUS_INSUFFICIENT_RESOUCRES;
……
}
dst.Length = dst.MaximumLength = src->Length;
ExAllocatePoolWithTag 的第一个参数NonpagedPool 表明分配的内存是非分页内存,这样它们可以永远存在于物理内存,而不会被分页交换到硬盘上去;第二个参数是长度;第三个参数是一个所谓的“内存分配标记”。
内存分配标记用于检测内存泄漏。一般每个驱动程序定义一个自己的内存标记,也可以在每个模块中定义单独的内存标记。内存标记是随意的32位数字,即使冲突也不会有什么问题。此外也可以分配可分页内存,使用PagedPool标识第一个参数即可。ExAllocatePoolWithTag分配的内存可以使用 ExFreePool来释放。
ExFreePool 只需要提供需要释放的指针即可。举例如下:
ExFreePool(dst.Buffer);
dst.Buffer = NULL;
dst.Length = dst.MaximumLength = 0;
注意,ExFreePool不能用来释放一个栈空间的指针,否则系统立刻崩溃。诸如下面的代码将会招致立刻蓝屏的灾难:
UNICODE_STRING src = RTL_CONST_STRING(L”My source string!”);