我的公司已经开始让许多客户打电话,因为我们的程序因其系统上的访问冲突而崩溃 .
崩溃发生在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(它是),则零标志应由 test eax, eax
设置,但它设置's non-zero. Because the zero flag isn', je
不跳转,然后应用程序崩溃尝试执行 call eax (00000000)
.
更新: eax
应始终为0,因为 sqlite3Hooks.xBenignBegin
未在我们的代码构建中设置 . 我可以使用 SQLITE_OMIT_BUILTIN_TEST
defined来重建SQLite,这将在代码中打开 #define sqlite3BeginBenignMalloc()
并完全省略此代码路径 . 这可能会解决问题,但感觉不像是"real"修复;什么会阻止它在其他代码路径中发生?
到目前为止,常见的因素是所有客户都在运行“Windows 7 Home Premium 64位(6.1,Build 7601)Service Pack 1”并拥有以下CPU之一(根据DxDiag):
-
AMD A6-3400M APU配Radeon(tm)高清显卡(4个CPU),~1.4GHz
-
AMD A8-3500M APU配Radeon(tm)高清显卡(4个CPU),~1.5GHz
-
AMD A8-3850 APU配Radeon(tm)高清显卡(4个CPU),~2.9GHz
根据维基百科的AMD Fusion article,这些都是基于K10核心的AMD融合芯片,并于2011年6月发布,这是我们刚开始收到报告的时候 .
最常见的客户系统是东芝Satellite L775D,但我们也有HP Pavilion dv6&dv7和Gateway系统的崩溃报告 .
这次崩溃是由CPU错误引起的(见Errata for AMD Family 12h Processors),还是有其他可能的解释我忽略了? (根据雷蒙德的说法,它是could be overclocking,但奇怪的是这个特定的CPU模型会受到影响,如果是这样的话 . )
老实说,它似乎不太可能是CPU或操作系统错误,因为客户没有在其他应用程序中获得蓝屏或崩溃 . 必须有其他更可能的解释 - 但是什么?
8月15日更新:我已经购买了配备AMD A6-3400M处理器的东芝L745D笔记本电脑,可以在运行程序时始终如一地重现故障 . 崩溃总是在同一条指令上; .time
报告在崩溃前的1m30到7m的用户时间 . 我在原帖中忽略的一个事实(可能与问题相关)是应用程序是多线程的,并且具有高CPU和I / O使用率 . 该应用程序默认生成四个工作线程,并发布80%的CPU使用率(对于I / O以及SQLite代码中的互斥锁有一些阻塞),直到崩溃为止 . 我修改了应用程序只使用两个线程,它仍然崩溃(虽然它需要更长的时间) . 我'm now running a test with just one thread, and it hasn' t坠毁了 .
另请注意,它似乎不是纯粹的CPU负载问题;我可以在系统上运行Prime95而没有错误,它会将CPU温度提高到> 70°C,而我的应用程序在运行时几乎不会达到50°C以上的温度 .
8月16日更新:稍微扰乱说明会导致问题"go away" . 对于eaxmple,用 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
的值是什么)允许程序运行而不会崩溃 .
3 回答
我在Microsoft Build Session 上与AMD工程师讨论了这个错误,并向他展示了我的责任 . 他今天早上给我发了电子邮件:
以下是该错误的描述:
665整数除法指令可能导致不可预测的行为
说明
在高度具体和详细的内部时序条件下,处理器内核可以中止推测DIV或IDIV整数除法指令(由于推测执行被重定向,例如由于错误预测的分支)但可能挂起或过早地完成非推测路径的第一条指令 .
对系统的潜在影响
不可预测的系统行为,通常会导致系统挂起 .
建议的解决方法
BIOS应设置MSRC001_1029 [31] .
此解决方法更改了AMD系列10h和12h处理器的软件优化指南中指定的DIV / IDIV指令延迟,订单#40546 . 应用此解决方法后,AMD系列12h处理器的DIV / IDIV延迟类似于DIV / IDIV延迟适用于AMD系列10h处理器 .
修复计划
没有
在考虑CPU错误的可能性之前,请尝试排除更可能的原因
调用指令的不同代码路径 . 使用
uf
命令反汇编函数并查找调用指令的其他跳转/分支从钩子函数跳转/调用0 .
dps SQLite_Interop!sqlite3Hooks l 2
并验证它是否显示空值 .我有点担心为if(wsdHooks.xBenignBegin)生成的代码不是很通用 . 它假设唯一的真值是1,而它应该真正测试任何非零值 . 尽管如此,MSVC有时会这样令人困惑 . 它可能没什么 . 没关系:这些说明适用于未提供的
C
代码 .假设eflag
Z
位清零且EAX
为零,则执行该指令时代码无法到达此处必须从其他地方跳转到(
719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d
)之后的指令,甚至是call
指令本身 .另一个复杂因素是,对于x86系列,无效的跳转目标(如
JE
指令的第二个字节)通常会为很多指令执行无干扰(无故障),通常最终会恢复正确的指令对齐 . 换句话说,你可能不会寻找跳转到任何这些指令的开头:跳转可能在它们的字节中间,导致执行不起眼的操作,如add [al+ebp],al
,往往不会被注意到 .我预测
test
指令的断点不会被异常击中 . 找到这些原因的唯一方法是要么非常幸运,要么怀疑一切,并逐一证明他们是无辜的 .