En primer lugar, me disculpo por la longitud de esta pregunta.
Soy el autor de IronScheme . Recientemente he estado trabajando duro para emitir información de depuración decente, para poder usar el depurador .NET 'nativo'.
Si bien esto ha sido parcialmente exitoso, me encuentro con algunos problemas iniciales.
El primer problema está relacionado con los pasos.
Debido a que Scheme es un lenguaje de expresión, todo tiende a estar entre paréntesis, a diferencia de los principales lenguajes .NET que parecen estar basados en sentencias (o líneas).
El código original (Esquema) se ve así:
(define (baz x)
(cond
[(null? x)
x]
[(pair? x)
(car x)]
[else
(assertion-violation #f "nooo" x)]))
A propósito he presentado cada expresión en una nueva línea.
El código emitido se transforma en C # (a través de ILSpy) se ve así:
public static object ::baz(object x)
{
if (x == null)
{
return x;
}
if (x is Cons)
{
return Builtins.Car(x);
}
return #.ironscheme.exceptions::assertion-violation+(
RuntimeHelpers.False, "nooo", Builtins.List(x));
}
Como puedes ver, bastante simple.
Nota: Si el código se transformó en una expresión condicional (? :) en C #, todo sería solo un paso de depuración, tenlo en cuenta.
Aquí está la salida IL con números de fuente y línea:
.method public static object '::baz'(object x) cil managed
{
// Code size 56 (0x38)
.maxstack 6
.line 15,15 : 1,2 ''
//000014:
//000015: (define (baz x)
IL_0000: nop
.line 17,17 : 6,15 ''
//000016: (cond
//000017: [(null? x)
IL_0001: ldarg.0
IL_0002: brtrue IL_0009
.line 18,18 : 7,8 ''
//000018: x]
IL_0007: ldarg.0
IL_0008: ret
.line 19,19 : 6,15 ''
//000019: [(pair? x)
.line 19,19 : 6,15 ''
IL_0009: ldarg.0
IL_000a: isinst [IronScheme]IronScheme.Runtime.Cons
IL_000f: ldnull
IL_0010: cgt.un
IL_0012: brfalse IL_0020
IL_0017: ldarg.0
.line 20,20 : 7,14 ''
//000020: (car x)]
IL_0018: tail.
IL_001a: call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
IL_001f: ret
IL_0020: ldsfld object
[Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
IL_0025: ldstr "nooo"
IL_002a: ldarg.0
IL_002b: call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
.line 22,22 : 7,40 ''
//000021: [else
//000022: (assertion-violation #f "nooo" x)]))
IL_0030: tail.
IL_0032: call object [ironscheme.boot]#::
'ironscheme.exceptions::assertion-violation+'(object,object,object)
IL_0037: ret
} // end of method 'eval-core(033)'::'::baz'
Nota: Para evitar que el depurador simplemente resalte todo el método, hago que el punto de entrada del método tenga solo 1 columna de ancho.
Como puede ver, cada expresión se asigna correctamente a una línea.
Ahora el problema con los pasos (probado en VS2010, pero el mismo problema / similar en VS2008):
Estos son con IgnoreSymbolStoreSequencePoints
no aplicado.
- Llame baz con argumento nulo, funciona correctamente. (nulo? x) seguido de x.
- Llame baz con Cons arg, funciona correctamente. (nulo? x) luego (par? x) luego (carro x).
- Llama a baz con otro argumento, falla. (nulo? x) luego (par? x) luego (carro x) luego (afirmación-violación ...).
Al aplicar IgnoreSymbolStoreSequencePoints
(según lo recomendado):
- Llame baz con argumento nulo, funciona correctamente. (nulo? x) seguido de x.
- Llama a baz con Cons arg, falla. (nulo? x) entonces (par? x).
- Llama a baz con otro argumento, falla. (nulo? x) luego (par? x) luego (carro x) luego (afirmación-violación ...).
También encuentro en este modo que algunas líneas (no mostradas aquí) están resaltadas incorrectamente, están apagadas en 1.
Aquí hay algunas ideas sobre cuáles podrían ser las causas:
- Tailcalls confunde al depurador
- Las ubicaciones superpuestas (no mostradas aquí) confunden al depurador (lo hace muy bien al establecer un punto de interrupción)
- ????
El segundo problema, pero también grave, es que el depurador no logra romper / alcanzar puntos de interrupción en algunos casos.
El único lugar donde puedo hacer que el depurador se rompa correctamente (y de manera consistente), es en el punto de entrada del método.
La situación mejora un poco cuando IgnoreSymbolStoreSequencePoints
no se aplica.
Conclusión
Puede ser que el depurador VS sea simplemente defectuoso :(
Referencias
Actualización 1:
Mdbg no funciona para ensamblados de 64 bits. Entonces eso está fuera. No tengo más máquinas de 32 bits para probarlo. Actualización: estoy seguro de que esto no es un gran problema, ¿alguien tiene una solución? Editar: Sí, tonto, simplemente inicia mdbg en el símbolo del sistema x64 :)
Actualización 2:
Creé una aplicación C # e intenté diseccionar la información de la línea.
Mis hallazgos:
- Después de cualquier
brXXX
instrucción, debe tener un punto de secuencia (si no es válido, también conocido como '#line hidden', emita anop
). - Antes de cualquier
brXXX
instrucción, emita un '#line hidden' y anop
.
Sin embargo, aplicar esto no soluciona los problemas (¿solo?).
Pero al agregar lo siguiente, se obtiene el resultado deseado :)
- Después
ret
, emita una '# línea oculta' y anop
.
Esto está usando el modo donde IgnoreSymbolStoreSequencePoints
no se aplica. Cuando se aplica, algunos pasos aún se omiten :(
Aquí está la salida IL cuando se ha aplicado lo anterior:
.method public static object '::baz'(object x) cil managed
{
// Code size 63 (0x3f)
.maxstack 6
.line 15,15 : 1,2 ''
IL_0000: nop
.line 17,17 : 6,15 ''
IL_0001: ldarg.0
.line 16707566,16707566 : 0,0 ''
IL_0002: nop
IL_0003: brtrue IL_000c
.line 16707566,16707566 : 0,0 ''
IL_0008: nop
.line 18,18 : 7,8 ''
IL_0009: ldarg.0
IL_000a: ret
.line 16707566,16707566 : 0,0 ''
IL_000b: nop
.line 19,19 : 6,15 ''
.line 19,19 : 6,15 ''
IL_000c: ldarg.0
IL_000d: isinst [IronScheme]IronScheme.Runtime.Cons
IL_0012: ldnull
IL_0013: cgt.un
.line 16707566,16707566 : 0,0 ''
IL_0015: nop
IL_0016: brfalse IL_0026
.line 16707566,16707566 : 0,0 ''
IL_001b: nop
IL_001c: ldarg.0
.line 20,20 : 7,14 ''
IL_001d: tail.
IL_001f: call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
IL_0024: ret
.line 16707566,16707566 : 0,0 ''
IL_0025: nop
IL_0026: ldsfld object
[Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
IL_002b: ldstr "nooo"
IL_0030: ldarg.0
IL_0031: call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
.line 22,22 : 7,40 ''
IL_0036: tail.
IL_0038: call object [ironscheme.boot]#::
'ironscheme.exceptions::assertion-violation+'(object,object,object)
IL_003d: ret
.line 16707566,16707566 : 0,0 ''
IL_003e: nop
} // end of method 'eval-core(033)'::'::baz'
Actualización 3:
Problema con el 'semi-arreglo' anterior. Peverify informa errores en todos los métodos debido a lo nop
posterior ret
. Realmente no entiendo el problema. ¿Cómo puede una nop
verificación de interrupción después de a ret
. Es como un código muerto (excepto que ni siquiera es código) ... Oh, bueno, la experimentación continúa.
Actualización 4:
De vuelta a casa ahora, eliminé el código 'no verificable', ejecuté en VS2008 y las cosas son mucho peores. Quizás ejecutar el código no verificable en aras de una depuración adecuada podría ser la respuesta. En el modo 'lanzamiento', toda la salida aún sería verificable.
Actualización 5:
Ahora he decidido que mi idea anterior es la única opción viable por ahora. Aunque el código generado no es verificable, aún no he encontrado ninguno VerificationException
. No sé cuál será el impacto en el usuario final con este escenario.
Como beneficio adicional, mi segundo problema también se ha resuelto. :)
Aquí hay un pequeño screencast de lo que terminé. Alcanza los puntos de interrupción, realiza pasos adecuados (entrada / salida / sobre), etc. En general, el efecto deseado.
Sin embargo, todavía no estoy aceptando esto como la forma de hacerlo. Se siente demasiado hacky para mí. Tener una confirmación sobre el problema real sería bueno.
Actualización 6:
Acabo de tener el cambio para probar el código en VS2010, parece haber algunos problemas:
La primera llamada ahora no pasa correctamente. (afirmación-violación ...) es golpeado. Otros casos funciona bien.Algunos códigos antiguos emitían posiciones innecesarias. Eliminado el código, funciona como se esperaba. :)- Más en serio, los puntos de interrupción fallan en la segunda invocación del programa (al usar la compilación en memoria, volcar el ensamblado en el archivo parece hacer felices nuevamente los puntos de interrupción).
Ambos casos funcionan correctamente bajo VS2008. La principal diferencia es que bajo VS2010, la aplicación completa se compila para .NET 4 y bajo VS2008, compila a .NET 2. Ambos ejecutan 64 bits.
Actualización 7:
Como se mencionó, obtuve mdbg ejecutándose bajo 64 bits. Desafortunadamente, también tiene el problema del punto de interrupción donde no se rompe si vuelvo a ejecutar el programa (esto implica que se vuelve a compilar, por lo que no usa el mismo ensamblaje, pero sigue usando la misma fuente).
Actualización 8:
He presentado un error en el sitio de MS Connect con respecto al problema del punto de interrupción.
Actualización: arreglado
Actualización 9:
Después de pensarlo mucho, la única forma de hacer feliz al depurador parece estar haciendo SSA, por lo que cada paso puede ser aislado y secuencial. Sin embargo, todavía tengo que demostrar esta noción. Pero parece lógico. Obviamente, limpiar las temperaturas de SSA interrumpirá la depuración, pero eso es fácil de alternar, y dejarlas no tiene mucha sobrecarga.
nop
s, el paso falla (lo verificaré nuevamente para estar seguro). Es un sacrificio que supongo que tendría que hacer. No es que VS incluso pueda ejecutarse sin derechos de administrador :) Por cierto, usando Reflection.Emit a través del DLR (uno muy ramificado que se ha pirateado).