【问题标题】:Is this "should not happen" crash an AMD Fusion CPU bug?这是“不应该发生”的崩溃 AMD Fusion CPU 错误吗?
【发布时间】:2011-10-23 16:48:41
【问题描述】:

我的公司已经开始有一些客户来电,因为我们的程序因系统访问冲突而崩溃。

崩溃发生在我们作为应用程序的一部分提供的 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 设置,但它不是零。由于未设置零标志,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,带 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 内核的“Llano”型号 AMD Fusion 芯片,发布于 2011 年 6 月,也就是我们第一次收到报告的时候。

最常见的客户系统是东芝 Satellite L775D,但我们也有来自 HP Pavilion dv6 和 dv7 以及 Gateway 系统的崩溃报告。

这个崩溃可能是由 CPU 错误引起的(请参阅Errata for AMD Family 12h Processors),还是我忽略了其他一些可能的解释? (根据 Raymond 的说法,它是 could be overclocking,但奇怪的是只有这个特定的 CPU 型号会受到影响,如果有的话。)

老实说,这似乎不太可能真的是 CPU 或操作系统错误,因为客户在其他应用程序中没有遇到蓝屏或崩溃。肯定还有其他一些更可能的解释——但是什么?

8 月 15 日更新: 我购买了一台配备 AMD A6-3400M 处理器的东芝 L745D 笔记本电脑,并且在运行该程序时可以始终如一地重现崩溃。崩溃总是在同一条指令上; .time 报告崩溃前 1m30s 到 7m 的用户时间。我在原帖中没有提及的一个事实(可能与该问题相关)是该应用程序是多线程的,并且 CPU 和 I/O 使用率都很高。该应用程序默认生成四个工作线程并发布 80+% 的 CPU 使用率(在 SQLite 代码中存在一些 I/O 和互斥锁阻塞),直到它崩溃。我将应用程序修改为仅使用两个线程,但它仍然崩溃(尽管需要更长的时间才能发生)。我现在只用一个线程运行测试,它还没有崩溃。

还要注意,这似乎并不是纯粹的 CPU 负载问题;我可以在系统上运行 Prime95 而不会出现错误,它会将 CPU 温度提高到 >70°C,而我的应用程序在运行时几乎不会超过 50°C。

8 月 16 日更新:稍微扰乱说明会使问题“消失”。例如,将内存负载 (mov eax,dword ptr [SQLite_Interop!sqlite3Hooks (71a7813c)]) 替换为 xor eax, eax 可以防止崩溃。修改原始 C 代码以在 if( createFlag==1 ) 语句中添加额外检查会更改编译代码中各种跳转的相对偏移量(以及 test eax, eaxcall eax 语句的位置),并且似乎也可以防止问题。

到目前为止我发现的最奇怪的结果是将jne 719f9fa0 更改为两条nop 指令(这样控制总是 落入test eax, eax 指令,无论createFlag/esi 的值是多少)都允许程序运行而不会崩溃。

【问题讨论】:

  • 这几乎肯定不是 CPU 错误。您是否考虑过制作一个更简单的测试用例?
  • @Mehrdad:是的,一些代码可以直接跳转到那里,但如果是这样的话,它在伪造调用堆栈方面做得非常好。
  • 我必须同意奥利。像test 设置不正确的标志这样基本的问题不太可能在内部 QA 测试中被捕获。特别是因为这个test then jump 操作似乎是一种非常常见的编译器优化,在大量程序中使用。
  • 我只是想插话说这是一个写得很好的问题。 +1
  • @flolo:这是一个在 64 位 Windows (WOW64) 上运行的 32 位进程;这个输出是正常的。

标签: assembly crash x86 windbg amd-processor


【解决方案1】:

我在 Microsoft Build 大会上与一位 AMD 工程师讨论了这个错误,并向他展示了我的重现。他今天早上给我发了电子邮件:

我们已经调查并发现这是由于一个已知的勘误 Llano APU 系列。它可以通过 BIOS 更新来修复,具体取决于 OEM – 如果可能的话,请将它推荐给您的客户(甚至 虽然你有一个解决方法)。

如果您有兴趣,勘误表是 Family 12h 中的 665 修订指南(参见第 45 页): http://support.amd.com/TechDocs/44739_12h_Rev_Gd.pdf#page=45

以下是对该勘误的描述:

665 整数除法指令可能导致不可预测的行为

说明

