Win7的内核新增了一系列带有Tag参数的对象增加引用(Refrence)/减少引用(Derefrence)函数,更易于找出对象使用中的“泄漏”(即Refrence和Derefrence次数不匹配)。
在Win7中,以下所有不带Tag的函数均使用一个默认的Tag("tlfD")直接调用带Tag参数的函数完成相应功能。相关函数如下:
ObfDereferenceObjectWithTag/ObfReferenceObjectWithTag
ObDereferenceObjectDeferDelete/ObDereferenceObjectDeferDeleteWithTag
ObReferenceObjectByHandle/ObReferenceObjectByHandleWithTag
ObReferenceObjectByPointer/ObReferenceObjectByPointerWithTag
ObfReferenceObject/ObfReferenceObjectWithTag
ObOpenObjectByPointer/ObOpenObjectByPointerWithTag
本文将具体分析该机制的内部实现。由于内容实在太多,有些细节只好略过了。
概述:
对象的引用跟踪机制类似于我们所熟悉的PoolTag内存泄漏跟踪机制,不同的是PoolTag跟踪的是内存的申请/释放操作,通过比对内存的申请/释放计数判断是否存在内存泄漏。而对象引用跟踪则跟踪的是对象的增加引用(Refrence)/减少引用(Derefrence)过程,通过比对两个操作的计数判断是否存在对象引用的“泄漏”。Win7内核提供了这样一种跟踪机制,在对象增加引用(Refrence)/减少引用(Derefrence)时插入一个操作,获取当前调用的上下文及引用的对象、引用计数等信息存入全局变量中,通过Windbg的辅助观察,可以很容易找到问题所在,方便程序员快速排查代码问题。
一、如何进行对象跟踪设置?
对象跟踪的相关参数主要有两个:
第一个是拥有哪些Tag的对象的增加引用(Refrence)/减少引用(Derefrence)操作将会被跟踪
第二个是哪些进程的增加引用(Refrence)/减少引用(Derefrence)操作会被跟踪
进程这个可能好理解一点,而这个Tag参数的理解则稍有点困难(我最初因为那些带Tag的内核函数所影响就理解错了)。这个值实际上应该是对象类型中的Key值,即_OBJECT_TYPE->Key。如果你要跟踪某一类型的对象,那么就把这个Tag参数设置成那个对象类型的Key值就行了,也就是申请/释放那个对象内存时使用的PoolTag。这样的Tag最多可以设置16个,也就是说最多可以跟踪16种不同类型的对象的引用操作。
设置对象跟踪的相关参数有两种方法,一种是通过注册表,可以使用gFlags工具来完成设置。具体地参考以下文章:
Configuring Object Reference Tracing(852K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3#2K6k6r3&6Q4x3X3g2E0K9h3y4J5L8%4y4G2k6Y4c8Q4x3X3g2U0L8$3#2Q4x3V1k6W2L8W2)9J5k6s2g2K6i4K6u0r3L8r3W2T1M7X3q4J5P5g2)9J5c8X3k6X3y4e0x3&6x3U0p5@1i4K6t1^5N6W2)9K6c8q4k6e0i4K6u0W2z5o6g2Q4x3U0W2Q4x3X3g2S2M7%4m8^5)
附张图,一目了然:

通过gFlags设置的内容实际上被保存在HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Kernel,如下:

通过注册表设置的内容需要重新启动才能起作用。
另一种方式则是实时设置,只需调用相关函数正确设置就可以立即开始对象跟踪。不过该方式MS似乎并未公开相关的内容。但是通过对内核中相关函数的逆向,我完整得出了这种设置方法的过程。设置对象跟踪参数是通过ZwSetSystemInformation实现的,InfoClass的值为86。
#define SystemObjectTraceInfoClass (86)
typedef struct _SYSTEM_OBTRACE_INFORMAION{
/*off=0x00*/BOOLEAN bTraceOn;//是否开启对象引用跟踪
/*off=0x01*/BOOLEAN bPermanent;//是否启用Permanent标志
/*off=0x02*/BOOLEAN bUnknow1;
/*off=0x03*/BOOLEAN bUnknow2;
/*off=0x04*/ULONG InputProcessNameLength;//传入的进程名称的长度
/*off=0x08*/WCHAR *InputProcessName;//进程名称
/*off=0x0C*/ULONG InputTagsLen;//传入的Tags缓冲的长度
/*off=0x10*/WCHAR *InputTags;//传入的Tags
}SYSTEM_OBTRACE_INFORMAION,*PSYSTEM_OBTRACE_INFORMAION;//size=0x14
使用该功能需要启用SeDebugPrevilege。具体的代码如下:
WCHAR ProcessName[MAX_PATH]=L"notepad.exe";//进程名称
WCHAR TraceTags[100]=L"Proc\0Thri\0File\0";//要跟踪的对象类型,注意格式
ULONG TagCnt=3;//对象类型个数,本次为3个
NTSTATUS status;
SYSTEM_OBTRACE_INFORMAION SystemObTraceInfo={0};
ZeroMemory(&SystemObTraceInfo,sizeof(SYSTEM_OBTRACE_INFORMAION));
SystemObTraceInfo.bTraceOn=TRUE;
SystemObTraceInfo.InputProcessNameLength=wcslen(ProcessName)*sizeof(WCHAR);
SystemObTraceInfo.InputProcessName=ProcessName;
SystemObTraceInfo.InputTags=TraceTags;
SystemObTraceInfo.InputTagsLen=TagCnt*(wcslen(TraceTags)+1)*sizeof(WCHAR);//总长度
printf("InputProcessNameLen=%d InputTagsLen=%d\n",SystemObTraceInfo.InputProcessNameLength,SystemObTraceInfo.InputTagsLen);
if (SetPrivilege()==FALSE)
{
printf("无法提升SeDebugPrevilege!\n");
return 0;
}
status=ZwSetSystemInformation((enum _SYSTEM_INFORMATION_CLASS)SystemObjectTraceInfoClass,&SystemObTraceInfo,sizeof(SYSTEM_OBTRACE_INFORMAION));
//要停止跟踪时,只要设置SystemObTraceInfo.bTraceOn为FALSE再次调用ZwSetSystemInformation就可以了。
if ( ObpTraceFlags )//检查Trace标志是否有效
{
if (ObjectHeader->TraceFlags & 1 )//检查当前对象是否被标记为Trace
{
ObpPushStackInfo(ObjectHeader, 1, 1, 'tlfD');
}
}
BOOLEAN __stdcall
ObpPushStackInfo(
PVOID ObjectHeader, //对象头
char bRefrenceOrDefrence, //当前操作是增加引用还是减少引用
WORD Count,//当前操作增加或减少的计数
LONG Tag//当前增加或减少引用时所使用的Tag参数
)
{
BOOLEAN result;
PVOID CallStack[16];
ULONG NextSequence;
memset(CallStack, 0, 0x40);//初始化为零
if ( KeAreInterruptsEnabled() )
{
if ( KeGetCurrentIrql() <= DISPATCH_LEVEL )
{
if ( RtlCaptureStackBackTrace(1, 16, CallStack, 0) >= 1 )//关键,获取调用栈中的所有返回地址
{
NextSequence=InterlockedIncrement(ObpStackSequence);
if ( MmCanThreadFault() == TRUE )
result = ObpPushRefDerefInfo(ObjectHeader, bRefrenceOrDefrence, Count, NextSequence, CallStack, Tag);//记录调用信息
else
result = ObpDeferPushRefDerefInfo(ObjectHeader, bRefrenceOrDefrence, Count, NextSequence, CallStack, Tag);
}
}
}
return result;
}
BOOLEAN __stdcall
ObpPushRefDerefInfo(
PVOID ObjectHeader,//对象头
BOOLEAN bRefrenceOrDefrence,//当前操作是增加引用还是减少引用
WORD Count,//当前操作增加或减少的计数
ULONG CurrentSequence,//一个序数
POBJECT_REF_TRACE Stacks,//当前调用栈地址信息
LONG Tag//当前增加或减少引用时所使用的Tag参数
)
{
WORD Index=0;
WORD NextPos;
OBJECT_REF_INFO RefInfo={0};
POBJECT_REF_STACK_INFO pObjRefStackInfo;
POBJECT_REF_STACK_INFO RefStackInfo,PreRefStackInfo;
//判断ObpTraceFlag及获取ObpStackTraceLock这个锁,过程略过略过
if ( NT_SUCCESS(ObpGetObjectRefInfo(ObjectHeader, &RefInfo))) //查找ObpObjectTable获取该Object对应的RefInfo,此時RefInfo->ObjectHeader即查找的目标
{
CurRefInfo = RefInfo;
if ( RefInfo )
{
Index = ObpGetTraceIndex(Stacks);//该函数通过计算调用栈地址的Hash值,将其存入ObpStackTable表中,并返回在表中的索引
if ( Index >= 16381 ) //判断Index是否超过了允许的最大值,若超过则认为溢出
{
DbgPrintEx(0, 1, "ObpPushRefDerefInfo - ObpStackTable overflow\n");
}
else //若没有超过最大值,正常处理
{
NextPos = RefInfo->NextPos;//取当前可用的位置指针
while ( NexPos )//若有效
{
RefStackInfo=RefInfo.StackInfo[NextPos];//当前要保存栈信息的位置
PreRefStackInfo=RefInfo.StackInfo[NextPos-1];//最后一次保存栈信息的位置
if ( CurrentSequence >= PreRefStackInfo->Sequence )//上一序数未超过当前值,则认为正常,跳出循环
break;
//超出的情况处置
RefStackInfo->Sequence=PreRefStackInfo->Sequence;
RefStackInfo->Index=PreRefStackInfo->Index;
RefStackInfo->NumTraces=PreRefStackInfo->NumTraces;
RefStackInfo->Tag=PreRefStackInfo->Tag;
NextPos -= 1;//上移一个位置
}
pObjRefStackInfo=RefInfo.StackInfo[NextPos];//取当前可用的位置
pObjRefStackInfo->Index = Index | (WORD)(-(bRefrenceOrDefrence != 0) & 0x8000);//保存Index,并根据是增加引用还是减少引用设置标志位
pObjRefStackInfo->NumTraces = Count;//保存此次的引用计数
pObjRefStackInfo->Sequence = NextSequence;
pObjRefStackInfo->Tag = Tag;
RefInfo->NextPos+=1; //NextPos加1,指向下一个可用位置
}
}
}
//释放锁及其它,略
}
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课