为 valgrind 和 gdb 记录禁用 glibc 中的 AVX 优化函数(LD_HWCAP_MASK、/etc/ld.so.nohwcap)

2024-11-04 08:43:00
admin
原创
229
摘要:问题描述:带有 glibc 的现代 x86_64 linux 将检测 CPU 是否支持 AVX 扩展,并将许多字符串函数从通用实现切换到AVX优化版本(借助 ifunc 调度程序:1、2)。此功能可以提高性能,但它会阻止一些工具(如 valgrind(旧版 libVEX,valgrind-3.8之前)和 gd...

问题描述:

带有 glibc 的现代 x86_64 linux 将检测 CPU 是否支持 AVX 扩展,并将许多字符串函数从通用实现切换到AVX优化版本(借助 ifunc 调度程序:1、2)。

此功能可以提高性能,但它会阻止一些工具(如 valgrind(旧版 libVEX,valgrind-3.8之前)和 gdb 的“ target record”(反向执行))正常工作(Ubuntu“Z”17.04 beta、gdb 7.12 .50.20170207-0ubuntu2、gcc 6.3.0-8ubuntu1 20170221、Ubuntu GLIBC 2.24-7ubuntu2):

$ cat a.c
#include <string.h>
#define N 1000
int main(){
        char src[N], dst[N];
        memcpy(dst, src, N);
        return 0;
}
$ gcc a.c -o a -fno-builtin
$ gdb -q ./a
Reading symbols from ./a...(no debugging symbols found)...done.
(gdb) start
Temporary breakpoint 1 at 0x724
Starting program: /home/user/src/a

Temporary breakpoint 1, 0x0000555555554724 in main ()
(gdb) record
(gdb) c
Continuing.
Process record does not support instruction 0xc5 at address 0x7ffff7b60d31.
Process record: failed to record execution log.

Program stopped.
__memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:416
416             VMOVU   (%rsi), %VEC(4)
(gdb) x/i $pc
=> 0x7ffff7b60d31 <__memmove_avx_unaligned_erms+529>:   vmovdqu (%rsi),%ymm4

gdb 在执行“目标记录”时出现错误消息“ Process record does not support instruction 0xc5”,因为记录/重放引擎不支持 AVX 指令(有时问题是在_dl_runtime_resolve_avx函数上检测到的):https://sourceware.org/ml/gdb/2016-08/msg00028.html “进程记录不支持某些 AVX 指令”,https://bugs.launchpad.net/ubuntu/+source/gdb/+bug/1573786, https: //bugs.debian.org/cgi-bin/bugreport.cgi ? bug=836802 ,https: //bugzilla.redhat.com/show_bug.cgi ?id=1136403

https://sourceware.org/ml/gdb/2016-08/msg00028.html中提出的解决方案“您可以重新编译 libc(因此 ld.so),或者在运行时破解 __init_cpu_features 并因此破解 __cpu_features(例如,参见 strcmp)。”或 set LD_BIND_NOW=1,但重新编译的 glibc 仍然具有 AVX,而 ld bind-now 没有帮助。

我听说 glibc 中有/etc/ld.so.nohwcapLD_HWCAP_MASK配置。它们可以用来禁用 glibc 中针对 AVX 优化的字符串函数的 ifunc 调度吗?

glibc(rtld?)如何使用cpuid、使用/proc/cpuinfo(可能不是)或 HWCAP aux(LD_SHOW_AUXV=1 /bin/echo |grep HWCAP命令给出AT_HWCAP: bfebfbff)检测 AVX?


解决方案 1:

看起来在 glibc 的最新版本中实现了一个很好的解决方法:一个“可调”功能,用于指导选择优化的字符串函数。您可以在此处找到此功能的一般概述,以及 glibc 中的ifunc-impl-list.c中的相关代码。

以下是我解决问题的方法。首先,我获取了 gdb 抱怨的地址:

Process record does not support instruction 0xc5 at address 0x7ffff75c65d4.

然后我在共享库表中查找:

(gdb) info shared
From                To                  Syms Read   Shared Object Library
0x00007ffff7fd3090  0x00007ffff7ff3130  Yes         /lib64/ld-linux-x86-64.so.2
0x00007ffff76366b0  0x00007ffff766b52e  Yes         /usr/lib/x86_64-linux-gnu/libubsan.so.1
0x00007ffff746a320  0x00007ffff75d9cab  Yes         /lib/x86_64-linux-gnu/libc.so.6
...

可以看到这个地址在 glibc 中。但是具体是哪个函数呢?

(gdb) disassemble 0x7ffff75c65d4
Dump of assembler code for function __strcmp_avx2:
   0x00007ffff75c65d0 <+0>:     mov    %edi,%eax
   0x00007ffff75c65d2 <+2>:     xor    %edx,%edx
=> 0x00007ffff75c65d4 <+4>:     vpxor  %ymm7,%ymm7,%ymm7

我可以在ifunc-impl-list.c中找到控制选择 avx2 版本的代码:

  IFUNC_IMPL (i, name, strcmp,
          IFUNC_IMPL_ADD (array, i, strcmp,
                  HAS_ARCH_FEATURE (AVX2_Usable),
                  __strcmp_avx2)
          IFUNC_IMPL_ADD (array, i, strcmp, HAS_CPU_FEATURE (SSE4_2),
                  __strcmp_sse42)
          IFUNC_IMPL_ADD (array, i, strcmp, HAS_CPU_FEATURE (SSSE3),
                  __strcmp_ssse3)
          IFUNC_IMPL_ADD (array, i, strcmp, 1, __strcmp_sse2_unaligned)
          IFUNC_IMPL_ADD (array, i, strcmp, 1, __strcmp_sse2))

看起来这AVX2_Usable是需要禁用的功能。让我们重新运行 gdb:

GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2_Usable gdb...

在这个迭代中,它抱怨了__memmove_avx_unaligned_erms,这似乎是由启用的- 但我在ifunc-memmove.h中找到了另一条由启用的AVX_Usable路径。回到绘图板:AVX_Fast_Unaligned_Load

GLIBC_TUNABLES=glibc.cpu.hwcaps=-AVX2_Usable,-AVX_Fast_Unaligned_Load gdb ...

在最后一轮中,我发现了rdtscpASAN 共享库中的一条指令,因此我重新进行了编译,没有使用地址清理器,最后,它成功了。

总之:通过一些工作,可以从命令行禁用这些指令并使用 gdb 的记录功能,而无需严重的黑客攻击。

解决方案 2:

似乎没有一种直接的运行时方法来修补功能检测。此检测在动态链接器 (ld.so) 中发生得相当早。

目前看来,二进制修补链接器是最简单的方法。@osgx描述了一种覆盖跳转的方法。另一种方法是伪造 cpuid 结果。通常cpuid(eax=0)返回支持的最高函数,eax而制造商 ID 则返回到寄存器 ebx、ecx 和 edx 中。我们在 glibc 2.25 中有这个代码片段sysdeps/x86/cpu-features.c

__cpuid (0, cpu_features->max_cpuid, ebx, ecx, edx);

/* This spells out "GenuineIntel".  */
if (ebx == 0x756e6547 && ecx == 0x6c65746e && edx == 0x49656e69)
  {
      /* feature detection for various Intel CPUs */
  }
/* another case for AMD */
else
  {
    kind = arch_kind_other;
    get_common_indeces (cpu_features, NULL, NULL, NULL, NULL);
  }

__cpuid行翻译为/lib/ld-linux-x86-64.so.2/lib/ld-2.25.so)中的以下指令:

