2012-10-01 10 views
6

Acabo de comenzar a buscar IL un poco y tengo curiosidad si mi intento (mostrado a continuación) para eliminar el exceso de código de la salida del compilador tuvo efectos secundarios no deseados.Preguntas sobre IL codificado a mano basado en código simple desensamblado de C#

Un par de quesiton acerca de los resultados:

  1. ¿Cuál es el propósito de las operaciones nop en el original?
  2. ¿Cuál es el propósito de los br.s al final de los métodos en el original?
  3. ¿Es incorrecta la versión reescrita de alguna manera?

original C# Código:

class Program { 
    public static int Main() { 
     return Add(1, 2); 
    } 
    public static int Add(int a, int b) { 
     return a + b; 
    } 
} 

compilado con csc.exe y desmontado con ildasm.exe (original):

.method public hidebysig static int32 Main() cil managed 
    { 
    .entrypoint 
    .maxstack 2 
    .locals init (int32 V_0) 
    IL_0000: nop 
    IL_0001: ldc.i4.1 
    IL_0002: ldc.i4.2 
    IL_0003: call  int32 Program::Add(int32, int32) 
    IL_0008: stloc.0 
    IL_0009: br.s  IL_000b 
    IL_000b: ldloc.0 
    IL_000c: ret 
    } 
    .method public hidebysig static int32 Add(int32 a, 
              int32 b) cil managed 
    { 
    .maxstack 2 
    .locals init (int32 V_0) 
    IL_0000: nop 
    IL_0001: ldarg.0 
    IL_0002: ldarg.1 
    IL_0003: add 
    IL_0004: stloc.0 
    IL_0005: br.s  IL_0007 
    IL_0007: ldloc.0 
    IL_0008: ret 
    } 

reescrito (produce una salida idéntica):

.method public hidebysig static int32 Main() cil managed 
    { 
    .entrypoint 
     .maxstack 2 
    ldc.i4.1 
    ldc.i4.2 
    call int32 Program::Add(int32, int32) 
    ret 
    } 

    .method public hidebysig static int32 Add(int32 a, int32 b) cil managed 
    { 
    .maxstack 2 
     ldarg.0 
     ldarg.1 
    add 
    ret 
    } 
+4

el nop "sin operación" es donde puede establecer su punto de interrupción durante la depuración. Intenta compilarlo para su lanzamiento. – Johnny

Respuesta

6

Todo el 'exceso' código que ves es específico de versiones de depuración (y generalmente obtiene optimizado para las versiones de lanzamiento de distancia) y permite llevar a cabo algo que no se puede hacer de ordinario en la liberación de construcción.

El código de compilación de depuración es tal que permite la máxima independencia en la configuración de puntos de interrupción y el cambio/examen de los valores de la pila durante una sesión de depuración. Además, el código IL debe imitar el código de nivel superior tanto como sea posible para que cada 'causa' y 'efecto' se pueda mapear a líneas de código de nivel superior.

Ahora, para ser específico a sus preguntas:

¿Cuál es el propósito de las operaciones nop en el original?

NOP le permite establecer puntos de interrupción en lugares que no están 'ejecutados'. Por ej. las llaves de apertura de un método, bucle o instrucción if. Entre estas instrucciones no ejecutables, romper en la abrazadera de apertura le permite modificar/examinar la pila justo antes de que comience un bloque (aunque es cierto que puede lograr esto muy fácilmente rompiendo la primera línea de ejecución del bloque en lugar de abrir la abrazadera pero todavía le permite la independencia de la ruptura en la llave de apertura)

¿Cuál es el propósito de la br.s al final de los métodos en el original?

Al ver el código original, puede parecer absurdo 'saltar' a la siguiente línea en lugar de permitir que el código 'caiga' naturalmente a la siguiente línea. Pero leerlo como:

"En una versión de depuración, siempre que sea un método necesita volver, ir al final del método, leer el valor de retorno de la pila y luego devolver el valor"

Entonces, ¿qué ventaja tiene se ofrece a la depuración?

Si tiene más de una declaración de devolución en el código, ambas "saltarán" al final del código antes de leer el valor de retorno de la pila. Esto le permite exactamente un lugar (el corchete de cierre del método) donde puede poner un punto de quiebre y modificar el valor de retorno antes de que se devuelva al método de llamada. Muy útil ¿no?

¿Es incorrecta la versión reescrita de alguna manera?

No hay nada incorrecto en su código. De hecho, si construyes el original en modo de lanzamiento y verificas el CIL generado, notarás que es casi igual al tuyo.

2

Descargo de responsabilidad: yo no soy un experto en IL por cualquier medio.

  1. ¿Cuál es el propósito de la operación de nop?

    Hubo una gran discusión sobre esto en términos de x86 ASM en programmers.stackexchange.com hace un tiempo, mira aquí: Purpose of NOP instruction and align statement in x86 assembly. Sería esencialmente lo mismo.

  2. ¿Cuál es el propósito de los br.s al final de los métodos en el original?

    Esto es solo una rama hasta el final del método. Si tuviera varias rutas de retorno en esta función, tendría más sentido mirarlas. Tal como está, el compilador lo ha incluido en lugar de optimizarlo (posiblemente un compilador lo optimice).

  3. ¿Es incorrecta la versión reescrita de alguna manera?

    No es que yo pueda ver. Acabas de despojar a la mayor parte del trabajo del compilador que no es necesario para una aplicación tan simple. Si tuviera que hacer más adiciones a este código, entonces se requeriría la IL adicional aquí para completar sus tareas.

+0

Creo que NOP en IL realmente no se puede usar maliciosamente de la forma en que se describe la respuesta que vinculó. Con IL, el compilador verifica el código antes de que JIT lo compile y creo que tampoco hay forma de hacer un salto que pueda saltar a un rango de direcciones. – svick

+0

@svick Si bien eso es cierto ... hay una gran cantidad de información adicional sobre ese hilo :) –