首页 > 系统相关 >Windows内核开发-[6]、内核编程基础(3)

Windows内核开发-[6]、内核编程基础(3)

时间:2024-02-02 15:11:41浏览次数:28  
标签:函数 Windows 编程 后备 Tag 内核 列表 Lookaside 内存

内存分配

在应用层编程时,系统提供了GlobalAlloc/HeapAlloc/LocalAlloc等函数。C/C++库提供了malloc函数,以及new操作符在堆上分配内存。

在我前面一个关于Windows页交换文件的博客中,介绍了虚拟内存,

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

堆内存是基于虚拟内存上更小粒度的分割,这个分割由堆管理器管理,根据需求,堆管理器会申请 一页(或多页虚拟内存),然后对这块虚拟内存进行更小粒度的内存分割与管理,以满足开发者对内存的需求。

堆的大小是在应用程序启动时设置,但可以随着空间的需要而增长(分配器从操作系统请求更多内存)。

 

与应用层的堆概念类似,在内核中有一种称为“池(Pool)"的概念,我们可以从Pool中申请内存

WDK提供了一系列内存分配函数,其中最基本的是ExAllocatePoolWithTag,函数原型如下:

 

1 NTKERNELAPI
2 PVOID
3 NTAPI
4 ExAllocatePoolWithTag (
5     _In_ __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType,
6     _In_ SIZE_T NumberOfBytes,
7     _In_ ULONG Tag
8     );

PoolType:表示 需要申请哪种类型的内存,PoolTypePOOL_TYPE枚举类型

POOL_TYPE定义如下:

 1 typedef _Enum_is_bitflag_ enum _POOL_TYPE {
 2     NonPagedPool,
 3     NonPagedPoolExecute = NonPagedPool,
 4     PagedPool,
 5     NonPagedPoolMustSucceed = NonPagedPool + 2,
 6     DontUseThisType,
 7     NonPagedPoolCacheAligned = NonPagedPool + 4,
 8     PagedPoolCacheAligned,
 9     NonPagedPoolCacheAlignedMustS = NonPagedPool + 6,
10     MaxPoolType,
11     NonPagedPoolBase = 0,
12     NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2,
13     NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4,
14     NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6,
15     NonPagedPoolSession = 32,
16     PagedPoolSession = NonPagedPoolSession + 1,
17     NonPagedPoolMustSucceedSession = PagedPoolSession + 1,
18     DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1,
19     NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1,
20     PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1,
21     NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1,
22 
23     NonPagedPoolNx = 512,
24     NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4,
25     NonPagedPoolSessionNx = NonPagedPoolNx + 32,
26 
27 } _Enum_is_bitflag_ POOL_TYPE;

 

常用的值是:NonPagedPool(非分页内存)与PagedPool(分页内存)。

在前面介绍过分页内存与非分页内存的概念。

非分页内存是指这块内存的内容不会被置换到磁盘上,非分页内存非常宝贵,一般用于高IRQL(>= DISPATCH_LEVEL) 的代码中。

分页内存是指这块内存的内容可以被转换到磁盘上。

 

除了NonPagedPoolPagedPool类型,我们还需要关心的类型是NonPagedPoolExecuteNonPagedPoolNx

NonPagedPoolExecute类型的内存属性为“可执行”,意味着开发者可以将这块内存写入二进制指令然后执行,这个机制虽然很灵活,但存在一定的安全隐患︰对于一些存在漏洞的代码来说,攻击者可以使用“缓存区溢出攻击”技术,在目标内存(缓冲区)中写入可执行指令,由于这块内存具有“可执行“属性,所以攻击者可以成功实施攻击 。

 

从Win8开始,推荐使用NonPagedPoolNx类型来替代NonPagedPool类型。

从Windows 10 2004开始,使用POOL_FLAG_NON_PAGED类型

 

NumberOfTypes:表示 需要申请的内存大小

Tag:一个4个字节的标志,用于标志一块内存的使用者,这个Tag—般用于问题排查,如内存泄露,系统蓝屏等。对于内存泄露的情况,可以通过WindbgPoolMon等一些小工具,查看系统中各Tag标志对应的内存大小,找到最大的或者持续增长的内存块。标记中的每个 ASCII 字符必须是0x7E (平铺) 0x20 空间范围内的值。