172a8:       31 c0                   xor    eax,eax
172aa:       c7 44 24 38 00 00 00    mov    DWORD PTR [rsp+0x38],0x0
172b1:       00 
172b2:       c7 44 24 3c 00 00 00    mov    DWORD PTR [rsp+0x3c],0x0
172b9:       00 
172ba:       0f a2                   cpuid  

因此,我们不必修补分支,而是可以将 更改cpuid为一条nop将导致调用最后一个else分支的指令(因为寄存器将不包含“GenuineIntel”)。由于 最初也将为 0,因此eax=0也将被绕过。cpu_features->max_cpuid`if (cpu_features->max_cpuid >= 7)`

可以使用此实用程序进行二进制修补(适用于 x86 和 x86-64 cpuid(eax=0)):nop

#!/usr/bin/env python
import re
import sys

infile, outfile = sys.argv[1:]
d = open(infile, 'rb').read()
# Match CPUID(eax=0), "xor eax,eax" followed closely by "cpuid"
o = re.sub(b'(x31xc0.{0,32}?)x0fxa2', b'\\1x66x90', d)
assert d != o
open(outfile, 'wb').write(o)

等效的 Perl 变体-0777可确保立即读取文件,而不是以换行符分隔记录:

perl -0777 -pe 's/x31xc0.{0,32}?Kx0fxa2/x66x90/' < /lib/ld-linux-x86-64.so.2 > ld-linux-x86-64-patched.so.2
# Verify result, should display "Success"
cmp -s /lib/ld-linux-x86-64.so.2 ld-linux-x86-64-patched.so.2 && echo 'Not patched' || echo Success

