最近遇到了一个 lib 库编译的二进制产物在目标机器报 invalid opcode 的问题。导致这个问题的原因是目标机器缺少 AVX-512 相关指令集,而编译机 CPU 指令集版本较高,含有 AVX-512 ,且在编译时打开了 O2 选项。

这里有个隐含的信息:GCC 在打开 O2 编译优化选项后,若 march 选项1 指定了 native (即本机),GCC 会尽可能使用本机 CPU 所能支持最强大的 SIMD 指令 2。由于编译机版本很高,在这种情况下,-O2 -march=native 所生成的二进制很可能会包含 AVX-512 的指令。

由于目标机的 CPU 微架构为 Broadwell,故解决方案是在编译中指定参数 -march=broadwell 。这样 GCC 所生成的二进制产物就不会使用 Broadwell 架构中“不存在”的指令,解决了库文件的 CPU 架构兼容问题。

CPU info

查看当前系统的虚拟化软件:

$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                8
On-line CPU(s) list:   0-7
Thread(s) per core:    2
Core(s) per socket:    4
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 85
Model name:            Intel(R) Xeon(R) Gold 6161 CPU @ 2.20GHz
Stepping:              4
CPU MHz:               2200.000
BogoMIPS:              4400.00
Hypervisor vendor:     KVM
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              1024K
L3 cache:              30976K
NUMA node0 CPU(s):     0-7

Hypervisor vendor 的值即为虚拟化软件。

查看当前 CPU 支持的指令集(Flags):

$ cat /proc/cpuinfo
...
processor       : 7
vendor_id       : GenuineIntel
cpu family      : 6
model           : 85
model name      : Intel(R) Xeon(R) Gold 6161 CPU @ 2.20GHz
stepping        : 4
microcode       : 0x1
cpu MHz         : 2200.000
cache size      : 30976 KB
physical id     : 0
siblings        : 8
core id         : 3
cpu cores       : 4
apicid          : 7
initial apicid  : 7
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nop
l xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowpr
efetch invpcid_single ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw a
vx512vl xsaveopt xsavec xgetbv1 arat spec_ctrl intel_stibp flush_l1d
bogomips        : 4400.00
clflush size    : 64
cache_alignment : 64
address sizes   : 42 bits physical, 48 bits virtual
power management:

flags 为当前 CPU 支持的指令集。上例中可看出该 CPU 支持 AVX-512 等指令集。

GCC info

查看当前 GCC 版本。

$ gcc --verison

查看当前 GCC 所支持的微架构类型 3

$ gcc --target-help | grep march -C 20
...
  -march=CPU[,+EXTENSION...]
                          generate code for CPU and EXTENSION, CPU is one of:
                           generic32, generic64, i386, i486, i586, i686,
                           pentium, pentiumpro, pentiumii, pentiumiii, pentium4,
                           prescott, nocona, core, core2, corei7, l1om, k1om,
                           k6, k6_2, athlon, opteron, k8, amdfam10, bdver1,
                           bdver2, bdver3, bdver4, znver1, btver1, btver2
                          EXTENSION is combination of:
                           8087, 287, 387, no87, mmx, nommx, sse, sse2, sse3,
                           ssse3, sse4.1, sse4.2, sse4, nosse, avx, avx2,
                           avx512f, avx512cd, avx512er, avx512pf, avx512dq,
                           avx512bw, avx512vl, noavx, vmx, vmfunc, smx, xsave,
                           xsaveopt, xsavec, xsaves, aes, pclmul, fsgsbase,
                           rdrnd, f16c, bmi2, fma, fma4, xop, lwp, movbe, cx16,
                           ept, lzcnt, hle, rtm, invpcid, clflush, nop, syscall,
                           rdtscp, 3dnow, 3dnowa, padlock, svme, sse4a, abm,
                           bmi, tbm, adx, rdseed, prfchw, smap, mpx, sha,
                           clflushopt, prefetchwt1, se1, clwb, pcommit,
                           avx512ifma, avx512vbmi, ospke, clzero, mwaitx
