概述
写书是很花时间的事,偏巧这两年的写作计划又撞车到了一起。周一到周五一般没时间写,就只有周六和周日的时间。而在周六和周日的时间里,又容易冒出写文章的念头,如果写文章,那么写书的时间就又被抢占了。
昨天是周六,本来是打算拿出一整天,冲一冲书稿的,可是早上一起来,有了写文章的兴致,想起了在脑海中萦绕许久的一个题目:《江湖上的那些传说》。于是自己欺骗自己,小声对自己说:“文章一会就写好,写好了再写书稿。”但其实文章也没那么好写,一写可能就是半天。
于是,打开电脑,登录微信公众号,开始新的图文,敲出题目:江湖上的那些传说,然后开始铺开正文。
江湖是什么?
本意就是江河湖海。延申一下,就是泛指四方各地。
因为流浪的人,居无定所,漂流四方,所以“江湖”便有了流浪的意味。
其实不止穷人流浪,一些武艺高强的人也喜欢行走于“江湖之间”,劫富济贫,扬善惩恶,于是江湖一词又有了一种“侠义”的味道。
江湖不总是风平浪静的,可能阴云密布,杀声震耳,血流满地......某些时候,正义也干不过邪恶,于是江湖又有了“阴险”、“狡诈”等负面的含义。
......
因为是酝酿已久的题目,大多数素材都在心里,倾泻而出,一行行转化成文字,这样畅快的写作体验也是难得的。
写了一半的时候,因为要放插图,我不得不打开一个PPT文件,从中复制一幅图过来,因此切换了几次窗口。
当我从另一个窗口切回Chrome浏览器里的公众号图文编辑窗口,正输入系统两个字时,敲入的xi两个字符只显示出来一个x,窗口便僵住了。
我立刻有了个不好的预感,根据多年的经验,浏览器似乎挂住了。
有时挂住是短时间的,过一会会苏醒,于是我怀着焦急的心情等它醒来。
过了几秒钟,Chrome窗口的标题变了,出现了我不想看到的“Not Responding”。
糟糕,怕什么什么就来了,前面写的文字只有一小部分有备份。本来准备速战速决的事在半路卡壳了。
先截屏留个证据吧。
等了一会没有变化。试了简单的方法也没有效果。
怎么办呢?放弃?那已经写了一半的文章就白写了。
或者根据记忆赶紧重写一遍?那不符合我的方法论啊!
眼睛看着已经僵死的Chrome窗口,我的大脑里在思考下一步的作战方案......
很多年前,曾经使用WinDBG抢救丢失的博客。并把过程写成了文章,先在《程序员》杂志发表,而后又作为首篇首章收录到《格蠹汇编》里。今年盛格塾小程序推出后,又成为小程序里的第一批作品。
看上图中的日期,上次“战役”发生在2010年的元旦假期,是11年前了。
11年间,计算机系统的软硬件环境变化巨大,当时使用的还是IE浏览器,今天IE已经被Chrome打的一败涂地,几乎销声匿迹了。
要和Chrome动手,第一个问题就是要找到合适的进程。与IE的简单进程模型不同,Chrome是多进程模型,打开一个网站就会有一群进程。如果搞错了进程,那就是白折腾。就像两个小孩打架,如果有一方是双胞胎,那么就可能搞错人。
Chrome的很多设计秉承邪恶理念,野蛮占用系统资源,在上面的任务管理器“详细信息”视图中,可以看到一群Chrome进程。
值得注意的是,详细视图里不显示进程是否挂死这个信息,所以不好判断那一群Chrome进程里哪个是挂住的。“进程”页可能显示这样的信息,当我切换到进程页后,果然看到有个Chrome进程的状态是“已挂起”。
确定了进程后,我先产生了一个转储文件。把内存里的所有信息永久存档。转储文件本来在temp目录,我将其复制到我专门用来归档转储文件的地方。
从上图看,转储文件有936 MB,接下来的目标是如何在这接近1GB的数据里,找到我想要的图文信息。
在寻找我的数据之前,我想先看看Chrome到底如何挂死的,是挂在哪里。如果是简单挂死,那么还可能将其救活。
唤出WinDBG,选择转储文件,点击确定,可是WinDBG极其缓慢,显示BUSY,不能进入命令状态。
点击Break也没有效果,等了几分钟,仍是BUSY,让人心烦。
于是我又唤出了NanoCode,选择转储文件,点击确定,瞬间就进入命令状态了。
去年在英特尔分享时,也遇到类似的情况,NanoCode也表现卓越。
执行~* k观察线程概况,可以看到有30多个线程。
因为是GUI挂死,所以切换到0号线程,再k,仔细观察它的栈回溯。
函数调用很长,为了方便观察,执行kcn显示简洁的函数调用关系。
# Call Site
00 ntdll!NtDelayExecution
01 KERNELBASE!SleepEx
02 chrome_elf!DumpHungProcessWithPtype_ExportThunk
03 KERNELBASE!UnhandledExceptionFilter
04 ntdll!RtlUserThreadStart$filt$0
05 ntdll!_C_specific_handler
06 ntdll!RtlpExecuteHandlerForException
07 ntdll!RtlDispatchException
08 ntdll!RtlRaiseException
09 KERNELBASE!RaiseException
0a chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
0b chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
0c chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
0d chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
0e chrome!GetHandleVerifier
0f chrome!GetHandleVerifier
10 chrome!GetHandleVerifier
11 chrome!GetHandleVerifier
12 chrome!GetHandleVerifier
13 chrome!GetHandleVerifier
14 chrome!GetHandleVerifier
15 chrome!GetHandleVerifier
16 chrome!GetHandleVerifier
17 chrome!GetHandleVerifier
18 chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
19 chrome!Ordinal0
1a chrome!Ordinal0
1b chrome!Ordinal0
1c chrome!ovly_debug_event
1d chrome!ovly_debug_event
1e chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
1f chrome!GetHandleVerifier
20 chrome!IsSandboxedProcess
21 chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
22 chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
23 chrome!RelaunchChromeBrowserWithNewCommandLineIfNeeded
24 chrome!CrashForExceptionInNonABICompliantCodeRange
25 chrome!CrashForExceptionInNonABICompliantCodeRange
26 chrome!CrashForExceptionInNonABICompliantCodeRange
27 chrome!ChromeMain
28 chrome!ChromeMain
29 chrome_exe!GetHandleVerifier
2a chrome_exe!GetHandleVerifier
2b chrome_exe!IsSandboxedProcess
2c kernel32!BaseThreadInitThunk
2d ntdll!RtlUserThreadStart
上面的栈回溯就像一个人的履历,里面有些很平淡,有些很惊险。去掉平淡,留下关键环节,那就是:
00 ntdll!NtDelayExecution
01 KERNELBASE!SleepEx
02 chrome_elf!DumpHungProcessWithPtype_ExportThunk
03 KERNELBASE!UnhandledExceptionFilter
09 KERNELBASE!RaiseException
27 chrome!ChromeMain
看栈回溯一般应该从下往上看,栈帧27是进程的入口,栈帧09表示有软件发起异常,一般就是通过throw这样的机制拨打110,报警。
栈帧03代表无人处理异常,进入所谓的“无人处理”流程。栈帧02表示chrome_elf模块注册了一个顶级的“未处理异常”处理器,它接管了这个未处理异常。微软的Office程序一般也会注册这个顶级异常处理器,用来紧急存盘。
审视chrome_elf这个模块名,其中的elf后缀让我看的好别扭。在软件领域,ELF是个著名的术语,代表的是Linux下广泛使用的可执行文件格式标准。
可是,现在明明运行的是Chrome的Windows版本啊。这个ELF是何含义呢?使用内存搜索命令列出这个模块的所有可读字符:
s -sa 0007ffe`1af20000 00007ffe`1b01e000
得到的信息量很大,暂时不想细读。
继续看栈帧01,是邪恶的Sleep,老雷焦急万分,这里却在调用Sleep睡觉,真是糟糕的软件啊。
反汇编调用SleepEx的函数,观察调用SleepEx之前的指令,是mov ecx, 0ea60。
根据经验,这是传递给SleepEx的参数,代表要睡觉的时间。翻译成10进制,是6万,单位为毫秒,那就是60秒,一分钟。
0:000> .formats ea60
Evaluate expression:
Hex: 00000000`0000ea60
Decimal: 60000
如此看来Chrome的GUI线程里,发生了一次异常,这次异常无人处理,转为“未处理异常”进入顶级处理流程,在顶级处理流程里,糟糕的chrome_elf模块接管了执行权,估计是做了一些写log的动作,然后就调用Sleep装死了。
Google是个商业上很成功的软件公司,可是看了几次它的代码,给我的印象都不好,最严重的问题就是里面经常有野路子,使用不合乎常理的野蛮做法。比如上面这样在非常敏感的异常处理代码中调用Sleep就非常简单粗暴,写这个代码的程序员就没有认真学习过Windows系统的异常处理流程,没有理解透里面所蕴含的哲学思想。
可是责怪Google也没有用。接下来还是要找丢失的图文。当年拯救丢失的博客时,面对的是32位的进程,用户空间只有2GB,搜索的难度较小。可是现在面对的是64位的Chrome,用户空间有128TB。
暴力搜索128TB的空间难以想象,需要的时间可能太久了。
那么如何缩小搜索范围呢?
思考片刻,主意来了,图文的信息应该是动态分配的,Chrome的大多数代码都是C/C++系列,那么图文信息就应该在普通堆上。
于是执行!heap列出进程里的所有堆。
!heap
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
HEAPEXT: Unable to get address of ntdll!RtlpHeapInvalidBadAddress.
Index Address Name Debugging options enabled
1: 27417160000
2: 27416fc0000
3: 274172d0000
4: 274199c0000
5: 2741d1d0000
6: 274269d0000
接下来就可以使用内存搜索命令来搜索堆了。
s -u 27417160000 274269d0000+100000 "江湖"
发出命令后,很快得到一群结果:
因为文章的题目就是江湖,我又曾经在浏览器里搜索过江湖两个字,所所以堆上这个词很多。
细化搜素条件,搜索“江湖是什么”,这一次结果少了一些,可是还有几十个。
使用du命令观察其中的一个:
仔细一看,正是我写的图文内容。虽然有点丑陋,换行符号不起作用了,一个个字符都可怜兮兮地挤在了一起.......但是,看到它们,我好高兴,敝帚自珍,其中的每个字都是从内心流出来的,看到它们,仿佛看到了自己的孩子!
接下来至少可以把文字一行行的恢复了。因为调试器显示的信息是带地址的,在文本内容中间插入了很多数字。不过,也有颁发解决这个问题,可以使用调试器的存盘命令,即:
0:000> .writemem f:dumpssgsjh.txt
Writing 10000 bytes
Unable to read memory at 00000000`00000000, file is incomplete
0:000> .writemem f:dumpssgsjh.txt 00000274`1bff1d40 L200
Writing 200 bytes.
使用这个命令,就可以把大段文字保存成文件了。存成文件后,直接打开文件,看到的是乱码。
不过,这个问题好办,再用一下10年前的方法就行了。唤出老伙伴Visual Studio,以二进制方式打开jh.txt.
打开后看到的是二进制的数据:
接下来,只需要在数据开头加入代表Unicode的FF FE。
保存,再用记事本打开,图文的信息就又以适合人类阅读的形式出现了。
美哉,丢失的图文终于找回来了。
接下来我把本来写好的图文复制回公众号的编辑窗口,略作调整和修饰,加上没有写完的内容,一篇图文便发出去开始传播了。
今天的软件都体积庞大,逻辑复杂,这样的高复杂度的软件需要程序员具有非常强的技术能力来控制这个复杂度。因为发明最短路径搜索算法和倡导结构化编程而著名的计算机前辈Ew Dijkstra有句名言:
I had learned that a programmer should never make something more complicated than he can control.
(我已经领悟到,程序员永远不应该编写复杂度超出其控制能力的代码。)
在软件调试研习班中,我曾多次讲到Dijkstra前辈的这句名言,并将其简称为“戴氏原则”。作为一个有责任心的严肃程序员来说,应该不断提高自己的控制力,恪守戴氏原则。但事实上,今天的大多数程序员都在违反这个原则,包括上述Google代码的作者。
(写文章很辛苦,恳请各位读者点击“在看”,本文作者精心设计的计算机系统在线课程正在招生,欢迎有志提升软件控制力的同行报名或垂询)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物
也欢迎关注格友公众号
最后
以上就是自由钢笔为你收集整理的从挂死的Chrome中抢救未提交的图文的全部内容,希望文章能够帮你解决从挂死的Chrome中抢救未提交的图文所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复