我正在调试一个在英特尔 CPU 上正常运行的应用程序,但不能在另一个较新的 AMD 处理器上运行。我怀疑它可能已被编译为使用某些英特尔特定的指令,从而导致崩溃。但是,我正在寻找一种方法来验证这一点。我没有访问原始源代码。
是否有一个工具可以扫描二进制文件并列出它可能使用的 CPU 特定功能?
有两种好的方法:
在调试器下运行并查看导致非法指令错误的指令
在模拟器 / 仿真器下运行,可以显示指令组合,如 SDE。
但是你的想法,静态扫描二进制文件,不能区分只在检查cpuid
后调用的函数中的代码。
使用调试器查看出错指令
选择任何调试器。GDB 很容易安装在任何 Linux 发行版上,也可能安装在 Windows 或 Mac (或那里的 lldb) 上。或者选择任何其他调试器,例如一个带有 GUID 的
运行程序。一旦出现故障,请使用调试器检查故障指令。
在英特尔或 AMD 的 x86 asm 参考手册中查找它,例如https://www.felixcloutier.com/x86/是英特尔 PDF 的 HTML 抓取。查看此指令的形式需要哪个 ISA 扩展。
例如,这个源代码可以编译使用 X-512 指令,如果你让编译器这样做,但只需要 SSE2 编译在第一位。
#include <immintrin.h>
// stores to global vars typically aren't optimized out, even witut volatile
int buf[16];
int main(int argc, char **argv)
{
__m128i v = _mm_set1_epi32(argc); // broadcast scalar to vector
_mm_storeu_si128((__m128i*)buf, v);
}
(请参阅Godbolt与不同的编译选项。)
使用gcc -march=skylake-avx512 -O3 ill.c
构建。
然后尝试运行它,例如在我的 Skylake 客户端(非 X512)GNU / Linux 桌面上。(我还使用strip a.out
删除符号表(函数名称),就像仅二进制软件版本一样)。
$ ./a.out
Illegal instruction (core dumped)
$ gdb a.out
...
(gdb) run
Starting program: /tmp/a.out
Program received signal SIGILL, Illegal instruction.
0x0000555555555020 in ?? ()
(gdb) disas
No function contains program counter for selected frame.
(gdb) disas /r $pc,+20 # from current program counter to +20 bytes
Dump of embler code from 0x555555555020 to 0x555555555034:
=> 0x0000555555555020: 62 f2 7d 08 7c c7 vpbroadcastd xmm0,edi
0x0000555555555026: c5 f9 7f 05 32 30 00 00 vmovdqa XMMWORD PTR [rip+0x3032],xmm0 # 0x555555558060
0x000055555555502e: 31 c0 xor eax,eax
0x0000555555555030: c3 ret
0x0000555555555031: 66 2e 0f 1f 84 00 00 00 00 00 cs nop WORD PTR [rax+rax*1+0x0]
End of embler dump.
=>
表示当前程序计数器(x86-64 中的 RIP,但 GDB 可移植地将$pc
定义为任何 ISA 上的别名。)
所以我们在vpbroadcastd xmm0,edi
上出错。(GCC 实现_mm_set1_epi32(argc)
的方式,当我们告诉它 X512 可用时。)
这不涉及内存访问,而且故障是非法的-指令不是分段-故障,所以我们可以肯定,实际上试图执行一个不受支持的指令是这里崩溃的直接原因。(它也有可能是一个间接原因,例如,一个使用lzcnt eax, ecx
的程序,但一个旧的 CPU 将其运行为bsr eax, ecx
,然后使用那个不同的整数作为数组索引。lzcnt 不太可能为你
因此,让我们检查 vpbroadcastd:英特尔手册中有多个vpbroadcast
条目:
VPBROADCAST Load Integer and Broadcast-不,只有 XMM 和内存源的条目。
VBROADCAST — Load with Broadcast Floating-Point Data-不,这也只是内存或向量寄存器源操作数。并且是vbroadcastss
等,而不是vP...
整数指令。(英特尔的惯例是p...
是 packed-integer,...ps/pd
是 packed-single 或 packed-float。)
如果助记符以v
开头,并且您找不到条目,例如vaddps
,那是因为该指令存在于 X 之前,并且记录在其传统 SSE 助记符下,例如 SSE1addps
,其中列出了addps
和vaddps
1编码,例如 X-512 ZX
无论如何,回到我们的示例。与错误指令匹配的表条目如下。请注意,对操作数进行编码的 ModR / M(/r
)之前的7C
操作码字节。它出现在 4 字节 EVEX 前缀之后,作为交叉检查,这确实是我们正在寻找的操作码。
EVEX.128.66.0F38.W0 7C /r
VPBROADCASTD xmm1 {k1}{z},r32
根据表,它需要“X512VL X512F”。{k1}{z}
是可选的掩码。r32
是 32 位通用整数寄存器,在这种情况下与edi
类似。xmm1
表示任何 XMM 寄存器都可以是该指令的第一个 xmm 操作数;在这种情况下,GCC 选择了 XMM0。
我的 CPU 根本没有 X-512,所以它出现故障。
SDE 指令组合
这应该在 Windows 或任何其他操作系统上同样有效。
Intel's SDE (Software Development Emulator)有一个-mix
选项,其输出包括按所需的 ISA 扩展进行分类。请参阅How do I monitor the amount of SIMD instruction usagere:using it。
使用相同的例子a.out
我使用 GDB:
运行/opt/sde-external-8.33.0-2019-02-07-lin/sde64 -mix -- ./a.out
创建了一个文件sde-mix-out.txt
,其中包含很多内容,包括不同基本块执行频率的统计信息。(动态链接器中的一些运行了很多次。)IDK 如果有一个选项可以忽略它,因为对于大型程序来说,它会变得非常臃肿。我认为它可能只打印前几个块,即使还有更多。
然后我们得到我们想要的部分:
...
# END_TOP_BLOCK_STATS
# EMIT_DYNAMIC_STATS FOR TID 0 OS-TID 1168465 EMIT #1
#
# $dynamic-counts
#
# TID 0
# opcode count
#
*stack-read 8806
*stack-write 8314
*iprel-read 1003
*iprel-write 437
...
*isa-ext-X 4
*isa-ext-X2 5
*isa-ext-X512EVEX 1
*isa-ext-BASE 133338
*isa-ext-LONODE 545
*isa-ext-SSE 56
*isa-ext-SSE2 2560
*isa-ext-XSE 1
*isa-set-X 4
*isa-set-X2 5
*isa-set-X512F_128 1
*isa-set-CMOV 266
*isa-set-FAT_NOP 891
*isa-set-I186 2676
*isa-set-I386 7626
*isa-set-I486REAL 71
*isa-set-I86 121192
*isa-set-LONODE 545
*isa-set-PENTIUMREAL 8
*isa-set-PPRO 608
*isa-set-SSE 56
*isa-set-SSE2 2560
*isa-set-XSE 1
isa-set-X512F_128
的 1 计数是在我的 CPU 上出现故障的指令,它根本不支持 X-512。X512F_128 是 X512F(基础)+X512VL(向量长度,允许 512 位 ZMM 寄存器以外的向量)。
(它也被计为isa-ext-X512EVEX
。EVEX 是 X-512 向量指令的机器代码前缀。X-512 掩码指令,如kandw k0, k1, k2
,使用 VEX 编码,如 X1 / X2 SIMD 指令。但这不会区分冰湖新指令,如vpermb
在支持 X-512BMI 的 Skylake 服务器 CPU 上出现故障)
除了 X-512 以外的其他东西可能更简单,因为每个扩展都有一个完全的名称。
静态拆卸
您可以反汇编大多数二进制文件;如果它们没有被混淆,那么反汇编应该找到可能执行的所有指令。(而且使用新指令的高性能代码不太可能使用会抛出反汇编程序的黑客,就像跳到直线反汇编将被视为不同指令的中间;x86 机器代码是可变长度指令的字节流。)
但这并不能告诉你哪些指令实际执行;有些可能是在检查 CPUID 后才调用的函数,以确定是否支持必要的扩展。
(我不知道一个工具来按 ISA 扩展对它们进行分类,虽然我从来没有寻找过一个;通常开发人员希望确保他们没有在代码中使用 X2 指令,这些代码将在 X1-only CPU 上运行,使用构建时检查,或通过在模拟器或真实 CPU 上运行进行测试。)
本站系公益性非盈利分享网址,本文来自用户投稿,不代表边看边学立场,如若转载,请注明出处
评论列表(50条)