Skip to content
← Todas las publicaciones
· Delta1 Labs Decompilador.NETIL

Cómo ver el IL de un ensamblado .NET (y por qué)

Cómo ver el IL de un ensamblado .NET, qué es realmente el CIL y cuándo leer IL supera al C# decompilado — con un ejemplo práctico pequeño y correcto de C# a IL.

La mayoría de las veces, cuando abres un ensamblado que no escribiste, quieres C# legible. Un decompilador te lo da, y es lo correcto por defecto. Pero de vez en cuando el C# decompilado hace algo que te hace entornar los ojos — una conversión que no debería estar ahí, una llamada que no esperabas, una salida que se lee de forma rara pero es “técnicamente correcta”. Ese es el momento de dejar de leer la reconstrucción y mirar lo que el ensamblado contiene realmente: el IL.

Este artículo trata de la capa que hay debajo del C#: qué es el IL, cómo verlo y — lo más útil — cuándo leer IL es realmente mejor que leer C# decompilado. Mantendré la teoría corta y terminaré con un ejemplo práctico pequeño y correcto que puedes reproducir.

En resumen: el IL es el conjunto de instrucciones real dentro de cada DLL gestionada; el C# decompilado es una reconstrucción de él. Para ver el IL, abre el ensamblado en una herramienta como Glass.NET y cambia cualquier miembro de su vista de C# a su vista de IL (o usa el ildasm.exe del SDK). Lee IL cuando el C# te sorprenda, cuando necesites ver comportamiento oculto como el boxing, o cuando el C# no se reconstruya limpiamente.

Qué es realmente el IL

Cuando compilas un proyecto de C#, el compilador no emite código máquina. Emite Common Intermediate Language — CIL, a menudo llamado simplemente IL (e históricamente MSIL). El IL es un conjunto de instrucciones compacto y basado en pila: en lugar de registros, la mayoría de las operaciones apilan y desapilan valores en una pila de evaluación. Junto al IL, el compilador escribe una rica tabla de metadatos que describe cada tipo, método, campo y parámetro por nombre y firma. Ambos viven dentro del .dll o .exe.

El código nativo no se produce hasta el tiempo de ejecución. Cuando un método se llama por primera vez, el compilador JIT (just-in-time) convierte su IL en instrucciones máquina para la CPU actual. Por eso la misma DLL gestionada se ejecuta en distintas arquitecturas y — relevante aquí — por eso el ensamblado lleva una descripción completa y legible de tu programa. Un decompilador reconstruye C# a partir de ese IL y esos metadatos; un visor de IL simplemente te muestra el IL directamente, sin paso de reconstrucción en medio. Hay más contexto en cómo decompilar una DLL de .NET a C#.

Cómo ver el IL

Tienes varias opciones, de la más amable a la más de bajo nivel:

  • Un decompilador con vista de IL. La vía más cómoda. Abre el ensamblado, selecciona un método y cambia ese miembro de su vista de C# a su vista de IL. En Glass.NET esto es un conmutador por miembro, así que puedes leer el C# reconstruido y el IL subyacente del mismo método en paralelo sin salir de la herramienta. No hace falta código fuente ni PDB.
  • ildasm.exe. El Desensamblador de IL viene con el SDK de .NET / Windows SDK. Es la forma clásica y sin florituras de volcar el IL y los metadatos de un ensamblado a una ventana o a un archivo de texto. Genial para scripting y para un volcado canónico.
  • monodis / otros desensambladores. Existen alternativas multiplataforma si no estás en la cadena de herramientas de Microsoft.

Para el trabajo interactivo — saltar a un método, leer su C# y luego confirmar contra su IL — una vista de decompilador es con diferencia la más rápida, porque estás a una pulsación de tecla del C# que ya estabas leyendo.

Cuándo leer IL supera a leer C#

