主要是将链表结构的使用,在内核开发中使用起来比较方便的一种数据结构【LIST_ENTRY】。
一、内存的分配
主要是学习一些基本操作。现在推荐使用的动态分配函数【ExAllocatePoolWithTag】
PVOID tempbuffer = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'xxaa');
这样可以通过工具来找到标签对应的内存地址,我没找到视频里的【PoolMonEX.exe】,但是找到了【PoolMonX.exe】这个程序也可以用🤣
可以看到实际上的内容是和代码里边的顺序相反的,而且我之前申请的【‘aaxx’】非分页内存块,在驱动卸载后没有被释放掉(故意没free,驱动卸载不会自动释放),这样就只能重启操作系统了。
这个函数的第一参数确定这块内存的形式:
- 【 PagedPool 】分页池—在需要时能够将页面换出的内存池。
- 【 NonPagedPool 】非分页池—永远不会换出页面,保证驻留在RAM里的内存池。
引用书中的描述,我觉非常好,虽然第一次看的时候没什么感觉😗😗😗
很显然,非分页池是一个“更好”的内存池,因为它不会导致页 面错误。在本书的后面我们会看到一些需要从非分页池分配的例子。 驱动程序要尽可能少地使用非分页池,除非必需。其他任何情况驱动 程序都应该使用分页池。POOL_TYPE这个枚举类型表示内存池的类型。 这个枚举类型包括很多内存池的“类型”,但是只有三种可以被驱动 程序使用:PagedPool、NonPagedPool和NonPagePoolNx(没有执行权限 的非分页池)。
除此之外,在tiechuiDL视频中提到了更多的参数,在头文件【wdm.h】有这样的定义:
【NonPagedPoolCacheAligned】传入这个参数,有一个对齐的概念,就是要求操作系统分配的这个内存是宽度对齐的。宽度的大小和寻址基线有关系,比方说32位系统就是四字节对齐,64位就是8字节对齐。
【NonPagedPoolMustSucceed】传入这个参数,就要求操作系统必须分配成功一个非分页内存。
【NonPagedPoolCacheAlignedMustS】传入这个参数,是上边的超级组合:非分页+字节对齐+必须成功!
1.调试看细节
接下来是双机调试的骚操作,真的炫呀,tiechuiDL的双机调试连接怎么这么快!我就暂时用了WinDbg来看(VS2019调试Win7一直gg😒):
先搞一下入口断点:
bu ListEntry!driverentry
进入入口函数后使用Windbg打断点:
之后使用g命令跳出,就和视频里边一样命中断点调试了...(还是好奇为啥tiechuiDL这么快???
【 bl 】查看已经打过的断点:
【 dv /v 】查看变量状态
【 db 0xfffffa80`1b178000 】查看tempbuffer内存地址的变化
清零之后【RtlZeroMemory(tempbuffer, 0x1000);】
填充之后【RtlFillMemory(tempbuffer, 0x1000, 0xcc);】
释放之后【ExFreePoolWithTag(tempbuffer, 'dcba');】中断一下再看,被别的进程用了...
调试就这样了,学到了咋通过命令看内存,然后介绍了两个运行时函数,用来内存比较:
- RtlCompareMemory
- RtlEqualMemory
RtlEqualMemory其实是宏定义而且用的比较多,主要是看一些区域相不相等。
2. Lookaside
最后还有一个概念是【Lookaside】
Windows内存管理中使用了类似于容器的东西,叫做Lookaside对象,每次程序员申请内存都会从Lookaside里面申请,只有不足的时候,Lookaside才会向内存又一次申请内存空间,这样减少了频繁申请内存而导致的内存碎片。
当Lookaside对象内部有大量没有使用的内存时候,它会自动让windows回收一部分内存,总之,Lookaside很智能。
一般Lookaside用于以下情况:
- 程序员每次申请固定的内存大小
- 申请和回收内存的次数较多,很频繁
其实这个在官网的例子还出现挺频繁的,不过不在这里展开了,下次一定😉
二、链表 LIST_ENTRY