Modernes x86_64-Linux mit glibc erkennt, dass die CPU die AVX-Erweiterung unterstützt und viele String-Funktionen von der generischen Implementierung in AVX-optimierte Version (mit Hilfe von ifunc Dispatchern: 1 , 2 ).
Diese Funktion kann für die Leistung gut sein, aber sie verhindert mehrere Tools wie Valgrind ( ältere libVEXs ) , bevor valgrind-3.8 ) und gdbs " target record
" ( Umgekehrte Ausführung ) funktioniert nicht richtig (Ubuntu" Z "17.04 beta, gdb 7.12 .50.20170207-0ubuntu2, gcc 6.3.0- 8ubuntu1 20170221, Ubuntu GLIBC 2.24-7ubuntu2):
Es gibt die Fehlermeldung " Process record does not support instruction 0xc5
" aus der gdb-Implementierung von "target record", weil AVX-Anweisungen nicht von der Record / Replay-Engine unterstützt werden (manchmal wird das Problem an _dl_runtime_resolve_avx
function erkannt): Ссылка "Einige AVX-Anweisungen werden von der Prozessaufzeichnung nicht unterstützt", Ссылка ,
Lösung vorgeschlagen in Ссылка "Sie können libc (also ld.so) neu kompilieren, oder hack __init_cpu_features und somit __cpu_features zur Laufzeit (siehe zB strcmp). " oder setze LD_BIND_NOW=1
, aber rekompilierte glibc hat immer noch AVX, und ld bind - hilft jetzt nicht.
Ich habe gehört, dass es in glibc /etc/ld.so.nohwcap
und LD_HWCAP_MASK
Konfigurationen gibt. Können sie verwendet werden, um die ifunc-Verteilung an AVX-optimierte String-Funktionen in glibc zu deaktivieren?
Wie erkennt glibc (rtld?) AVX, indem es cpuid
, mit /proc/cpuinfo
(wahrscheinlich nicht) oder HWCAP aux LD_SHOW_AUXV=1 /bin/echo |grep HWCAP
AT_HWCAP: bfebfbff
)?
Nicht die beste oder vollständige Lösung, nur ein kleiner Bit-Editing-Klud, um valgrind und gdb record für meine Aufgabe zuzulassen.
wie man AVX / SSE ausblendet, ohne glibc neu zu kompilieren
Ich habe den unmodifizierten glibc vollständig neu erstellt, was in debian und ubuntu ziemlich einfach ist: nur sudo apt-get source glibc
, sudo apt-get build-dep glibc
und cd glibc-*/; dpkg-buildpackage -us -uc
( manual , um die ld.so ohne entpackte Debugging-Informationen zu erhalten.
Dann habe ich ein binäres (Bit-) Patchen der Ausgabedatei ld.so in der von __get_cpu_features
verwendeten Funktion durchgeführt. Zielfunktion wurde zusammengestellt von get_common_indeces
der Quelldatei sysdeps/x86/cpu-features.c
unter dem Namen get_common_indeces.constprop.1
(direkt nach dem __get_cpu_features
im Binärcode). Es hat mehrere cpuids, erste ist cpuid eax=1
"Prozessor-Info und Feature-Bits" ; und später gibt es „jle 0x6“ überprüfen und abspringen um den Code " cpuid eax=7 ecx=0
Gesamt Features ", um den AVX2-Status zu erhalten. Da ist der Code, der in diese Logik übersetzt wurde:
Das cpu_features->max_cpuid
wurde in init_cpu_features
der die gleiche Datei in __cpuid (0, cpu_features->max_cpuid, ebx, ecx, edx);
line. Es war einfacher, die if
-Anweisung zu deaktivieren, indem jle
nach cmp 0x6
durch jg
(Byte 0x7e bis 0x7f) ersetzt wurde. (Tatsächlich wurde dieser Binär-Patch erneut manuell auf die Funktion __get_cpu_features
des realen Systems ld-linux.so.2
angewendet - zuerst jle, bevor mov 7 eax; xor ecx,ecx; cpuid
in jg geändert wurde.)
Das neu kompilierte Paket und das modifizierte ld.so wurden nicht im System installiert; Ich habe die Befehlszeilensyntax von ld.so ./my_program
(oder mv ld.so /some/short/path.so
und patchelf --set-interpreter ./my_program
) verwendet.
Andere mögliche Lösungen:
if (cpu_features->max_cpuid >= 7)
in glibc patchen und 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 die einfachste Methode zu sein. @osgx beschrieb eine Methode, bei der ein Sprung überschrieben wird. Ein anderer Ansatz besteht darin, das CPU-Ergebnis zu fälschen. Normalerweise gibt cpuid(eax=0)
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
:
Die Zeile __cpuid
übersetzt diese Anweisungen in /lib/ld-linux-x86-64.so.2
( /lib/ld-2.25.so
):
Anstatt also Zweige zu patchen, könnten wir auch die cpuid
in eine nop
Anweisung ändern, was zum Aufruf des letzten else
Zweigs führen würde (da die Register kein "GenuineIntel" enthalten). Da anfangs auch eax=0
, cpu_features->max_cpuid
ebenfalls 0 sind, wird auch if (cpu_features->max_cpuid >= 7)
umgangen.
Binäres Patchen cpuid(eax=0)
by nop
Dies kann mit diesem Dienstprogramm durchgeführt werden (funktioniert sowohl für x86 als auch für x86-64):
Das war der einfache Teil. Jetzt wollte ich den systemweiten dynamischen Linker nicht ersetzen, sondern nur ein bestimmtes Programm mit diesem Linker ausführen. Sicher, das kann mit ./ld-linux-x86-64-patched.so.2 ./a
gemacht werden, aber die naiven gdb-Aufrufe konnten keine Haltepunkte setzen:
Eine manuelle Problemumgehung ist in beschrieben. Wie Programm mit benutzerdefinierten Elf Interpreter zu debuggen? Es funktioniert, aber es ist leider ein manuelle Aktion mit add-symbol-file
. Es sollte möglich sein, es ein bisschen zu automatisieren, indem man GDB Catchpoints verwendet.
Ein alternativer Ansatz, der keine binäre Verknüpfung bietet, ist LD_PRELOAD
in einer Bibliothek, die benutzerdefinierte Routinen für memcpy
, memove
usw. definiert. Diese hat dann Vorrang vor den glibc-Routinen. Die vollständige Liste der Funktionen finden Sie in sysdeps/x86_64/multiarch/ifunc-impl-list.c
. Der aktuelle HEAD hat mehr Symbole im Vergleich zur glibc 2.25 Version ( grep -Po 'IFUNC_IMPL \(i, name, \K[^,]+' sysdeps/x86_64/multiarch/ifunc-impl-list.c
):
memchr, memcmp, __memove_chk, behüte, memrchr, __memset_chk, memset, rawmemchr, streichen, strnlen, stpncpy, stpcpy, strasecmp, 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,
Ich habe gehört, dass es in glibc
/etc/ld.so.nohwcap
undLD_HWCAP_MASK
Konfigurationen gibt. Können sie verwendet werden, um die ifunc-Verteilung an AVX-optimierte String-Funktionen in glibc zu deaktivieren?
Ja: Wenn Sie LD_HWCAP_MASK=0
einstellen, wird GLIBC so tun, als ob keine CPU-Fähigkeiten verfügbar sind. Code .
Wenn Sie die Maske auf 0 setzen, wird wahrscheinlich ein Fehler ausgelöst. Sie müssen wahrscheinlich das genaue Bit herausfinden, das AVX steuert, und nur dieses Bit maskieren.