.Net Reflector y ILSpy, ¿podrían inferir mejor el código a partir del IL?

Muchas veces uno cree que el código que .Net Reflector muestra es fiel reflejo de lo que el desarrollador escribió, pero obviamente eso no puede ser cierto ya que esta herramienta toma el IL de un ensamblado y trata de mostrar su equivalente en los lenguajes que se le pida (C#, VB.NET, F# entre otros). Claro que muchas veces hacen tan buen trabajo que uno se olvida de eso.

Como a ILSpy le falta una vueltita de rosca en este aspecto, esto se hace más evidente ya que uno termina viendo código menos pulido como instrucciones GOTOs y otras yerbas en el propio código que uno escribió. Ahora bien, ¿que sucede cuando uno escribe 2 métodos con distintas instrucciones de un lenguaje pero equivalentes en funcionamiento? ¿Pueden estas herramientas hacer un buen trabajo?

Para responder a esta pregunta escribí tres métodos que hacen exactamente lo mismo: imprimen 100 líneas en la consola con los números del 0 al 99:

image
Quería ver si .Net Reflector y ILSpy podían ver la diferencia y la respuesta es: NO, NO PUEDEN. La razón es obvia ya que los tres métodos generan exactamente el mismo código intermedio (IL):

image

Dado que los tres métodos son idénticos bit a bit, ninguna herramienta podría determinar si corresponden a una instrucción for, while o a un enredo de gotos. Tampoco podríamos inferir cual fue el lenguaje con que se escribieron estos métodos (aunque eso poco importa). No obstante, esta es la estructura típica de un bucle for y por lo eso es que tanto .Net Reflector como ILSpy, cuando se les pide que muestren el código C# equivalente, muestran el siguiente fragmento para los tres métodos:

image

Ahora bien, el código IL es idéntico cuando se lo compila en modo Release pero no así cuando se lo compila en modo Debug ya que en este último caso, entre cada instrucción del lenguaje (digamos C#) se coloca una instrucción nop para habilitarnos a poner puntos de interrupción en partes de nuestro código que no se traducen a IL (no son instrucciones ejecutables). Por ejemplo, gracias a estos nops es que podemos poner un breakpoint al inicio de un bloque en modo Debug pero no así en modo Release:

image
En modo Debug esto este breakpoint es válido mientras que en modo Release el depurador pone automáticamente el breakpoint en la primera línea ejecutable despues de la llave {.

image
Ahora bien, dado que cada compilador agrega distinto número de instrucciones nop (esto podría ser falso ya que no probé todos los compiladores, obvio!) a los ensamblados compilador en modo Debug, herramientas como .Net Reflector y ILSpy podrían determinar cual es el lenguaje (en realidad, cual es el compilador) con que se generó ese ensamblado y también saber que un mismo algoritmo originalmente se desarrolló a partir de distintos conjuntos de instrucciones. Veamos el IL de los tres métodos originales en Modo Debug:

image

Como se ve estos tres ahora son diferentes y son aún más diferentes si los compilamos en VB.Net por esto es que digo que sería posible que cuando un ensamblado ha sido compilado en modo debug, estas herramientas cuentan con más información para inferir el código original. Y ya sé que el mundo no es solo C# y VB.Net y que esto quizás no vale la pena, solo digo que si estas herramientas quieren mostrar el código equivalente al IL en algunos de estos lenguajes, y el ensamblado está compilado en modo debug (casi nunca lo está), entonces podrían usar esta info para mostrar el código más parecido al original.

Sin categoría

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *