【旧文章搬运】深入分析Win7的对象引用跟踪机制
原文发表于百度空间及看雪论坛,2010-09-12
看雪论坛地址:https://bbs.pediy.com/thread-120296.htm
==========================================================================
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(http://msdn.microsoft.com/en-us/library/ff539214(v=VS.85).aspx)
附张图,一目了然:
通过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[]=L"Proc\0Thri\0File\0";//要跟踪的对象类型,注意格式
ULONG TagCnt=;//对象类型个数,本次为3个 NTSTATUS status;
SYSTEM_OBTRACE_INFORMAION SystemObTraceInfo={};
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)+)*sizeof(WCHAR);//总长度
printf("InputProcessNameLen=%d InputTagsLen=%d\n",SystemObTraceInfo.InputProcessNameLength,SystemObTraceInfo.InputTagsLen);
if (SetPrivilege()==FALSE)
{
printf("无法提升SeDebugPrevilege!\n");
return ;
}
status=ZwSetSystemInformation((enum _SYSTEM_INFORMATION_CLASS)SystemObjectTraceInfoClass,&SystemObTraceInfo,sizeof(SYSTEM_OBTRACE_INFORMAION));
//要停止跟踪时,只要设置SystemObTraceInfo.bTraceOn为FALSE再次调用ZwSetSystemInformation就可以了。
需要注意的是,进程名称这个参数是可选参数,未设置这个参数就是跟踪所有的调用,否则就只是相关进程的调用。
二、对象引用跟踪机制的运作原理
(1)内核中相关变量的初始化
与对象引用跟踪有关的一些关键内核变量如下:
//
//
// Stack Trace code
//
//以下三项来自gflags程序在注册表中的设置
WCHAR ObpTraceProcessNameBuffer[] = { }; //进程名称,与该名称匹配的进程的对象引用过程才会被跟踪记录,详见gflags程序
WCHAR ObpTracePoolTagsBuffer[] = { }; //Tag标志,最多32个,凡是与该Tag匹配的对象被引用时才会被跟踪,详见gflags程序
ULONG ObpTracePermanent = ; //永久性标志,详见gflags程序 ULONG ObpTracePoolTagsLength = sizeof(ObpTracePoolTagsBuffer);
//对象引用跟踪有关的Tag信息
PVOID ObpTracePoolTags;//指向ObpRTTracePoolTags或ObpRegTracePoolTags中的一个
ULONG ObpRegTracePoolTags[];//来自注册表设置的Tag
ULONG ObpRTTracePoolTags[];//实时跟踪的Tag //对象引用跟踪有关的Process信息
PUNICDODE_STRING ObpTraceProcessName;//指向以下两个结构中的一个
UNICODE_STRING ObpRegTraceProcessName = { };//保存被跟踪的进程的名称,名称来自注册表
UNICODE_STRING ObpRTTraceProcessName = { };//保存被跟踪的进程的名称,名称来自实时设置 //ObpTraceFlags具体每位的涵义,逆向而来
#define OBTRACE_FALG_REGSET 0x01 //是否来自注册表设置
#define OBTRACE_FALG_REALTIME 0x02 //是否来自实时设置
#define OBTRACE_FALG_TAGS 0x10 //是否指定了Tag
#define OBTRACE_FLAG_PROCESSNAME 0x20 //是否指定了进程名称
#define OBTRACE_FLAG_PERMANENT 0x40 //是否指定了永久标志 ULONG ObpTraceFlags = ; //跟踪标志
....
OBJECT_TRACE_STACK_TABLE *ObpStackTable = NULL;
POBJECT_REF_INFO *ObpObjectTable = NULL; //完整内容可参考附件中的ObTrace.h
如果跟踪设置来自注册表,那么相关变量在ObInitSystem() -> ObInitStackTrace()中设置,相关变量为ObpRegXxxx.
如果跟踪设置来自实时设置,那么相关变量在NtSetSystemInformation() -> ObSetRefTraceInformation() -> ObpStartRTStackTrace()中设置,相关变量为ObpRTXxxx.
不管来自哪种设置方式,最终有关的参数被保存在以下关键变量中:
ULONG ObpTraceFlags = ; //跟踪标志
PUNICDODE_STRING ObpTraceProcessName;//指定的进程名称
PVOID ObpTracePoolTags;//待跟踪的Tag
同时,与存储跟踪信息相关的两个重要表ObpObjectTable和ObpStackTable也完成初始化。
(2)设置的跟踪参数如何生效?
一个对象的增加引用(Refrence)/减少引用(Derefrence)是否会被跟踪记录,是在它“出生”的时候就已经决定了的。ObCreateObject()在对象创建完毕后检查ObpTraceFlags标志是否有效,若有效,则调用ObRegisterObject()向ObpObjectTable表中注册这个对象。ObRegisterObject()首先会检查所创建的这个对象的Tag是不是包含在ObpTracePoolTags中(若设置了ObpTraceProcessName的话会先检查当前进程的EPROCESS->Flag2是否有0x200对象引用跟踪标志),若包含,说明该类型的对象被设置为被跟踪,那么就将其按照一定的Hash算法放入ObpObjectTable(有关重要常量为ObpObjectBuckets,具体算法后面有讲述),并设置该对象的ObjectHeader->TraceFlags为1,表明以后该对象的增加引用(Refrence)/减少引用(Derefrence)操作将会被跟踪记录.
而指定的进程名称,也就是ObpTraceProcessName的生效,是在目标进程创建时决定的。进程创建函数NtCreateUserProcess和PspCreateProcess都调用了PspInsertProcess().PspInsertProcess()内部调用了ObCheckRefTraceProcess().原型如下:
VOID _stdcall ObCheckRefTraceProcess(PEPROCESS Process);
该函数先判断ObpTraceFlags是否具备0x20标志(即进程名称标志),若有,则取参数Process->ImageFileName与ObpTraceProcessName比较,看进程名称是否匹配,若匹配,则设置EPROCESS->Flags2标志里的RefTraceEnabled标志位0x200,这样,该进程就具备了跟踪对象引用的先天条件。
(3)真正的跟踪记录过程
前面所说的都是准备工作,所有与对象引用有关的函数(文章开头提到的那些,及部分句柄复制有关的函数)内部都会检查ObpTraceFlags标志的有效性和ObjectHeader->TraceFlags的有效性,在这些函数中都可以见到以下代码:
if ( ObpTraceFlags )//检查Trace标志是否有效
{
if (ObjectHeader->TraceFlags & )//检查当前对象是否被标记为Trace
{
ObpPushStackInfo(ObjectHeader, , , 'tlfD');
}
}
若都有效,就会调用函数ObPushStackInfo()来完成记录工作。该函数还原如下:
BOOLEAN __stdcall
ObpPushStackInfo(
PVOID ObjectHeader, //对象头
char bRefrenceOrDefrence, //当前操作是增加引用还是减少引用
WORD Count,//当前操作增加或减少的计数
LONG Tag//当前增加或减少引用时所使用的Tag参数
)
{
BOOLEAN result;
PVOID CallStack[];
ULONG NextSequence; memset(CallStack, , 0x40);//初始化为零
if ( KeAreInterruptsEnabled() )
{
if ( KeGetCurrentIrql() <= DISPATCH_LEVEL )
{
if ( RtlCaptureStackBackTrace(, , CallStack, ) >= )//关键,获取调用栈中的所有返回地址
{
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;
}
该函数调用RtlCaptureStackBackTrace取得调用栈信息(内部调用了RtlWalkFrameChain),即返回地址,回溯的层数上限由ObpTraceDepth决定,Win7下该值为16。
并取全局变量ObpStackSequence作为序数(用后将其增1),调用ObpPushRefDerefInfo(),(此时若MmCanThreadFault()返回FALSE,则调用ObpDeferPushRefDerefInfo在一个WorkItem中完成该操作,关键过程相同)
该函数还原成代码如下,具体过程已经写在注释中了:
BOOLEAN __stdcall
ObpPushRefDerefInfo(
PVOID ObjectHeader,//对象头
BOOLEAN bRefrenceOrDefrence,//当前操作是增加引用还是减少引用
WORD Count,//当前操作增加或减少的计数
ULONG CurrentSequence,//一个序数
POBJECT_REF_TRACE Stacks,//当前调用栈地址信息
LONG Tag//当前增加或减少引用时所使用的Tag参数
)
{
WORD Index=;
WORD NextPos;
OBJECT_REF_INFO RefInfo={};
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 >= ) //判断Index是否超过了允许的最大值,若超过则认为溢出
{
DbgPrintEx(, , "ObpPushRefDerefInfo - ObpStackTable overflow\n");
}
else //若没有超过最大值,正常处理
{
NextPos = RefInfo->NextPos;//取当前可用的位置指针
while ( NexPos )//若有效
{
RefStackInfo=RefInfo.StackInfo[NextPos];//当前要保存栈信息的位置
PreRefStackInfo=RefInfo.StackInfo[NextPos-];//最后一次保存栈信息的位置
if ( CurrentSequence >= PreRefStackInfo->Sequence )//上一序数未超过当前值,则认为正常,跳出循环
break;
//超出的情况处置
RefStackInfo->Sequence=PreRefStackInfo->Sequence;
RefStackInfo->Index=PreRefStackInfo->Index;
RefStackInfo->NumTraces=PreRefStackInfo->NumTraces;
RefStackInfo->Tag=PreRefStackInfo->Tag;
NextPos -= ;//上移一个位置
}
pObjRefStackInfo=RefInfo.StackInfo[NextPos];//取当前可用的位置
pObjRefStackInfo->Index = Index | (WORD)(-(bRefrenceOrDefrence != ) & 0x8000);//保存Index,并根据是增加引用还是减少引用设置标志位
pObjRefStackInfo->NumTraces = Count;//保存此次的引用计数
pObjRefStackInfo->Sequence = NextSequence;
pObjRefStackInfo->Tag = Tag;
RefInfo->NextPos+=; //NextPos加1,指向下一个可用位置
}
}
}
//释放锁及其它,略
}
上述函数执行结束以后,本次对象引用时的调用上下文、引用时的Tag、引用记数、增加还是减少等信息就被记录到了ObpStackTable中.
其中ObpGetTraceIndex()计算调用地址Hash的算法如下:
ULONG GetStackInfoHash(PVOID Addrs)
{
ULONG Index=;
ULONG Value=;
ULONG Hash;
PUSHORT Key = (PUSHORT)Addrs;
for (Index = ; Index < 0x40 / sizeof(*Key); Index += ) { Value += Key[Index] ^ Key[Index + ];
}
Hash = ((ULONG)Value) % OBTRACE_STACKS;
return Hash;
}
三、如何查看跟踪记录的结果?
根据MS的说明,Windbg提供了命令!obtrace来查看某一对象的增加引用(Refrence)/减少引用(Derefrence)跟踪信息。
具体用法:!obtrace [对象地址]
然后Windbg将显示出所有对该对象的增加引用(Refrence)/减少引用(Derefrence)操作的记录。在这个记录中,可以清楚看到每次调用的调用栈,便于查找出问题的位置,同时还有Tag、计数等信息。可参考http://msdn.microsoft.com/en-us/library/ff564594(v=VS.85).aspx
下面我们来手工实现一下这个过程,同时也是从反方向加深对上述存储过程及相关结构的理解。
命令:!obtrace 0x862551b8
(1)在ObpObjectTable表中查找该对象
ObpObjectTable的地址为0x84daf548
Hash算法是:
#define GetObjHash(Object) ((((ULONG)Object>>4)&0xFFFFF)% 401)
Object=0x862551b8
计算Hash,GetObjHash(Object)=0x4E,
Offset=Hash*4=0x138;
所以位于ObpObjectTable+0x138处
kd>dd ObpObjectTable+
84daf678 8626a000
84daf688
可以看到,偏移0x138处的值为8626a000,这是一个_OBJECT_REF_INFO结构的指针。
kd> dt _OBJECT_REF_INFO 8626a000
nt!_OBJECT_REF_INFO
+0x000 ObjectHeader : 0x862551a0 _OBJECT_HEADER
+0x004 NextRef : (null) //用于链接Hash值相同的Object
+0x008 ImageFileName : [] "svchost.exe"
+0x018 NextPos : 0x243//本结构中StackInfo数组的索引,表明下一个可用的位置,达到MaxStacks时会自动申请更大的空间
+0x01a MaxStacks : 0x3fc//当前最大可用StackIndex
+0x01c StackInfo : [] _OBJECT_REF_STACK_INFO//_OBJECT_REF_STACK_INFO数组,偏移为0x1C
接下来查看本进程的第一次引用跟踪记录:
kd> dt _OBJECT_REF_STACK_INFO 8626a000+1C//第一个StackInfo
nt!_OBJECT_REF_STACK_INFO
+0x000 Sequence : 0x230//序列号,即ObpPushRefDerefInfo函数中的Sequence参数,只是一个全局的序数
+0x004 Index : 0x8000//在ObpStackTable中的索引,需要去掉0x8000标志位
+0x006 NumTraces : //此次引用的计数
+0x008 Tag : 0x746c6644//该次引用的Tag
这里得到一些有用的信息,Sequence为0x230,而Index为0(需要去掉0x8000标志位,有这个标志表明本次操作是增加计数的,否则是减少计数的,这个可以在ObpPushRefDerefInfo()的代码中得到印证),Tag为0x746c6644,即Dflt,这是内核中相关函数使用的默认Tag。这里面的Index是个很关键的值,它就是实际存储的调用栈信息在ObpStackTable中的索引。
ObpStackTable是下面这样一个结构(逆向得来):
typedef struct _OBJECT_TRACE_STACK_TABLE
{
WORD CurUsedStacksCnt;//当前已经使用的数目,从0开始递增,直至1024时对表项进行扩充
WORD TotalStacksCnt;//当前可用栈的总数目
POBJECT_REF_TRACE StackBuckets[];//总共16个表项,每个表的大小为0x10000,共包括0x10000/0x40=0x400=1024个项目
WORD IndexTable[];//最大的Index为16381,最后一项为0,其余初始化为0xFFFF,该值与_OBJECT_REF_STACK_INFO结构中的Index值对应
}OBJECT_TRACE_STACK_TABLE,*POBJECT_TRACE_STACK_TABLE;//TotalSize=0x8040 实际观察如下: kd> dd 84e35000 //ObpStackTable
84e35000 0400009f 84e3e000
84e35010
84e35020
84e35030
84e35040 ffffffff ffffffff ffffffff
84e35050 ffffffff ffffffff ffffffff ffffffff
84e35060 ffffffff ffffffff ffffffff ffffffff
84e35070 ffffffff ffffffff ffffffff ffffffff
可以看到,总共有16张表来存储调用栈地址,每个调用栈结构的大小为回溯深度16*sizeof(PVOID)=0x40,而每张表的大小为0x10000,所以每张表可放置0x400项内容。当超出0x400时,会申请第二张表,整体结构和三层句柄表有些相似,也是可扩充的。因为我们刚才得到的索引是0,所以显然是位于第一张表里,并且是第一项;如果索引是0x403,那么将位于第二张表里,位于第四项。
kd> dd 84e3e000+*0x40
84e3e000 83c25eff 83c70c43 83c582fa 83a4542a
84e3e010
84e3e020
84e3e030
可以看到,调用栈的返回地址已经在这里躺着了,共有4个有效的地址,看一看第一个地址:
kd> u 83c25eff
nt!ObCreateObject+0x1c4:
83c25eff 8b4528 mov eax,dword ptr [ebp+28h]
这和我们用!obtrace看到的结果是一样的:
+ Dflt nt!ObCreateObject+1c4
nt!PspAllocateProcess+e0
nt!NtCreateUserProcess+51a
nt!KiFastCallEntry+12a
这样,就对整个实现过程有了一个清晰的认识。
关于Win7的对象引用跟踪机制,分析到此结束,部分内容参考自Wrk,其它为逆向所得。
【旧文章搬运】深入分析Win7的对象引用跟踪机制的更多相关文章
- 【旧文章搬运】Win7可变对象头结构之InfoMask解析
原文发表于百度空间,2010-08-11========================================================================== 对Wind ...
- 【旧文章搬运】Win7 OBJECT_HEADER之TypeIndex解析
原文发表于百度空间,2010-08-09========================================================================== 在Wind ...
- 【旧文章搬运】从XP到Win7看Windows对象管理的变化(概述)
原文发表于百度空间,2010-08-01========================================================================== 今天花了一 ...
- 【旧文章搬运】PsVoid中IrpCreateFile函数在Win7下蓝屏BUG分析及解决
原文发表于百度空间,2010-04-05========================================================================== 这也许是我 ...
- 【旧文章搬运】Windbg+Vmware驱动调试入门(四)---VirtualKD内核调试加速工具
原文发表于百度空间,2009-01-09========================================================================== 今天又想起 ...
- 【旧文章搬运】分析了一下360安全卫士的HOOK(二)——架构与实现
原文发表于百度空间及看雪论坛,2009-10-14 看雪论坛地址:https://bbs.pediy.com/thread-99460.htm 刚发这篇文章的时候,因为内容涉及360的核心产品,文章被 ...
- 【旧文章搬运】Windbg+Vmware驱动调试入门(二)---Vmware及GuestOS的设置
原文发表于百度空间,2009-01-08========================================================================== 这一篇是主 ...
- 【旧文章搬运】PE感染逆向之修复(Serverx.exe专杀工具出炉手记)
原文发表于百度空间,2008-10-4看雪论坛发表地址:https://bbs.pediy.com/thread-73948.htm================================== ...
- 【旧文章搬运】加载PE文件时IAT的填充时机
原文发表于百度空间,2011-06-20========================================================================== 大致过程如 ...
随机推荐
- Spring <context:annotation-config> 和 <context:component-scan>区别
转自:http://www.cnblogs.com/leiOOlei/p/3713989.html <context:annotation-config> 和 <context:co ...
- MySQL获得指定数据表中auto_increment自增id值的方法及实例
http://kb.cnblogs.com/a/2357592/很多情况下,我们要提前用到当前某个表的auto_increment自增列id,可以通过执行sql语句来查询到这个id值. show ta ...
- 【每日Scrum】第五天(4.26) TD学生助手Sprint2站立会议
站立会议 组员 昨天 今天 困难 签到 刘铸辉 (组长) 今天增加了几个页面的子菜单,然后设计了几个要用的界面 今天和楠哥做了课程事件和日历表操作的例子,并尝试做时间表和日历表的数据库设计 安卓的数据 ...
- C语言函数的递归和调用
函数记住两点: (1)每个函数运行完才会返回调用它的函数:每个函数运行完才会返回调用它的函数,因此,你可以先看看这个函数不自我调用的条件,也就是fun()中if条件不成立的时候,对吧,不成立的时候就是 ...
- DDR硬件设计要点详解(包括电源部分)
转自 http://www.fairchildic.org/module/forum/thread-658-1-1.html (原帖包括详细的附件内容) 1. 电源 DDR的电源可以分为三类A.主电源 ...
- Matlab princomp函数浅析
matlab中的princomp函数主要是实现主成分分析的功能,有1一个输入参数,4个返回参数,形式如下: [coef, score, latent, t2] = princomp(X) 输入: X为 ...
- Centos7安装配置ansible运维自动化工具
准备至少两台机器 Centos7,这两台机器都关闭 selinux IP:106.13.118.132 服务端(ansible) masterIP:148.70.60.244 节点 slaver 服务 ...
- 怎么理解RSA算法
原文地址:http://www.ittenyear.com/414/rsa/ 怎么理解RSA算法 能够把非对称加密算法里的公钥想象成一个带锁的箱子,把私钥想象成一把钥匙 能够把对称加密算法里的密钥想象 ...
- EasyCamera海康摄像机向EasyDarwin云平台推送音视频数据的缓存设计
本文转自EasyDarwin团队成员Alex的博客:http://blog.csdn.net/cai6811376 EasyCamera在向EasyDarwin云平台推送音视频数据时,有时一个I帧会很 ...
- 关于indexOf的使用
今天项目中出现一个bug,在筛选数据的时候出现了冗余数据,查找发现在indexOf方法判断的时候找到了问题的所在. package demo; public class test { public s ...