25

调试之剑:从堆里抢救丢失的博客文章

作者: wuzhimin 分类:坊间人语   阅读:13,308 次 添加评论

/张银奎


很多使用计算机的人都曾经遇到过丢失数据的尴尬。记得我读大学时,很多文字编辑软件还没有自动存盘功能,而且寝室里偶尔会因为用电超过负荷而跳闸停电。每次断电时,如果赶上有人在电脑前写代码或者编辑文字,那么常常听到那位先生狠狠一跺脚(或者使劲一拍大腿),然后痛叫一声“唉呀,还有东西没存盘呢!”。因为输入的内容是临时保存在内存中的,如果忘记存盘或者在存盘前电脑死机或者突然断电,那么辛辛苦苦输入的东西就会化为乌有。

随着常用办公软件加入自动保存功能,因为忘记存盘而丢失数据的“悲剧”少了。但是偶尔也还会发生,因为自动存盘机制也有失灵的时候。最近一些年,基于网络的写作方式日益流行,比如写博客,在论坛上发帖子,或者点评别人的文章,对新闻发表看法等。这些写作很多是在浏览器里进行的,文字输入到不同形式的编辑框里,写作完毕时,发送到网站的服务器端保存。由于这种嵌入在网页中的编辑功能常常是没有自动存盘机制的,编辑内容一直临时保存在浏览器程序的内存空间中,如果编辑内容没有发送到服务器端,那么编辑的内容就可能丢失。笔者前不久就遇到了一次。


假日写作忙

故事发生在2010年的元旦假日,因为年底一个月很忙碌,所以久未写博客了,趁着元旦放假,略有空闲,于是想写点东西。一来练练手,免得本来就不熟练的文笔变得更加生疏;二来与网友们交流一下,免得朋友也生疏了。

