GNU/Linux >> Linux Esercitazione >  >> Linux

Disabilita le funzioni ottimizzate per AVX in glibc (LD_HWCAP_MASK, /etc/ld.so.nohwcap) per valgrind e record gdb

Non sembra esserci un metodo di runtime diretto per correggere il rilevamento delle funzionalità. Questo rilevamento avviene piuttosto presto nel linker dinamico (ld.so).

L'applicazione di patch binarie al linker sembra il metodo più semplice al momento. @osgx ha descritto un metodo in cui un salto viene sovrascritto. Un altro approccio è solo quello di falsificare il risultato cpuid. Normalmente cpuid(eax=0) restituisce la funzione più alta supportata in eax mentre gli ID produttore vengono restituiti nei registri ebx, ecx ed edx. Abbiamo questo frammento in 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);
  }

Il __cpuid line si traduce in queste istruzioni in /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  

Quindi, piuttosto che correggere i rami, potremmo anche cambiare il cpuid in un nop istruzione che risulterebbe nell'invocazione dell'ultimo else filiale (poiché i registri non conterranno "GenuineIntel"). Dall'inizio eax=0 , cpu_features->max_cpuid sarà anche 0 e if (cpu_features->max_cpuid >= 7) verrà anche ignorato.

Patch binaria cpuid(eax=0) di nop questo può essere fatto con questa utility (funziona sia per x86 che per 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)

Una variante Perl equivalente, -0777 assicura che il file venga letto immediatamente invece di separare i record con avanzamenti di riga:

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

Quella era la parte facile. Ora, non volevo sostituire il linker dinamico a livello di sistema, ma eseguire solo un programma particolare con questo linker. Certo, questo può essere fatto con ./ld-linux-x86-64-patched.so.2 ./a , ma le invocazioni ingenue di gdb non sono riuscite a impostare i punti di interruzione:

$ 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                                                                                                                                                                         

Una soluzione manuale è descritta in Come eseguire il debug del programma con l'interprete elf personalizzato? Funziona, ma sfortunatamente è un'azione manuale che utilizza add-symbol-file . Tuttavia, dovrebbe essere possibile automatizzarlo un po' utilizzando i GDB Catchpoints.

Un approccio alternativo che non prevede il collegamento binario è LD_PRELOAD ing una libreria che definisce le routine personalizzate per memcpy , memove , ecc. Questo avrà quindi la precedenza sulle routine glibc. L'elenco completo delle funzioni è disponibile in sysdeps/x86_64/multiarch/ifunc-impl-list.c . L'attuale HEAD ha più simboli rispetto alla versione glibc 2.25, in totale (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,__mempcpy_chk,mempcpy,strncmp,__wmemset_chk,


Sembra che ci sia una bella soluzione per questo implementata nelle recenti versioni di glibc:una funzione "sintonizzabili" che guida la selezione di funzioni di stringa ottimizzate. Puoi trovare una panoramica generale di questa funzione qui e il relativo codice all'interno di glibc in ifunc-impl-list.c.

Ecco come l'ho capito. Per prima cosa, ho preso l'indirizzo lamentato da gdb:

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

L'ho quindi cercato nella tabella delle librerie condivise:

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

Puoi vedere che questo indirizzo è all'interno di glibc. Ma quale funzione, nello specifico?

(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

Posso cercare in ifunc-impl-list.c per trovare il codice che controlla la selezione della versione 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))

Sembra AVX2_Usable è la funzione da disabilitare. Rieseguiamo gdb di conseguenza:

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

In questa iterazione si è lamentato di __memmove_avx_unaligned_erms , che sembrava essere abilitato da AVX_Usable - ma ho trovato un altro percorso in ifunc-memmove.h abilitato da AVX_Fast_Unaligned_Load . Tornando al tavolo da disegno:

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

In questo round finale ho scoperto un rdtscp istruzione nella libreria condivisa ASAN, quindi l'ho ricompilata senza il disinfettante dell'indirizzo e alla fine ha funzionato.

In sintesi:con un po' di lavoro è possibile disabilitare queste istruzioni dalla riga di comando e utilizzare la funzione di registrazione di gdb senza gravi hack.


Ho riscontrato anche questo problema di recente e ho finito per risolverlo utilizzando l'errore CPUID dinamico per interrompere l'esecuzione dell'istruzione CPUID e sovrascriverne il risultato, evitando di toccare glibc o il linker dinamico. Ciò richiede il supporto del processore per l'errore CPUID (Ivy Bridge+) e il supporto del kernel Linux (4.12+) per esporlo allo spazio utente tramite ARCH_GET_CPUID e ARCH_SET_CPUID sottofunzioni di arch_prctl() . Quando questa funzione è abilitata, un SIGSEGV il segnale verrà consegnato a ogni esecuzione di CPUID, consentendo a un gestore di segnale di emulare l'esecuzione dell'istruzione e sovrascrivere il risultato.

La soluzione completa è un po' complicata poiché devo anche interporre il linker dinamico, perché il rilevamento delle capacità hardware è stato spostato lì a partire da glibc 2.26+. Ho caricato la soluzione completa online su https://github.com/ddcc/libcpuidoverride .


Linux
  1. In che modo Linux gestisce più separatori di percorsi consecutivi (/home////nomeutente///file)?

  2. La differenza tra ~/.profile, ~/.bashrc, ~/.bash_profile, ~/.gnomerc, /etc/bash_bashrc, /etc/screenrc …?

  3. Controllo della versione per /etc Sotto *bsd?

  4. Linux – Disabilitare /net Ghosting per Autofs5?

  5. Come viene aggiornato /etc/motd?

Il modo giusto per modificare i file /etc/passwd e /etc/group in Linux

Come monitorare i file /etc/shadow e /etc/passwd per le modifiche con Auditd?

Comprendere i file /proc/mounts, /etc/mtab e /proc/partitions

Perché le directory /home, /usr, /var, ecc. hanno tutte lo stesso numero di inode (2)?

Differenza tra /etc/hosts e /etc/resolv.conf

Come configurare /etc/issues per mostrare l'indirizzo IP per eth0