链表的使用
DDK给我们自带了一个链表,比较好用。
这个链表有一个比较特殊的性质,就是他并没有数据字段,但是他又是如何设置的呢?其实这点比较聪明,我们创建一个含有这个链表的结构体:
1 2 3 4 5
| typedef struct _MyStruct { int number; LIST_ENTRY listEntry; }*pMyStruct,MyStruct;
|
这个链表只有一个是我们的数据,另一个是我们的链表,这个LIST_ENTRY
就是DDK提供给我们的,显示初始话,然后就是申请我们结构体类型的内存块,然后给数据赋值,然后我们插入的时候我们是讲结构体的LIST_ENTRY
字段插入进去,这样子我们LIST_ENTRY
字段的内存地址就插件链表中了,当我们要使用的时候我们只需要计算一下我们结构体相对于这个LIST_ENTRY
的偏移位置就可以了,这里的话呢DDK给了我们一个宏,CONTAINING_RECORD
这个可以通过字段计算出来我们结构体的地址,然后我们就可以通过链表得到结构体的地址了。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void LinkListTest() { LIST_ENTRY listHead; InitializeListHead(&listHead); pMyStruct pData; KdPrint(("开始插入数据\n")); for (int i = 0; i < 10;i++) { pData = (pMyStruct)ExAllocatePool(PagedPool, sizeof(MyStruct)); pData->number = i; InsertHeadList(&listHead, &pData->listEntry); } KdPrint(("开始删除链表\n")); while (!IsListEmpty(&listHead)) { PLIST_ENTRY pEntry = RemoveTailList(&listHead); pData = CONTAINING_RECORD(pEntry, MyStruct, listEntry); KdPrint(("%d\n", pData->number)); ExFreePool(pData); }
}
|
内存的使用
这里的话呢大部分的函数都是Rt开头的,并且大部分都是宏,这些宏是讲我们的运行时函数规范了一下罢了,运行时函数是底层编译器提供的。
简单的示例操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define BUFFER_SIZE 1024 #pragma INITCODE void testMemory() { PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool, BUFFER_SIZE); RtlZeroMemory(pBuffer, BUFFER_SIZE); PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool, BUFFER_SIZE); RtlFillMemory(pBuffer2, BUFFER_SIZE, 0xAA); RtlCopyMemory(pBuffer, pBuffer2, BUFFER_SIZE); ULONG ulRet = RtlCompareMemory(pBuffer, pBuffer2, BUFFER_SIZE); if (ulRet == BUFFER_SIZE) { KdPrint(("Same")); } }
|
INITCODE
代表我们的这块用完就删除,不浪费空间的,一般来说DriverEntry
就是这个样子的。
其他的代码都和之前的类似。
异常捕获
使用try_except进行异常的捕获,可以有效的防止操作系统进入蓝屏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void probeTest() { PVOID badPointer = NULL; __try { KdPrint(("进入try\n")); ProbeForWrite(badPointer, 100, 4); KdPrint(("不会执行的语句")); } __except(EXCEPTION_EXECUTE_HANDLER) { KdPrint(("捕获到了异常\n"));
} KdPrint(("程序继续运行了\n")); }
|
这段代码比较简单理解ProbeForWrite
这个函数其实就是判断是不是可写的内存,但明显不可写。EXCEPTION_EXECUTE_HANDLER
这个异常处理的方式是执行出错的话呢就跳到except中,这个也是最常用的。
其实也有事try-finally,这个比较特殊,就算你是在try中返回了,但是还是会执行finally中的代码的,这很有效的帮助我们执行销毁变量的操作(个人感觉比goto好用一些)
多行宏注意事项
在我们喜欢用:
这个样子写的时候,如果我们test函数是一个宏,那么这样执行是不对的,多行宏我们是要加大括号的!
断言
ASSERT是一个宏,来判断是否出错,如果出错则进入异常。