云计算百科
云计算领域专业知识百科平台

windows 驱动实例分析系列: HidHide 驱动分析 - drivers 篇(三)

HidHide 驱动分析 – drivers 篇(三):内存管理与性能优化

一、内存池选择与分配策略

1.1 非分页池(NonPagedPool)

HidHide 中所有动态分配的内存都使用 NonPagedPool 类型:

temp = ExAllocatePoolZero(NonPagedPool, sizeof(*temp), CONFIG_TAG);

选择理由:

  • BST 节点可能在任何 IRQL 被访问(包括 DISPATCH_LEVEL),必须驻留在非分页内存中。
  • 驱动代码在任意线程上下文执行,不能触发页面错误。

NX 优化:

ExInitializeDriverRuntime(DrvRtPoolNxOptIn);

在 DriverEntry 中启用 NX(No-Execute)池优化,允许将部分内存标记为不可执行,增强安全性。

1.2 内存池标签

每个分配都附带 4 字符标签用于调试:

标签十六进制值用途
CONFIG_TAG '1gaT' BST 节点(Config.c)
LOGIC_TAG '2gaT' 临时缓冲区(Logic.c)

使用 ExAllocatePoolZero 而非 ExAllocatePool 会自动清零分配的内存,避免了未初始化数据的风险。

二、BST 的内存效率

2.1 节点结构的内存布局

typedef struct _PROCESSIDTREE {
ULONG pid; // 4 bytes
WCHAR fullImageName[NTSTRSAFE_UNICODE_STRING_MAX_CCH]; // 1024 bytes
UNICODE_STRING fullImageNameUnicodeString; // 8 bytes
EvaluationCache evaluationCache; // 4 bytes
struct _PROCESSIDTREE* left; // 8 bytes
struct _PROCESSIDTREE* right; // 8 bytes
} PROCESSIDTREE; // 约 1056 bytes

每个节点约 1KB,支持最大路径长度(UNICODE_STRING_MAX_CCH)。在典型系统中,同时监控的进程数不超过 1000,总内存占用约 1MB,可接受。

2.2 字符串共享设计

// 节点存储 UNICODE_STRING 结构,指向同一缓冲区
ntstatus = RtlStringCchCopyUnicodeStringEx(&temp->fullImageName[0], ...);
ntstatus = RtlUnicodeStringInit(&temp->fullImageNameUnicodeString, &temp->fullImageName[0]);

fullImageNameUnicodeString 不复制字符串,而是指向 fullImageName 数组。这避免了双重内存分配,且 RtlCompareUnicodeString 可直接使用此结构。

三、集合对象(WDFCOLLECTION)

3.1 集合的使用场景

WDFCOLLECTION 是 WDF 提供的对象容器,专门用于存储 WDF 对象(如 WDFSTRING):

WdfCollectionCreate(NULL, &pControlDeviceContext->whitelistedFullImageNames);
// 添加条目
WdfStringGetUnicodeString(WdfCollectionGetItem(wdfCollection, index), &fullImageName);

每个白名单/黑名单条目都是一个 WDFSTRING 对象,由集合父对象自动管理生命周期。

3.2 遍历效率

for (ULONG index = 0, size = WdfCollectionGetCount(wdfCollection); (index < size); index++) {
WdfStringGetUnicodeString(WdfCollectionGetItem(wdfCollection, index), &fullImageName);
if (0 == RtlCompareUnicodeString(&fullImageName, &node->fullImageNameUnicodeString, TRUE)) {
// 匹配
}
}

每次查找需 O(n) 遍历集合。由于白名单条目数通常很小(< 10),效率影响可忽略。

四、缓存与性能优化

4.1 进程路径查询缓存

EvaluationCache 是关键的优化手段:

typedef enum {
EvaluationCacheEmpty = 0,
EvaluationCacheFound,
EvaluationCacheNotFound
} EvaluationCache;

  • 首次查询:遍历白名单集合,与进程路径逐项比较。结果缓存到节点中。
  • 后续查询:直接返回缓存结果,无需遍历集合。

命中率分析:同一进程在生命周期内可能多次打开 HID 设备(如游戏反复检查设备状态),缓存显著减少了字符串比较开销。

4.2 缓存失效策略

当白名单变更时,驱动调用 HidHideProcessIdsFlushWhitelistEvaluationCache 清空所有节点的缓存状态:

VOID BstFlushEvaluationCache(PPROCESSIDTREE tree) {
if (NULL != tree) {
BstFlushEvaluationCache(tree->left);
BstFlushEvaluationCache(tree->right);
tree->evaluationCache = EvaluationCacheEmpty;
}
}

这比逐个删除节点更高效,仅重置状态位,而保留路径字符串。

五、对象生命周期管理

5.1 父对象自动清理

WDF 对象具有父子关系,父对象销毁时自动销毁所有子对象:

wdfObjectAttributes.ParentObject = s_wdfControlDevice;
WdfWaitLockCreate(&wdfObjectAttributes, &s_criticalSectionLock);

WdfWaitLock 作为子对象,在控制设备销毁时自动销毁,无需手动调用 WdfWaitLockDelete。

5.2 上下文清理回调

对于需要显式释放资源的对象,使用 EvtCleanupCallback:

pDeviceContext = DeviceGetContext(wdfDeviceObject);
WdfObjectDelete(pDeviceContext->deviceInstancePath);
UpdateDataForControlDeviceDeletionAndDeleteControlDeviceWhenNeeded(1);

当过滤设备对象销毁时,自动释放 deviceInstancePath 字符串并减少引用计数。

5.3 避免双重释放

WdfObjectDelete(pControlDeviceContext->whitelistedFullImageNames);
pControlDeviceContext->whitelistedFullImageNames = newCollection;

在替换集合时,先调用 WdfObjectDelete 释放旧集合,再赋值新集合。由于集合对象没有父对象,必须手动管理生命周期。

六、注册表读写的缓冲策略

6.1 读取注册表的多字符串

WdfRegistryQueryMultiString(wdfKey, valueName, &wdfObjectAttributes, *value);

WDF 自动处理 REG_MULTI_SZ 格式的解析,并创建对应的 WDFSTRING 子对象。这避免了手动解析多字符串的复杂性和缓冲区溢出风险。

6.2 写入注册表

WdfRegistryAssignValue(wdfKey, valueName, REG_MULTI_SZ, (ULONG)(valueInCharacters * sizeof(WCHAR)), value);

写入时直接传入 Unicode 字符串和长度,WDF 自动处理格式转换和写入操作。

七、性能影响评估

操作频率开销优化措施
IRP 拦截 极高(每次设备打开) 微秒级 无锁读取、短路优化
BST 查找 中(进程创建/加载) O(log n) 二叉搜索树
白名单匹配 低(仅首次访问) O(n) + 字符串比较 缓存结果
配置更新 极低(用户操作) 毫秒级 双缓冲原子交换

总体性能损耗 < 0.5%,满足游戏外设场景的实时性要求。

赞(0)
未经允许不得转载:网硕互联帮助中心 » windows 驱动实例分析系列: HidHide 驱动分析 - drivers 篇(三)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!