Observando la obtención de instrucciones obsoletas en x86 con código de auto modificación

Me han dicho y he leído en los manuales de Intel que es posible escribir instrucciones en la memoria, pero la cola de captación previa de instrucciones ya ha obtenido las instrucciones obsoletas y las ejecutará. No he tenido éxito en observar este comportamiento. Mi metodología es la siguiente.

El manual de desarrollo de software de Intel establece en la sección 11.6 que

Una escritura en una ubicación de memoria en un segmento de código que está actualmente almacenado en la memoria caché en el procesador causa la invalidación de la línea (o líneas) de la memoria caché asociada. Esta verificación se basa en la dirección física de la instrucción. Además, la familia P6 y los procesadores Pentium verifican si una escritura en un segmento de código puede modificar una instrucción que se ha captado previamente para su ejecución. Si la escritura afecta a una instrucción extraída previamente, la cola de captación previa se invalida. Esta última comprobación se basa en la dirección lineal de la instrucción.

Entonces, parece que si espero ejecutar instrucciones obsoletas, necesito tener dos direcciones lineales diferentes para referirme a la misma página física. Entonces, la memoria asigna un archivo a dos direcciones diferentes.

int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO); assert(fd>=0); write(fd, zeros, 0x1000); uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FILE | MAP_SHARED, fd, 0); uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FILE | MAP_SHARED, fd, 0); assert(a1 != a2); 

Tengo una función de ensamblaje que toma un solo argumento, un puntero a la instrucción que quiero cambiar.

 fun: push %rbp mov %rsp, %rbp xorq %rax, %rax # Return value 0 # A far jump simulated with a far return # Push the current code segment %cs, then the address we want to far jump to xorq %rsi, %rsi mov %cs, %rsi pushq %rsi leaq copy(%rip), %r15 pushq %r15 lretq copy: # Overwrite the two nops below with `inc %eax'. We will notice the change if the # return value is 1, not zero. The passed in pointer at %rdi points to the same physical # memory location of fun_ins, but the linear addresses will be different. movw $0xc0ff, (%rdi) fun_ins: nop # Two NOPs gives enough space for the inc %eax (opcode FF C0) nop pop %rbp ret fun_end: nop 

En C, copio el código en el archivo asignado a la memoria. Invoco la función desde la dirección lineal a1 , pero paso un puntero a a2 como el objective de la modificación del código.

 #define DIFF(a, b) ((long)(b) - (long)(a)) long sz = DIFF(fun, fun_end); memcpy(a1, fun, sz); void *tochange = DIFF(fun, fun_ins); int val = ((int (*)(void*))a1)(tochange); 

Si la CPU recogió el código modificado, val == 1. De lo contrario, si las instrucciones antiguas se ejecutaron (dos nops), val == 0.

Lo he ejecutado con un Intel Core i5 de 1,7 GHz (2011 macbook air) y una Intel (R) Xeon (R) CPU X3460 a 2.80 GHz. Sin embargo, cada vez que veo val == 1 indica que la CPU siempre nota las nuevas instrucciones.

¿Alguien ha experimentado con el comportamiento que quiero observar? ¿Mi razonamiento es correcto? Estoy un poco confundido acerca del manual que menciona los procesadores P6 y Pentium, y la falta de mención de mi procesador Core i5. ¿Tal vez está pasando algo más que hace que la CPU vacíe su cola de captación previa de instrucciones? ¡Cualquier idea sería muy útil!

Creo que debería verificar el contador de rendimiento MACHINE_CLEARS.SMC (parte del evento MACHINE_CLEARS ) de la CPU (está disponible en Sandy Bridge 1 , que se utiliza en su Powerbook de air, y también está disponible en su Xeon, que es Nehalem 2 – búsqueda “smc”). Puede usar oprofile , perf o Intel Vtune para encontrar su valor:

http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/GUID-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm

La máquina borra

Descripción métrica

Ciertos eventos requieren que toda la tubería sea limpiada y reiniciada justo después de la última instrucción retirada. Esta métrica mide tres de estos eventos: violaciones de órdenes de memoria, código de auto modificación y ciertas cargas a rangos de direcciones ilegales.

Posibles problemas

Una parte importante del tiempo de ejecución se emplea en el manejo de la máquina limpia. Examine los eventos MACHINE_CLEARS para determinar la causa específica.

