0%

驱动操作内存

链表的使用

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好用一些)

多行宏注意事项

在我们喜欢用:

1
2
if(xxx)
test();

这个样子写的时候,如果我们test函数是一个宏,那么这样执行是不对的,多行宏我们是要加大括号的!

断言

ASSERT是一个宏,来判断是否出错,如果出错则进入异常。