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
指向data
,64
应该从这个地址中减去。这是通过添加 -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);
我的问题是:
- 这是错误还是功能?
- 是否有一些 GCC 标志可以防止这种行为?
- 如何确保我的整个 7-zip 二进制文件没有此类错误?
更新:
事实证明,此行为不依赖于优化标志,仅在 Windows
上观察到
data[i * 4 + 0 - kBlockSize] = (Byte)(d); // <<= DISASSEMBLED
这是错误的。当 i
为零时,减法会尝试生成负数、无符号数。没有这样的东西。
当 int
和 unsigned int
为 32 位宽时,这是标准要求的行为。表达式 i * 4 + 0 - kBlockSize
的计算结果为 (i * 4 + 0) - kBlockSize
。减号运算符左侧的类型是 int
,而右侧是 unsigned int
。这些需要转换为通用类型,标准 C 表示该类型为 unsigned int
。所以在循环的第一次迭代中,它变成 (unsigned) 0 - 64U
即 0xffffffc0
。如果指针是 32 位,那么 data[0xffffffc0]
将 "wrap around" 与 data[-64]
相同,但它们是 64 位,所以你会崩溃。
在 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
指向data
,64
应该从这个地址中减去。这是通过添加 -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);
我的问题是:
- 这是错误还是功能?
- 是否有一些 GCC 标志可以防止这种行为?
- 如何确保我的整个 7-zip 二进制文件没有此类错误?
更新: 事实证明,此行为不依赖于优化标志,仅在 Windows
上观察到 data[i * 4 + 0 - kBlockSize] = (Byte)(d); // <<= DISASSEMBLED
这是错误的。当 i
为零时,减法会尝试生成负数、无符号数。没有这样的东西。
当 int
和 unsigned int
为 32 位宽时,这是标准要求的行为。表达式 i * 4 + 0 - kBlockSize
的计算结果为 (i * 4 + 0) - kBlockSize
。减号运算符左侧的类型是 int
,而右侧是 unsigned int
。这些需要转换为通用类型,标准 C 表示该类型为 unsigned int
。所以在循环的第一次迭代中,它变成 (unsigned) 0 - 64U
即 0xffffffc0
。如果指针是 32 位,那么 data[0xffffffc0]
将 "wrap around" 与 data[-64]
相同,但它们是 64 位,所以你会崩溃。