如果不需要使用Tag标志,可以传递0,或者调用ExAllocatePool函数。

返回值:成功,返回分配内存的首地址。失败返回NULL。

 

说明:在Windows 10 2004版本中,ExAllocatePoolWithTag函数已经弃用,替换为ExAllocatePool2函数,详细可以参考以下链接:

https://learn.microsoft.com/zh-cn/windows-hardware/drivers/kernel/updating-deprecated-exallocatepool-calls

 

内存使用完毕后需要释放,使用ExFreePoolWithTag函数,声明如下:

1 NTKERNELAPI
2 VOID
3 ExFreePoolWithTag (
4     _Pre_notnull_ __drv_freesMem(Mem) PVOID P,
5     _In_ ULONG Tag
6     );

 

P:需要释放的内存地址

Tag:内存申请 时的标记,如果分配内存时使用的Tag等于0,释放时也传0即可。

 

后备列表(Lookaside Lists)

在频繁使用ExAllocatePoolWithTag函数分配内存时,容易 造成”内存碎片“。为了提高性能,系统提供了一种被称为”后备列表(Lookaside Lists)“的内存分配方法。

注意:Lookaside的翻译在不同的地方可能有出入,知道这个概念就行了。在《Windows内核编程》一书中,使用的是”旁视列表“,官方文档上显示的是”后备列表“。官方的中文文档是机翻的,但是我这里还是使用了这个名称。

 

使用”后备列表“的步骤如下:

1、初始化一个”后备列表“

这里以非分页内存为例,分页内存使用方法基本一样

使用ExInitializeNPagedLookasideList函数初始化”后备列表“对象,声明如下:

 1 NTKERNELAPI
 2 VOID
 3 ExInitializeNPagedLookasideList (
 4     _Out_ PNPAGED_LOOKASIDE_LIST Lookaside,
 5     _In_opt_ PALLOCATE_FUNCTION Allocate,
 6     _In_opt_ PFREE_FUNCTION Free,
 7     _In_ ULONG Flags,
 8     _In_ SIZE_T Size,
 9     _In_ ULONG Tag,
10     _In_ USHORT Depth
11     );

Lookaside:表示 被初始化的”后备列表“对象的指针,在64位系统下,这个指针必须以16字节对齐。ExInitializeNPagedLookasideList执行后,Lookaside会被初始化。

Allocate:一个函数指针(回调函数),当我们从”后备列表“对象分配内存时,系统会调用这个函数。

ALLOCATE_FUNCTION声明如下:

1 PVOID
2 ALLOCATE_FUNCTION (
3     _In_ POOL_TYPE PoolType,
4     _In_ SIZE_T NumberOfBytes,
5     _In_ ULONG Tag
6     );

这个参数可以根据自己实际情况使用,不需要使用时,传NULL,系统会使用默认的内存分配函数。

Free:一个函数指针,当我们从”后备列表”释放申请的内存块时,系统会调用这个函数。

FREE_FUNCTION声明如下:

1 VOID
2 FREE_FUNCTION (
3     _In_ __drv_freesMem(Mem) PVOID Buffer
4     );

这个参数可以根据自己实际情况使用,不需要使用时,传NULL,系统会使用默认的内存释放函数。

Flags:内存分配行为。可选以下值

POOL_NX_ALLOCATION:表示分配的非分页内存的属性为“不可执行”,类似上一节介绍的NonPagedPoolNx标志。
POOL_RAISE_IF_ALLOCATION_FAILURE:表示如果内存失败,将抛出一个异常。
0:如果没有特殊要求,可以把Flags参数设置为0。

Size:每次从“后备列表”对象中申请内存的固定大小,单位是字节,这个值不能小于LOOKASIDE_MINIMUM_BLOCK_SIZE

 LOOKASIDE_MINIMUM_BLOCK_SIZE = ((((LONG)__builtin_offsetof(SLIST_ENTRY, Next)) + (sizeof(((SLIST_ENTRY*)0)->Next))))

