本书是一本介绍Windows系统上的用户态程序排错方法和技巧的书。本书分为4个章节,先介绍最重要的、通用的思考方法,以便制定排错步骤;再介绍对排错有帮助的知识点和工具;并介绍了.NET Framework(CLR)的相关知识和调试技巧;最后一章针对常见的几大类问题进行了总结。\r\n 本书案例丰富,对现实中的实际问题进行了研究,并和读者一起分析解决办法;本书的写作思路为先给出问题描述,然后提供线索,再进行分析,让读者在阅读中也进行思考,以提高实际解决问题的能力。本书适合希望学习排错、调试知识的软件开发、测试人员,希望深入学习Windows系统上用户态程序的排错知识的软件开发、测试人员。
第1章 比工具、技巧和经验都重要的是你的思考——从四个风格迥异的案例说起 \r\n 1.1 绝望的性能问题:ADO.NET 2.0竟然比1.0要慢 \r\n 1.1.1 问题描述 \r\n 1.1.2 悲观和绝望 \r\n 1.1.3 换位思考 \r\n 1.1.4 排错 \r\n 1.1.5 结论和收获 \r\n 1.1.6 题外话和相关讨论 \r\n Safehandle的更多讨论 \r\n 平衡、取舍、双赢和RFC 1925\r\n Profiler的下载地址和相关资源 \r\n 1.2 不可思议:一个API同时打开了两个文件 \r\n 1.2.1 问题描述 \r\n 1.2.2 第一印象 \r\n 1.2.3 深入分析 \r\n 1.2.4 革命尚未成功 \r\n 1.2.5 结论 \r\n 1.2.6 题外话和相关讨论 \r\n MSDN是最值得信赖的吗 \r\n 你敢说CPU坏了 \r\n DWORD和文件长度 \r\n 程序输出0xcdcdcdcd,想到了什么 \r\n 1.3 简单的问题最棘手:稀疏平常的ASP.NET Session Lost问题 \r\n 1.3.1 问题描述 \r\n 1.3.2 制定策略 \r\n 1.3.3 具体操作和结论 \r\n 1.3.4 题外话和相关讨论 \r\n 排查session lost的经验 \r\n 1.4 本可以做得更好:SharePoint中文界面变英文 \r\n 1.4.1 问题描述 \r\n 1.4.2 排错步骤 \r\n 1.4.3 错过的线索 \r\n第2章 汇编、异常、内存、同步和调试器——重要的知识点和神兵利器 \r\n 2.1 排错的工具:调试器Windbg\r\n 2.1.1 调试器的功能:检查代码和资料,保存dump文件, 断点控制程序的执行 \r\n 2.1.2 符号文件(Symbol file),把二进制和源代码对应起来 \r\n 2.1.3 一个简单的上手程序 \r\n 2.1.4 用Internet Explorer来操练调试器的基本命令 \r\n vertarget检查进程概况 \r\n !peb 显示Process Environment Block\r\n lmvm 检查模块的加载信息 \r\n .reload / !sym 加载符号文件 \r\n lmf 列出当前进程中加载的所有模块 \r\n r,d,e 寄存器,内存的检查和修改 \r\n !address显示内存页信息 \r\n S 搜索内存 \r\n !runaway 检查线程的CPU消耗 \r\n ~ 切换目标线程 \r\n k,kb,kp,kv,kn 检查call stack\r\n u 反汇编 \r\n x 查找符号的二进制地址 \r\n dds 对应二进制地址的符号 \r\n 2.1.5 检查程序资料的小例子 \r\n .frame 在栈中切换以便检查局部变量 \r\n dt 格式化显示资料 \r\n 2.1.6 用Windbg控制程序进行实时调试(Live Debug) \r\n Wt Watch and Trace, 跟踪执行的强大命令 \r\n 断点和条件断点(condition breakpoint),高效地控制观测目标 \r\n 伪寄存器,帮助保存调试的中间信息 \r\n Step Out的实现 \r\n 2.1.7 远程调试(Remote debug) \r\n 2.1.8 如何通过Windbg命令行让中文魔兽争霸运行在英文系统上 \r\n 2.1.9 Dump文件 \r\n 2.1.10 CDB、NTSD和重定向到Kernel Debugging\r\n 2.1.11 Debugger Extension,扩展Windbg的功能 \r\n 2.2 读懂机器的语言:汇编,CPU执行指令的最小单元 \r\n 2.2.1 需要用汇编来排错的常见情况 \r\n 案例分析:用汇编读懂VC编译器的优化 \r\n 问题描述 \r\n 我的分析 \r\n 案例分析:VC2003 编译器的bug、debug模式正常,release模式会崩溃 \r\n 例子程序 \r\n 跟踪汇编指令来分析 \r\n 案例分析:臭名昭著的DLL Hell如何导致ASP.NET出现Server Unavailable\r\n 2.2.2 题外话和相关讨论 \r\n Release比 Debug快吗 \r\n 2.3 理解操作系统对程序的反馈:异常(Exception)和通知(Debug Event) \r\n 2.3.1 异常(Exception)的方方面面和一篇字字珠玑的文章 \r\n 案例分析:如何让C++像C#一样打印出函数调用栈(callstack) \r\n 2.3.2 Adplus,抓取dump的方便工具 \r\n 案例分析:华生医生(Dr. Watson)在什么情况下不能记录Dump文件 \r\n 问题描述 \r\n 背景知识 \r\n 问题分析 \r\n 新的做法 \r\n 问题解决了,可是为什么华生医生(Dr. Watson)抓不到dump呢 \r\n 2.3.3 通知(Debug Event)是操作系统跟调试器交流的一种方法 \r\n 案例分析:VB6的版本问题 \r\n 2.3.4 题外话和相关讨论 \r\n 错过第一现场后还从dump中分析出线索吗 \r\n Adplus,天天都用的工具 \r\n 未处理异常发生后的主动退出 \r\n 如何调试UnhandledExceptionFilter \r\n 2.4 平坦内存空间中的层次结构:Heap和Stack\r\n 2.4.1 Heap是对平坦空间的高效管理和利用 \r\n 2.4.2 PageHeap,调试Heap问题的工具 \r\n 简单例子的多种情况 \r\n Heap上的内存泄漏和内存碎片 \r\n 2.4.3 Stack overrun/corruption\r\n 2.4.4 题外话和相关讨论 \r\n PageHeap的/unaligned参数 \r\n Heap trace,系统帮你记录下每次Heap的操作 \r\n 为何才分配了300MB内存,就报告Out of memory\r\n 2.5 找准排查问题的对应层次 \r\n 2.5.1 从C运行库看层次 \r\n 2.5.2 简单的_CRTDBG_MAP_ALLOC定义就可以让内存泄漏无可遁形 \r\n 2.5.3 BSTR Cache,建立在Heap之上的COM字符串内存管理 \r\n 2.5.4 题外话和相关讨论 \r\n CRT Debug Heap一定对Debug有帮助吗 \r\n C++中new操作符的尴尬 \r\n 2.6 理清多个线程对资源的竞争:同步和锁 \r\n 2.6.1 句柄泄漏、死锁和线程争用,三个典型问题 \r\n 句柄泄漏(Handle Leak) \r\n 死锁(Deadlock) \r\n 线程争用 (contention) \r\n 2.6.2 Windbg中的对应排错 \r\n !handle 检查句柄信息 \r\n !htrace 检查操作句柄的历史记录 \r\n !cs 列出CriticalSection的详细信息 \r\n 排查CriticalSection leak( Orphan CriticalSection) \r\n Invalid handle exception\r\n 案例分析:ArrayList.Add的时候发生IndexOutOfRangeException \r\n 问题描述 \r\n 这个异常不简单 \r\n 具体操作 \r\n 结论 \r\n 2.7 调试和设计 \r\n 2.7.1 一位热心朋友的提问 \r\n 案例分析:反被聪明误 \r\n第3章 .NET Framework的原理和SOS调试——剖析CLR程序和CLR本身 \r\n 3.1 MetaData、JIT、GC和Exception的关键点 \r\n 3.1.1 MetaData(元资料)和引擎初始化 \r\n 3.1.2 JIT动态编译 \r\n 3.1.3 GC 内存管理 \r\n 3.1.4 Exception Handling异常处理 \r\n 3.2 用Windbg探索CLR的实现 \r\n 3.2.1 开源的CLR实现:Rotor \r\n 3.2.2 对一个Hello world的WinForm程序庖丁解牛 \r\n mscoree!_CorExeMain CLR引擎的入口 \r\n EEStartupHelper 重要的引擎初始化函数 \r\n mscorwks!SystemDomain::ExecuteMainMethod 执行托管代码的入口 \r\n CallDescr /MakeJitWorker Jit引擎发动的地方 \r\n NtUserWaitMessage 托管程序完成加载 \r\n gc_heap::allocate_more_space/ GCHeap::GarbageCollect 通过GC管理内存的分配和 释放 \r\n AppDomain,ThreadPool,Exception,StackWalk,Security都是有趣的话题 \r\n 3.3 通过SOS快捷方便地调试托管程序 \r\n 3.3.1 CLR让托管程序的调试变得非常简单 \r\n 3.3.2 SOS的命令介绍 \r\n 3.4 用简单的程序演示SOS的常见操作 \r\n 3.4.1 .load SOS 加载SOS到Windbg\r\n 3.4.2 !dumpheap 统计托管内存使用信息 \r\n 3.4.3 !do 显示托管对象的详细信息 \r\n 3.4.4 !gcroot 查找托管对象的引用关系 \r\n 案例分析:ASP.NET High CPU和更多的CLR命令演示 \r\n !threads查看托管线程 \r\n !tp查看线程池和CPU占用率 \r\n !SyncBlk查看托管线程的lock\r\n !ip2md 映像内存地址到托管函数名 \r\n !savemodule 保存模块到本地以便用reflector分析 \r\n 著名的blog:If broken it is,fix it you should\r\n 3.5 题外话和相关讨论 \r\n 3.5.1 ReleaseCOMObject 释放COM对象时候的两难困境 \r\n 3.5.2 PInvoke应该Pin住内存防止崩溃 \r\n 3.5.3 Pin住内存又会导致内存碎片 \r\n 3.5.4 臭名昭著的mixed DLL loading deadlock\r\n 3.5.5 有趣且有用的练习和更多的资料 \r\n第4章 崩溃,性能和资源泄漏——分享一些经验 \r\n 4.1 排错开始前的准备工作 \r\n 4.1.1 用正确的态度对待问题 \r\n 4.1.2 用简单的提问缩小排错的范围 \r\n 4.1.3 通过MPS REPORT获取系统的详细信息 \r\n 4.1.4 通过简单的Dump分析获取基本信息 \r\n 4.2 崩溃(Crash) \r\n 4.2.1 崩溃的万千种不同死相 \r\n 4.2.2 准确获取Dump\r\n Adplus:最容易上手的dump脚本 \r\n 华生医生(dr Watson) \r\n 通过Image File Execution Options让调试器随目标程序一起启动 \r\n COM+和ASP.NET的dump获取需要特殊配置 \r\n 4.2.3 crash dump中需要重点关注的信息 \r\n 案例分析:VC程序的崩溃 \r\n 问题描述 \r\n MessageBox 嵌套调用 \r\n 从源代码中发现的疑点 \r\n 从This指针找崩溃的根源 \r\n 结论 \r\n 4.2.4 小结和更多的资源 \r\n 4.2.5 题外话和相关讨论 \r\n HeapCorruption\r\n StackCorruption \r\n 4.3 性能(Performance) \r\n 4.3.1 “你真牛,不如你再给我缩短10秒吧!”不是想要多快就能调到多快 \r\n 4.3.2 性能调优的步骤,CPU利用率是关键 \r\n 4.3.3 无所不知的性能监视器 \r\n 使用性能监视器的基本步骤 \r\n 重要的计数器 \r\n 案例分析:博客园的性能问题 \r\n 案例分析:堵塞在SqlCommand.ExecuteReader上就一定在等sql吗 \r\n 问题背景 \r\n 案例分析:堵塞在Assembly.Load上的deadlock\r\n 问题背景 \r\n 案例分析:196个线程织成的一张网 \r\n 问题背景 \r\n 小结 \r\n 4.3.4 用Profiler精确定位性能瓶颈 \r\n 案例分析:DataTable中foreach和for loop性能差了50% \r\n 问题背景 \r\n 4.3.5 题外话和相关讨论 \r\n Task manager跟performance monitor的差别 \r\n 性能监视器的超级用法 \r\n C++跟C#到底谁快 \r\n 没有profiler怎么办 \r\n 4.4 资源泄漏(Resource Leak) \r\n 4.4.1 资源泄漏分轻重缓急 \r\n 4.4.2 内存泄漏排错的基本步骤 \r\n 泄漏了什么,谁分配的,为什么无法释放 \r\n 定位泄漏内存的类型和增长趋势 \r\n 区分managed heap leak和native leak\r\n 案例分析:IE7的内存泄漏 \r\n 问题描述 \r\n 重现问题和基本分析 \r\n 用传统的Pageheap+UMDH找到问题根源 \r\n 方便强大的IIS Diagnostics工具 \r\n 结论 \r\n 分析IIS Diag\r\n 4.4.3 托管内存泄漏 \r\n 案例分析:object chain让排错简单明了 \r\n 问题背景 \r\n 案例分析:一个bt的案例 \r\n 碎片的其他原因 \r\n 4.4.4 句柄泄漏(Handle Leak) \r\n 4.4.5 题外话和相关讨论 \r\n GDI Leak \r\n Desktop heap issue\r\n更多的资源
到2006年初,我做了两年的技术支持。有好多做过的案例非常有趣,但是随着时间的推移,我所记得的情况越来越模糊。我不想让这些跟技术相关的趣事毫无声息地消失。我想写一个备忘录,专门记录这些有趣的问题,以及解法。这份备忘录,后来就变成了这本书。
在最初的备忘录中,我描述了几个最让我难忘的问题,也就是本书的第1章。从技术上讲,这几个问题各有特点,体现了排错的基本思路。但真正让我想把这些问题写下来的,还有背后的一些原因。每个技术问题背后,都是有故事的。
最初跟客户接触的时候,无法理解客户的一些无理要求。无法让心急如焚的客户冷静下来有步骤地排错。有了一定经验后,学会了体会客户的压力和焦虑,懂得如何通过交流来建立彼此的信任。到了后来,客户会打电话来点名要我帮忙解决问题,会邀请我去他们公司做培训。由于技术问题,客户的态度从发生问题时候的失望、误解,变到排错过程中的配合和信任,以及最后问题解决后的感激和赞扬。而我的经验、自信也随着客户关系的深入而成长。由于技术问题,我跟好多客户成为朋友,有好多客户因为跟我接触而成为我的同事。因为技术讨论,我才有机会跟很多著名的技术牛人交流。由技术研究而带来的成长,由解决问题而收获的喜悦,由技术分享而建立的信任,是无法用文字描述的。
2006年年初,我把我做过的有趣案例整理成30来页的PDF文档。当时《程序员》杂志的技术主编孟岩看到后,极力鼓励我继续完善,并且在他的blog上帮我推广。在后来的整理修改过程中,博文的特约策划编辑刘铁锋针对文章结构和内容定位提出了不少宝贵意见。
2007年年初我不再做技术支持。希望这本书能帮我记录下这一段美好的经历。
VCHELP的dr0,CCF的Newer2K,是两位对我写作本书帮助最大的朋友。
谢谢购买这本书的你。希望你觉得值。
熊力
2007年9月于上海
熊力,2004年开始在上海微软技术支持中心担任技术支持工程师。他所在的小组负责帮助企业客户解决开发领域的技术难题。作者专注于.NET Framework、 C/C++、COM和Web开发,现任微软中国研发集团服务器与开发工具事业部测试工程师。
0.1 本书介绍什么
这是一本介绍Windows系统上用户态(User Mode)程序的排错(troubleshooting)方法和技巧的书。
无论是开发、测试还是技术支持,都会遇上程序运行结果跟预期效果不一致的情况。找到问题根源和解决的过程,就是排错。如果问题发生的情况很特殊,比如特别难于重现,或者没有源代码可以参考,在这样的情况下解决问题,就非常具有挑战性!
本书会通过例子来跟大家分享排错过程中的经验和技巧,例如:
ASP.NET程序在测试环境中一切正常,部署到生产环境中后,在压力比较大的时候,发生Session丢失现象(ASP.NET Session lost);
VC开发的程序运行一段时间后,不定时发生内存访问错误,然后崩溃;
程序消耗的句柄数量持续增长,内存使用也持续增长,最后性能下降非常严重(Handle leak, Memory leak);
VC程序中,使用ShellExecute打开一个本地的TXT文本文件。TXT格式关联到UltraEdit。ShellExecute执行后,发现UltraEdit除了打开这个TXT外,另外还打开了一个GIF文件。
问题可以表现得非常简单,或者非常复杂,可能涉及不同的开发工具和技术。如何分析解决这些问题,正是后面要讨论的。
0.2 本书的组织结构
本书共分为4章:
第1章介绍最重要的、通用的思考方法。正确的思考方法能帮助找出问题的核心,从而制定排错步骤以及决定采用何种技术和工具进行研究;
第2章介绍对排错非常有帮助的知识点和工具,包括调试器(debugger)、异常(exception)、内存工具、同步等。选择恰当的工具,在恰当的时间,获取关键信息,结合对应的知识就可以分析出问题的根源;
第3章介绍.NET Framework(CLR)的相关知识和调试技巧。随着CLR应用的普及,Windows平台上企业级的应用越来越多地用CLR开发,CLR调试的地位举足轻重;
第4章结合前面的内容,针对常见的几大类问题进行了总结,包括崩溃(crash)、性能问题(performance)和资源泄漏(resource leak)。
0.3 本书的适合读者群
这不是一本教你如何去做的教材,而是一份描述现实中的实际问题,以及对解决办法的归纳总结。如果你想学习程序设计语言的语法,或者某种技术的详细使用方法,这本书并不适合你。
如果你是项目骨干,时常需要排查程序的崩溃,优化程序的性能,这本书会告诉你很多背景知识和充足的实际案例。不管是访问违例,未处理的异常,dump文件分析,数据库性能,程序的死锁,超高CPU使用率,内存泄漏,都有真实详细的分析说明。
如果你是技术爱好者,熟悉C++、汇编、COM技术、CLR、Win32 SDK开发及Web应用,本书不仅会让你看到这些技术后面更多的话题,而且还会介绍调试工具windbg的详细使用。比如汇编级别的优化,C++编译器的bug,COM跟CLR的冲突和性能问题,ADO 在两个CLR版本中的性能比较,还有用windbg分析CLR源代码级别的实现。
如果你还是一个学生,本书会告诉你现实商业环境中的程序问题是怎样的。除了在论坛上讨论C++的虚拟多重继承,抓狂的char * const *(*ss[23][200])(void (*)(int h,void (*)(void)));语法,Java和C#孰优孰劣之余,你可以看到企业级客户面临的问题到底是怎样的。操作系统课程中讲到的死锁,到底是怎样活生生地发生到惠普公司或者花旗银行身上的。
本书适合于所有乐于思考,参与Windows用户态程序开发的人。
如果读者熟悉Windows用户态程序的机理,您将会得到更多的收获!
0.4 本书叙述上的特点
本书的目的在于分享经验,开阔思路。
书中会介绍很多现实的案例和分析方法,但是这些分析方法并不是死板的,也不见得是最优的,希望读者能开动脑筋,参与到作者的思考中来,体会排错的快乐。
由于案例多种多样,有个人用户,有企业级用户,有桌面程序,也有网络服务,所以书中会涉及很多的知识点。对这些知识点的介绍并不是本书的目的,书中只会分析部分重要的知识点。对于书中出现的陌生名词、概念,希望读者能够利用书中给出的链接,通过网络如MSDN来开阔知识面。
在内容组织上,原则是先给出问题描述,然后提供线索,再进行分析,最后是结论和思考。目的是鼓励读者边阅读边思考,体会思考的乐趣。
后面的案例都是这两年来笔者亲手处理过的案例。跟案例相关的具体信息,比如客户信息、源代码和函数名字等都已删除或混淆。为了保护
无封面