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

7 minutos de lectura

avatar de usuario
cris

Me han dicho y he leído en los manuales de Intel que es posible escribir instrucciones en la memoria, pero la cola de búsqueda previa de instrucciones ya ha obtenido las instrucciones obsoletas y las ejecutará. No he tenido éxito en la observación de 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 en caché en el procesador hace que la línea (o líneas) de caché asociada se invalide. Esta verificación se basa en la dirección física de la instrucción. Además, la familia P6 y los procesadores Pentium comprueban si una escritura en un segmento de código puede modificar una instrucción que se ha obtenido previamente para su ejecución. Si la escritura afecta a una instrucción precargada, la cola de precarga 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 que se refieran a la misma página física. Entonces, mapeo en memoria 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. a1pero le paso un puntero a a2 como destino 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 se ejecutaron las instrucciones obsoletas (dos nops), val==0.

Ejecuté esto en un Intel Core i5 de 1,7 GHz (macbook air de 2011) y una CPU Intel(R) Xeon(R) X3460 a 2,80 GHz. Sin embargo, cada vez que veo val==1 indica que la CPU siempre nota la nueva instrucción.

¿Alguien tiene experiencia con el comportamiento que quiero observar? ¿Es correcto mi razonamiento? Estoy un poco confundido sobre el manual que menciona los procesadores P6 y Pentium, y la falta de mención de mi procesador Core i5. ¿Quizás está sucediendo algo más que hace que la CPU elimine su cola de búsqueda previa de instrucciones? ¡Cualquier idea sería muy útil!

  • ¿Cuál es el manual que usó (verifique el “número de pedido” en la primera página y escríbalo aquí)?

    – osgx

    30 de junio de 2013 a las 23:26

  • Consulte también la sección “8.1.3 Manejo de códigos de modificación automática y de modificación cruzada” del manual de instrucciones: download.intel.com/products/processor/manual/325462.pdf

    – osgx

    30 de junio de 2013 a las 23:57

  • Hmm, intente desactivar PROT_EXEC de a2… Esto puede afectar a algunos Intel Atoms

    – osgx

    1 de julio de 2013 a las 1:25

  • +1 por lanzar “air powerbook” en una discusión tan precisa de las minucias de Intel 🙂

    – Patatas

    30 de junio de 2013 a las 23:22

  • Si mira más allá, verá patentes relacionadas con SMC en las que soy inventor. AFAIK, inventé los mecanismos de inclusión I $ e ITLB de P6 para espiar “instrucciones en vuelo”. // Considero que estos son errores. Creo que hubiera sido más fácil crear un CAM totalmente asociativo con los bloques de instrucciones de todas las instrucciones en la canalización física. Con un filtro de floración si quieres ahorrar energía. // Creo que fueron errores (a) porque complicados y difíciles de corregir, a pesar de que salvaron muchas puertas, y (b) mordazas de vidrio en el rendimiento.

    – Krazy Glew

    22/08/2013 a las 19:00


  • ¿Qué sucede si el código que modificó siempre estuvo, digamos, 1k bytes por delante de la dirección de ejecución actual? ¿Eso evitaría el problema?

    – diez veces

    15 de diciembre de 2013 a las 9:44

  • Otro ejemplo interesante de CPU que van más allá de los requisitos especificados en el manual x86 ISA: Pases de página coherentes para las entradas de TLB que solo podrían haberse cargado especulativamente, para evitar romper Win9x. AMD eliminó la coherencia con Bulldozer, ya que supongo que decidieron que Win9x y otro software que dependía de esto ya no eran relevantes.

    – Peter Cordes

    22 de octubre de 2016 a las 0:57


  • He tenido la intención de elaborar el comentario de @PeterCordes sobre el recorrido “coherente” de la tabla de páginas para fallas de TLB, pero seré rápido: (1) la razón principal por la que Intel comenzó a ejecutar la tabla de páginas recorre el caché, en lugar de pasar por alto el caché , fue el rendimiento. Antes de la página P6, los paseos por la tabla de páginas eran lentos, no se beneficiaban del caché y no eran especulativos. Lo suficientemente lento como para que el software TLB pierda el manejo fue una ganancia de rendimiento. P6 aceleró los fallos de TLB haciéndolos especulativamente, usando el caché y también almacenando en caché nodos intermedios como entradas de directorio de páginas.

    – Krazy Glew

    7 noviembre 2016 a las 19:39

  • (2a’) uno de los errores más vergonzosos estaba relacionado con agregar con llevar a la memoria. En microcódigo temprano. La carga desaparecería, la bandera de acarreo se actualizaría y la tienda podría fallar, pero la bandera de acarreo ya se había actualizado, por lo que la instrucción no se pudo reiniciar. // fue una simple solución de microcódigo, hacer la tienda antes de que se escribiera la bandera de acarreo, pero un uop adicional fue suficiente para que esa instrucción no encajara en el sistema ucode de “velocidad media”.

    – Krazy Glew

    7 noviembre 2016 a las 20:05


  • Gracias, Andy. ¡Esta es una gran historia! Siento que pertenece a una respuesta en algún lugar, ya sea este como un aparte gigante, o tal vez una pregunta y respuesta auto respondida si podemos pensar en una buena “pregunta” que tenga esta como respuesta: P Santo cielo, así que eso es ¿De dónde proviene esa ALU uop adicional en el ADC de destino de memoria, incluso en Core2 y SnB-family? Nunca lo habría adivinado, pero me había desconcertado.

    – Peter Cordes

    7 noviembre 2016 a las 20:46


  • Algo similar se aplica al código automodificable: no era tanto que quisiéramos hacer que el código automodificable se ejecutara rápido, sino que tratar de hacer que los mecanismos heredados para el código automodificable (drenar la canalización para serializar instrucciones como CPUID) fuera más lento que simplemente fisgoneando el Icache y el oleoducto. Pero, nuevamente, esto se aplica a una máquina de gama alta: en una máquina de gama baja, los mecanismos heredados son lo suficientemente rápidos y económicos.

    – Krazy Glew

    8 de noviembre de 2016 a las 5:11

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad