实战分析一个崩溃的bug

------------------------------------------------------------------
大家好,我是Mike,微软拼音的开发工程师。在微软拼音的开发维护过程中,我们经常会遇到各种奇怪问题,很多都是客户机器那里出现,而在公司没有复现环境的,在这种情况下,如何调试解决问题是微软开发工程师必须具备的技能之一。下面就以一个崩溃问题做具体分析。
------------------------------------------------------------------
最近,我们的测试工程师从公司Dr. Watson (它是Windows下程序错误调试器,用于当程序发生错误时收集系统和程序信息,http://support.microsoft.com/kb/308538) 服务器上发现:非常频繁出现一种程序崩溃。输入法工作时,它只是一个动态链接库,运行在其他应用进程中,譬如,word,notepad,所以编程或设计时稍有地方考虑不周全,可能就会引起崩溃或其他错误。

感谢Windows的Error Report机制,让我们很及时了解到产品的问题。好,今天就分析一下这个崩溃的原因。Watson服务器上返回的具体错误报告如下:

错误应用程序名称: FATrayAlert.exe,版本: 2.4.7.1,时间戳: 0x4a42b696
错误模块名称: IMSCCORE.DLL,版本: 14.0.4734.1000,时间戳: 0x4b580fae
异常代码: 0xc00000fd
错误偏移量: 0x0005a7da
错误进程 ID: 0x90
错误应用程序启动时间: 0x01cae11f8ce3ff8c
错误应用程序路径: C:/Program Files (x86)/Sensible Vision/Fast Access/FATrayAlert.exe
错误模块路径: C:/PROGRA~2/COMMON~1/MICROS~1/IME14/IMESC/IMSCCORE.DLL

微软发布任何产品都有严格的流程,微软拼音输入法也一样,所以可以很快通过以下方法找出哪行代码出问题:

1. 从Watson 服务器上下载到crash时的dump文件

2. 用Windbg打开它,设置好这个版本的symbol路径。然后就可以看到call stack:

(Windbg是微软发布的一个超级强大的调试工具)

017efbb4 704a92fb IMxx!_chkstk+0x27 //crash
017efbc8 704ad697 IMSCCore!CIMExxx::OnConfigChange+0x2b
017efc54 704acb15 IMSCCore!CIMExxx::InitXXX+0x447

可以看到,程序崩溃在标注为红色函数中:_chkstk.

在MSDN查阅_chkstk函数,http://msdn.microsoft.com/en-us/library/ms648426(VS.85).aspx

如下:

扫描二维码关注公众号,回复: 5282611 查看本文章

_chkstk Routine

Called by the compiler when you have more than one page of local variables in your function.

Remarks

_chkstk Routine is a helper routine for the C compiler. For x86 compilers, _chkstk Routine is called when the local variables exceed 4K bytes; for x64 compilers it is 8K.

从上面说明可以看到,_chkstk这个函数只有在local变量超过4K(x86 OS),才会被编译器加入代码中。但是这个也应该没什么问题呀,因为在几乎所有微软拼音输入法的宿主程序中,这个函数都会被频繁调用到,而且都工作得好好的。什么原因呢?

虽然还不知道具体原因,但至少从上面的callstack看到,问题基本上是跟stack溢出有关。还是先看看OnConfigChange的代码吧,有些代码年代久了,说不定就有问题。

BOOL CIMExxx::OnConfigChanged(CIMEXXXConfig* pConfig)
{
...
SXXXConfig sCfg = {0};
...
}

计算了一下SStncConfig, sCfg是一个大约98K的对象. Oops,这么大的家伙呀,居然还在栈中分配的。是有些不合理,但是在正常wordpad和notepad用微软拼音,他们都没有问题呀。突然想起,VC连接器默认的栈是1M。默认?如果这个程序不是默认栈大小,是不是就有问题?

检查这个很容易,在windbg command窗口输入!teb, 这个命令显示thread environment block(TEB), 即线程环境信息。结果如下:

0:001> !teb
TEB at 7ffdd000

TEB的定义如下:

typedef struct _TEB {

NT_TIB NtTib;

PVOID EnvironmentPointer;

CLIENT_ID ClientId;

PVOID ActiveRpcHandle;

PVOID ThreadLocalStoragePointer;

PPEB ProcessEnvironmentBlock;

ULONG LastErrorValue;

.....

PVOID DeallocationStack;

.....

} TEB;

typedef struct _NT_TIB {

struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;

PVOID StackBase;

PVOID StackLimit;

.....

} NT_TIB;

我们关心的是StackBase和StackLimit, 通过他们可以知道剩余的栈空间。从上面看出,他们的偏移分别是+4, +8 字节。运行

0:001> dd 7ffdd000 L4
7ffdd000 017efc48 017f0000 017e1000 00000000

可见stackBase是017f0000, StackLimit是017e1000, 剩余空间是:

0:001> ? 17f0000-17e1000
Evaluate expression: 61440 = 0000f000

大概61K,不够分配上面98K的大对象L 原因知道了。但为求更安全(毕竟环境在实际客户那,我们分析的是一个瞬时镜像),我们可以进一步的验证:

0:001> dd 7ffdd000+E0C L1
7ffdde0c 017e0000
0:001> ? 17f0000-17e0000
Evaluate expression: 65536 = 00010000

E0C是存放最大栈空间的偏移。可以看到这个程序最大栈空间是64K。

当然可以更进一步验证,从本文开首的错误报告看到程序名称是FATrayAlert.exe。从网上搜索到它,这是一款识别应用程序,下载安装后,运行:

Dumpbin /headers FATrayAlert.exe
2800 size of stack reserve (hex)
1000 size of stack commit

可以看到10k左右,但从windbg分析栈空间应该是64K?原来操作系统会将做64K的向上圆整,所以小于64K的程序,运行时,操作系统都会给64K的栈空间。

原因找到了,修这个bug就简单了,将这个大对象放在heap中分配就可以了。

猜你喜欢

转载自blog.csdn.net/MSPinyin/article/details/6141535