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

2024-11-04 08:43:00
admin
原创
35
摘要:问题描述:带有 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. ,
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用