Pregunta

Estoy pasando por MSIL y noto que hay muchos nop instrucciones. El artículo de MSDN dice que no toman medidas y se utilizan para llenar el espacio si el código de operación está parcheado. Se usan mucho más en las versiones de depuración que en las versiones de lanzamiento. Sé que este tipo de declaraciones se usan en lenguajes de ensamblaje para asegurarse de que un código de operación se ajuste a un límite de palabra, pero ¿por qué se necesita en MSIL?

¿Fue útil?

Solución

Los NOP sirven para varios propósitos:

  • Permiten que el depurador coloque un punto de interrupción en una línea, incluso si se combina con otros en el código generado.
  • Permite que el cargador parchee un salto con un desplazamiento objetivo de diferente tamaño.
  • Permite que un bloque de código se alinee en un límite particular, lo que puede ser bueno para el almacenamiento en caché.
  • Permite la vinculación incremental para sobrescribir fragmentos de código con una llamada a una nueva sección sin tener que preocuparse por el cambio de tamaño de la función general.

Otros consejos

Aquí se explica cómo se utilizan los nops en la depuración:

Los compiladores de lenguaje (C #, VB, etc.) utilizan los nops para definir puntos de secuencia implícitos. Estos le indican al compilador JIT dónde asegurarse de que las instrucciones de la máquina puedan volver a asignarse a las instrucciones de IL.

Entrada de blog de Rick Byer en DebuggingModes.IgnoreSymbolStoreSequencePoints , explica algunos de los detalles.

C # también coloca Nops después de las instrucciones de la llamada para que la ubicación del sitio de retorno en la fuente sea la llamada en lugar de la línea después de la llamada.

Proporciona una oportunidad para marcadores basados ??en líneas (por ejemplo, puntos de interrupción) en el código donde una versión de lanzamiento no emitiría ninguno.

También puede hacer que el código se ejecute más rápido, al optimizar para procesadores o arquitecturas específicos:

Los procesadores emplean durante mucho tiempo múltiples canalizaciones que funcionan aproximadamente en paralelo, por lo que se pueden exceder dos instrucciones independientes al mismo tiempo. En un procesador simple con dos tuberías, el primero puede admitir todas las instrucciones, mientras que el segundo solo admite un subconjunto. Además, hay algunos puestos entre las tuberías cuando uno tiene que esperar el resultado de una instrucción anterior que aún no ha terminado.

En estas circunstancias, un nop dedicado puede forzar la próxima instrucción en una canalización específica (la primera, o no la primera), y mejorar el emparejamiento de las siguientes instrucciones para que el costo de la < em> nop está más que amortizado.

¡Amigo! ¡No-op es increíble! Es una instrucción que no hace más que consumir tiempo. En las oscuras eras oscuras, lo usaría para hacer microajustes en el tiempo en bucles críticos o, lo que es más importante, como relleno en el código de modificación automática.

En un procesador en el que trabajé recientemente (durante cuatro años) se usó NOP para asegurarme de que la operación anterior terminara antes de que se iniciara la siguiente. Por ejemplo:

valor de carga para registrar (toma 8 ciclos) nop 8 agrega 1 al registro

Esto aseguró que el registro tuviera el valor correcto antes de la operación de agregar.

Otro uso era completar unidades de ejecución, como los vectores de interrupción que tenían que ser de cierto tamaño (32 bytes) porque la dirección para el vector0 era, digamos 0, para el vector 1 0x20 y así sucesivamente, por lo que el compilador puso NOP allí si es necesario.

Un uso clásico para ellos es que su depurador siempre pueda asociar una línea de código fuente con una instrucción IL.

Podrían estar usándolos para admitir editar y continuar durante la depuración. Proporciona al depurador espacio para trabajar y reemplazar el código antiguo por uno nuevo sin cambiar las compensaciones, etc.

Nop es esencial en la carga útil de las vulnerabilidades de desbordamiento de búfer.

Como dijo ddaa, los nops le permiten explicar la variación en la pila, de modo que cuando sobrescribe la dirección de retorno salta al trineo nop (muchos nops seguidos) y luego golpea el código ejecutable correctamente, en lugar de saltar a algún byte en la instrucción que no es el comienzo.

50 años demasiado tarde pero hey.

Los nop son útiles si está escribiendo el código de ensamblaje a mano. Si tuviera que eliminar el código, podría eliminar los códigos de operación anteriores.

de manera similar, puede insertar un nuevo código al sobrescribir un código de operación y saltar a otra parte. Allí colocas los códigos de operación sobrescritos e insertas tu nuevo código. Cuando estés listo, retrocedes.

A veces tenías que usar las herramientas que estaban disponibles. En algunos casos, este era solo un editor de código de máquina muy básico.

Hoy en día, con los compiladores, las técnicas ya no tienen ningún sentido.

En la escena de descifrado de software, un método clásico para desbloquear una aplicación sería parchear con un NOP la línea que verifica la clave o el registro o el período de tiempo o cualquier otra cosa para que no haga nada y simplemente continúe iniciando la aplicación como si está registrado.

También he visto NOP en el código que se modifica a sí mismo para ofuscar lo que hace como un marcador de posición (una protección de copia muy antigua).

Un uso poco ortodoxo son NOP-Slides , utilizado en explotaciones de desbordamiento de búfer.

Permiten que el enlazador reemplace una instrucción más larga (típicamente salto largo) con una más corta (salto corto). El NOP ocupa el espacio extra: el código no se pudo mover, ya que evitaría que otros saltos funcionen. Esto sucede en el momento del enlace, por lo que el compilador no puede saber si un salto largo o corto sería apropiado.

Al menos, ese es uno de sus usos tradicionales.

Esta no es una respuesta a tu pregunta específica, pero en los viejos tiempos, puedes usar un NOP para rellenar un ranura de retardo de derivación , si no pudiera llenarla con una instrucción que de otra manera sería útil.

¿Los compiladores .NET alinean la salida de MSIL? Me imagino que podría ser útil para acelerar el acceso al IL ... Además, entiendo que está diseñado para ser portátil y se requieren accesos alineados en algunas otras plataformas de hardware.

El primer ensamblaje que aprendí fue SPARC, así que estoy familiarizado con la ranura de retardo de bifurcación, si no puedes completarla con otra instrucción, generalmente la instrucción que ibas a poner sobre la instrucción de bifurcación o incrementar un contador en los bucles , usas un NOP.

No estoy familiarizado con las grietas, pero creo que es común sobrescribir la pila usando NOP, por lo que no tienes que calcular exactamente dónde comienza tu función maliciosa.

Utilicé NOP para ajustar automáticamente la latencia acumulada después de ingresar un ISR. Muy útil para clavar el tiempo de encendido.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top