GNU/Linux >> Linux の 問題 >  >> Linux

valgrind および gdb レコードの glibc (LD_HWCAP_MASK、/etc/ld.so.nohwcap) で AVX 最適化関数を無効にします。

機能検出にパッチを適用するための簡単なランタイム メソッドはないようです。この検出は、動的リンカー (ld.so) のかなり早い段階で行われます。

現時点では、リンカーにバイナリ パッチを適用するのが最も簡単な方法のようです。 @osgx は、ジャンプが上書きされる 1 つの方法について説明しました。別のアプローチは、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」が含まれないため)。最初から eax=0cpu_features->max_cpuid も 0 になり、if (cpu_features->max_cpuid >= 7) もバイパスされます。

バイナリパッチ cpuid(eax=0) nop で これは、このユーティリティで実行できます (x86 と x86-64 の両方で機能します):

#!/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'(\x31\xc0.{0,32}?)\x0f\xa2', b'\\1\x66\x90', d)
assert d != o
open(outfile, 'wb').write(o)

同等の Perl バリアント -0777 改行でレコードを区切るのではなく、ファイルが一度に読み取られるようにします:

perl -0777 -pe 's/\x31\xc0.{0,32}?\K\x0f\xa2/\x66\x90/' < /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

それは簡単な部分でした。ここで、システム全体の動的リンカーを置き換えたくはありませんでしたが、このリンカーを使用して特定のプログラムを 1 つだけ実行しました。確かに、それは ./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 キャッチポイントを使用して少し自動化できるはずです。

バイナリ リンクを行わない別の方法は LD_PRELOAD です。 memcpy のカスタム ルーチンを定義するライブラリを作成する 、 memove など。これは、glibc ルーチンよりも優先されます。関数の完全なリストは sysdeps/x86_64/multiarch/ifunc-impl-list.c にあります .現在の HEAD には、glibc 2.25 リリースと比較して、合計でより多くのシンボルがあります (grep -Po 'IFUNC_IMPL \(i, name, \K[^,]+' 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,__memcpy_chk,mempcpy,strncmp,__wmemset_chk,


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 について不平を言いました 、 AVX_Usable によって有効になったようです - しかし、 AVX_Fast_Unaligned_Load によって有効化された ifunc-memmove.h で別のパスを見つけました .振り出しに戻る:

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

この最終ラウンドで rdtscp を発見しました 命令を ASAN 共有ライブラリに追加したため、アドレス サニタイザーを使用せずに再コンパイルしたところ、ようやく機能しました。

要約すると、いくつかの作業を行うことで、コマンド ラインからこれらの命令を無効にして、重大なハックなしで gdb の記録機能を使用することが可能です。


私も最近この問題に遭遇し、動的CPUIDフォルトを使用してCPUID命令の実行を中断し、その結果をオーバーライドして解決しました。これにより、glibcまたは動的リンカに触れることを回避できます。これには、CPUID フォールト (Ivy Bridge+) に対するプロセッサ サポートと、ARCH_GET_CPUID を介してユーザー空間に公開するための Linux カーネル サポート (4.12+) が必要です。 と ARCH_SET_CPUID arch_prctl() のサブ関数 .この機能を有効にすると、SIGSEGV シグナルは CPUID の実行ごとに配信されるため、シグナル ハンドラは命令の実行をエミュレートし、結果をオーバーライドできます。

ハードウェア機能の検出が glibc 2.26+ から動的リンカに移動されたため、動的リンカも挿入する必要があるため、完全なソリューションは少し複雑です。完全なソリューションを https://github.com/ddcc/libcpuidoverride にオンラインでアップロードしました。


Linux
  1. Linuxは複数の連続したパスセパレーター(/ home //// username /// file)をどのように処理しますか?

  2. 〜/ .profile、〜/ .bashrc、〜/ .bash_profile、〜/ .gnomerc、/ etc / bash_bashrc、/ etc / screenrcの違い…?

  3. *bsdの下の/etcのバージョン管理?

  1. Linux –Autofs5の/net Ghostingを無効にしますか?

  2. / etc / motdはどのように更新されますか?

  3. /etc/shadow および /etc/passwd ファイルの変更を Auditd で監視するにはどうすればよいですか?

  1. grpck コマンド – /etc/group および /etc/gshadow ファイル内の破損したエントリまたは重複したエントリを削除します。

  2. /etc/passwd はグループ内のユーザーを表示しますが、/etc/group は表示しません

  3. Linux の /etc/init.d と /etc/rcX.d ディレクトリの間の接続は何ですか?