ProcMem —— 进程内存查看工具

ProcMem是一个进程内存的查看工具,他可以显示进程中的内存分配情况,以及内存大概的用途,并且Dump指定的内存模块。工具界面如下图

20130421231145

ProcMem并不是实时监控目标进程的内存情况,而是对内存情况作了一次快照和统计,并且显示出来。所以想看到进程最新的内存状态,可以点击Refresh菜单。

工具上半部分就是显示的目标进程的内存分布情况,以及一些细节信息。这里必须要谈到一点,Windows的标准控件中没有TreeList,对我这个写100个程序99个没有界面的人来说,自绘这个东西差点没要了我的命。

工具的下半部分用来显示TreeList选中项的内存情况,十六进制表示。值得注意的是,这里只会显示选中内存头个PAGE_SIZE大小的内存情况。如果想查看该项内存的全部情况,可以使用Dump功能,把内存Dump下来,然后用WinHex这样的工具查看,这个简单的内存显示区,只是为了提供一个预览功能而已。

值得一提的是,菜单Find,不是用来查找下方十六进制内存显示的内容,而是用来查找TreeList中的项目。例如想找到有关ntdll的内存区域,可以在查找框中输入ntdll,这样就可以定位如图所示的项目了。

这个工具是我花了大半周的业余时间弄的,时间比较仓促,不可避免的可能会有些bug。如果你刚好用上了这个工具,而且发现了bug,不妨通过邮件联系我(邮箱地址见About Me页面)。

下载ProcMem(包括32和64位版本)

DebuggingNTInternals

记VC6中STL的map的一处BUG

今天和同事一起调了一个vc6.0中stl的map的一个bug。

BUG的起因是,我们的项目中使用了stl的map类。而这个map类的对象被用在了不同的DLL模块中,在这样的条件写,BUG就产生了,一个模块内部map对象指针能正常工作,另一个模块内部就出了问题。起初我们就觉得很奇怪,很简单一份代码,怎么会出现访问无效内存的情况,我们还是通常的思路,先在自己身上找问题。调了一会发现,自己的代码确实没有错误。于是我们把目光转向了vc6的stl本身。

跟踪了一下stl的代码,发现错误发生在下面这段代码内部。

20130419140128

在正常的模块中while (_x != _Nil)这个循环只经历了一次,就跳出循环了。而发生错误模块中第二次进入这个循环,也就在这次的循环中,出现了内存访问异常的情况。很明显就要看两次_x != _Nil比较的详细结果。刚开始我被误导了,以为_Nil就是一个为0的常量,把注意力留在_X上后来发现,正确和错误的模块中,_X值一直都是相同的。这才缓过神来,_Nil这个值有问题。

确实,这个不是一个0,更不是一个常量,他是一个静态指针变量。在map对象被创建的时候,生成了一个填充为0的结构体,并且把结构体指针存到了这个变量中。

真相大白了,由于在不同的模块中都是用了stl的map代码,这样map的代码就被编译了两份,同样每个模块中map的_Nil也存放在各自的模块地址范围内。这样就使得_Nil值是不相同的,如果在非创建这个map对象的模块中引用对象指针,并且调用map的函数。如果遇到了_Nil,就会引用此模块自己的_Nil,而不是创建对象模块的_Nil,如果这个模块没有初始化过map对象,那么这个模块的_Nil就是0,即使初始化过,两个模块的_Nil也没可能是同一个值。

新版stl中这个bug必然已经解决,简单来看看vs2010的stl

20130419142503

循环中检查是否为空,用到了函数_Isnil这个,而这个函数查看了_Nodeptr结构中的_Isnil成员变量。判断空放在成员对象内部,这样在多模块之间调用该对象就不会有任何问题了。

话说,vc6的stl确实是bug一堆,很早之前,人们就喜欢用sgi-stl来代替vc6自带的stl了。

DebuggingTips

文件搜索工具everythings工作原理简介

everythings是一个非常强大而且好用的文件搜索工具。他搜索文件的速度非常之快,基本上刚刚输入要查找的文件名,文件已经搜索了出来。这里就简单介绍一下他的工作原理。

有一点已经非常明显,everythings在查找工作开始之前会建立全盘文件索引的数据库,那么这个数据库必然就是搜索文件快速的最重要的原因。而这个数据库的建立的用时似乎非常之短,是普通方法下遍历整个卷所不能及的。