在一组高度具体和详细的​​内部时序条件下,处理器内核可能会中止推测性 DIV 或 IDIV 整数除法指令(由于推测性执行被重定向,例如由于错误预测的分支),但可能会挂起或过早完成非推测路径的第一条指令。

对系统的潜在影响

不可预测的系统行为,通常会导致系统挂起。

建议的解决方法

BIOS 应设置 MSRC001_1029[31]。

此解决方法会更改 AMD 系列 10h 和 12h 处理器的软件优化指南,订单号 40546 中指定的 DIV/IDIV 指令延迟。应用此解决方法后,AMD 系列的 DIV/IDIV 延迟12h 处理器类似于 AMD 系列 10h 处理器的 DIV/IDIV 延迟。

修复计划

没有

【讨论】:

  • Passmark 论坛中讨论了“665 整数除法”问题:passmark.com/forum/… 那里的评论说该问题仅发生在双通道 RAM 上。因此,如果没有 BIOS 修复,一台具有 4GB RAM 棒和 Llano CPU 的计算机可能会很好。但是如果你花 20 美元升级到 8 GB,你就会遇到问题——你可能会(错误地!)归咎于 RAM。不幸的是,BIOS“修复”导致 Passmark 的整数数学基准测试速度降低 >80%,Passmark 分数降低 >30%。
【解决方案2】:

我有点担心为if (wsdHooks.xBenignBegin) 生成的代码不是很通用。它假定唯一的真实值是1,而它实际上应该测试 any 非零值。尽管如此,MSVC 有时会以这种方式令人费解。可能没什么。 没关系:这些说明适用于未提供的 C 代码。

鉴于 eflag Z 位清零且 EAX 为零,因此执行指令时代码没有到达这里

719f9fa7    test    eax,eax

必须从其他地方跳转到 (719f9fa9 je SQLite_Interop!pcache1Fetch+0x2d) 之后的指令,甚至是 call 指令本身。

另一个复杂情况是,对于 x86 系列,无效的跳转目标(如 JE 指令的第二个字节)通常会在相当多的指令上不受干扰(没有错误)执行,通常最终会重新执行正确的指令对齐。换句话说,您可能不会寻找到任何这些指令开头的跳转:跳转可能在它们的字节中间,导致执行像add [al+ebp],al 这样的不起眼的操作,往往不会被注意到。

我预测test 指令处的断点不会因为异常而被命中。找到此类原因的唯一方法是要么非常幸运,要么怀疑一切并一一证明他们是无辜的。

【讨论】:

  • 关于您的第一段:test 仅在 eax & eax 等于 0 时设置 ZF,因此后续的 je 非常安全。
  • you asm 分析已关闭,检查 vs 1 是因为 C 代码正在检查 vs 1,因为它的行 if( createFlag==1 ) sqlite3BeginBenignMalloc(); 而不是 if (wsdHooks.xBenignBegin) (请参阅 OP 对 sqlite3BeginBenignMalloc 的评论内联)
  • @Michael Foukarakis:很公平,所以我已经编辑了我的评论。
  • 我并不想争论,但我认为你的理论与我的发现相矛盾,即用test 替换test 之前的jne 指令似乎可以防止崩溃。 (如果没有该更改,100% 可重现,在一天的测试中可重现 0%。)如果其他一些指令跳到 je 的中间或直接跳到 call,它不会受到影响改变。此外,其他代码跳转到jecall 的理论如何解释这只发生在 Llano APU 上?
【解决方案3】:

在考虑 CPU 错误的可能性之前,请尝试排除更可能的原因

  1. 调用指令的代码路径不同。使用uf命令反汇编函数,寻找调用指令的其他跳转/分支

  2. 从钩子函数跳转/调用到 0。 dps SQLite_Interop!sqlite3Hooks l 2 并验证它是否显示空值。

【讨论】:

  • 1. (我已经在评论中回答了这个问题,但没有更新原始问题,所以这并不明显;我很抱歉。)崩溃代码是函数中的 0x2B 字节(几乎就在序言之后)。我拆开了整个功能,没有那么早的跳转;此语句出现在函数体中的循环之前。当然,从函数外部跳转是可能的,但很难与堆栈协调。 2.dps SQLite_Interop!sqlite3Hooks l 2显示00000000 00000000
  • 我还设置了一个数据断点 (ba w 4 SQLite_Interop!sqlite3Hooks) 并且在崩溃之前没有写入该地址。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-29
  • 2017-11-29
  • 1970-01-01
  • 1970-01-01
  • 2013-01-17
  • 2014-02-26
相关资源
最近更新 更多