这是最简单的部分。现在,我不想替换系统范围的动态链接器,而是只用这个链接器执行一个特定的程序。当然,这可以用 来完成./ld-linux-x86-64-patched.so.2 ./a,但简单的 gdb 调用无法设置断点:

$ gdb -q -ex "set exec-wrapper ./ld-linux-x86-64-patched.so.2" -ex start ./a
Reading symbols from ./a...done.
Temporary breakpoint 1 at 0x400502: file a.c, line 5.
Starting program: /tmp/a 
During startup program exited normally.
(gdb) quit
$ gdb -q -ex start --args ./ld-linux-x86-64-patched.so.2 ./a
Reading symbols from ./ld-linux-x86-64-patched.so.2...(no debugging symbols found)...done.
Function "main" not defined.
Temporary breakpoint 1 (main) pending.
Starting program: /tmp/ld-linux-x86-64-patched.so.2 ./a
[Inferior 1 (process 27418) exited normally]
(gdb) quit                                                                                                                                                                         

如何使用自定义 elf 解释器调试程序?中描述了一种手动解决方法。它有效,但不幸的是,使用 需要手动操作add-symbol-file。不过,应该可以使用GDB Catchpoints稍微自动化一下。

另一种不使用二进制链接的方法是使用为、等LD_PRELOAD定义自定义例程的库。这将优先于 glibc 例程。完整的函数列表可在 中找到。与 glibc 2.25 版本相比,当前 HEAD 总共包含更多符号():memcpy`memovesysdeps/x86_64/multiarch/ifunc-impl-list.cgrep -Po 'IFUNC_IMPL (i, name, K1+' sysdeps/x86_64/multiarch/ifunc-impl-list.c`

memchr、memcmp、__memmove_chk、memmove、memrchr、__memset_chk、memset、rawmemchr、strlen、strnlen、stpncpy、stpcpy、strcasecmp、strcasecmp_l、strcat、strchr、strchrnul、strrchr、strcmp、strcpy、strcspn、strncasecmp、strncasecmp_l、strncat、strncpy、strpbrk、strspn、strstr、wcschr、wcsrchr、wcscpy、wcslen、wcsnlen、wmemchr、wmemcmp、wmemset、__memcpy_chk、memcpy、__mempcpy_chk、mempcpy、strncmp、__wmemset_chk,

解决方案 3:

我最近也遇到了这个问题,最后使用动态 CPUID 故障来中断 CPUID 指令的执行并覆盖其结果,从而避免触及 glibc 或动态链接器。这需要处理器支持 CPUID 故障(Ivy Bridge+)以及 Linux 内核支持(4.12+),以便通过ARCH_GET_CPUID和 的ARCH_SET_CPUID子函数将其暴露给用户空间arch_prctl()。启用此功能后,SIGSEGV每次执行 CPUID 时都会发出一个信号,允许信号处理程序模拟指令的执行并覆盖结果。

完整的解决方案有点复杂,因为我还需要插入动态链接器,因为从 glibc 2.26+ 开始,硬件能力检测就被移到了那里。我已将完整的解决方案上传到网上https://github.com/ddcc/libcpuidoverride

解决方案 4:

这不是最好或最完整的解决方案,只是一个最小的位编辑组合,以允许 valgrind 和 gdb 记录我的任务。

Lekensteyn 问道:

如何在不重新编译 glibc 的情况下屏蔽 AVX/SSE

