GNU/Linux >> Belajar Linux >  >> Linux

Nonaktifkan fungsi yang dioptimalkan AVX di glibc (LD_HWCAP_MASK, /etc/ld.so.nohwcap) untuk catatan valgrind &gdb

Tampaknya tidak ada metode runtime langsung untuk menambal deteksi fitur. Deteksi ini terjadi lebih awal di linker dinamis (ld.so).

Biner menambal linker tampaknya merupakan metode termudah saat ini. @osgx menjelaskan satu metode di mana lompatan ditimpa. Pendekatan lain adalah hanya memalsukan hasil cpuid. Biasanya cpuid(eax=0) mengembalikan fungsi tertinggi yang didukung di eax sedangkan ID pabrikan dikembalikan dalam register ebx, ecx dan edx. Kami memiliki cuplikan ini di 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 baris menerjemahkan instruksi ini di /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  

Jadi daripada menambal cabang, kita juga bisa mengubah cpuid menjadi nop instruksi yang akan menghasilkan pemanggilan else terakhir cabang (karena register tidak akan berisi "GenuineIntel"). Sejak awal eax=0 , cpu_features->max_cpuid juga akan menjadi 0 dan if (cpu_features->max_cpuid >= 7) juga akan dilewati.

Penambalan biner cpuid(eax=0) oleh nop ini dapat dilakukan dengan utilitas ini (berfungsi untuk x86 dan 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)

Varian Perl yang setara, -0777 memastikan bahwa file dibaca sekaligus alih-alih memisahkan catatan pada umpan baris:

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

Itu bagian yang mudah. Sekarang, saya tidak ingin mengganti linker dinamis di seluruh sistem, tetapi hanya menjalankan satu program tertentu dengan linker ini. Tentu, itu bisa dilakukan dengan ./ld-linux-x86-64-patched.so.2 ./a , tetapi pemanggilan gdb yang naif gagal menyetel breakpoint:

$ 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                                                                                                                                                                         

Solusi manual dijelaskan dalam Bagaimana cara men-debug program dengan juru bahasa elf khusus? Ini berfungsi, tetapi sayangnya tindakan manual menggunakan add-symbol-file . Seharusnya mungkin untuk mengotomatiskannya sedikit menggunakan GDB Catchpoints.

Pendekatan alternatif yang tidak menghubungkan biner adalah LD_PRELOAD membuat pustaka yang mendefinisikan rutin khusus untuk memcpy , memove , dll. Ini kemudian akan didahulukan dari rutinitas glibc. Daftar lengkap fungsi tersedia di sysdeps/x86_64/multiarch/ifunc-impl-list.c . HEAD saat ini memiliki lebih banyak simbol dibandingkan dengan rilis glibc 2.25, secara total (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,strchr,strcmp,strcpy,strcspn,strncasecmp,strncasecmp_l,strcat,strncpy, strpbrk,strspn,strstr,wcschr,wcsrchr,wcscpy,wcslen,wcsnlen,wmemchr,wmemcmp,wmemset,__memcpy_chk,memcpy,__mempcpy_chk,mempcpy,strncmp,__wmemset_chk,


Sepertinya ada solusi yang bagus untuk penerapan ini di glibc versi terbaru:fitur "merdu" yang memandu pemilihan fungsi string yang dioptimalkan. Anda dapat menemukan gambaran umum fitur ini di sini dan kode yang relevan di dalam glibc di ifunc-impl-list.c.

Begini cara saya mengetahuinya. Pertama, saya mengambil alamat yang dikeluhkan oleh gdb:

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

Saya kemudian mencarinya di tabel perpustakaan bersama:

(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
...

Anda dapat melihat bahwa alamat ini ada di dalam glibc. Tapi fungsi apa, khususnya?

(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

Saya dapat melihat ifunc-impl-list.c untuk menemukan kode yang mengontrol pemilihan versi 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))

Sepertinya AVX2_Usable adalah fitur untuk menonaktifkan. Mari jalankan kembali gdb sesuai:

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

Pada iterasi ini ia mengeluh tentang __memmove_avx_unaligned_erms , yang tampaknya diaktifkan oleh AVX_Usable - tetapi saya menemukan jalur lain di ifunc-memmove.h yang diaktifkan oleh AVX_Fast_Unaligned_Load . Kembali ke papan gambar:

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

Pada babak terakhir ini saya menemukan rdtscp instruksi di perpustakaan bersama ASAN, jadi saya mengkompilasi ulang tanpa pembersih alamat dan akhirnya berhasil.

Singkatnya:dengan beberapa pekerjaan, Anda dapat menonaktifkan petunjuk ini dari baris perintah dan menggunakan fitur catatan gdb tanpa peretasan parah.


Saya mengalami masalah ini baru-baru ini juga, dan akhirnya menyelesaikannya menggunakan kesalahan CPUID dinamis untuk menghentikan eksekusi instruksi CPUID dan mengesampingkan hasilnya, yang menghindari menyentuh glibc atau penghubung dinamis. Ini memerlukan dukungan prosesor untuk kesalahan CPUID (Ivy Bridge+) serta dukungan kernel Linux (4.12+) untuk membukanya ke ruang pengguna melalui ARCH_GET_CPUID dan ARCH_SET_CPUID subfungsi dari arch_prctl() . Saat fitur ini diaktifkan, SIGSEGV sinyal akan dikirimkan pada setiap eksekusi CPUID, memungkinkan penangan sinyal dapat meniru eksekusi instruksi dan mengganti hasilnya.

Solusi lengkapnya sedikit terlibat karena saya juga perlu memasang linker dinamis, karena deteksi kemampuan perangkat keras dipindahkan ke sana dimulai dengan glibc 2.26+. Saya telah mengunggah solusi lengkap secara online di https://github.com/ddcc/libcpuidoverride .


Linux
  1. Bagaimana Linux Menangani Beberapa Pemisah Jalur Berturut-turut (/home////username///file)?

  2. CentOS / RHEL :Cara Memulihkan dari file /etc/passwd yang dihapus

  3. /etc/passwd menampilkan pengguna dalam grup, tetapi /etc/group tidak

  1. Template untuk skrip startup?

  2. Buat kata sandi untuk /etc/shadow secara manual

  3. Mengapa direktori /home, /usr, /var, dll. Semuanya memiliki nomor inode yang sama (2)?

  1. Perbedaan Antara ~/.profile, ~/.bashrc, ~/.bash_profile, ~/.gnomerc, /etc/bash_bashrc, /etc/screenrc …?

  2. Perbedaan antara /etc/hosts dan /etc/resolv.conf

  3. Cara mengatur /etc/issues untuk menampilkan alamat IP untuk eth0