于是上网,打开网页,准备写个短文。写什么呢?平时写的一般都是技术,不适合假日的气氛,于是想写点技术之外的闲话。放假前去了次闵行老街,就写这个吧,怀怀旧。写文章不是件简单的活,很多时候的确像赵本山说的那样,半天憋不出俩字。于是乎,就一篇简简单单的《重访闵行老街》(http://advdbg.com/blogs/advdbg_system/articles/3412.aspx),前前后后也用了几个小时。当然中间也有一些停顿,有时陪女儿玩一会,有时喝杯茶,这样到中午时,基本写好了,检查一遍就可以发出去了。但中饭时间到了,女儿强烈要求我煮意大利面给她吃,于是只好把写了大半的文章搁置下来。

厨房里忙活了一阵后,意大利面做好了。“老爸,意大利面煮的很棒”,女儿怪腔怪调的表扬给了我最好的奖励。吃过午饭,又想起写文章的事,看了一遍,改掉几个错别字,部分语句润色一下,自我感觉可以了,于是点击“发送”按钮,准备发布出去。


大意失博文

鼠标点下去后我就后悔了,或许被女儿刚才的表扬冲昏了头脑,我这个发送动作显然有些草率。应该在发送前先保存一下呀!虽然网页里没有保存功能,但是可以把内容Copy-Paste到记事本里保存到本地一份的。因为博客是在网页上写的,没有自动保存,如果发送失败,后果可能很严重。

导致发送失败的原因有很多,可能因为与服务器端的登录会话超时,可能因为服务器端网站服务存在故障,可能因为网络连接有问题。但是这时已经没有机会取消了,虽然还可以看到编辑的文字,可以选中,但是菜单中的Copy命令已经不管用了。

接下来看到的是浏览器与服务器艰难对话的过程,进度条缓慢的向前移动,状态条上提示“Connecting with server(正在连接服务器)”。这时我预感到事情不妙,这篇博客可能要白写,抱着无可奈何的心情又等了几秒钟后,问题真的出现了,浏览器切换到一个丑陋的错误提示页面,标题条上显示“Cannot find server(无法连接服务器)” (见图1),我一拍桌子,无语。

打开另一个浏览器,试图访问服务器,真的连不上,后来证明,服务器确实宕掉了,具体时间没有仔细查过,但肯定是在我写文章或者吃午饭的过程中,因为开始写这篇博客时还是连得上的。

根据经验,有时利用浏览器的回退功能还可以退回到刚才编辑的页面,救回所写的内容。于是点击Back按钮,但是浏览器并没有老老实实的回退到刚才的编辑页面,而是再次试图连接服务器,连接失败,又显示 “Cannot find server”,原因是前一个页面上的“自动脚本”会触发连接服务器的动作,糟糕的软件!

向前又向后来回了几次,仍无济于事,我意识到,刚才几个小时的工夫要白费了。这时老婆从书桌路过,听说我刚才写的东西丢了后,她哈哈大笑,“你也会犯这种错误的啊,我还以为只有我们这些人才犯这种错误哪,呵呵呵!”

是啊,这个错误是够低级的,要么应该用更好一点的工具来写,要么该先保存一下……但是现在说这个都没用了。


上调试器!

怎么办,重写一遍?不可接受。放弃拉倒?不可接受。想了一会后,我决定用调试器来寻找刚才写的文章。上调试器,对上调试器。因为文章应该还在内存里,在内存里,就应该可以用调试器找得到,找得到就可以读出来,读出来了就可以找办法恢复成可读的形式。

图1  向服务器端发送博客文章时失败

图1 向服务器端发送博客文章时失败

启动WinDBG,点击File→ Attach to Process,然后选择浏览器进程(iexplore.exe)。因为系统中常常有多个浏览器程序的运行实例,因此首先要确认附加上的进程就是刚才写博客的那个进程,这可以通过尝试切换到刚才的浏览器窗口来判断,如果切换不过去,那就对了,证明这个进程已经被中断到调试中。

执行“lm”命令,可以看到进程中有几十个模块;执行“~”命令列一下线程,可以看到进程中有四十几个线程;用!heap命令列一下堆,有44个。要找的文章应该保存在当前进程的内存空间中,因此我们重点关心的是内存。在32位系统中,一个进程的内存空间有4GB大小,通常低2GB是用户态空间,给用户态代码使用,高2GB是内核空间,给系统代码使用。执行“!address”命令可以列出用户态空间中的所有区域:

技术4-2

在列出每个区域后,!address命令还会给出一个非常友好的统计报告,如图2所示。

图2  !address命令给出的内存统计报告

图2 !address命令给出的内存统计报告

根据经验,用于保存编辑文字的内存块应该是从堆上动态分配出来的。根据图2,当前进程里堆空间有116756KB大小,也就是100多MB,1亿多字节。也就是说,我们要在一亿多字节的堆中寻找几千个字节的博客文章。


搜索

看来我们要在1亿多字节的空间中寻找几千个字节的文章。如果用笨办法一个字节一个字节地找,那么真有点像大海捞针。茫茫1亿多字节的堆空间,在哪里找自己写的文章呢?

当然要依靠自动搜索,好在WinDBG已经为我们准备好了强大的内存搜索命令,可以在任意指定的空间范围内搜索不同类型的数据。

搜索吧!就从当前进程用户态空间的较低地址开始搜,找文章里的一句有特色的话:

0:045> s -u 10000 L80000 ”当年在交大”

前几次尝试,什么都没找到,看来要扩大搜索范围:

0:045> s -u 10000 L8000000 ”当年在交大”

延迟一刹那后,刷刷出来很多结果(见图3)。搜到了!这让人很高兴,说明数据还在内存里。

图3  搜索博客文章

图3 搜索博客文章

但是仔细看看搜索的结果后,又有点怀疑。因为WinDBG不支持中文显示,所以有点不确认上面搜到的信息。为了确认,执行“r”命令,观察ESP寄存器的值:

0:043> r esp

esp=02c9ffcc

然后使用内存编辑命令在栈上输入上面的中文字符(见图4),再用内存观察命令查看,证实上面搜索到的结果是对的,5f53就是“当”字的编码, 4ea4 5927 就是“交大”。

图4  编辑和观察栈

图4 编辑和观察栈

图3中的搜索结果告诉我们搜到很多处,看来文章被复制了很多次,可能是不同阶段的处理函数分别作了拷贝。

使用哪一处的数据呢?当然希望是完整的,再搜索一下文章末尾的一个特征字:

s -u 10000 L8000000 ”曙光”

也找到很多份(见图5),看来只要用对了工具和方法,刚刚苦苦寻找不可见的数据现在可以变得俯拾即是。

图5  搜索博客文章末尾的特征字符

图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):

