code c gcc assembly x86 machine-code

inline assembly code in c



AMD64-nopw instrucciones de montaje? (4)

En esta salida del compilador, estoy tratando de entender cómo funciona la codificación de código de máquina de la instrucción nopw :

00000000004004d0 <main>: 4004d0: eb fe jmp 4004d0 <main> 4004d2: 66 66 66 66 66 2e 0f nopw %cs:0x0(%rax,%rax,1) 4004d9: 1f 84 00 00 00 00 00

Hay alguna discusión sobre "nopw" en http://john.freml.in/amd64-nopl . ¿Alguien puede explicar el significado de 4004d2-4004e0? Al mirar la lista de códigos de operación, parece que los 66 .. códigos son expansiones de múltiples bytes. Creo que probablemente podría obtener una mejor respuesta a esto aquí de lo que lo haría a menos que tratara de asimilar la lista de códigos de operación durante unas horas.

Esa salida de asm proviene del siguiente código (insano) en C, que se optimiza hasta un simple bucle infinito:

long i = 0; main() { recurse(); } recurse() { i++; recurse(); }

Cuando se compila con gcc -O2 , el compilador reconoce la recursión infinita y la convierte en un bucle infinito; lo hace tan bien, de hecho, que en realidad hace un bucle en main() sin llamar a la función recurse() .

Nota del editor: las funciones de relleno con NOP no son específicas de bucles infinitos. Aquí hay un conjunto de funciones con un rango de longitudes de NOP, en el explorador del compilador Godbolt.


Creo que el nopw es basura: nunca se lee en su programa y, por lo tanto, no hay necesidad de incrementarlo.


El ensamblador (no el compilador) rellena el código hasta el siguiente límite de alineación con la instrucción NOP más larga que pueda encontrar. Esto es lo que estás viendo.


Los 0x66 bytes son un prefijo "Anulación de tamaño de operando". Tener más de uno de ellos es equivalente a tener uno.

El 0x2e es un ''prefijo nulo'' en modo de 64 bits (es una anulación de segmento CS: de lo contrario, por lo que aparece en el mnemónico de ensamblaje).

0x0f 0x1f es un código de operación de 2 bytes para un NOP que toma un byte ModRM

0x84 es un byte ModRM que en este caso codifica para un modo de direccionamiento que usa 5 bytes más.

Algunas CPU tardan en decodificar las instrucciones con muchos prefijos (por ejemplo, más de tres), por lo que un byte ModRM que especifica un SIB + disp32 es una forma mucho mejor de usar 5 bytes adicionales que cinco bytes más de prefijo.

Decodificadores AMD K8 en el microarchivo de Agner Fog pdf :

Cada uno de los decodificadores de instrucciones puede manejar tres prefijos por ciclo de reloj. Esto significa que tres instrucciones con tres prefijos pueden decodificarse en el mismo ciclo de reloj. Una instrucción con 4 - 6 prefijos requiere un ciclo de reloj adicional para decodificar.

Esencialmente, esos bytes son una larga instrucción NOP que nunca se ejecutará de todos modos. Se encuentra allí para garantizar que la siguiente función se alinee en un límite de 16 bytes, porque el compilador emitió una directiva .p2align 4 , por lo que el ensamblador rellenó con un NOP. el valor predeterminado de gcc para x86 es
-falign-functions=16 . Para los NOP que se ejecutarán, la elección óptima de largo NOP depende de la microarquitectura. Para una microarquitectura que se ahoga en muchos prefijos, como Intel Silvermont o AMD K8, dos NOP con 3 prefijos cada uno se podrían haber descodificado más rápido.

El artículo del blog al que se vincula la pregunta ( http://john.freml.in/amd64-nopl ) explica por qué el compilador utiliza una instrucción NOP única complicada en lugar de un grupo de instrucciones NOP 0x90 de un solo byte.

Puede encontrar los detalles sobre la codificación de instrucciones en los documentos de ref de tecnología de AMD:

Principalmente en el "Volumen 3 del Manual del Programador de Arquitectura AMD64: Propósito general e instrucciones del sistema". Estoy seguro de que las referencias técnicas de Intel para la arquitectura x64 tendrán la misma información (y podrían incluso ser más comprensibles).


Supongo que esto es sólo la instrucción de demora de rama.