gdiplus函数调用错误导致程序Crash分析
来源:互联网 发布:爱否商城 知乎 编辑:程序博客网 时间:2024/06/05 04:50
前几天看一个Crash的问题。功能是Word上的一个插件,点击上面的一个button,cation实现了,但点击结束后,过一会Word就crash了。下面是问题的分析.
调试问题
问题复现
程序crash时,获得如下log.
0x4aea74b2,指令无效。是访问内存溢出导致EIP被修改,还是对应模块从内存中unload出去了?
开始调试
上调试器,看看是否有模块被unload了。
从L6和L29的内容,说明准备执行unloaded模块GdiPlus中的函数,导致访问违例。
这时我们可以继续跟踪看看这个地址偏移量是什么code,也可以使用Application Verifier看看它能不能给我们更多的信息。
Application Verifier
下面是启动Application Verifier后的调试结果。
上面的调试告诉我们,一个Critical section在程序unloading gdiplus模块时没有释放。函数调用指令地址为0x0c974948,说明是模块dllistmt.dll启动了这个函数。但从那里启动的,我们看不出来。
寻找entire stack trace
给gdiplus!GdiplusStartup下断点,看看整个函数调用堆栈。
断点如期命中,但没看到期望中的完整函数堆栈。
去official的build package中找不到dllistmt模块的pdb文件。尝试恢复stack trace第一次失败。
找map文件,没有。尝试恢复stack trace第二次失败。
手动build一个新的dllistmt,同时获得pdb文件,替换到程序中,问题不能复现了。尝试恢复stack trace第三次失败。
虽然上面提示Following frames may be wrong,但我们看到ebp=0就闪出一个念头,是否是stack overflow导致EIP被改写,而使程序稀里糊涂执行了这条指令?
stack overflow?
看dllistmt模块的code,看到dllmain第一个函数调用是DisableThreadLibraryCalls,下断点再次运行。
从上面的分析,我们可知dllistmt!DllUnregisterServer+0x108df这是在执行dllistmt的dllmain函数,但这个地方编译器生成了一个奇怪的指令,把ebp设为0了,这样在没有pdb的情况下stack就不能正确地恢复了(这里尝试了汇编代码的单步跟踪,最后是运行到了gdiplus!GdiplusStartup。不过,这个过程比较枯燥,这里就忽略了)。调试时发现,这个简单的动作加载了8~9 dll,还启动了几个线程,感觉里面的初始化动作很复杂。怀疑里面一个模拟COM进程内通讯的模块有问题,化时间看了部分code,做了些调试,也没有发现根本问题(dllistmt模块里,有一个地方调用GdiplusStartup,但这个button的action根本就不执行到这段code)。
第一次迷茫
走不下去了,困惑中想到GdiplusStartup是干什么的,为什么需要Critical section,而且一直不释放它?
上google找,打开URL http://msdn.microsoft.com/en-us/library/ms534077(v=vs.85).aspx获得如下信息。
原来初始化gdiplus需要调用GdiplusStartup,关闭gdiplus需要调用GdiplusShutdown。这个Critical section是在初始化的时候产生的,那可能是调用GdiplusShutdown时释放它,但这里却说没释放它,奇怪。对GdiplusShutdown下断点,再次调试,结果到异常发生了,这个断点也没有命中。
第二次迷茫
很是困惑,再次停下来分析。
1。action开始时,dllms模块动态加载dllistmt模块,dllistmt静态加载gdiplus模块。
2。action结束时,dllms模块动态unload dllistmt模块,因为这个时候gdiplus模块的引用计数为1,gdiplus这个时候也被自动unload出系统。
3。整个过程中,dllistmt没显示调用gdiplus的code,可gdiplus模块的初始化函数被调用了,对应的析构函数却没被调用,这导致6号线程发生了异常。等等,6号线程,难道就是上面说的gdiplus的后台线程?
大胆的假设
dllms在加载dllistmt模块时,一个我们现在还不清楚系统调用,激活了gdiplus,调用了GdiplusStartup。这个函数又创建了gdiplus的后台线程。当程序unload dllistmt模块,gdiplus模块也被unload,但没有机会调用GdiplusShutdown,所以gdiplus的后台线程还在,这样当系统切换到这个线程,因调用无效内存(gpiplus原来再系统中的代码段)指令而产生访问违例。这个分析跟当前的现象很吻合。
验证假设
调试结果跟分析的一样,gdiplus启动了线程gdiplus!BackgroundThreadProc。这样当gdiplus模块unload出去后,这个线程在执行BackgroundThreadProc函数时就产生访问违例而crash了。
解决办法
由于不是太清楚具体的code逻辑,就建议维护code的人员在load dllistmt模块时,显式的调用GdiplusStartup,在unload时再显式的调用GdiplusShutdown来归避这个问题。
经验与教训
1。如果dllistmt模块有pdb文件,那就可以立刻定位哪里调用了GdiplusStartup,而发现问题的root cause。
2。如果一开始就熟悉gdiplus的调用规则,知道GdiplusStartup和GdiplusShutdown需要配对。同时GdiplusStartup可能会创建后台线程,那就不用等到google后,才对特殊的规则做相应的调试。丰富的背景知识是调试的基础啊。
3。其实在遇到critical section错误的时候,如果看一下所有的线程stack也可能早的想到这个假设,但当时好像注意这个东西。有时,成功看起来是离我们如此的接近。
- gdiplus函数调用错误导致程序Crash分析
- GetStringUTFChars()函数导致的程序Crash
- 分析程序调用几次函数
- VirtualProtect导致程序crash的问题。
- java调用数据库函数传值有误导致Transaction错误
- 没有声明函数,直接调用导致的错误
- 程序错误及故障分析(指针问题导致)
- 如何分析Android libc crash,把调用地址转换为函数名显示调用堆栈
- /proc/sys/vm/max_map_count耗尽时,调用glibc 2.11.3 free()导致程序crash,问题追踪和解决
- /proc/sys/vm/max_map_count耗尽时,调用glibc 2.11.3 free()导致程序crash,问题追踪和解决
- MinGW 编译 GDIPlus 程序
- [ios/oc] UIWebView 首次非主线程调用导致crash
- 调用opencv库的destroyAllWindows()导致crash的解决方法
- 由于错误传入Marshal.GetTypedObjectForIUnknown参数导致的应用程序crash
- Python提示[Errno 32] Broken pipe 导致线程crash错误
- setlocale 多线程调用引发程序crash
- setlocale 多线程调用引发程序crash
- 已知程序crash 地址来分析具体crash 代码
- 非接触式通信技术的三次浪潮
- 好书推荐——嵌入式Linux应用开发完全手册(作者韦东山)
- Java格式化时间戳
- iPhone面试题解答
- AJAX之UpdatePanel 篇
- gdiplus函数调用错误导致程序Crash分析
- 已发归档邮件做备份
- 第22章 网络编程
- Android SDK中tools详解
- 换项目组了
- 已发归档邮件做备份(补充)
- Net设计模式实例之建造者模式(Builder Pattern)
- 信号量与自旋锁
- 获得文件版本等信息