在64位系统下,LOOKASIDE_MINIMUM_BLOCK_SIZE的值为8。

Tag:表示 内存分配时所使用的标记,与ExAllocatePoolWithTag中的Tag参数一样。

Depth:保留参数,传0即可。

 

2、需要内存时,直接从”后备列表“对象申请 内存

 申请内存使用ExAllocateFromNPagedLookasideList函数,该函数声明如下:

1 PVOID
2 ExAllocateFromNPagedLookasideList (
3     _Inout_ PNPAGED_LOOKASIDE_LIST Lookaside
4     )

Lookaside:“后备列表”对象指针

返回值:执行成功,返回相应的内存块首地址,否则 返回NULL

ExAllocateFromNPagedLookasideList分配的内存大小为ExInitializeNPagedLookasideList函数所指定的Size

 

3、使用完成后,通过”后备列表“回收这些内存

 释放内存使用ExFreeToNPagedLookasideList函数,该函数声明如下:

1 VOID
2 ExFreeToNPagedLookasideList (
3     _Inout_ PNPAGED_LOOKASIDE_LIST Lookaside,
4     _In_ __drv_freesMem(Mem) PVOID Entry
5     )

Lookaside:“后备列表“对象指针

Entry:表示需要释放的内存块

 

4、当不再需要”后备列表“时,将其对象删除。

 删除”后备列表“时,使用ExDeleteNPagedLookasideList函数。该函数声明如下:

1 NTKERNELAPI
2 VOID
3 ExDeleteNPagedLookasideList (
4     _Inout_ PNPAGED_LOOKASIDE_LIST Lookaside
5     );

Lookaside:表示需要删除的”后备列表“对象指针

 

 5、完整的示例

下面使用简单的代码演示一下,如何使用”后备列表“

 

//直接申请内存
PVOID pAllocFromPool = ExAllocatePoolWithTag(NonPagedPoolNx, 10240, 0);

if (pAllocFromPool != NULL)
    ExFreePoolWithTag(pAlloc, 0);


//使用后备列表(Lookaside Lists)
PNPAGED_LOOKASIDE_LIST pLookasideList = 
(PNPAGED_LOOKASIDE_LIST)ExAllocatePoolWithTag(NonPagedPool, sizeof(NPAGED_LOOKASIDE_LIST), 'urfh');

