本文主要是介绍调试之剑:从堆里抢救丢失的博客文章,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文/张银奎
很多使用计算机的人都曾经遇到过丢失数据的尴尬。记得我读大学时,很多文字编辑软件还没有自动存盘功能,而且寝室里偶尔会因为用电超过负荷而跳闸停电。每次断电时,如果赶上有人在电脑前写代码或者编辑文字,那么常常听到那位先生狠狠一跺脚(或者使劲一拍大腿),然后痛叫一声“唉呀,还有东西没存盘呢!”。因为输入的内容是临时保存在内存中的,如果忘记存盘或者在存盘前电脑死机或者突然断电,那么辛辛苦苦输入的东西就会化为乌有。
随着常用办公软件加入自动保存功能,因为忘记存盘而丢失数据的“悲剧”少了。但是偶尔也还会发生,因为自动存盘机制也有失灵的时候。最近一些年,基于网络的写作方式日益流行,比如写博客,在论坛上发帖子,或者点评别人的文章,对新闻发表看法等。这些写作很多是在浏览器里进行的,文字输入到不同形式的编辑框里,写作完毕时,发送到网站的服务器端保存。由于这种嵌入在网页中的编辑功能常常是没有自动存盘机制的,编辑内容一直临时保存在浏览器程序的内存空间中,如果编辑内容没有发送到服务器端,那么编辑的内容就可能丢失。笔者前不久就遇到了一次。
假日写作忙
故事发生在2010年的元旦假日,因为年底一个月很忙碌,所以久未写博客了,趁着元旦放假,略有空闲,于是想写点东西。一来练练手,免得本来就不熟练的文笔变得更加生疏;二来与网友们交流一下,免得朋友也生疏了。
于是上网,打开网页,准备写个短文。写什么呢?平时写的一般都是技术,不适合假日的气氛,于是想写点技术之外的闲话。放假前去了次闵行老街,就写这个吧,怀怀旧。写文章不是件简单的活,很多时候的确像赵本山说的那样,半天憋不出俩字。于是乎,就一篇简简单单的《重访闵行老街》(http://advdbg.com/blogs/advdbg_system/articles/3412.aspx),前前后后也用了几个小时。当然中间也有一些停顿,有时陪女儿玩一会,有时喝杯茶,这样到中午时,基本写好了,检查一遍就可以发出去了。但中饭时间到了,女儿强烈要求我煮意大利面给她吃,于是只好把写了大半的文章搁置下来。
厨房里忙活了一阵后,意大利面做好了。“老爸,意大利面煮的很棒”,女儿怪腔怪调的表扬给了我最好的奖励。吃过午饭,又想起写文章的事,看了一遍,改掉几个错别字,部分语句润色一下,自我感觉可以了,于是点击“发送”按钮,准备发布出去。
大意失博文
鼠标点下去后我就后悔了,或许被女儿刚才的表扬冲昏了头脑,我这个发送动作显然有些草率。应该在发送前先保存一下呀!虽然网页里没有保存功能,但是可以把内容Copy-Paste到记事本里保存到本地一份的。因为博客是在网页上写的,没有自动保存,如果发送失败,后果可能很严重。
导致发送失败的原因有很多,可能因为与服务器端的登录会话超时,可能因为服务器端网站服务存在故障,可能因为网络连接有问题。但是这时已经没有机会取消了,虽然还可以看到编辑的文字,可以选中,但是菜单中的Copy命令已经不管用了。
接下来看到的是浏览器与服务器艰难对话的过程,进度条缓慢的向前移动,状态条上提示“Connecting with server(正在连接服务器)”。这时我预感到事情不妙,这篇博客可能要白写,抱着无可奈何的心情又等了几秒钟后,问题真的出现了,浏览器切换到一个丑陋的错误提示页面,标题条上显示“Cannot find server(无法连接服务器)” (见图1),我一拍桌子,无语。
打开另一个浏览器,试图访问服务器,真的连不上,后来证明,服务器确实宕掉了,具体时间没有仔细查过,但肯定是在我写文章或者吃午饭的过程中,因为开始写这篇博客时还是连得上的。
根据经验,有时利用浏览器的回退功能还可以退回到刚才编辑的页面,救回所写的内容。于是点击Back按钮,但是浏览器并没有老老实实的回退到刚才的编辑页面,而是再次试图连接服务器,连接失败,又显示 “Cannot find server”,原因是前一个页面上的“自动脚本”会触发连接服务器的动作,糟糕的软件!
向前又向后来回了几次,仍无济于事,我意识到,刚才几个小时的工夫要白费了。这时老婆从书桌路过,听说我刚才写的东西丢了后,她哈哈大笑,“你也会犯这种错误的啊,我还以为只有我们这些人才犯这种错误哪,呵呵呵!”
是啊,这个错误是够低级的,要么应该用更好一点的工具来写,要么该先保存一下……但是现在说这个都没用了。
上调试器!
怎么办,重写一遍?不可接受。放弃拉倒?不可接受。想了一会后,我决定用调试器来寻找刚才写的文章。上调试器,对上调试器。因为文章应该还在内存里,在内存里,就应该可以用调试器找得到,找得到就可以读出来,读出来了就可以找办法恢复成可读的形式。
启动WinDBG,点击File→ Attach to Process,然后选择浏览器进程(iexplore.exe)。因为系统中常常有多个浏览器程序的运行实例,因此首先要确认附加上的进程就是刚才写博客的那个进程,这可以通过尝试切换到刚才的浏览器窗口来判断,如果切换不过去,那就对了,证明这个进程已经被中断到调试中。
执行“lm”命令,可以看到进程中有几十个模块;执行“~”命令列一下线程,可以看到进程中有四十几个线程;用!heap命令列一下堆,有44个。要找的文章应该保存在当前进程的内存空间中,因此我们重点关心的是内存。在32位系统中,一个进程的内存空间有4GB大小,通常低2GB是用户态空间,给用户态代码使用,高2GB是内核空间,给系统代码使用。执行“!address”命令可以列出用户态空间中的所有区域:
在列出每个区域后,!address命令还会给出一个非常友好的统计报告,如图2所示。
根据经验,用于保存编辑文字的内存块应该是从堆上动态分配出来的。根据图2,当前进程里堆空间有116756KB大小,也就是100多MB,1亿多字节。也就是说,我们要在一亿多字节的堆中寻找几千个字节的博客文章。
搜索
看来我们要在1亿多字节的空间中寻找几千个字节的文章。如果用笨办法一个字节一个字节地找,那么真有点像大海捞针。茫茫1亿多字节的堆空间,在哪里找自己写的文章呢?
当然要依靠自动搜索,好在WinDBG已经为我们准备好了强大的内存搜索命令,可以在任意指定的空间范围内搜索不同类型的数据。
搜索吧!就从当前进程用户态空间的较低地址开始搜,找文章里的一句有特色的话:
0:045> s -u 10000 L80000 "当年在交大"
前几次尝试,什么都没找到,看来要扩大搜索范围:
0:045> s -u 10000 L8000000 "当年在交大"
延迟一刹那后,刷刷出来很多结果(见图3)。搜到了!这让人很高兴,说明数据还在内存里。
但是仔细看看搜索的结果后,又有点怀疑。因为WinDBG不支持中文显示,所以有点不确认上面搜到的信息。为了确认,执行“r”命令,观察ESP寄存器的值:
0:043> r esp
esp=02c9ffcc
然后使用内存编辑命令在栈上输入上面的中文字符(见图4),再用内存观察命令查看,证实上面搜索到的结果是对的,5f53就是“当”字的编码, 4ea4 5927 就是“交大”。
图3中的搜索结果告诉我们搜到很多处,看来文章被复制了很多次,可能是不同阶段的处理函数分别作了拷贝。
使用哪一处的数据呢?当然希望是完整的,再搜索一下文章末尾的一个特征字:
s -u 10000 L8000000 "曙光"
也找到很多份(见图5),看来只要用对了工具和方法,刚刚苦苦寻找不可见的数据现在可以变得俯拾即是。
考虑到文章的长度在几千个字节大小,因此找图3和图5两个结果中差值大约为几KB的地址,观察一下属性:
0:045> !address 0728988a
06fa0000 : 07288000 - 00005000
Type 00020000 MEM_PRIVATE
Protect 00000004 PAGE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageHeap
Handle 00150000
在堆上,靠谱!
保存到文件
接下来的目标是将数据从内存堆保存到文件,WinDBG已经帮我们准备好了一条命令:
0:045> .writemem c:\dumps\blog.txt 07288600 L2000
Writing 2000 bytes....
写好了,用记事本打开看一下(见图6):
乱码,应该高兴还是失望?看到这种情况,老实说,我当时挺高兴的,根据经验这很像是中文,Unicode编码的,只不过没有按中文显示罢了。
怎么才能让编辑器程序按中文显示呢?方法很简单,对于文本文件,只要在文件开头加入Unicode文件的两个标识字符就行了,也就是0xFF FE。
怎么加入这两个字符呢?使用任何二进制编辑工具都可以,手头最方便的就是VC6。右键选择使用VC6打开,文件开头刚好有几个空闲的字符(<P>之类),将其修改为0xFF FE后,另存为blog_u.txt。再打开观看,便可以看到亲切的中文字符了(见图7)。
哦,成功了!其实刚才加入0xFF FE那一步也可以在调试器里使用内存编辑命令来完成,编辑好再保存成文件。
回味
费了一番周折,丢失的文章终于又找到了。待服务器恢复正常后,整理一下,一篇失而复得的怀旧博文便发布出去了。回顾一下,首先多亏自己没有轻易放弃,一旦把浏览器程序关掉退出,那么内存空间就被销毁了,再想找到文章就更难了。另外,要感谢以调试器为核心的调试技术,正如我在很多地方都曾经说到的,调试技术绝对不仅仅是用来找BUG的,它的用途还有很多很多,在以后的文章中我们会不断给大家介绍。
主持人:张银奎
《软件调试》一书作者,从事软件开发和研究10余年,对IA-32架构、操作系统内核、虚拟技术,尤其对软件调试有较深入的研究。翻译(合译)作品包括《数据挖掘原理》、《机器学习》、《人工智能:复杂问题求解的结构和策略》等。
(本文来自《程序员》杂志10年03期)
这篇关于调试之剑:从堆里抢救丢失的博客文章的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!