问题描述
某设备使用 C3758 cpu,在运行 vpp 时触发了指令异常,gdb 中看到的关键信息如下:
Thread 1 "vpp" received signal SIGILL, Illegal instruction.
0x00007fff8c22ceff in mm_loadu_si128 (__P=0x7fff8c52b3a8) at /usr/lib/gcc/x86_64-buildroot-linux-gnu/5.4.0/include/emmintrin.h: 698
环境信息
cpuflags
查看 /proc/cpuinfo 确认 C3758 cpu 支持 SSE、SSE2、SSE4 向量指令,不支持 AVX 指令。
问题分析
使用 gdb 反汇编确认非法指令的位置如下:
→ 0x00007fff8c22ceff <+2472>: vmovdqu (%rax),%xmm0
初步结论是该款 cpu 不支持 vmovdqu 指令。
对比测试
不指定额外编译参数
使用如下 demo 调用 mm_loadu_si128 函数测试:
#include <stdio.h>
#include <emmintrin.h>
int main() {
int data[4] = {1, 2, 3, 4};
__m128i xmm_data = _mm_loadu_si128((__m128i*)data);
int loaded_data[4];
_mm_storeu_si128((__m128i*)loaded_data, xmm_data);
for (int i = 0; i < 4; ++i) {
printf("%d ", loaded_data[i]);
}
printf("\n");
return 0;
}
仅指定 -g 编译参数,gcc 编译,在相同的环境中程序能够正常运行。反汇编查看相关函数的指令,有如下内容:
11c8: 48 8b 45 a8 mov -0x58(%rbp),%rax
11cc: f3 0f 6f 00 movdqu (%rax),%xmm0
__m128i xmm_data = _mm_loadu_si128((__m128i*)data);
11d0: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp)
11d4: 48 8d 45 e0 lea -0x20(%rbp),%rax
11d8: 48 89 45 a0 mov %rax,-0x60(%rbp)
11dc: 66 0f 6f 45 b0 movdqa -0x50(%rbp),%xmm0
11e1: 0f 29 45 c0 movaps %xmm0,-0x40(%rbp)
可以看到并未生成使用 vmovdqu 指令的机器码,程序能够正常运行。
指定 vpp 编译的部分参数
使用如下编译参数:
-g -mno-avx512f -gdwarf-4 -march=native
编译出来的程序在目标环境中运行也会触发指令异常,反汇编查看指令,得到如下信息:
11c8: 48 8b 45 a8 mov -0x58(%rbp),%rax
11cc: c5 fa 6f 00 vmovdqu (%rax),%xmm0
__m128i xmm_data = _mm_loadu_si128((__m128i*)data);
11d0: c5 f8 29 45 b0 vmovaps %xmm0,-0x50(%rbp)
11d5: 48 8d 45 e0 lea -0x20(%rbp),%rax
11d9: 48 89 45 a0 mov %rax,-0x60(%rbp)
11dd: c5 f9 6f 45 b0 vmovdqa -0x50(%rbp),%xmm0
11e2: c5 f8 29 45 c0 vmovaps %xmm0,-0x40(%rbp)
可以看到这次生成的机器码中使用了 vmovdqu 指令,确定与编译参数强相关。
问题原因分析
为什么会触发指令异常?
在 https://www.intel.com/content/dam/develop/external/us/en/documents/319433-024-697869.pdf 这篇 pdf 文档中,找到如下内容:
使用 vmovdqu cpu 需要支持 avx 指令集,C3758 不支持 avx 指令集,进而不支持 vmovdqu 指令,执行该指令时就会触发指令异常。
为什么会生成使用 avx 指令集的机器码?
gcc 编译 vpp 时指定了 -march 为 native ,这时 gcc 会根据当前主机的处理器类型和特性来优化生成的机器码,以提高代码的执行效率。编译环境支持 avx 指令集,gcc 使用 avx 指令优化代码执行效率。
解决方法
修改 -march 参数指定的 cpu 类型,例如 corei7,生成未使用 avx 指令集的机器码。
联想
dpdk 中有许多向量指令的使用场景,收发包函数优化就是一个很典型的示例,一般来说一个网卡驱动可能有几套收发包函数,根据向量指令划分,有支持 sse、avx、avx512 这几个版本,实际应用中编译环境一般只有一套,运行环境却可能存在多套,这样就存在不兼容的问题。
如何解决这种问题呢?
dpdk 会在启动的时候获取 cpu 支持的 CPIID feature flag,根据 flag 去判断,动态选择最适合目标机器的收发包函数向量实现,很好地解决了目标环境与编译环境不兼容的问题。