竟是CPU BUG?--记ARM指令SWP导致的QT卡住问题
现象:QT运行一段时间后会卡住
环境:QT版本为4.4
CPU为三星EXYNOS 4412,Cortex A9 4核心,1.5G
过程及原因分析:
既然程序会卡住,一定是什么地方出了问题,这个时候,最直接高效的方式是使用GDB查看进程的堆栈。
但是,如果进程编译时没有使用-g选项,也就是编译的release版本,或者连接的库有release版本,则堆栈有可能出现符合无法解析的问题。
这个时候,可以尝试使用addr2line工具来分析,或者查看进程proc下的mmap,猜测地址在哪个模块中,看是否能够缩小范围。
如果缩小范围后的代码属于本工程编译的,也就是非第三方的,则可以重新编译debug版本替代,再次复现问题,打印堆栈。
通过gdb挂载进程,发现问题不在QT应用中,而是在QT框架内,重新编译了debug版本的QT框架。debug版本的体积会大很多,如果可以的话,可以只对特定库编译带符号版本。
再次gdb挂载,就可以看到出问题时的完整堆栈了:
(gdb) bt
#0 0x40cd901c in qt_atomic_yield () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#1 0x407a0f0c in QRegion::copy () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#2 0x407a10c4 in QRegion::detach () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#3 0x407a1930 in QRegion::translate () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#4 0x407a1a30 in QRegion::translated () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#5 0x406e7260 in QWidgetPrivate::getOpaqueSiblings () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#6 0x406e7474 in QWidgetPrivate::subtractOpaqueSiblings () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#7 0x407fab50 in QWidgetBackingStore::updateWidget () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#8 0x407faf74 in QWidget::update () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#9 0x40a4b1f4 in QAbstractItemView::viewportEvent () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#10 0x409fdce4 in QAbstractScrollAreaFilter::eventFilter () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#11 0x40dcbacc in QCoreApplicationPrivate::sendThroughObjectEventFilters () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#12 0x406b0514 in QApplicationPrivate::notify_helper () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#13 0x406b0940 in QApplication::notify () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#14 0x4021481c in QtopiaApplication::notify () from /opt/Qtopia4.4.3/lib/libqtopia.so.4
(gdb) bt
#0 0x4050a2bc in nanosleep () from /lib/libpthread.so.0
#1 0x40d160b0 in qt_atomic_yield () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#2 0x40d2322c in QMutex::lock () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#3 0x40e3e6e8 in QObjectPrivate::clearGuards () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#4 0x40e42758 in QObject::~QObject () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#5 0x406e1a78 in QWSTtyKbPrivate::~QWSTtyKbPrivate () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#6 0x406e1a9c in QWSTtyKbPrivate::~QWSTtyKbPrivate () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#7 0x40e448b0 in QObjectCleanupHandler::clear () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#8 0x406d07c4 in QWSSignalHandler::handleSignal () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#9 <signal handler called>
#10 0x411316cc in sched_yield () from /lib/libc.so.6
#11 0x40d160c0 in qt_atomic_yield () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#12 0x400c2a2c in QBasicAtomicInt::ref () from /opt/Qtopia4.4.3/lib/libQtXml.so.4
#13 0x40df08d4 in QConfFileSettingsPrivate::children () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
(gdb) bt
#0 0x40d160b0 in qt_atomic_yield () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#1 0x40d2322c in QMutex::lock () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#2 0x40e3e6e8 in QObjectPrivate::clearGuards () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#3 0x40e42758 in QObject::~QObject () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#4 0x406e1a78 in QWSTtyKbPrivate::~QWSTtyKbPrivate () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#5 0x406e1a9c in QWSTtyKbPrivate::~QWSTtyKbPrivate () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
#6 0x40e448b0 in QObjectCleanupHandler::clear () from /opt/Qtopia4.4.3/lib/libQtCore.so.4
#7 0x406d07c4 in QWSSignalHandler::handleSignal () from /opt/Qtopia4.4.3/lib/libQtGui.so.4
对上面的堆栈进行分析,我们可以发现几个关键的可疑点:qt_atomic_yield 、 QMutex::lock () 、 QBasicAtomicInt::ref。从这几个调用,猜测是跟信号量或者原子操作有关。
在QT框架中找这些函数,会发现有很多,但是出现了一些 asm volatile 标记的代码。
上述标记是嵌入汇编的意思,就是为了提高性能,会在一些高频调用上使用汇编指令来提高性能,属于优化的一种;也有通过嵌入汇编指令执行特殊指令,实现特殊功能,比如上面所说的原子操作。
原子操作和信号量有天然的关系,并且也属于非常高频的调用。
把这些因素结合起来,高度怀疑上述堆栈最终指向的应该是某段嵌入汇编。
使用grep 在代码中搜索 "asm volatile" 字符串,会看到有很多地方使用了嵌入汇编,这里截取一段
./qt-extended-4.4.3/src/3rdparty/libraries/tremor/asm_arm.h: asm volatile("smull\t%0, %1, %2, %3"
./qt-extended-4.4.3/src/3rdparty/libraries/tremor/asm_arm.h: asm volatile("smull %0, %1, %2, %3\n\t"
./qt-extended-4.4.3/src/3rdparty/libraries/tremor/asm_arm.h:#define MB() asm volatile ("" : : : "memory")
./qt-extended-4.4.3/src/3rdparty/libraries/tremor/asm_arm.h: asm volatile("subs %1, %0, #32768\n\t"
./qt-extended-4.4.3/src/libraries/qtopiagfx/samples/starlist/mmx.c: asm volatile(
./qt-extended-4.4.3/src/libraries/qtopiagfx/samples/starlist/mmx.c: asm volatile(
./qt-extended-4.4.3/src/libraries/qtopiagfx/samples/starlist/mmx.c: asm volatile (
./qt-extended-4.4.3/src/libraries/qtopiagfx/def_blend.cpp: asm volatile("pld [%0, #32]\n\t" \
./qt-extended-4.4.3/src/libraries/qtopiagfx/plugin/mmx/mmx32_blend.c: asm volatile (
./qt-extended-4.4.3/src/libraries/qtopiagfx/plugin/mmx/mmx16_blend.c: asm volatile(
./qt-extended-4.4.3/src/libraries/qtopiagfx/plugin/mmx/mmx16_blend.c: asm volatile(
./builddir/sdk/qtopiacore/target/include/QtCore/qatomic_arm.h: asm volatile("swpb %0,%2,[%3]"
./builddir/sdk/qtopiacore/target/include/QtCore/qatomic_arm.h: asm volatile("swp %0,%2,[%3]"
./builddir/sdk/qtopiacore/target/include/QtCore/qatomic_arm.h: asm volatile("swp %0,%2,[%3]"
其实这里很多与该问题并无直接关系,还有很多是其他平台下的代码,我们这里关注跟arm平台相关的。
如上面最后部分挑选的,qatomic_arm.h很可能是我们问题所在的代码文件。查看该代码文件,发现使用了SWPB指令。
搜索该指令,了解到这是ARM中实现的交换内存和寄存器的原子指令。但是该指令好像只在低版本处理器核心中使用,高版本中为了兼容,比如这里用的EXYNOS4412,其实是提供的模拟仿真版本。
难道是该指令出现了问题。很有可能,因为之前使用的一款EXYNOS2410,同样的QT版本框架,就不存在问题。
我们看看内核中两款CPU的信息。对于EXYNOS4412,如下
shell@:/proc/cpu $ ls -l
-rw-r--r-- root root 0 1970-12-25 11:42 alignment
-r--r--r-- root root 0 1970-12-25 11:42 swp_emulation
对于EXYNOS2410,如下:
ls /proc/cpu -l
-rw-r--r-- 1 root root 0 Dec 28 12:58 alignment
看出4412多了swp_emulation,离问题似乎越来越近了。进一步查看swp_emulation信息
[root@]# cat /proc/cpu/swp_emulation
Emulated SWP: 0
Emulated SWPB: 1086289
Aborted SWP{B}: 0
Last process: 13787 ---该进程是QT进程
我们看到,短短一小会,QT进程使用该模拟指令集下的SWPB指令的次数高达1086289
莫非是该指令有问题?以我们的常识,CPU应该是不会出问题的的的的...
本着”尽信书不如无书“的原则,与其猜测,不如做个实验,写一个既简单的程序,反复调用嵌入上述指令的汇编代码,看看可否出问题。
说干就干,很快结果就出来了:程序跑一会就不动了,问题复现并确认了了了!!!
意犹未尽,莫非真让我遇到CPU指令的BUG了?马上修改QT框架中对该指令的调用,重新运行测试,结果UI确实不再卡住了。看来问题就出自该指令调用上。
补充:EXYNOS4412其实也用在三星早期Galaxy 一款旗舰机型上,按说不应该有类似问题。正好手头也有这款手机,就adb连接手机,查看了下cpu信息,结果如下:
shell@ja3g:/proc/cpu $ ls
ls
alignment
swp_emulation
shell@ja3g:/proc/cpu $ cat swp_emulation
cat swp_emulation
Emulated SWP: 0
Emulated SWPB: 0
Aborted SWP{B}: 0
原来,虽然手机上也有该指令,但是没有使用!!!