GCC 在 64 位模式下崩溃

GCC crash in 64bit mode

在 Windows64 上使用 MinGW GCC 4.8.2 编译 7-zip 源代码时发现崩溃。

这是 7-zip 9.20 的代码片段,Sha1.cpp:

const unsigned kBlockSize = 64;

...

void CContext::UpdateRar(Byte *data, size_t size, bool rar350Mode) {
   ...
   for (int i = 0; i < kBlockSizeInWords; i++) {
       UInt32 d = _buffer[i];
       data[i * 4 + 0 - kBlockSize] = (Byte)(d);      // <<= DISASSEMBLED
       data[i * 4 + 1 - kBlockSize] = (Byte)(d >>  8);
       data[i * 4 + 2 - kBlockSize] = (Byte)(d >> 16);
       data[i * 4 + 3 - kBlockSize] = (Byte)(d >> 24);
   }
}

使用 MinGW GCC 4.8.2(在 Windows 上)编译得到:

0x0000000045b65f75 <+149>:   mov    eax,0xffffffc0
0x0000000045b65f7a <+154>:   nop    WORD PTR [rax+rax*1+0x0]
0x0000000045b65f80 <+160>:   mov    r10d,DWORD PTR [r11+0x24]
0x0000000045b65f84 <+164>:   mov    edx,eax
0x0000000045b65f86 <+166>:   add    r11,0x4
0x0000000045b65f8a <+170>:   mov    ecx,r10d
0x0000000045b65f8d <+173>:   mov    BYTE PTR [rbx+rdx*1],r10b

最后一行rbx指向data64应该从这个地址中减去。这是通过添加 -64 来完成的。在汇编代码片段的第一行 0xffffffc0 (-64 (Int32)) 保存到 eax 然后移动到 edx。但是在最后一行

0x0000000045b65f8d <+173>:   mov    BYTE PTR [rbx+rdx*1],r10b

使用 rdx 寄存器而不是 edx 寄存器。此时 rdx 的顶部为 0,因此 +64 可以正常工作。但是添加 -64 需要 rdx 寄存器的顶部来保存 0xFFFFFFFF。在 64 位模式下添加 &data+0xffffffc0 会产生无效地址并导致崩溃。

问题已通过更改分配(添加 (int) 演员表)解决:

       data[i * 4 + 0 - (int)kBlockSize] = (Byte)(d);
       data[i * 4 + 1 - (int)kBlockSize] = (Byte)(d >>  8);
       data[i * 4 + 2 - (int)kBlockSize] = (Byte)(d >> 16);
       data[i * 4 + 3 - (int)kBlockSize] = (Byte)(d >> 24);

我的问题是:

更新: 事实证明,此行为不依赖于优化标志,仅在 Windows

上观察到
   data[i * 4 + 0 - kBlockSize] = (Byte)(d);      // <<= DISASSEMBLED

这是错误的。当 i 为零时,减法会尝试生成负数、无符号数。没有这样的东西。

intunsigned int 为 32 位宽时,这是标准要求的行为。表达式 i * 4 + 0 - kBlockSize 的计算结果为 (i * 4 + 0) - kBlockSize。减号运算符左侧的类型是 int,而右侧是 unsigned int。这些需要转换为通用类型,标准 C 表示该类型为 unsigned int。所以在循环的第一次迭代中,它变成 (unsigned) 0 - 64U0xffffffc0。如果指针是 32 位,那么 data[0xffffffc0] 将 "wrap around" 与 data[-64] 相同,但它们是 64 位,所以你会崩溃。