2.2 读懂机器的语言:汇编,CPU执行指令的最小单元
2.2.1 需要用汇编来排错的常见情况
汇编是CPU执行指令的最小单元。下面一些情况下,汇编级别的分析通常是必要的:
1. 阅读代码看不出问题,但是跑出来的结果就是不对,怀疑编译器甚至CPU有毛病。
2. 没有源代码可以阅读。比如,调用某一个API的时候出问题,没有Windows的源代码,那就看汇编。
3. 当程序崩溃,访问违例的时候,调试器里看到的直接信息就是汇编。
调试中涉及的汇编知识分为两部分:
1. 寄存器的运算,对内存地址的寻址和读写。这部分是跟CPU本身相关的。
2. 函数调用时候堆栈的变化,局部变量全局变量的定位,虚函数的调用。这部分是跟编译器相关的。
汇编的知识可以在大学计算机教程里面找到。建议先熟悉简单的8086/80286的汇编,再结合IA32芯片结构和32位Windows汇编知识深入。建议的资源:
AoGo汇编小站
http://www.aogosoft.com/
Intel Architecture Manual volume 1,2,3
http://www.intel.com/design/pentium4/manuals/index_new.htm
案例分析:用汇编读懂VC编译器的优化
问题描述
客户在开发一个性能敏感的程序,想知道VC编译器对下面这段代码的优化做得怎么样:
int hgt=4; |
最直接的方法就是查看编译器生成的汇编代码分析。有兴趣的话先自己调试一下,看看跟我的分析是否一样。
我的分析
我分析的平台是,VC6,release mode下编译:(因为当时做这个case的时候,客户用的VC6。现在VC6已经退出历史舞台,微软不再提供支持)。
int hgt=4; |
这段代码涉及到的优化有:
1. i*i在每次内循环中是不变化的,所以只需要在外循环里面重新计算。编译器把外循环计算好的i*i放到ebx寄存器中,内循环直接使用。
2. 对A[i*wid+j]寻址的时候,在内循环里面,变化的只有j,而且每次j都是增加1,由于A是整型数组,所以每次寻址的变化就是增加1*sizeof(int),就是4。编译器把i*wid+j的结果放到了EDI中,在内循环中每次add edi,4来实现了这个优化。
3. 对于中间变量,编译器都是保存在寄存器中,并没有读写内存。
如果这段汇编让你手动来写,你能做得比编译器更好一点吗?
案例分析:VC2003 编译器的bug、debug模式正常,release模式会崩溃
不要迷信编译器没有bug。如果你在VS2003中测试下面的代码,会发现在release mode下面,程序会崩溃或者异常,但是在debug mode下工作正常。
例子程序
// The following code crashes/abnormal in release build |
编译设定如下:
1. 取消precompiled header。 |
跟踪汇编指令来分析
拿到这个问题后,首先在本地重现。根据下面一些测试和分析,认为很有可能是编译器的bug:
1. 程序中除了cout和printf外,没有牵涉到系统相关的API,所有的操作都是寄存器和内存上的操作。所以不会是环境或者系统因素导致的,可能性是代码错误(比如边界问题)或者编译器有问题。
2. 检查代码后没有发现异常。同时,如果调整一下std::transform的位置,在for loop后面调用的话,问题就不会发生。
3. 问题发生的情况跟编译模式相关。
代码中的std::transform和for loop的作用都是对整个vector调用func_template作转换。可以比较transform和for loop的执行情况进行比较分析,看看func_template的执行过程有什么区别。在VS2003里面利用main函数设定断点,停下来后用ctrl+alt+D进入汇编模式单步跟踪。下面的分析证明了这是编译器的bug:
在VisualStudio附带的STL源代码中,发现 std::transform的实现中用这样的代码来调用传入的转换函数:
*_Dest = _Func(*_First); |
编译器对于该代码的处理是:
EAX = 0012FEA8 EBX = 0037138C ECX = 003712BC EDX = 00371338 ESI |
ESI寄存器中保存的是需要传入_Func的参数*_First。可以看到,std::transform把这个参数通过push指令传入stack给_Func调用。
对于for loop中的*_Dest = func_templatefloatstd::string>(*_First);编译器是这样处理的:
EAX = 003712B0 EBX = 00371338 ECX = 003712BC EDX = 00000000 |
可以看到,使用for loop的时候,参数通过mov指令保存到ebx寄存器中传入func_template调用。
最后,看一下func_template函数是如何来获取传入的参数的。
004021A0 push esi |
这里直接把ebx推入stack,然后调用std::cout,并没有读取stack中的资料,说明func_template(callee)认为参数应该是从寄存器中传入的。然而transform函数(caller)却把参数通过stack传递。于是使用transform调用func_template的时候,func_template无法拿到正确的参数,因而导致崩溃。通过for loop调用的时候,由于参数通过寄存器传递,所以func_template就可以正常工作。
结论是编译器对参数的传入、读取、处理不统一,导致了这个问题。
为何问题在debug模式下不发生,或者调换函数次序后也不发生,留作你的练习吧 :-P
案例分析:臭名昭著的DLL Hell如何导致ASP.NET出现Server Unavailable
客户的ASP.NET程序,访问任何页面都报告Server Unavailable。观察发现,ASP.NET的宿主w3wp.exe进程每次刚启动就崩溃。通过调试器观察,崩溃的原因是访问了一个空指针。但是从call stack看,这里所有的代码都是w3wp.exe和.net framework的代码,还没有开始执行客户的页面,所以跟客户的代码无关。通过代码检查,发现该空指针是作为函数参数从调用者(caller)传到被调用者(callee)的,当callee使用这个指针的时候问题发生。接下来应该检查caller为什么没有把正确的指针传入callee。
奇怪的时候,caller中这个指针已经正常初始化了,是一个合法的指针,调用call语句执行callee的以前,这个指针已经被正确地push到stack上了。为什么caller从stack上拿的时候,却拿到一个空指针呢?再次单步跟踪,发现问题在于caller把参数放到了callee的[ebp+8],但是callee在使用这个参数的时候,却访问[ebp+c]。是不是跟前一个案例很像?但是这次的凶手不是编译器,而是文件版本。Caller和callee的代码位于两个不同的DLL,其中caller是.NET Framework 1.1带的,而callee是.NET Framework 1.1 SP1带的。在.NET Framework 1.1中,callee函数接受4个参数,但是新版本SP1对callee这个函数作了修改,增加了1个参数。由于caller还使用SP1以前的版本,所以caller还是按照4个参数在传递,而callee按照5个参数在访问,所以拿到了错误的参数,典型的DLL Hell问题。在重新安装.NET Framework 1.1 SP1让两个DLL保持版本一致,重新启动后,问题解决。
导致DLL Hell的原因有很多。根据经验猜测版本不一致的原因可能是:
1. 安装了.NET Framework 1.1 SP1后没有重新启动,导致某些正在使用的DLL必须要等到重新启动后才能够完成更新。
2. 由于使用了Application Center做Load Balance,集群中的服务器没有做好正确的设置,导致系统自动把老版本的文件还原回去了:
PRB: Application Center Cluster Members Are Automatically Synchronized After Rebooting |
| 回书目 上一节 下一节 |
|
· 第六章 你能帮我吗?.. · Linux笔试面试题选摘测.. · 08年5月软考网管上午真.. · 性能测试从零开始 目录 · 08年5月软考网工上午真.. · 上周拒绝服务攻击(DDo.. |
· 08年5月各大网上书店及.. · 2008年5月24日软考试题.. · 软件设计师专家临考模.. · 上周网络管理员专家自.. · 网络工程师自测获奖名.. · 08年4月各大网上书店及.. |
|
||||
| · NAC安全访问控制 · 网络布线测试仪器 · Windows Server 2008专.. · Windows远程桌面应用 · 网络故障排除宝典 · 运营商封堵ADSL共享 中.. · 解析35岁技术人的价值.. · 世纪枭雄比尔盖茨的王.. |
· 主流品牌防火墙配置 · ASP.NET开发教程 · 超级计算机TOP500专题 · Vista SP1对决XP SP3 · SQL Server 2008/2005.. · 程序员如何成长? · C#技术开发指南 · 虚拟化技术还有点“虚” |
|||
|
||||
| · SOA 面向服务架构 · SQL Server 2008/2005.. · Apache技术专题 · 三层交换技术专题 · SQL Server入门到精通 · Windows远程桌面应用 · C#技术开发指南 · Apache技术专题 |
· Windows集群服务应用 · C#技术开发指南 · 国际文档格式标准开战 · 路由器设置与口令恢复 · Linux 集群技术专题 · PHP开发应用手册 · SOA 面向服务架构 · 企业数据恢复指南 |
|||
|
||||
| · SQL Server入门到精通 · SQL Server 2008/2005.. · SOA 面向服务架构 · Apache技术专题 · C#技术开发指南 · 三层交换技术专题 · Apache技术专题 · C#技术开发指南 |
· Windows远程桌面应用 · 企业数据恢复指南 · Windows集群服务应用 · 路由器设置与口令恢复 · Linux 集群技术专题 · SOA 面向服务架构 · 了解统一威胁管理(UTM).. · 反垃圾邮件技术应用 |
|||