实际上,建立数据库的方法确实比较特殊。简单来说就是遍历目标卷NTFS文件系统的Master File Table(简称MFT)的记录,这也解释了为什么everythings只能工作在是NTFS的文件系统的卷上。MFT可以看成ntfs中文件的索引,MFT中的每条记录都是指向卷中的一个文件。遍历这个索引的速度可要比按照目录递归整个卷的速度要快得多了。不过可惜的是,Windows并没有提供能够直接访问MFT的API,除非你直接解析NTFS磁盘格式(参考这篇文章)。而且就算让你直接遍历的MFT,要监控文件的变化并且写入数据库也是一个不好办的工作。不过可喜的是,微软提供了一种间接遍历MFT记录的方式,并且通过这样的方式可以监控卷上文件的变化,他就是Change Journal(官方文档.aspx))。

Change Journal实际上是Windows 2000的NTFS文件系统就提供了的功能。其目的就是如同名字一样,记录文件改变的日志,方便NTFS文件系统对文件的恢复,这确实是个不错的特性。如何使用这个功能去遍历和监控MFT的记录,我这里就不做详细介绍了。因为有一片更加好的文章已经写的非常的清楚,我的demo也参考了他的很多代码。这篇文章叫做《Keeping an Eye on Your NTFS Drives: the Windows 2000 Change Journal Explained》是1999年9月份的msdn杂志发表的。

我这里假设你已经阅读了这篇文章。我们已经知道了遍历和监控MFT记录的方法,并且通过这个方法获得了每个文件的文件名,file reference和parent reference(可以理解为文件id和其父目录id)。那么这里就可以把这三个元素作为一条记录存储在我们制定的数据库中。

下面简单说下如何利用这个数据库,当需要查找文件的时候:
1.获得用户输入的文件名。
2.通过文件名A可以从数据库中筛选出一些记录,而这些记录中就包括了文件的parent reference。
3.通过parent reference再次查找数据库的file reference字段,获得对应的文件名B,这个文件名B就是文件A的父目录了。重复这一步直到根目录为止。
这样就能找到文件的详细路径了。

我写demo的时候并没有考虑怎么构建这个数据库,直接用了sqlite。不过如果能自己设计一个专门为存储这些数据的小数据库,也应该有更高的效率吧。everythings的数据库就是经过bzip压缩的自定义的文件。

DEMO——构建文件索引数据库:
20130409003609

DEMO——查找文件
20130409003752

P.S 如果采用sqlite作为文件数据库的话,建立索引的插入数据操作一定要利用sqlite的事务机制,否则会在插入数据上花费很多时间。

P.S.2 everythings之所以需要管理员权限才能工作,是因为他需要打开本地卷的句柄,这个就需要管理员权限了。

最后说一点这种方法的不足吧。那就是这种方法对于有多个hardlinks的文件,只能枚举出一个路径。关于hardlink的介绍可以参考这篇文章。由于这个不足,你在查找system32下的文件的时候,往往搜索不到,搜索到的又往往在其他目录,出现尤其多的应该是Winsxs目录了(而关于Winsxs可以看看这篇翻译,翻译质量很不错)。例如我们搜索C盘下的notepad.exe,everythings搜索的结果是这样的:
20130409021029

可以注意到并没有system32下notepad.exe的身影。我们的demo同样也会遇到这样的情况:
20130409021252

下图显示的是同一个notepad.exe的4个存储位置。
20130409021829

虽然everythings有这样的一个不足,但是瑕不掩瑜,他的的确确是一个非常非常优秀的工具!我这里探讨的也仅仅是他的大概原理。实际上这个工具如此优秀,必然是在很多细节上做的非常好才行的。强烈推荐大家使用!

NTInternals

proc_dump_study —— 进程dump工具

proc_dump_study是逆向sysinternals的Procdump的一个工具。在功能上几乎和procdump一模一样。有一点差距就是目前没有支持clr的异常,也就是procdump的-g参数。其他的usage基本上相同,这里也不细说了。想说的一点是,proc_dump_study和procdump一样,功能比较强大,参数也比较多。所以为了方便使用,我把用的比较多的功能总结了一下,写了一个带UI shell程序。这样就方便测试人员或者不想深入理解命令行程序的人员使用。

20130404013936

简单介绍一下使用方法
1.选择要监控或者dump的进程,确定生成dump的文件位置。
2.选择dump类型,包括mini dump,full dump以及effective dump,dump的大小分别为小,大,中等。
3.选择是否监控进程的cpu使用率
4.选择是否监控进程的内存提交数量
5.选择是否监控进程窗口是否挂起
6.选择是否监控进程发生异常
7.选择是否监控进程推出
8.最后点击dump按钮

这样,一旦监控的时候任何监控点达到要求,就会产生dump文件了。如果不需要监控任何进程窗口,程序会立刻dump进程。实际上直接使用proc_dump_study会有更多的功能可以使用,不过这个UI版本应该可以应付大多数的情况了吧。

下载proc_dump_ui

最后放一张测试图
20130402230714

DebuggingNTInternals