SMC: http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html

MACHINE_CLEARS Código de evento: 0xC3 SMC Máscara: 0x04

Código de auto-modificación (SMC) detectado.

Número de borrado automático de la máquina de código de modificación detectado.

Intel también dice acerca de smc http://software.intel.com/en-us/forums/topic/345561 (vinculado a la taxonomía del analizador de rendimiento de botella Intel Performance)

Este evento se dispara cuando se detecta código de auto modificación. Esto puede ser utilizado normalmente por gente que hace edición binaria para obligarlo a tomar cierta ruta (por ejemplo, los piratas informáticos). Este evento cuenta la cantidad de veces que un progtwig escribe en una sección de código. El código de auto modificación causa una penalización severa en todos los procesadores Intel 64 e IA-32. La línea de caché modificada se escribe en las cachés L2 y LLC. Además, las instrucciones deberían volver a cargarse, lo que ocasionaría una penalización en el rendimiento.

Creo que verán algunos de esos eventos. Si lo son, entonces la CPU pudo detectar el acto de auto-modificar el código y planteó el “Despeje de la máquina” – reinicio completo de la tubería. Las primeras etapas son Obtener y pedirán a la memoria caché L2 un nuevo código de operación. Estoy muy interesado en el recuento exacto de eventos SMC por ejecución de su código, esto nos dará una estimación de las latencias. (SMC se cuenta en algunas unidades donde se supone que 1 unidad es de 1.5 ciclos de CPU – B.6.2. 6 del manual de optimización de Intel)

Podemos ver que Intel dice “se reinició desde justo después de la última instrucción retirada”, por lo que creo que la última instrucción retirada será mov ; y tus nops ya están en la tubería. Pero SMC se levantará en el retiro de mov y matará a todo en la tubería, incluso nops.

Este reinicio de tubería inducida por SMC no es barato, Agner tiene algunas medidas en Optimizing_assembly.pdf – “17.10 Código de auto-modificación (Todos los procesadores)” (Creo que cualquier Core2 / CoreiX es como PM aquí):

La penalización por ejecutar una pieza de código inmediatamente después de modificarla es de aproximadamente 19 relojes para P1, 31 para PMMX y 150-300 para PPro, P2, P3, PM. El P4 purgará toda la caché de seguimiento después del código de modificación automática. Los procesadores 80486 y anteriores requieren un salto entre el código modificado y modificado para eliminar el caché del código. …

El código de auto modificación no se considera una buena práctica de progtwigción. Se debe usar solo si la ganancia de velocidad es sustancial y el código modificado se ejecuta tantas veces que la ventaja supere las penalizaciones por usar código de auto modificación.

Se recomendó el uso de diferentes direcciones lineales para fallar Detector SMC aquí: https://stackoverflow.com/a/10994728/196561 – Trataré de encontrar la documentación de Intel real … Realmente no puedo responder a su pregunta real ahora.

Puede haber algunas sugerencias aquí: Manual de optimización, 248966-026, abril de 2012 “3.6.9 Código y datos de mezcla”:

Colocar datos escribibles en el segmento de código podría ser imposible de distinguir del código de auto modificación. Los datos que se pueden escribir en el segmento de código pueden sufrir la misma penalización de rendimiento que el código de auto modificación.

y la siguiente sección

El software debe evitar escribir en una página de códigos en la misma subpágina de 1 KByte que se está ejecutando o buscar código en la misma subpágina de 2 KByte que se está escribiendo. Además, compartir una página que contiene código ejecutado directa o especulativamente con otro procesador como una página de datos puede desencadenar una condición de SMC que hace que toda la interconexión de la máquina y la memoria caché de rastreo se borren. Esto se debe a la condición de código de auto modificación.

Entonces, posiblemente hay algunos esquemas que controlan las intersecciones de las subpáginas editables y ejecutables.

Puede intentar hacer modificaciones desde el otro hilo (código de modificación cruzada), pero se necesita una sincronización de hilo muy cuidadosa y un lavado de tuberías (es posible que desee incluir algunos forzados brutales de retrasos en el hilo del escritor; CPUID justo después de la sincronización es deseado). Pero debes saber que ELLOS ya arreglaron esto usando ” nukes ” – revisa la patente US6857064 .

Estoy un poco confundido acerca del manual que menciona los procesadores P6 y Pentium

