由于用于构建 GCC 的 CPU 的体系结构,这是 g++ 的“非法指令错误”吗?

Posted

技术标签:

【中文标题】由于用于构建 GCC 的 CPU 的体系结构,这是 g++ 的“非法指令错误”吗?【英文标题】:Is this g++ 'illegal instruction error' due to the architecture of the CPU that was used to build GCC? 【发布时间】:2018-11-30 19:33:38 【问题描述】:

我们有一个基于 centos 的 docker 镜像,它使用 gcc 5.4 构建大型 C++ 代码库。 docker 镜像从源代码构建和安装 gcc。由于我们的私有 Docker 注册表中的一些数据丢失,我们不得不重建/推送这个 Docker 镜像到我们的注册表,并且开始发现使用这个 Docker 镜像的本地构建存在问题。

我们看到的错误是:

/usr/include/c++/5.4.0/limits:1601:7: internal compiler error: Illegal instruction
       max() _GLIBCXX_USE_NOEXCEPT  return __FLT_MAX__; 
       ^
0xa4f0cf crash_signal
        ../../gcc-5.4.0/gcc/toplev.c:383

我的理论是这个错误是由于运行构建的底层 CPU 的架构造成的,因为我们是从源代码构建 GCC。

以前,我们有一个基于 Xeon E5 v3 CPU(Haswell 架构)的 CI 基础架构。这个 docker 镜像的构建最初是在其中一台 CI 机器上完成的,因此在本地 Haswell 开发盒上运行良好。我们的 CI 基础架构已迁移到使用 Xeon Platinum CPU(Skylake 架构)。当我重建图像时,我在我们的一个新 Skylake 盒子上进行了重建。

由于我有一个较新的开发盒,我有一个基于 Broadwell 的 CPU,无法在本地重现该问题。我们的 CI 构建运行良好。本地出现此错误的用户有一个 Haswell CPU。

我的理论合理吗?我已经要求用户在他们的 CPU 上本地构建我们的 docker 映像并测试结果,但是有没有更通用的解决方法?

我遇到了this answer,它把我指向this documentation,其中指出我可以通过-march=*** 自己指定处理器架构。我由此产生的想法是:

    在构建 GCC 时设置 -march=haswell 以防止启用较新的指令集 在为 Haswell 上不可用但 Broadwell/Skylake 上存在的指令集扩展构建 GCC 时设置 -mno-***

作为参考,lscpu 的输出具有 Haswell 框上不存在的这些 Broadwell 标志(与 -mno-*** 标志相关联):

3dnowprefetch
hle
rtm
rdseed
adx
smap
arch_capabilities

如果这些想法中的任何一个解决了问题,是否值得测试?我希望得到一些外部输入,因为这个 docker 构建的开发循环非常长,老实说,我不知道这些 -m 标志会解决问题。

也供参考,下面是我们构建 gcc 的方式:

# build/install gcc
RUN tar xvf /tmp/archive/gcc-5.4.0.tar.gz && \
  mkdir gcc-build && \
  pushd gcc-build && \
  ../gcc-5.4.0/configure --prefix=/usr --enable-languages=c,c++,fortran --disable-multilib --with-gmp=/usr --with-mpfr=/usr --with-mpc=/usr && \
  make -j32 && \
  popd && \
  yum remove -y gcc gcc-c++ gcc-gfortran && \
  pushd gcc-build && \
  make install && \
  popd && \
  rm -rf gcc-build gcc-5.4.0

【问题讨论】:

@MatthieuBrucher:这是预期的方向。 “当我重建映像时,我在我们的一个新 Skylake 盒子上进行了重建。在本地收到此错误的用户使用的是 Haswell CPU。” 答案就在问题中。最可能的原因是 CPU 架构不匹配,首先想到的是重建到原生目标。 GCC 默认不执行本地构建,正因为如此,你的 Dockerfile 不会改变这一点。这是另一回事。尝试获取 coredump(使用 -dH 运行 gcc 可能会有所帮助)并查看错误指令。另请注意,根本不建议以这种方式替换系统编译器,这实际上可能是问题的根本原因。 【参考方案1】:

如 wikipedia (https://en.wikipedia.org/wiki/List_of_Intel_CPU_microarchitectures) 所示,Skylake 在 Broadwell 之后,本身在 Haswell 之后。

因此,Skylake 上的此类构建可能无法在较旧的 CPU 上运行,因此您应始终将 -march=haswell 添加到您的默认构建中,以生成必须在 Haswell 及更高版本上运行的二进制文件。

使用 -march 为您的最小平台调整架构,因为您知道在启用其他指令集时可能会出现数值差异。

您还可以使用-mtune 指定您将优化代码的目标(这意味着在此平台上,代码应该更快)。只要march 低于mtune,你可以混合使用。

【讨论】:

如果大部分盒子都是skylake,那么可能-march=haswell -mtune=skylake 确实,如果生产箱是 Skylake,-mtune 是有道理的。 谢谢!我不清楚这个错误是由于 gcc 的构建还是我们的源代码的构建。是否应该将其添加到 docker 构建过程或我们的构建脚本(或两者)? 我会建议两者。我想测试是在您构建的机器上完成的,但是如果二进制文件随后用于其他用途,那么您将遇到同样的问题。

以上是关于由于用于构建 GCC 的 CPU 的体系结构,这是 g++ 的“非法指令错误”吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 gcc 32bit 编译,作为带有 -m64 的 64 位程序

在代码块中使用 mingw gcc 构建 64 位 exe

GitLab CI/CD 管道在构建 Debian 包时找不到 GCC

gcc/C++:如果 CPU 负载低,那么代码优化就没啥用了,对吗?

构建用于操作系统开发的GCC交叉编译器

gcc 内联 asm x86 CPU 标志作为输入依赖项