In primo luogo, mi scuso per la lunghezza di questa domanda.
Sono l'autore di IronScheme . Recentemente ho lavorato duramente sull'emissione di informazioni di debug decenti, in modo da poter utilizzare il debugger .NET "nativo".
Anche se questo ha avuto in parte successo, mi imbatto in alcuni problemi di dentizione.
Il primo problema è legato allo stepping.
Poiché Scheme è un linguaggio di espressione, tutto tende ad essere racchiuso tra parentesi, a differenza dei principali linguaggi .NET che sembrano essere basati su istruzioni (o linee).
Il codice originale (Schema) è simile a:
(define (baz x)
(cond
[(null? x)
x]
[(pair? x)
(car x)]
[else
(assertion-violation #f "nooo" x)]))
Ho espressamente disposto ogni espressione su una nuova riga.
Il codice emesso si trasforma in C # (tramite ILSpy) è simile a:
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));
}
Come puoi vedere, abbastanza semplice.
Nota: se il codice fosse trasformato in un'espressione condizionale (? :) in C #, l'intera cosa sarebbe solo un passo di debug, tienilo a mente.
Ecco l'output IL con i numeri di sorgente e di riga:
.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: per impedire al debugger di evidenziare semplicemente l'intero metodo, faccio in modo che il punto di ingresso del metodo sia largo solo 1 colonna.
Come puoi vedere, ogni espressione è mappata correttamente su una linea.
Ora il problema con stepping (testato su VS2010, ma problema uguale / simile su VS2008):
Questi IgnoreSymbolStoreSequencePoints
non sono applicati.
- Chiama baz con null arg, funziona correttamente. (null? x) seguito da x.
- Chiama baz con contro arg, funziona correttamente. (null? x) quindi (pair? x) quindi (car x).
- Chiama baz con altri arg, fallisce. (null? x) quindi (pair? x) quindi (car x) quindi (asserzione-violazione ...).
Quando si applica IgnoreSymbolStoreSequencePoints
(come raccomandato):
- Chiama baz con null arg, funziona correttamente. (null? x) seguito da x.
- Chiama baz con contro arg, non riesce. (null? x) quindi (coppia? x).
- Chiama baz con altri arg, fallisce. (null? x) quindi (pair? x) quindi (car x) quindi (asserzione-violazione ...).
Trovo anche in questa modalità che alcune linee (non mostrate qui) sono evidenziate in modo errato, sono disattivate di 1.
Ecco alcune idee su quali potrebbero essere le cause:
- Tailcalls confonde il debugger
- Posizioni sovrapposte (non mostrate qui) confondono il debugger (lo fa molto bene quando si imposta un breakpoint)
- ????
Il secondo, ma anche grave, problema è che il debugger non riesce a rompere / colpire i punti di interruzione in alcuni casi.
L'unico posto in cui posso fare in modo che il debugger si interrompa correttamente (e coerentemente), è il punto di ingresso del metodo.
La situazione migliora leggermente quando IgnoreSymbolStoreSequencePoints
non viene applicata.
Conclusione
È possibile che il debugger VS sia semplicemente buggy :(
Riferimenti:
Aggiornamento 1:
Mdbg non funziona per gli assembly a 64 bit. Quindi è fuori. Non ho più macchine a 32 bit su cui testarlo. Aggiornamento: sono sicuro che questo non è un grosso problema, qualcuno ha una soluzione? Modifica: Sì, sciocco me, basta avviare mdbg al prompt dei comandi x64 :)
Aggiornamento 2:
Ho creato un'app C # e ho provato a sezionare le informazioni sulla linea.
Le mie scoperte:
- Dopo ogni
brXXX
istruzione è necessario disporre di un punto sequenza (se non valido anche noto come '#line hidden', emettere anop
). - Prima di qualsiasi
brXXX
istruzione, emetti un '#line nascosto' e unnop
.
L'applicazione di questo, tuttavia, non risolve i problemi (da solo?).
Ma aggiungendo quanto segue, si ottiene il risultato desiderato :)
- Dopo
ret
, emetti un '#line nascosto' e unnop
.
Questo utilizza la modalità in cui IgnoreSymbolStoreSequencePoints
non viene applicato. Se applicati, alcuni passaggi vengono comunque ignorati :(
Ecco l'output IL quando è stato applicato sopra:
.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'
Aggiornamento 3:
Problema con "semi-fix" sopra. Peverify riporta errori su tutti i metodi a causa del nop
dopo ret
. Non capisco davvero il problema. Come può una nop
verifica interruzione dopo a ret
. È come un codice morto (tranne per il fatto che NON è nemmeno un codice) ... Vabbè, la sperimentazione continua.
Aggiornamento 4:
Tornato a casa ora, ho rimosso il codice "non verificabile", in esecuzione su VS2008 e le cose vanno molto peggio. Forse l'esecuzione di codice non verificabile per il corretto debugging potrebbe essere la risposta. In modalità 'rilascio', tutto l'output sarebbe comunque verificabile.
Aggiornamento 5:
Ora ho deciso che la mia idea di cui sopra è l'unica opzione praticabile per ora. Sebbene il codice generato non sia verificabile, devo ancora trovarne uno VerificationException
. Non so quale sarà l'impatto sull'utente finale con questo scenario.
Come bonus, anche il mio secondo problema è stato risolto. :)
Ecco un piccolo screencast di quello che ho finito. Colpisce i punti di interruzione, fa il passo corretto (dentro / fuori / sopra), ecc. Tutto sommato, l'effetto desiderato.
Tuttavia, non sto ancora accettando questo come il modo di farlo. Mi sembra eccessivamente confuso. Avere una conferma sul vero problema sarebbe bello.
Aggiornamento 6:
Ho appena avuto la modifica per testare il codice su VS2010, sembra che ci siano alcuni problemi:
La prima chiamata ora non passa correttamente. (asserzione-violazione ...) viene colpito. Altri casi funziona bene.Alcuni vecchi codici emettevano posizioni non necessarie. Rimosso il codice, funziona come previsto. :)- Più seriamente, i punti di interruzione falliscono nella seconda invocazione del programma (usando la compilazione in memoria, il dumping dell'assembly su file sembra rendere di nuovo felici i punti di interruzione).
Entrambi questi casi funzionano correttamente con VS2008. La differenza principale è che in VS2010, l'intera applicazione viene compilata per .NET 4 e in VS2008, viene compilata in .NET 2. Entrambi in esecuzione a 64 bit.
Aggiornamento 7:
Come accennato, ho mdbg in esecuzione a 64 bit. Sfortunatamente, ha anche il problema del punto di interruzione in cui non riesce a rompersi se riesco a rieseguire il programma (questo implica che viene ricompilato, quindi non usando lo stesso assembly, ma usando sempre la stessa fonte).
Aggiornamento 8:
Ho segnalato un bug nel sito di MS Connect in merito al problema del punto di interruzione.
Aggiornamento: risolto
Aggiornamento 9:
Dopo una lunga riflessione, l'unico modo per rendere felice il debugger sembra essere SSA, quindi ogni passaggio può essere isolato e sequenziale. Devo ancora dimostrare questa nozione però. Ma sembra logico. Ovviamente, ripulire le temp da SSA interromperà il debug, ma è facile da attivare e lasciarle non ha molto sovraccarico.
nop
s, il passo fallisce (lo verificherò di nuovo per essere sicuro). È un sacrificio immagino che dovrei fare. Non è come se VS potesse funzionare anche senza i diritti di amministratore :) A proposito usando Reflection.Emit tramite il DLR (molto precocemente ramificato).