Esto es posible si ha buscado, decodificado y ejecutado alguna versión obsoleta del manual de instrucciones de Intel. Puede restablecer la interconexión y verificar esta versión: Número de pedido: 325462-047US, junio de 2013 “11.6 CÓDIGO DE AUTO MODIFICACIÓN”. Esta versión todavía no dice nada acerca de las CPU más nuevas, pero menciona que cuando se modifica utilizando direcciones virtuales diferentes, el comportamiento puede no ser compatible entre microarchitectures (puede funcionar en su Nehalem / Sandy Bridge y puede no funcionar en … Skymont)

11.6 CÓDIGO DE MODIFICACIÓN AUTOMÁTICA Una escritura en una ubicación de memoria en un segmento de código que está actualmente en caché en el procesador causa la invalidación de la línea (o líneas) de caché asociada. Esta verificación se basa en la dirección física de la instrucción. Además, la familia P6 y los procesadores Pentium verifican si una escritura en un segmento de código puede modificar una instrucción que se ha captado previamente para su ejecución. Si la escritura afecta a una instrucción extraída previamente, la cola de captación previa se invalida. Esta última comprobación se basa en la dirección lineal de la instrucción. Para los procesadores Pentium 4 e Intel Xeon, una escritura o un rastreo de una instrucción en un segmento de código, donde la instrucción objective ya está decodificada y residente en el caché de seguimiento, invalida todo el caché de seguimiento. El último comportamiento significa que los progtwigs que auto-modifican el código pueden causar una grave degradación del rendimiento cuando se ejecutan en los procesadores Pentium 4 e Intel Xeon.

En la práctica, la verificación en las direcciones lineales no debería crear problemas de compatibilidad entre los procesadores IA-32. Las aplicaciones que incluyen código de modificación automática utilizan la misma dirección lineal para modificar y recuperar la instrucción.

El software de sistemas, como un depurador, que posiblemente podría modificar una instrucción usando una dirección lineal diferente a la utilizada para captar la instrucción, ejecutará una operación de serialización, como una instrucción CPUID, antes de que se ejecute la instrucción modificada, que se resincronizará automáticamente la caché de instrucciones y la cola de captación previa. (Consulte la Sección 8.1.3, “Cómo manejar el código de auto modificación y modificación cruzada” para obtener más información sobre el uso del código de modificación automática).

Para los procesadores Intel486, una escritura en una instrucción en la memoria caché lo modificará tanto en la memoria caché como en la memoria, pero si la instrucción fue captada previamente antes de la escritura, la versión anterior de la instrucción podría ser la ejecutada. Para evitar que se ejecute la instrucción anterior, purgue la unidad de captación previa de la instrucción codificando una instrucción de salto inmediatamente después de cualquier escritura que modifique una instrucción

REAL Update , busca en Google la “Detección SMC” (con comillas) y hay algunos detalles sobre cómo el Core2 / Core iX moderno detecta SMC y también muchas listas de erratas con Xeons y Pentium colgando en el detector SMC:

  1. http://www.google.com/patents/US6237088 Sistema y método para rastrear instrucciones en vuelo en una tubería @ 2001

  2. DOI 10.1535 / itj.1203.03 (google para ello, hay versión gratuita en citeseerx.ist.psu.edu) – se agregó el “FILTRO DE INCLUSIÓN” en Penryn para disminuir el número de detecciones falsas de SMC; el “mecanismo de detección de inclusión existente” se muestra en la Fig. 9

  3. http://www.google.com/patents/US6405307 – patente anterior sobre la lógica de detección de SMC

De acuerdo con la patente US6237088 (FIG5, resumen) hay “buffer de dirección de línea” (con muchas direcciones lineales una dirección por instrucción captada, o en otras palabras, el buffer lleno de direcciones IP recuperadas con precisión de línea de caché). Cada tienda, o la fase más exacta de “dirección de la tienda” de cada tienda será enviada a un comparador paralelo para verificar, almacenará las intersecciones con cualquiera de las instrucciones que se estén ejecutando actualmente o no.

Ambas patentes no dicen claramente, usarán direcciones físicas o lógicas en la lógica de SMC … L1i en Sandy bridge es VIPT (Dirección virtual prácticamente indexada, físicamente etiquetada para el índice y la dirección física en la etiqueta) de acuerdo con http : //nick-black.com/dankwiki/index.php/Sandy_Bridge, así que tenemos la dirección física en el momento en que la memoria caché L1 devuelve datos. Creo que Intel puede usar direcciones físicas en la lógica de detección de SMC.

