17

我有一小段代码(这是我遇到的问题的一个最小工作示例):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void xorBuffer(unsigned char* dst, unsigned char* src, int len)
{
    while (len != 0)
    {
        *dst ^= *src;
        dst++;
        src++;
        len--;
    }
}

int main()
{
    unsigned char* a = malloc(32);
    unsigned char* b = malloc(32);
    int t;

    memset(a, 0xAA, 32);
    memset(b, 0xBB, 32);

    xorBuffer(a, b, 32);

    printf("result = ");
    for (t = 0; t < 32; t++) printf("%.2x", a[t]);
    printf("\n");

    return 0;
}

这段代码应该执行两个 32 字节内存缓冲区的异或(从概念上讲,应该这样做a = a ^ b)。由于 0xAA ^ 0xBB = 0x11,它应该打印“11”三十二次。

我的问题是,当我在 MinGW-GCC (Windows) 下编译它时,它在调试模式下完美运行(无优化),但是当启用从 -O3 开始的优化时,在 xorBuffer 循环中途出现 SIGILL 崩溃。此外,如果我将 printf 放入有问题的循环中,它会再次完美运行。我怀疑堆栈损坏,但我只是看不到我在这里做错了什么。

尝试在启用优化的情况下使用 GDB 进行调试是一个失败的原因,因为所有 GDB 都向我展示了针对每个变量的“变量优化”(当然,如果我尝试将变量打印出来,它会突然起作用)。

有人知道这里到底发生了什么吗?我在这个问题上花费了太长时间,我真的需要正确解决它才能继续前进。我的猜测是我缺少一些基本的 C 指针知识,但对我来说,代码看起来是正确的。它可能来自缓冲区增量,但据我所知,sizeof(unsigned char) == 1它应该一个一个地遍历每个字节。

值得一提的是,即使在我的 Linux 机器上对 GCC 进行了优化,该代码也能正常工作。

那么......这里有什么交易?谢谢!

根据要求,整个程序的汇编输出:

使用-O2:点击

使用-O3:点击

我在 GCC 4.6.2 上观察到这种行为(与 MinGW 一起运行)

4

2 回答 2

8

从我的评论:

确保编译器具有有关目标体系结构的正确信息。从读取-O3输出来看,编译器似乎正在为您设置 SIMD 优化,实际上是通过使用向量指令(例如movdqa)使代码更加并行。如果目标处理器不能 100% 匹配编译器发出的代码,您可能会得到非法指令。

于 2012-08-22T11:03:33.353 回答
8

我将此添加为 Unwind 答案的扩展(我接受它,因为它让我走上了正确的轨道)。

筛选优化后的代码后,我注意到了 AVX 指令。起初,我认为这不会造成问题,因为我的处理器支持 AVX 指令集。然而,事实证明有两个不同的 AVX 版本:AVX1 和 AVX2。而且,虽然我的处理器只支持 AVX1,但只要处理器支持这两个版本中的任何一个,gcc 就会不加选择地使用 AVX2 操作码(llvm 犯了同样的错误,对此有错误报告)。据我所知,这是不正确的操作和编译器错误。

结果是 AVX1 系统上的 AVX2 代码,这显然会导致非法指令。它解释了很多事情,从代码在小于 32 字节的输入上没有失败(因为 256 位寄存器宽度),到在我的 Linux 机器上运行的代码,它恰好是一个 CPU 支持仅限于 SSE3 的虚拟机。

修复方法是禁用 -O3 并返回到 -O2,其中 gcc 不会诉诸最核心的 SIMD 指令来优化简单代码,或者使用volatile强制它逐字节遍历缓冲区的关键字,煞费苦心,像这样:

*(unsigned char volatile *)dst ^= *(unsigned char volatile *)src;

这当然非常慢,并且可能比仅使用 -O2 (忽略整个程序的影响)更糟糕,但可以通过 int 遍历缓冲区 int 并在最后填充来解决它,这在以下方面已经足够好了速度。

另一个好的修复方法是升级到没有这个 bug 的 gcc 版本(这个版本可能还不存在,我没有检查过)。

编辑:最终的解决方法是在 GCC 上抛出 -mno-avx 标志,从而禁用任何和所有 AVX 操作码,完全消除错误而无需修改代码(一旦修补编译器版本可用,就可以轻松删除)。

多么不正当的编译器错误。

于 2012-08-22T11:30:06.450 回答