32位的CPU的寻址能力为4GB(2^32)个字节。用户最多可以使用4GB的真实物理内存。PC中的很多设备都提供了自己的设备内存,这部分的内存会映射到PC的物理内存上。
Windows的所有程序(ring0,ring3),可以操作的都是虚拟内存。CPU中寄存器CR0一个位PG位来告诉系统是否分页的。1为允许分页。DDK中宏PAGE_SIZE记录着分页大小,一般为4K,4GB的虚拟内存会被分割成1M个单元。
图 物理内存的映射
4G虚拟地址中,低2G(0~0x7FFFFFFFF)为用户模式,高2G(0x80000000~0xFFFFFFFF)为内核模式。Windows规定用户态程序只能访问用户模式地址,而内核态程序可以访问整个4G虚拟地址。进程切换时,所有进程的内核地址映射完全一致,进程切换时改变,只是改变用户模式地址的映射。
图 用户模式和内核模式
Windwos驱动程序里的不同例程运行在不同的进程中。
打印当前进程的进程名:
void DisplayItsProcessName()
{
//得到当前进程
PEPROCESS pEProcess = PsGetCurrentProcess();
//得到当前进程名称
PTSTR = ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);
KdPrint(("%s\n", ProcessName));
}
可以交换到文件中的虚拟内存页面称为分页内存,否则称为非分页内存。当程序的中断请求级大于等于DISPATCH_LEVEL时,程序只能使用非分页内存。
指定某个例程和某个全局变量是载入分页内存还是非分页内存,需要做如下定义
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
将PAGEDCODE 等放在函数前来表现是可否可以分页等情况。
#pragma PAGEDCODE
VOID SomeFunction()
{
PAGED_CODE();
//其他代码
}
PAGED_CODE()是DDK提供的宏,它只在check版本中生效,来检查运行是否低于DISPATCH_LEVEL的中断请求级,如果不低于则产生断言。
Windows驱动程序使用的内存资源非常珍贵,栈空间也不像应用程序那么大,所以在定义大型结构体是应在堆中申请。
堆中申请内存的函数有一下几个:
PVOID ExAllocatePool(
_In_ POOL_TYPE PoolType,
_In_ SIZE_T NumberOfBytes
);
PVOID ExAllocatePoolWithTag(
_In_ POOL_TYPE PoolType,
_In_ SIZE_T NumberOfBytes,
_In_ ULONG Tag
);
PVOID ExAllocatePoolWithQuota(
_In_ POOL_TYPE PoolType,
_In_ SIZE_T NumberOfBytes
);
PVOID ExAllocatePoolWithQuotaTag(
_In_ POOL_TYPE PoolType,
_In_ SIZE_T NumberOfBytes,
_In_ ULONG Tag
);
VOID ExFreePool(
_In_ PVOID P
);
VOID ExFreePoolWithTag(
_In_ PVOID P,
_In_ ULONG Tag
);
将分配的内存回收。
- p:要释放的地址。
- Tag:标签。
双向链表有两个指针,BLINK指向前一个元素,FLINK指向下一个元素。
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
初始化链表头用InitializeListHead
宏实现。
检查链表是否为空使用IsListEmpty(&head);
自定义链表
typedef struct _MYDATASTRUCT{
LIST_ENTRY ListEntry;
//自定义的数据
//......
}
(1)首部插入链表
InsertHeadList(&head, &mydata->ListEntry);
(2)尾部插入链表
InsertTailList(&head, &mydata->ListEntry);
(1)首部删除链表
PLIST_ENTRY pEntry = RemoveHeadList(&head);
(2)尾部删除链表
PLIST_ENTRY pEntry = RemoveTailList(&head);
其中head是链表头,pEntry是从链表删除下的元素中的ListEntry。
当LIST_ENTRY是自定义数据结构的第一个字段时,pEntry可以当做自定义数据的地址。
如果驱动程序频繁地从内存中申请回收固定大小的内存,可以使用Lookaside对象。可以将Lookaside对象想象成一个自动的内存分配容器。避免产生内存空洞。
VOID ExInitializeNPagedLookasideList(
_Out_ PNPAGED_LOOKASIDE_LIST Lookaside,
_In_opt_ PALLOCATE_FUNCTION Allocate,
_In_opt_ PFREE_FUNCTION Free,
_In_ ULONG Flags,
_In_ SIZE_T Size,
_In_ ULONG Tag,
_In_ USHORT Depth
);
VOID ExInitializePagedLookasideList(
_Out_ PPAGED_LOOKASIDE_LIST Lookaside,
_In_opt_ PALLOCATE_FUNCTION Allocate,
_In_opt_ PFREE_FUNCTION Free,
_In_ ULONG Flags,
_In_ SIZE_T Size,
_In_ ULONG Tag,
_In_ USHORT Depth
);
PVOID ExAllocateFromNPagedLookasideList(
_Inout_ PNPAGED_LOOKASIDE_LIST Lookaside
);
PVOID ExAllocateFromPagedLookasideList(
_Inout_ PPAGED_LOOKASIDE_LIST Lookaside
);
VOID ExFreeToNPagedLookasideList(
_Inout_ PNPAGED_LOOKASIDE_LIST Lookaside,
_In_ PVOID Entry
);
VOID ExFreeToPagedLookasideList(
_Inout_ PPAGED_LOOKASIDE_LIST Lookaside,
_In_ PVOID Entry
);
VOID ExDeleteNPagedLookasideList(
_Inout_ PNPAGED_LOOKASIDE_LIST Lookaside
);
VOID ExDeletePagedLookasideList(
_Inout_ PPAGED_LOOKASIDE_LIST Lookaside
);
由编译器提供,不同操作系统实现不同,但是接口一样,如malloc。
VOID RtlCopyMemory(
_Out_ VOID UNALIGNED *Destination,
_In_ const VOID UNALIGNED *Source,
_In_ SIZE_T Length
);
VOID RtlMoveMemory(
_Out_ VOID UNALIGNED *Destination,
_In_ const VOID UNALIGNED *Source,
_In_ SIZE_T Length
);
VOID RtlFillMemory(
_Out_ VOID UNALIGNED *Destination,
_In_ SIZE_T Length,
_In_ UCHAR Fill
);
VOID RtlZeroMemory(
_Out_ VOID UNALIGNED *Destination,
_In_ SIZE_T Length
);
SIZE_T RtlCompareMemory(
_In_ const VOID *Source1,
_In_ const VOID *Source2,
_In_ SIZE_T Length
);
DDK提供的运行时函数都是RtlXX形式。
驱动开发不能直接使用new和delete。因为MS编译器没有提供内核模式下的new操作符,我们可以对其进行重载来使用。
重载有两种方法,一种是类中重载,一种全局重载。
//全局new操作符
void * __cdecl operator new(size_t size,POOL_TYPE PoolType=PagedPool)
{
KdPrint(("global operator new\n"));
KdPrint(("Allocate size :%d\n",size));
return ExAllocatePool(PagedPool,size);
}
//全局delete操作符
void __cdecl operator delete(void* pointer)
{
KdPrint(("Global delete operator\n"));
ExFreePool(pointer);
}
class TestClass
{
public:
//构造函数
TestClass()
{
KdPrint(("TestClass::TestClass()\n"));
}
//析构函数
~TestClass()
{
KdPrint(("TestClass::~TestClass()\n"));
}
//类中的new操作符
void* operator new(size_t size,POOL_TYPE PoolType=PagedPool)
{
KdPrint(("TestClass::new\n"));
KdPrint(("Allocate size :%d\n",size));
return ExAllocatePool(PoolType,size);
}
//类中的delete操作符
void operator delete(void* pointer)
{
KdPrint(("TestClass::delete\n"));
ExFreePool(pointer);
}
private:
char buffer[1024];
};
void TestNewOperator()
{
TestClass* pTestClass = new TestClass;
delete pTestClass;
pTestClass = new(NonPagedPool) TestClass;
delete pTestClass;
char *pBuffer = new(PagedPool) char[100];
delete []pBuffer;
pBuffer = new(NonPagedPool) char[100];
delete []pBuffer;
}
图 NTSTATUS含义
常用NTSTATUS状态返回值
- STATUS_SUCCESS:函数执行成功
- STATUS_UNSUCCESSFUL:函数执行不成功
- STATUS_NOT_IMPLEMENTED:函数未被实现
- STATUS_INVALID_INFO_CLASS:输入参数是无效的类别
- STATUS_INFO_LENGTH_MISMATCH:输入参数长度不匹配
- STATUS_ACCESS_VIOLATION:不允许访问
- STATUS_IN_PAGE_ERROR:发生页故障
- STATUS_INVALID_HANDLE:输入是无效的句柄
- STATUS_INVALID_PARAMETER:输入是无效的参数
- STATUS_NO_SUCH_DEVICE:指定设备不存在
- STATUS_NO_SUCH_FILE:指定文件比存在
- STATUS_INVALID_DEVICE_REQUEST:无效的设备请求
- STATUS_END_OF_FILE:文件已到结尾
- STATUS_INVALID_SYSTEM_SERVICE:无效的系统调用
- STATUS_ACCESS_DENIED:访问被拒绝
- STATUS_BUFFER_TOO_SMALL:是蠕动缓冲区过小
- STATUS_OBJECT_TYPE_MISMATCH:是蠕动对象类型不匹配
- STATUS_OBJECT_NAME_INVALID:输入的对象名无效
- STATUS_OBJECT_NAME_NOT_FOUND:输入的对象没用找到
- STATUS_PORT_DISCONNECTED:需要的端口没用被连接
- STATUS_OBJECT_PATH_INVALID:输入的对象路径无效
VOID ProbeForRead(
_In_ PVOID Address,
_In_ SIZE_T Length,
_In_ ULONG Alignment
);
VOID ProbeForWrite(
_Inout_ PVOID Address,
_In_ SIZE_T Length,
_In_ ULONG Alignment
);
异常概念类似于中断
A)try-except块
try-except-statement :
__try
{
compound-statement
}
__except ( expression )
{
compound-statement
}
expression有三种情况:
- EXCEPTION_CONTINUE_EXECUTION:数值为1,进入到__except进行错误处理,处理完后不再回到__try{}块中,转而继续执行。
- EXCEPTION_CONTINUE_SEARCH:数值为0,不适用__except块中的异常处理,转而向上一层回卷。
- EXCEPTION_EXECUTE_HANDLER:数值为-1,重复先前错误指令,很少用到。
例子如下:
#pragma INITCODE
VOID ProbeTest()
{
PVOID badPointer = NULL;
KdPrint(("Enter ProbeTest\n"));
__try
{
KdPrint(("Enter __try block\n"));
//判断空指针是否可读,显然会导致异常
ProbeForWrite(badPointer,100,4);
//由于在上面引发异常,所以以后语句不会被执行!
KdPrint(("Leave __try block\n"));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("Catch the exception\n"));
KdPrint(("The program will keep going\n"));
}
//该语句会被执行
KdPrint(("Leave ProbeTest\n"));
}
其它引发异常函数:
- ExRaiseAccessViolation:触发STATUS_ACCESS_VIOLATION异常
- ExRaiseDatatypeMisalignment:触发STATUS_DATATYPE_MISALIGNMENT异常
- ExRaiseStatus:用指定状态代码触发异常
B)try-finally 块
try-finally-statement :
__try compound-statement
__finally compound-statement
强迫函数有退出前执行一段代码。常用来一些资源的回收工作。
#pragma INITCODE
NTSTATUS TryFinallyTest()
{
NTSTATUS status = STATUS_SUCCESS;
__try
{
//做一些事情
return STATUS_SUCCESS;
}
__finally
{
KdPrint(("Enter finallly block\n"));
return status;
}
}
也就是多行宏在if等语句中表达的意思出现了不同,在if,while,for等语句中,无论是否只有一句话,都不能省略{}。
NTSTATUS Foo(PCHAR* str)
{
ASSERT(srt!=NULL);
}