Aún más, http://www.google.com/patents/US6594734 @ 1999 (publicado en 2003, solo recuerde que el ciclo de diseño de la CPU es de alrededor de 3-5 años) dice en la sección “Resumen” que SMC ahora está en TLB y usa direcciones físicas (o en otras palabras, por favor, no intente engañar al detector SMC):

El código de auto modificación se detecta utilizando un buffer de traducción lookaside . [Que] tiene direcciones físicas de página almacenadas en el mismo sobre las cuales se pueden realizar fisgoneos usando la dirección de memoria física de un almacén en la memoria. … Para proporcionar una granularidad más fina que una página de direcciones, los bits FINE HIT se incluyen con cada entrada del caché que asocia información en el caché a partes de una página dentro de la memoria.

(La parte de la página, denominada cuadrante en la patente US6594734, suena como 1K subpáginas, ¿no?)

Entonces dicen

Por lo tanto , los fisgones, activados por las instrucciones almacenadas en la memoria , pueden realizar la detección SMC comparando la dirección física de todas las instrucciones almacenadas dentro de la memoria caché de instrucciones con la dirección de todas las instrucciones almacenadas en la página o páginas de memoria asociadas. Si hay una coincidencia de dirección, indica que se modificó una ubicación de memoria. En el caso de una coincidencia de direcciones, que indica una condición SMC, la unidad de retiro limpia la caché de instrucciones y la tubería de instrucciones, y las nuevas instrucciones se extraen de la memoria para almacenarlas en la memoria caché de instrucciones.

Debido a que los fisgones para la detección de SMC son físicos y la ITLB normalmente acepta como entrada una dirección lineal para traducir a una dirección física, la ITLB se forma adicionalmente como una memoria dirigida por contenido en las direcciones físicas e incluye un puerto de comparación de entrada adicional (al que se hace referencia como un puerto snoop o de traducción inversa)

– Entonces, para detectar SMC, obligan a las tiendas a reenviar la dirección física al buffer de instrucciones a través de snoop (se enviarán snoops similares desde otros núcleos / cpus o desde grabaciones DMA a nuestras memorias caché …), si phys de snoop. la dirección entra en conflicto con las líneas de caché, almacenadas en el búfer de instrucciones, reiniciaremos la conexión a través de la señal SMC entregada desde iTLB a la unidad de jubilación. Me imagino cuánto perderán los relojes de la CPU en ese loop de snoop de dTLB a través de iTLB y a la unidad de retiro (no puede retirar la siguiente instrucción “nop”, aunque se ejecutó antes que mov y no tiene efectos secundarios). Pero WAT? ITLB tiene una entrada de dirección física y una segunda CAM (grande y caliente) solo para respaldar y defenderse contra el código de auto-modificación loco y engañoso.

PD: ¿Y si trabajamos con páginas enormes (4M o puede ser 1G)? El L1TLB tiene enormes entradas de página, y puede haber una gran cantidad de falsas SMC detecta para 1/4 de 4 MB de página …

PPS: existe una variante, que el manejo erróneo de SMC con diferentes direcciones lineales solo estaba presente en los primeros P6 / Ppro / P2 …

Me han dicho y he leído en los manuales de Intel que es posible escribir instrucciones en la memoria, pero la cola de captación previa de instrucciones ya puede [haber] recuperado las instrucciones obsoletas y [puede] ejecutar esas instrucciones anteriores. No he tenido éxito en observar este comportamiento.

Sí, lo serías.

Todos o casi todos los procesadores Intel modernos son más estrictos que el manual:

Ellos fisgonean la tubería en base a la dirección física, no solo lineal.

Las implementaciones del procesador pueden ser más estrictas que los manuales.

Pueden elegir serlo porque han encontrado un código que no cumple con las reglas en los manuales, que no quieren romper.

O … porque la forma más fácil de cumplir con la especificación arquitectónica (que en el caso de SMC solía ser oficialmente “hasta la próxima instrucción de serialización” pero en la práctica, para el código heredado, era “hasta la siguiente twig tomada que está a más de “bytes de distancia”) podría ser más estricto.