...

若需要编译产物使用指定的 CPU 指令集,可以通过对应的 CPU 或扩展的值,使用 march 来实现。如:我们需要编译产物能够在 Core2 上运行,则需 -march=core2

查看当前 GCC 针对本机定义了哪些 SSE / AVX 宏 4

$ gcc -dM -E -march=native - < /dev/null | egrep "SSE|AVX" | sort
#define __AVX__ 1
#define __AVX2__ 1
#define __SSE__ 1
#define __SSE2__ 1
#define __SSE2_MATH__ 1
#define __SSE3__ 1
#define __SSE4_1__ 1
#define __SSE4_2__ 1
#define __SSE_MATH__ 1
#define __SSSE3__ 1

查看当前 GCC 在本机运行指定编译选项后的实际配置 5

$ gcc -O2 -march=native -Q --help=target
The following options are target specific:
  -m128bit-long-double                  [disabled]
  -m32                                  [disabled]
  -m3dnow                               [disabled]
  -m3dnowa                              [disabled]
  -m64                                  [enabled]
  -m80387                               [enabled]
  -m8bit-idiv                           [disabled]
  -m96bit-long-double                   [enabled]
  -mabi=                                sysv
  -mabm                                 [enabled]
  -maccumulate-outgoing-args            [disabled]
  -maddress-mode=                       short
  -madx                                 [enabled]
  -maes                                 [enabled]
  -malign-double                        [disabled]
  -malign-functions=                    0
  -malign-jumps=                        0
  -malign-loops=                        0
  -malign-stringops                     [enabled]
  -mandroid                             [disabled]
  -march=                               core-avx2
  -masm=                                att
  -mavx                                 [enabled]
  -mavx2                                [enabled]
...

寻找二进制库中特殊的指令。

$ objdump -M intel -d libmdec.so | ./opcode -r -a Haswell -v -m
...
    5619:       62 d1 fd 48 7f 02       vmovdqa64 ZMMWORD PTR [r10],zmm0
    561f:       62 d1 7e 48 7f 01       vmovdqu32 ZMMWORD PTR [r9],zmm0
    5625:       62 f1 7e 48 7f 06       vmovdqu32 ZMMWORD PTR [rsi],zmm0
...

这里我们借助了 opcode 脚本 6 来筛选出 Haswell 架构中“不认识”的指令。

事实上这个脚本有些老旧,只能支持到 Intel 的 Haswell 微架构。且 cmove 这类操作也不认识,还需根据实际情况查看 Intel 官方的指令集手册 7 来确定是否为问题指令。

BTW: elfx86exts 这个项目 8 中包含的指令集架构较新,作者使用 Rust 编写,有条件可以尝试一下 笔者没试过


  1. x86 Options
    https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-march-14 ↩︎

  2. Optimize options - Using the GNU Compiler Collection (GCC)
    https://gcc.gnu.org/onlinedocs/gcc-3.4.0/gcc/Optimize-Options.html#Optimize%20Options ↩︎

  3. What are my available march/mtune options?
    https://stackoverflow.com/questions/53156919/what-are-my-available-march-mtune-options ↩︎

  4. 使用 AVX 系列指令集进行向量化
    https://enigmahuang.me/2017/09/29/AVX-SIMD/ ↩︎

  5. gcc - How to see which flags -march=native will activate? - Stack Overflow
    https://stackoverflow.com/questions/5470257/how-to-see-which-flags-march-native-will-activate ↩︎

  6. x86 - How to check if a binary requires SSE4 or AVX on Linux - Kyselejsyreček - Super User
    https://superuser.com/a/832440/922081 ↩︎

  7. Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B, 2C & 2D): Instruction Set Reference, A-Z
    https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf ↩︎

  8. x86 - How to check if a binary requires SSE4 or AVX on Linux - Peter - Super User
    https://superuser.com/a/1254953/922081 ↩︎


知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。