图6  观察WinDBG保存出来的文章

图6 观察WinDBG保存出来的文章

乱码,应该高兴还是失望?看到这种情况,老实说,我当时挺高兴的,根据经验这很像是中文,Unicode编码的,只不过没有按中文显示罢了。

怎么才能让编辑器程序按中文显示呢?方法很简单,对于文本文件,只要在文件开头加入Unicode文件的两个标识字符就行了,也就是0xFF FE。

怎么加入这两个字符呢?使用任何二进制编辑工具都可以,手头最方便的就是VC6。右键选择使用VC6打开,文件开头刚好有几个空闲的字符(<P>之类),将其修改为0xFF FE后,另存为blog_u.txt。再打开观看,便可以看到亲切的中文字符了(见图7)。

图7  加入Unicode标识后再观察

图7 加入Unicode标识后再观察

哦,成功了!其实刚才加入0xFF FE那一步也可以在调试器里使用内存编辑命令来完成,编辑好再保存成文件。


回味

费了一番周折,丢失的文章终于又找到了。待服务器恢复正常后,整理一下,一篇失而复得的怀旧博文便发布出去了。回顾一下,首先多亏自己没有轻易放弃,一旦把浏览器程序关掉退出,那么内存空间就被销毁了,再想找到文章就更难了。另外,要感谢以调试器为核心的调试技术,正如我在很多地方都曾经说到的,调试技术绝对不仅仅是用来找BUG的,它的用途还有很多很多,在以后的文章中我们会不断给大家介绍。


主持人:张银奎

张银奎

《软件调试》一书作者,从事软件开发和研究10余年,对IA-32架构、操作系统内核、虚拟技术,尤其对软件调试有较深入的研究。翻译(合译)作品包括《数据挖掘原理》、《机器学习》、《人工智能:复杂问题求解的结构和策略》等。

(本文来自《程序员》杂志10年03期


转播到腾讯微博

----->立刻申请加入《程序员》杂志读者俱乐部,与杂志编辑直接交流,参与选题,优先投稿

19 Responses to “调试之剑:从堆里抢救丢失的博客文章”

  1. fox 说道:

    好厉害啊

  2. fox 说道:

    很好的文章,长见识了

  3. FireBurn 说道:

    呵呵,非常精彩的调试实战

  4. fc 说道:

    张老师就是牛啊….

  5. Smile 说道:

    唉,我以为是哪个牛人呢,原来就是张老师本人,呵呵!

  6. cdp_anhui 说道:

    我说是谁这么厉害呢?原来是张老师啦。
    买了本《软件调试》,到现在WinDbg还不熟。内核调试更不会了。

  7. 小杰 说道:

    呵呵,是啊,这个东西俺都会弄,张老师弄还不是小菜

  8. xxx 说道:

    居然用IE6, …..

  9. dcss 说道:

    楼主太强大了,我也遇到过这种问题可是苦于找出解决办法这回真是太开眼了

  10. jom 说道:

    同楼上,大开眼界了。

  11. 夏志菁 说道:

    好强大 我也要学会

  12. 路人~~~ 说道:

    厉害~~~

  13. crazypeter2005 说道:

    强人啊,拜读过您的 软件调试 大作。
    向您学习!

  14. foxit 说道:

    太牛了,看了这篇文章,觉得很崇拜,就买了软件调试,刚刚到货,慢慢啃,呵呵。谢谢张老师啊。

  15. 新苗 说道:

    不仅是太牛,简直可以用“彪悍”来形容吧!

  16. wang zi 说道:

    肯定上的是垃圾博客,现在好的博客系统都有自动保存功能

  17. xiaohuangdou 说道:

    wig sale 真的可以这么神奇吗?

  18. tom 说道:

    很强大!!

  19. 李安安 说道:

    技术很牛啊

请评论

preload preload preload
京ICP备06065162