GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Deaktivieren Sie AVX-optimierte Funktionen in glibc (LD_HWCAP_MASK, /etc/ld.so.nohwcap) für valgrind &gdb record

Es scheint keine einfache Laufzeitmethode zum Patchen der Feature-Erkennung zu geben. Diese Erkennung erfolgt ziemlich früh im dynamischen Linker (ld.so).

Das binäre Patchen des Linkers scheint im Moment die einfachste Methode zu sein. @osgx hat eine Methode beschrieben, bei der ein Sprung überschrieben wird. Ein anderer Ansatz besteht darin, das CPUID-Ergebnis einfach zu fälschen. Normalerweise cpuid(eax=0) gibt die höchste unterstützte Funktion in eax zurück während die Hersteller-IDs in den Registern ebx, ecx und edx zurückgegeben werden. Wir haben dieses Snippet 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);
  }

Die __cpuid Zeile übersetzt in diese Anweisungen 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  

Anstatt Zweige zu patchen, könnten wir also genauso gut den cpuid ändern in nop Anweisung, die zum Aufruf des letzten else führen würde (da die Register kein "GenuineIntel" enthalten). Seit Anfang eax=0 , cpu_features->max_cpuid wird auch 0 sein und der if (cpu_features->max_cpuid >= 7) wird ebenfalls umgangen.

Binäres Patchen cpuid(eax=0) durch nop Dies kann mit diesem Dienstprogramm erfolgen (funktioniert sowohl für x86 als auch für 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)

Eine äquivalente Perl-Variante, -0777 stellt sicher, dass die Datei sofort gelesen wird, anstatt Datensätze bei Zeilenvorschüben zu trennen:

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

Das war der einfache Teil. Nun wollte ich nicht den systemweiten dynamischen Linker ersetzen, sondern nur ein bestimmtes Programm mit diesem Linker ausführen. Klar, das geht mit ./ld-linux-x86-64-patched.so.2 ./a , aber die naiven gdb-Aufrufe konnten keine Haltepunkte setzen:

$ 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                                                                                                                                                                         

Eine manuelle Problemumgehung wird in How to debug program with custom elf interpreter beschrieben? Es funktioniert, aber es ist leider eine manuelle Aktion mit add-symbol-file . Es sollte jedoch möglich sein, es mit GDB Catchpoints ein wenig zu automatisieren.

Ein alternativer Ansatz, der keine binäre Verknüpfung verwendet, ist LD_PRELOAD Erstellen einer Bibliothek, die benutzerdefinierte Routinen für memcpy definiert , memove usw. Dies hat dann Vorrang vor den glibc-Routinen. Die vollständige Liste der Funktionen ist in sysdeps/x86_64/multiarch/ifunc-impl-list.c verfügbar . Das aktuelle HEAD hat insgesamt mehr Symbole im Vergleich zur Version 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,strcpy,strcpy,strcpy,strcpy strpbrk,strspn,strstr,wcschr,wcsrchr,wcscpy,wcslen,wcsnlen,wmemchr,wmemcmp,wmemset,__memcpy_chk,memcpy,__mempcpy_chk,mempcpy,strncmp,__wmemset_chk,


Es sieht so aus, als gäbe es eine nette Problemumgehung dafür, die in neueren Versionen von glibc implementiert ist:eine "tunables"-Funktion, die die Auswahl optimierter String-Funktionen steuert. Einen allgemeinen Überblick über diese Funktion finden Sie hier und den relevanten Code innerhalb von glibc in ifunc-impl-list.c.

Hier ist, wie ich es herausgefunden habe. Zuerst nahm ich die Adresse, über die sich gdb beschwerte:

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

Ich habe es dann in der Tabelle der Shared Libraries nachgeschlagen:

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

Sie können sehen, dass diese Adresse innerhalb von glibc liegt. Aber welche Funktion konkret?

(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

Ich kann in ifunc-impl-list.c nach dem Code suchen, der die Auswahl der avx2-Version steuert:

  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))

Es sieht aus wie AVX2_Usable ist die zu deaktivierende Funktion. Lassen Sie uns gdb entsprechend erneut ausführen:

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

Bei dieser Iteration hat es sich über __memmove_avx_unaligned_erms beschwert , die offenbar durch AVX_Usable aktiviert wurde - aber ich habe einen anderen Pfad in ifunc-memmove.h gefunden, der durch AVX_Fast_Unaligned_Load aktiviert wurde . Zurück zum Reißbrett:

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

In dieser letzten Runde habe ich eine rdtscp entdeckt Anweisung in der gemeinsam genutzten ASAN-Bibliothek, also habe ich ohne den Address Sanitizer neu kompiliert und endlich hat es funktioniert.

Zusammenfassend:Mit etwas Arbeit ist es möglich, diese Anweisungen von der Befehlszeile aus zu deaktivieren und die Aufzeichnungsfunktion von gdb ohne schwerwiegende Hacks zu verwenden.


Ich bin kürzlich auch auf dieses Problem gestoßen und habe es schließlich mit dynamischem CPUID-Fehler gelöst, um die Ausführung der CPUID-Anweisung zu unterbrechen und ihr Ergebnis zu überschreiben, wodurch vermieden wird, glibc oder den dynamischen Linker zu berühren. Dies erfordert Prozessorunterstützung für CPUID-Fehler (Ivy Bridge+) sowie Linux-Kernel-Unterstützung (4.12+), um es über ARCH_GET_CPUID dem Userspace zugänglich zu machen und ARCH_SET_CPUID Unterfunktionen von arch_prctl() . Wenn diese Funktion aktiviert ist, wird ein SIGSEGV Das Signal wird bei jeder Ausführung von CPUID geliefert, sodass ein Signalhandler die Ausführung der Anweisung emulieren und das Ergebnis überschreiben kann.

Die vollständige Lösung ist etwas umständlich, da ich auch den dynamischen Linker zwischenschalten muss, da die Hardwarefähigkeitserkennung ab glibc 2.26+ dorthin verschoben wurde. Ich habe die vollständige Lösung online unter https://github.com/ddcc/libcpuidoverride hochgeladen.


Linux
  1. Versionskontrolle für /etc Unter *bsd?

  2. Linux – /net Ghosting für Autofs5 deaktivieren?

  3. CentOS / RHEL:So stellen Sie eine gelöschte /etc/passwd-Datei wieder her

  4. Befehl grpck – Entfernen Sie beschädigte oder doppelte Einträge in den Dateien /etc/group und /etc/gshadow.

  5. /etc/passwd zeigt Benutzer in einer Gruppe an, /etc/group jedoch nicht

Der richtige Weg zum Bearbeiten von /etc/passwd- und /etc/group-Dateien unter Linux

Die Dateien /proc/mounts, /etc/mtab und /proc/partitions verstehen

Vorlagen für Startskript?

Warum haben die Verzeichnisse /home, /usr, /var usw. alle dieselbe Inode-Nummer (2)?

Unterschied zwischen /etc/hosts und /etc/resolv.conf

So richten Sie /etc/issues ein, um die IP-Adresse für eth0 anzuzeigen