我对未修改的 glibc 进行了完全重建,这在 debian 和 ubuntu 中相当容易:只需sudo apt-get source glibcsudo apt-get build-dep glibccd glibc-*/; dpkg-buildpackage -us -uc(手册即可获得不带剥离调试信息的 ld.so。

然后,我在 使用的函数中对输出的 ld.so 文件进行了二进制(位)修补__get_cpu_features。目标函数是从get_common_indeces源文件sysdeps/x86/cpu-features.c编译而来的,名称为get_common_indeces.constprop.1(在二进制代码中,它就在 之后__get_cpu_features)。它有几个 cpuid,第一个是cpuid eax=1“处理器信息和功能位”;后来检查“jle 0x6”并跳转到代码“cpuid eax=7 ecx=0扩展功能”以获取 AVX2 状态。有一段代码被编译成这个逻辑:

get_common_indeces (struct cpu_features *cpu_features,
            unsigned int *family, unsigned int *model,
            unsigned int *extended_model, unsigned int *stepping)
{ ...
  if (cpu_features->max_cpuid >= 7)
    __cpuid_count (7, 0,
           cpu_features->cpuid[COMMON_CPUID_INDEX_7].eax,
           cpu_features->cpuid[COMMON_CPUID_INDEX_7].ebx,
           cpu_features->cpuid[COMMON_CPUID_INDEX_7].ecx,
           cpu_features->cpuid[COMMON_CPUID_INDEX_7].edx);

在同一行中填写cpu_features->max_cpuidinit_cpu_features相同的文件。通过将后面的内容替换为(字节 0x7e 到 0x7f) __cpuid (0, cpu_features->max_cpuid, ebx, ecx, edx);,可以更轻松地禁用该语句。(实际上,此二进制补丁已手动重新应用于实际系统的功能- 首先将 jle改为 jg。)if`jlecmp 0x6jg__get_cpu_featuresld-linux.so.2`mov 7 eax; xor ecx,ecx; cpuid

重新编译的包和修改后的 ld.so 未安装到系统中;我使用了ld.so ./my_program(或mv ld.so /some/short/path.sopatchelf --set-interpreter ./my_program)的命令行语法。

其他可能的解决方案:

  • 尝试使用较新的 valgrind 和 gdb 记录工具

  • 尝试使用较旧的 glibc

  • 如果未完成,则在 gdb 记录中实现缺失指令模拟

  • if (cpu_features->max_cpuid >= 7)在 glibc 中对源代码进行修补并重新编译

  • 在 glibc 中对支持 avx2 的字符串函数进行源代码修补并重新编译

解决方案 5:

我听说 glibc 中有/etc/ld.so.nohwcapLD_HWCAP_MASK配置。它们可以用来禁用 glibc 中针对 AVX 优化的字符串函数的 ifunc 调度吗?

是:设置LD_HWCAP_MASK=0将使 GLIBC 假装没有任何 CPU 功能可用。代码。

将掩码设置为 0 可能会触发错误,您可能需要找出控制 AVX 的精确位,然后仅掩码该位。


  1. ,
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1124  
  IPD(Integrated Product Development,集成产品开发)流程是一种广泛应用于高科技和制造业的产品开发方法论。它通过跨职能团队的紧密协作,将产品开发周期缩短,同时提高产品质量和市场成功率。在IPD流程中,CDCP(Concept Decision Checkpoint,概念决策检查点)是一个关...
IPD培训课程   79  
  研发IPD(集成产品开发)流程作为一种系统化的产品开发方法,已经在许多行业中得到广泛应用。它不仅能够提升产品开发的效率和质量,还能够通过优化流程和资源分配,显著提高客户满意度。客户满意度是企业长期成功的关键因素之一,而IPD流程通过其独特的结构和机制,能够确保产品从概念到市场交付的每个环节都围绕客户需求展开。本文将深入...
IPD流程   70  
  IPD(Integrated Product Development,集成产品开发)流程是一种以跨职能团队协作为核心的产品开发方法,旨在通过优化资源分配、提高沟通效率以及减少返工,从而缩短项目周期并提升产品质量。随着企业对产品上市速度的要求越来越高,IPD流程的应用价值愈发凸显。通过整合产品开发过程中的各个环节,IPD...
IPD项目管理咨询   82  
  跨部门沟通是企业运营中不可或缺的一环,尤其在复杂的产品开发过程中,不同部门之间的协作效率直接影响项目的成败。集成产品开发(IPD)作为一种系统化的项目管理方法,旨在通过优化流程和增强团队协作来提升产品开发的效率和质量。然而,跨部门沟通的复杂性往往成为IPD实施中的一大挑战。部门之间的目标差异、信息不对称以及沟通渠道不畅...
IPD是什么意思   74  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用