GNU/Linux >> Tutoriales Linux >  >> Linux

Deshabilite las funciones optimizadas para AVX en glibc (LD_HWCAP_MASK, /etc/ld.so.nohwcap) para valgrind y gdb record

No parece haber un método de tiempo de ejecución sencillo para parchear la detección de funciones. Esta detección ocurre bastante temprano en el enlazador dinámico (ld.so).

El parche binario del enlazador parece el método más fácil en este momento. @osgx describió un método en el que se sobrescribe un salto. Otro enfoque es simplemente falsificar el resultado de cpuid. Normalmente cpuid(eax=0) devuelve la función admitida más alta en eax mientras que los ID del fabricante se devuelven en los registros ebx, ecx y edx. Tenemos este fragmento en 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);
  }

El __cpuid línea se traduce a estas instrucciones en /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  

Entonces, en lugar de parchear las ramas, también podríamos cambiar el cpuid en un nop instrucción que resultaría en la invocación del último else rama (ya que los registros no contendrán "GenuineIntel"). Desde inicialmente eax=0 , cpu_features->max_cpuid también será 0 y el if (cpu_features->max_cpuid >= 7) también se omitirá.

Parcheo binario cpuid(eax=0) por nop esto se puede hacer con esta utilidad (funciona tanto para x86 como para 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 de Perl equivalente, -0777 asegura que el archivo se lea de una vez en lugar de separar los registros en saltos de línea:

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

Esa fue la parte fácil. Ahora, no quería reemplazar el enlazador dinámico de todo el sistema, sino ejecutar solo un programa en particular con este enlazador. Claro, eso se puede hacer con ./ld-linux-x86-64-patched.so.2 ./a , pero las invocaciones ingenuas de gdb no pudieron establecer puntos de interrupción:

$ 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                                                                                                                                                                         

Se describe una solución manual en ¿Cómo depurar un programa con un intérprete elf personalizado? Funciona, pero desafortunadamente es una acción manual usando add-symbol-file . Sin embargo, debería ser posible automatizarlo un poco usando GDB Catchpoints.

Un enfoque alternativo que no vincula enlaces binarios es LD_PRELOAD ing una biblioteca que define rutinas personalizadas para memcpy , memove , etc. Esto tendrá prioridad sobre las rutinas glibc. La lista completa de funciones está disponible en sysdeps/x86_64/multiarch/ifunc-impl-list.c . HEAD actual tiene más símbolos en comparación con la versión glibc 2.25, en 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,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,


Parece que hay una buena solución para esto implementada en versiones recientes de glibc:una función de "ajustables" que guía la selección de funciones de cadena optimizadas. Puede encontrar una descripción general de esta característica aquí y el código relevante dentro de glibc en ifunc-impl-list.c.

Así es como lo descubrí. Primero, tomé la dirección de la que se queja gdb:

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

Luego lo busqué en la tabla de bibliotecas compartidas:

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

Puede ver que esta dirección está dentro de glibc. Pero, ¿qué función, específicamente?

(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

Puedo buscar en ifunc-impl-list.c para encontrar el código que controla la selección de la versión 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))

Parece AVX2_Usable es la característica a deshabilitar. Volvamos a ejecutar gdb en consecuencia:

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

En esta iteración, se quejó de __memmove_avx_unaligned_erms , que parecía estar habilitado por AVX_Usable - pero encontré otra ruta en ifunc-memmove.h habilitada por AVX_Fast_Unaligned_Load . De vuelta a la mesa de dibujo:

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

En esta ronda final descubrí un rdtscp instrucciones en la biblioteca compartida de ASAN, así que volví a compilar sin el desinfectante de direcciones y, por fin, funcionó.

En resumen:con un poco de trabajo es posible deshabilitar estas instrucciones desde la línea de comandos y usar la función de registro de gdb sin necesidad de hacks severos.


También encontré este problema recientemente y terminé resolviéndolo usando fallas dinámicas de CPUID para interrumpir la ejecución de la instrucción CPUID y anular su resultado, lo que evita tocar glibc o el enlazador dinámico. Esto requiere soporte de procesador para fallas de CPUID (Ivy Bridge+), así como soporte de kernel de Linux (4.12+) para exponerlo al espacio de usuario a través de ARCH_GET_CPUID y ARCH_SET_CPUID subfunciones de arch_prctl() . Cuando esta característica está habilitada, un SIGSEGV la señal se entregará en cada ejecución de CPUID, lo que permite que un controlador de señal pueda emular la ejecución de la instrucción y anular el resultado.

La solución completa es un poco complicada ya que también necesito interponer el enlazador dinámico, porque la detección de la capacidad del hardware se movió allí a partir de glibc 2.26+. Cargué la solución completa en línea en https://github.com/ddcc/libcpuidoverride.


Linux
  1. ¿Cómo monitorear los archivos /etc/shadow y /etc/passwd en busca de cambios con Auditd?

  2. Comando grpck:elimine las entradas corruptas o duplicadas en los archivos /etc/group y /etc/gshadow.

  3. ¿Cuál es la conexión entre los directorios /etc/init.d y /etc/rcX.d en Linux?

  4. ¿Por qué los directorios /home, /usr, /var, etc. tienen todos el mismo número de inodo (2)?

  5. ¿Deberían vivir los sitios web en /var/ o /usr/ según el uso recomendado?

La forma correcta de editar archivos /etc/passwd y /etc/group en Linux

Comprender los archivos /proc/mounts, /etc/mtab y /proc/partitions

¿Plantillas para el script de inicio?

Generar contraseña manualmente para /etc/shadow

Diferencia entre /etc/hosts y /etc/resolv.conf

Cómo configurar /etc/issues para mostrar la dirección IP de eth0