El C# decompilado es más fácil de leer, así que ¿por qué bajar alguna vez al IL? Porque el C# es una reconstrucción, y hay casos en los que la reconstrucción oculta o reconfigura algo que necesitas ver:

  • El C# parece sorprendente. La coincidencia de patrones de un decompilador produce de vez en cuando C# válido que se lee de forma rara. El IL te dice lo que el método hace literalmente, para que puedas confirmar el comportamiento en lugar de dudar del decompilador.
  • Comportamiento oculto. El boxing, las conversiones implícitas, la concatenación de string reducida a String.Concat, el objetivo exacto de una llamada virtual frente a una no virtual — son invisibles o ambiguos en C# pero explícitos en IL.
  • Orden de evaluación y cortocircuito. Cuando necesitas saber con precisión qué se evalúa y en qué orden, el IL es inequívoco.
  • El C# no se reconstruye limpiamente. Los ensamblados muy optimizados, ofuscados o inusuales a veces derrotan la decompilación limpia. El IL sigue siendo totalmente legible incluso cuando el C# no lo es.
  • Aprender cómo funciona el compilador. Si quieres entender cómo se reduce una función del lenguaje — cómo yield, async, lock o una expresión switch se convierten en IL — leer el IL es justo el objetivo.

Para todo lo demás — entender la lógica, rastrear un error, recuperar el código fuente — el C# decompilado es la mejor herramienta. El IL es el bisturí al que recurres cuando el C# no basta.

Un pequeño ejemplo práctico

Toma el método más aburrido imaginable:

public int Add(int a, int b)
{
    return a + b;
}

En una compilación de release sin variables locales, su IL es casi un mapa uno a uno de la máquina de pila:

.method public hidebysig instance int32 Add(int32 a, int32 b) cil managed
{
  .maxstack 2
  ldarg.1   // push a
  ldarg.2   // push b
  add       // pop both, push a + b
  ret       // return the top of the stack
}

ldarg.1 y ldarg.2 apilan los dos argumentos (ldarg.0 es this en un método de instancia), add los desapila y apila la suma, y ret la devuelve. Nada sorprendente — que es justo el punto: para código sencillo, el IL simplemente confirma lo que dice el C#.

Ahora observa cómo el IL revela algo que el C# oculta. Esta línea:

object o = 42;

se lee como una asignación trivial. Pero object es un tipo por referencia y 42 es un int (un tipo por valor), así que el runtime tiene que hacerle boxing — reservar un objeto en el montículo para contener el valor. El C# no lo muestra; el IL sí:

ldc.i4.s   42          // push the constant 42
box        [System.Runtime]System.Int32   // box the int into an object
stloc.0                // store into local 'o'

Esa instrucción box es una reserva en el montículo que nunca detectarías en el código fuente. En un bucle intensivo, detectarla en el IL es la diferencia entre “¿por qué esto reserva memoria?” y una respuesta de verdad. Este es el valor cotidiano de una vista de IL — hace visible lo invisible.

Límites honestos

Una vista de IL es una herramienta de lectura, no un descodificador mágico:

  • El IL es de más bajo nivel, así que es más lento de leer. Un método que son tres líneas de C# puede ser una docena de instrucciones de IL. Úsalo de forma selectiva.
  • Sigue siendo estático. Ver el IL no ejecuta el código. Si necesitas observar valores en tiempo de ejecución, ese es el trabajo de un depurador en vivo — dnSpyEx para depuración gestionada.
  • Los nombres de los metadatos siguen aplicándose. Los ensamblados ofuscados tienen nombres ofuscados también en el IL; las instrucciones son legibles, pero los identificadores pueden no tener sentido.
  • Analiza solo aquello sobre lo que tienes derecho. Leer el IL de tus propios binarios o de las dependencias que puedes inspeccionar es rutinario; el software de terceros puede estar restringido por su licencia.

Ve el IL por ti mismo

Si alguna vez has querido confirmar lo que un método hace realmente — no lo que el decompilador cree que hace — una vista de IL es la respuesta. Glass.NET es un decompilador de .NET gratuito que te muestra ambos: lee el C# reconstruido y luego cambia cualquier miembro a su IL en una pulsación de tecla. Sin licencia, sin puestos, sin registro. Descárgalo, abre un ensamblado y cambia a la vista de IL. Para el flujo de lectura más completo, consulta cómo depurar una app .NET cuando no tienes el código fuente.

Prueba Nebula.NET

Endurece tu código .NET en minutos — empieza con la edición gratuita.