我们公司最近接到了许多客户的电话,他们反映我们的程序在他们的系统上发生了访问冲突导致崩溃。
这个崩溃发生在 SQLite 3.6.23.1 中,我们将其作为应用程序的一部分提供(我们提供自定义版本,以便使用与应用程序其他部分相同的 VC++ 库,但它是基于原始的 SQLite 代码构建而成)。
当 pcache1Fetch
执行 call 00000000
时,程序会崩溃,WinDbg 堆栈跟踪显示如下:
0b50e5c4 719f9fad 06fe35f0 00000000 000079ad 0x0
0b50e5d8 719f9216 058d1628 000079ad 00000001 SQLite_Interop!pcache1Fetch+0x2d [sqlite3.c @ 31530]
0b50e5f4 719fd581 000079ad 00000001 0b50e63c SQLite_Interop!sqlite3PcacheFetch+0x76 [sqlite3.c @ 30651]
0b50e61c 719fff0c 000079ad 0b50e63c 00000000 SQLite_Interop!sqlite3PagerAcquire+0x51 [sqlite3.c @ 36026]
0b50e644 71a029ba 0b50e65c 00000001 00000e00 SQLite_Interop!getAndInitPage+0x1c [sqlite3.c @ 40158]
0b50e65c 71a030f8 000079ad 0aecd680 071ce030 SQLite_Interop!moveToChild+0x2a [sqlite3.c @ 42555]
0b50e690 71a0c637 0aecd6f0 00000000 0001edbe SQLite_Interop!sqlite3BtreeMovetoUnpacked+0x378 [sqlite3.c @ 43016]
0b50e6b8 71a109ed 06fd53e0 00000000 071ce030 SQLite_Interop!sqlite3VdbeCursorMoveto+0x27 [sqlite3.c @ 50624]
0b50e824 71a0db76 071ce030 0b50e880 071ce030 SQLite_Interop!sqlite3VdbeExec+0x14fd [sqlite3.c @ 55409]
0b50e850 71a0dcb5 0b50e880 21f9b4c0 00402540 SQLite_Interop!sqlite3Step+0x116 [sqlite3.c @ 51744]
0b50e870 00629a30 071ce030 76897ff4 70f24970 SQLite_Interop!sqlite3_step+0x75 [sqlite3.c @ 51806]
相关的C代码行为:
if( createFlag==1 ) sqlite3BeginBenignMalloc();
编译器会把定义为sqlite3BeginBenignMalloc
的函数进行内联处理:typedef struct BenignMallocHooks BenignMallocHooks;
static SQLITE_WSD struct BenignMallocHooks {
void (*xBenignBegin)(void);
void (*xBenignEnd)(void);
} sqlite3Hooks = { 0, 0 };
# define wsdHooksInit
# define wsdHooks sqlite3Hooks
SQLITE_PRIVATE void sqlite3BeginBenignMalloc(void){
wsdHooksInit;
if( wsdHooks.xBenignBegin ){
wsdHooks.xBenignBegin();
}
}
它的汇编代码如下:
719f9f99 mov esi,dword ptr [esp+1Ch]
719f9f9d cmp esi,1
719f9fa0 jne SQLite_Interop!pcache1Fetch+0x2d (719f9fad)
719f9fa2 mov eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)]
719f9fa7 test eax,eax
719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d (719f9fad)
719f9fab call eax ; *** CRASH HERE ***
719f9fad mov ebx,dword ptr [esp+14h]
寄存器包括:
eax=00000000 ebx=00000001 ecx=000013f0 edx=fffffffe esi=00000001 edi=00000000
eip=00000000 esp=0b50e5c8 ebp=000079ad iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
如果eax
是0(实际上它就是0),那么test eax, eax
应该设置零标志位,但它未被设置为零。因为零标志位没有被设置,所以je
不能跳转,然后应用程序在尝试执行call eax (00000000)
时崩溃。
更新:这里的eax
应该始终为0,因为sqlite3Hooks.xBenignBegin
在我们的代码中未设置。我可以使用指定了SQLITE_OMIT_BUILTIN_TEST
定义的选项重新构建SQLite,这将在代码中打开#define sqlite3BeginBenignMalloc()
并完全省略此代码路径。这可能会解决问题,但感觉不像是一个“真正”的修复;还有什么能够阻止它在其他代码路径中发生呢?
到目前为止,所有客户的共同点都是运行“Windows 7 Home Premium 64-bit (6.1, Build 7601) Service Pack 1”,并拥有以下其中之一的CPU(根据DxDiag):
- AMD A6-3400M APU with Radeon(tm) HD Graphics (4 CPUs), ~1.4GHz
- AMD A8-3500M APU with Radeon(tm) HD Graphics (4 CPUs), ~1.5GHz
- AMD A8-3850 APU with Radeon(tm) HD Graphics (4 CPUs), ~2.9GHz
根据维基百科的AMD Fusion文章,这些都是基于K10内核的“Llano”型AMD Fusion芯片,发布于2011年6月,这也是我们开始收到报告的时间。
最常见的客户系统是Toshiba Satellite L775D,但我们还有来自HP Pavilion dv6&dv7和Gateway系统的崩溃报告。
这个崩溃可能是由CPU错误(请参见AMD Family 12h Processors勘误表)引起的吗?还是我忽略了其他可能的解释?(根据Raymond,它可能是超频,但如果是这样,仅受到影响的是这个特定的CPU型号,这很奇怪。)
老实说,这似乎不可能是真正的CPU或操作系统错误,因为客户没有在其他应用程序中收到蓝屏或崩溃。一定有其他更可能的解释——但是是什么呢?
更新于8月15日:我得到了一台搭载AMD A6-3400M处理器的东芝L745D笔记本电脑,并可以在运行程序时稳定地复现崩溃。崩溃总是发生在相同的指令上; .time
报告在崩溃前的用户时间为1m30s至7m之间。在原始帖子中我忽略了一个可能与问题相关的事实,即应用程序是多线程的,具有高CPU和I / O使用率。该应用程序默认生成四个工作线程,并发布80%以上的CPU使用率(在SQLite代码中还有一些阻塞以及互斥锁),直到它崩溃。我修改了应用程序,只使用两个线程,但它仍会崩溃(尽管需要更长的时间)。现在我正在运行一个仅使用一个线程的测试,但它还没有崩溃。
另请注意,它似乎并不纯粹是CPU负载问题;我可以在系统上运行Prime95而没有错误,并且它将使CPU温度升高到> 70°C,而当它正在运行时,我的应用程序几乎没有使温度超过50°C。
更新于8月16日:略微干扰指令会使问题“消失”。例如,用xor eax,eax
替换内存负载(mov eax,dword ptr [SQLite_Interop!sqlite3Hooks(71a7813c)]
)可以防止崩溃。修改原始C代码以向if(createFlag == 1)
语句添加额外的检查会更改编译代码中各种跳转的相对偏移量(以及test eax,eax
和call eax
语句的位置),似乎也可以防止出现问题。
到目前为止我发现最奇怪的结果是,将jne
在719f9fa0
更改为两个nop
指令(因此控制始终落到test eax,eax
指令上,无论createFlag
/ esi
的值如何)允许程序在不崩溃的情况下运行。
test
设置了错误的标志位,这个问题非常基础,内部QA测试不可能没有发现。特别是因为“测试然后跳转”操作似乎是一种非常常见的编译器优化,在许多程序中都被使用。 - aroth