if (pLookasideList != NULL)
{
    memset(pLookasideList, 0, sizeof(NPAGED_LOOKASIDE_LIST));
    //初始化
    ExInitializeNPagedLookasideList(pLookasideList, NULL, NULL, 0, 1024, 'urfh', 0);

    //分配
    PVOID pAlloc = ExAllocateFromNPagedLookasideList(pLookasideList);

    if (pAlloc != NULL)
    {
        DbgPrint("Memory Allocate First:%p", pAlloc);

        //释放
        ExFreeToNPagedLookasideList(pLookasideList, pAlloc);
    }

    //再次分配
    pAlloc = ExAllocateFromNPagedLookasideList(pLookasideList);

    if (pAlloc != NULL)
    {
        DbgPrint("Memory Allocate Second:%p", pAlloc);

        //释放
        ExFreeToNPagedLookasideList(pLookasideList, pAlloc);
    }

    //删除Lookaside Lists
    ExDeleteNPagedLookasideList(pLookasideList);
    //释放
    ExFreePoolWithTag(pLookasideList, 'urfh');

注意:如果编译报错,请将ExAllocatePoolWithTag替换为ExAllocatePool2函数

 

1 PVOID pAllocFromPool = ExAllocatePool2(POOL_FLAG_NON_PAGED, 10240, 0);

 

 运行结果:

 

 

 

 

参考资料

比较内存分配方法

https://learn.microsoft.com/zh-cn/windows/win32/memory/comparing-memory-allocation-methods

What and where are the stack and heap?

https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap

使用后备列表

https://learn.microsoft.com/zh-cn/windows-hardware/drivers/kernel/using-lookaside-lists

标签:函数,Windows,编程,后备,Tag,内核,列表,Lookaside,内存
From: https://www.cnblogs.com/zhaotianff/p/17988744

相关文章

  • CATIA|Windows——如何修改网卡的MAC地址?
    如何修改网卡的MAC地址?https://blog.csdn.net/weixin_44657888/article/details/117281332现在我们使用的板卡都是其唯一标识的,在计算机相关设备中最为常见的是MAC地址,在手机端最为常见的是SID。但有些时候,我们需要修改MAC地址,本文就来分享一些计算机如何修改网卡的mac地址。方......
  • canonical 在计算机编程领域的含义
    canonical在计算机编程领域中有多重含义,主要取决于上下文和所指的领域。以下是canonical在不同情境下的含义及相应示例:数据结构与算法:在数据结构与算法中,canonical常用来描述一个问题或者数据结构的标准或典型表达。这通常是指最常见或最经典的表达方式,可以作为学习和理解的......
  • windows安装gvm
    Releases·voidint/g·GitHub--https://github.com/voidint/g/releases下载后1、设置windows环境变量G_MIRROR=https://golang.google.cn/dl/https://golang.google.cn/dl/2、设置g的工作目录G_HOME=D:\gvm3、配置GOROOT这个指向g工作目录下的go,g安装go版本后,会在这个......
  • Linux系统编程49 信号 - sigprocmask() 设置信号集当中信号的mask信号屏蔽字
    sigprocmask():虽然我不知道信号什么时候来,但是我可以决定什么时候响应信号信号集:NAMEsigemptyset,sigfillset,sigaddset,sigdelset,sigismember-POSIXsignalsetoperationsSYNOPSIS#include<signal.h>intsigemptyset(sigset_t*set);清空信号集intsigfi......
  • Windows Server 2025 Active Directory 新变化
    自WindowsServer2016以来,ADDS尚未收到任何重大更新,并且Server2019/2022中的功能级别没有增加。随着长期服务渠道(LTSC)中操作系统的下一个版本的发布,该版本暂且被称为WindowsServer2025。WindowsServer2025新功能级别提升域或林的功能级别通常是为了利用相应服务......
  • WebAssembly核心编程[3]: Module 与 Instance
    WebAssembly程序总是以模块来组织,模块是基本的部署、加载和编译单元。在JavaScript编程接口中,模块通过WebAssembly.Module类型表示。WebAssembly.Module通过加载的.wasm二进制文件创建而成,它承载了描述wasm模块的元数据,类似于描述程序集的Assembly对象。WebAssembly.Module自身是......
  • 通过Windows PE对离线系统提取配置信息
    通过WindowsPE对离线系统提取配置信息,如IP地址等,通常涉及访问和解析离线系统的注册表文件。Windows系统中的网络配置信息,包括IP地址、子网掩码、默认网关和DNS服务器等,主要存储在注册表中。下面是一个基本的步骤指南,展示了如何在WindowsPE环境中提取这些信息:1.启动到WindowsP......
  • Nexus系列:简介和安装(Windows、Linux)以及反向代理Nexus
    目录简介安装WindowsLinuxNexus相关命令Nginx反向代理Nexus简介SonatypeNexus是一个Maven仓库管理器,可以节省网络带宽并加速项目搭建的进程。它可以管理jar包的仓库,包括上传和下载jar包。此外,SonatypeNexus还可以配置其他远程maven仓库站点,作为公共maven仓库的专用代理服务器,......
  • Windows平台下Unity-ROS环境搭建
    最近在做AI+机器人的课程项目,因为平常用Unity比较多,所以就想着把Unity和ROS结合起来使用。上Github上面一查发现官方是有做适配的。虽然已经有一段时间没有更新了,但也还能用。搭建的步骤和在搭建过程中遇到的一些问题,在这里记录一下。ROS-Unity介绍ROS-Unity就是在原本独立的ROS......
  • Day01 GUI编程入门
    GUI编程入门告诉大家该怎么学?这是什么?它怎么玩?该如何去在我们平时运用?组件窗口弹窗面板文本框列表框按钮图片监听事件鼠标键盘事件破解工具1、简介Gui的核心技术:SwingAWT不流行的原因:​1.因为界面不美观。​2.需要jre环......