GNU/Linux >> Tutoriels Linux >  >> Linux

Désactiver les fonctions optimisées AVX dans la glibc (LD_HWCAP_MASK, /etc/ld.so.nohwcap) pour l'enregistrement valgrind &gdb

Il ne semble pas y avoir de méthode d'exécution simple pour corriger la détection des fonctionnalités. Cette détection se produit assez tôt dans l'éditeur de liens dynamique (ld.so).

Le patch binaire de l'éditeur de liens semble la méthode la plus simple pour le moment. @osgx a décrit une méthode où un saut est écrasé. Une autre approche consiste simplement à simuler le résultat cpuid. Normalement cpuid(eax=0) renvoie la fonction la plus élevée prise en charge dans eax tandis que les identifiants des fabricants sont retournés dans les registres ebx, ecx et edx. Nous avons cet extrait dans la 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);
  }

Le __cpuid la ligne se traduit par ces instructions dans /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  

Donc plutôt que de patcher les branches, on pourrait aussi bien changer le cpuid dans un nop instruction qui entraînerait l'invocation du dernier else branche (car les registres ne contiendront pas "GenuineIntel"). Depuis initialement eax=0 , cpu_features->max_cpuid sera également 0 et le if (cpu_features->max_cpuid >= 7) sera également contourné.

Correctif binaire cpuid(eax=0) par nop cela peut être fait avec cet utilitaire (fonctionne à la fois pour x86 et 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)

Une variante Perl équivalente, -0777 garantit que le fichier est lu en une seule fois au lieu de séparer les enregistrements au niveau des sauts de ligne :

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

C'était la partie facile. Maintenant, je ne voulais pas remplacer l'éditeur de liens dynamique à l'échelle du système, mais exécuter un seul programme particulier avec cet éditeur de liens. Bien sûr, cela peut être fait avec ./ld-linux-x86-64-patched.so.2 ./a , mais les invocations naïves de gdb n'ont pas réussi à définir des points d'arrêt :

$ 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                                                                                                                                                                         

Une solution de contournement manuelle est décrite dans Comment déboguer un programme avec un interpréteur elf personnalisé ? Cela fonctionne, mais c'est malheureusement une action manuelle utilisant add-symbol-file . Il devrait cependant être possible de l'automatiser un peu en utilisant GDB Catchpoints.

Une approche alternative qui ne fait pas de lien binaire est LD_PRELOAD une bibliothèque qui définit des routines personnalisées pour memcpy , memove , etc. Cela aura alors priorité sur les routines de la glibc. La liste complète des fonctions est disponible en sysdeps/x86_64/multiarch/ifunc-impl-list.c . HEAD actuel a plus de symboles par rapport à la version glibc 2.25, au total (grep -Po 'IFUNC_IMPL \(i, name, \K[^,]+' sysdeps/x86_64/multiarch/ifunc-impl-list.c ):

, strpbrk,strspn,strstr,wcschr,wcsrchr,wcscpy,wcslen,wcsnlen,wmemchr,wmemcmp,wmemset,__memcpy_chk,memcpy,__mempcpy_chk,mempcpy,strncmp,__wmemset_chk,


Il semble qu'il existe une bonne solution de contournement pour cela implémentée dans les versions récentes de la glibc :une fonctionnalité "tunables" qui guide la sélection des fonctions de chaîne optimisées. Vous pouvez trouver un aperçu général de cette fonctionnalité ici et le code correspondant à l'intérieur de la glibc dans ifunc-impl-list.c.

Voici comment je l'ai compris. Tout d'abord, j'ai pris l'adresse faisant l'objet de la plainte de gdb :

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

Je l'ai ensuite cherché dans le tableau des bibliothèques partagées :

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

Vous pouvez voir que cette adresse se trouve dans glibc. Mais quelle fonction, précisément ?

(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

Je peux regarder dans ifunc-impl-list.c pour trouver le code qui contrôle la sélection de la version 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))

Il ressemble à AVX2_Usable est la fonctionnalité à désactiver. Exécutons à nouveau gdb en conséquence :

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

Sur cette itération, il s'est plaint de __memmove_avx_unaligned_erms , qui semblait être activé par AVX_Usable - mais j'ai trouvé un autre chemin dans ifunc-memmove.h activé par AVX_Fast_Unaligned_Load . Retour à la planche à dessin :

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

Lors de ce tour final, j'ai découvert un rdtscp instruction dans la bibliothèque partagée ASAN, j'ai donc recompilé sans le désinfectant d'adresse et enfin, cela a fonctionné.

En résumé :avec un peu de travail, il est possible de désactiver ces instructions à partir de la ligne de commande et d'utiliser la fonction d'enregistrement de gdb sans piratage sévère.


J'ai également rencontré ce problème récemment et j'ai fini par le résoudre en utilisant une erreur CPUID dynamique pour interrompre l'exécution de l'instruction CPUID et remplacer son résultat, ce qui évite de toucher glibc ou l'éditeur de liens dynamique. Cela nécessite la prise en charge du processeur pour les défauts CPUID (Ivy Bridge+) ainsi que la prise en charge du noyau Linux (4.12+) pour l'exposer à l'espace utilisateur via le ARCH_GET_CPUID et ARCH_SET_CPUID sous-fonctions de arch_prctl() . Lorsque cette fonctionnalité est activée, un SIGSEGV le signal sera délivré à chaque exécution de CPUID, permettant à un gestionnaire de signal d'émuler l'exécution de l'instruction et de remplacer le résultat.

La solution complète est un peu impliquée car je dois également interposer l'éditeur de liens dynamique, car la détection des capacités matérielles y a été déplacée à partir de la glibc 2.26+. J'ai téléchargé la solution complète en ligne sur https://github.com/ddcc/libcpuidoverride .


Linux
  1. La différence entre ~/.profile, ~/.bashrc, ~/.bash_profile, ~/.gnomerc, /etc/bash_bashrc, /etc/screenrc … ?

  2. /etc/passwd affiche l'utilisateur dans un groupe, mais /etc/group ne le fait pas

  3. Générer manuellement un mot de passe pour /etc/shadow

  4. Différence entre /etc/hosts et /etc/resolv.conf

  5. Comment configurer /etc/issues pour afficher l'adresse IP pour eth0

La bonne façon de modifier les fichiers /etc/passwd et /etc/group sous Linux

Comment Linux gère-t-il plusieurs séparateurs de chemins consécutifs (/home////nom d'utilisateur///fichier) ?

Linux - Désactiver /net Ghosting pour Autofs5 ?

Comment /etc/motd est-il mis à jour ?

CentOS / RHEL :Comment récupérer à partir d'un fichier /etc/passwd supprimé

Comprendre les fichiers /proc/mounts, /etc/mtab et /proc/partitions