Zunächst entschuldige ich mich für die Länge dieser Frage.
Ich bin der Autor von IronScheme . Vor kurzem habe ich hart daran gearbeitet, anständige Debug-Informationen auszugeben, damit ich den 'nativen' .NET-Debugger verwenden kann.
Obwohl dies teilweise erfolgreich war, habe ich einige Kinderkrankheiten.
Das erste Problem hängt mit dem Treten zusammen.
Da Scheme eine Ausdruckssprache ist, wird im Gegensatz zu den wichtigsten .NET-Sprachen, die auf Anweisungen (oder Zeilen) basieren, alles in Klammern gesetzt.
Der ursprüngliche Code (Schema) sieht folgendermaßen aus:
(define (baz x)
(cond
[(null? x)
x]
[(pair? x)
(car x)]
[else
(assertion-violation #f "nooo" x)]))
Ich habe absichtlich jeden Ausdruck in einer neuen Zeile angeordnet.
Der ausgegebene Code, der in C # (über ILSpy) umgewandelt wird, sieht folgendermaßen aus:
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));
}
Wie Sie sehen können, ziemlich einfach.
Hinweis: Wenn der Code in C # in einen bedingten Ausdruck (? :) umgewandelt würde, wäre das Ganze nur ein Debug-Schritt. Denken Sie daran.
Hier ist die IL-Ausgabe mit Quell- und Zeilennummern:
.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'
Hinweis: Um zu verhindern, dass der Debugger einfach die gesamte Methode hervorhebt, mache ich den Methodeneintrittspunkt nur 1 Spalte breit.
Wie Sie sehen können, wird jeder Ausdruck einer Linie korrekt zugeordnet.
Nun das Problem mit dem Steppen (getestet auf VS2010, aber gleiches / ähnliches Problem auf VS2008):
Diese werden mit IgnoreSymbolStoreSequencePoints
nicht angewendet.
- Rufen Sie baz mit null arg auf, es funktioniert korrekt. (null? x) gefolgt von x.
- Rufen Sie baz mit Cons arg auf, es funktioniert korrekt. (null? x) dann (Paar? x) dann (Auto x).
- Rufen Sie baz mit anderen Argumenten an, es schlägt fehl. (null? x) dann (Paar? x) dann (Auto x) dann (Behauptung-Verletzung ...).
Bei der Anwendung IgnoreSymbolStoreSequencePoints
(wie empfohlen):
- Rufen Sie baz mit null arg auf, es funktioniert korrekt. (null? x) gefolgt von x.
- Rufen Sie baz mit Cons arg an, es schlägt fehl. (null? x) dann (Paar? x).
- Rufen Sie baz mit anderen Argumenten an, es schlägt fehl. (null? x) dann (Paar? x) dann (Auto x) dann (Behauptung-Verletzung ...).
Ich finde auch in diesem Modus, dass einige Zeilen (hier nicht gezeigt) falsch hervorgehoben sind, sie sind um 1 versetzt.
Hier sind einige Ideen, was die Ursachen sein könnten:
- Tailcalls verwirren den Debugger
- Überlappende Positionen (hier nicht gezeigt) verwirren den Debugger (dies ist sehr gut, wenn ein Haltepunkt festgelegt wird).
- ????
Das zweite, aber auch schwerwiegende Problem ist, dass der Debugger in einigen Fällen keine Haltepunkte erreicht.
Der einzige Ort, an dem ich den Debugger dazu bringen kann, korrekt (und konsistent) zu brechen, ist der Methodeneintrittspunkt.
Die Situation wird etwas besser, wenn sie IgnoreSymbolStoreSequencePoints
nicht angewendet wird.
Fazit
Es kann sein, dass der VS-Debugger einfach nur fehlerhaft ist :(
Verweise:
Update 1:
Mdbg funktioniert nicht für 64-Bit-Assemblys. Das ist also raus. Ich habe keine 32-Bit-Maschinen mehr, auf denen ich es testen kann. Update: Ich bin sicher, dass dies kein großes Problem ist. Hat jemand eine Lösung? Edit: Ja, dumm mich, starte mdbg einfach unter der x64-Eingabeaufforderung :)
Update 2:
Ich habe eine C # -App erstellt und versucht, die Zeileninformationen zu analysieren.
Meine Ergebnisse:
- Nach jeder
brXXX
Anweisung benötigen Sie einen Sequenzpunkt (falls nicht gültig, auch bekannt als '#line hidden', geben Sie a ausnop
). brXXX
Geben Sie vor jeder Anweisung eine '#line hidden' und a ausnop
.
Wenn Sie dies anwenden, werden die Probleme jedoch nicht behoben (allein?).
Wenn Sie jedoch Folgendes hinzufügen, erhalten Sie das gewünschte Ergebnis :)
- Nach dem
ret
, emit a '#line versteckt' und einnop
.
Dies verwendet den Modus, in dem IgnoreSymbolStoreSequencePoints
nicht angewendet wird. Bei der Anwendung werden einige Schritte noch übersprungen :(
Hier ist die IL-Ausgabe, wenn oben angewendet wurde:
.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'
Update 3:
Problem mit dem obigen 'Semi-Fix'. PEverify meldet Fehler auf allen Methoden aufgrund des nop
nach ret
. Ich verstehe das Problem nicht wirklich. Wie kann eine nop
Pausenüberprüfung nach a ret
. Es ist wie toter Code (außer dass es NICHT einmal Code ist) ... Na ja, das Experimentieren geht weiter.
Update 4:
Zurück zu Hause, den 'nicht überprüfbaren' Code entfernt, der auf VS2008 ausgeführt wird, und die Dinge sind viel schlimmer. Vielleicht könnte es die Antwort sein, nicht überprüfbaren Code zum ordnungsgemäßen Debuggen auszuführen. Im Freigabemodus sind alle Ausgaben weiterhin überprüfbar.
Update 5:
Ich habe jetzt entschieden, dass meine obige Idee die einzig praktikable Option ist. Obwohl der generierte Code nicht überprüfbar ist, habe ich noch keinen gefunden VerificationException
. Ich weiß nicht, welche Auswirkungen dieses Szenario auf den Endbenutzer haben wird.
Als Bonus wurde auch mein zweites Problem gelöst. :) :)
Hier ist ein kleiner Screencast von dem, was ich am Ende hatte. Es trifft Haltepunkte, macht das richtige Steppen (rein / raus / rüber) usw. Alles in allem der gewünschte Effekt.
Ich akzeptiere dies jedoch immer noch nicht als den Weg, dies zu tun. Es fühlt sich für mich übermäßig hackig an. Eine Bestätigung zum eigentlichen Problem wäre schön.
Update 6:
Hatte gerade die Änderung, um den Code auf VS2010 zu testen, scheint es einige Probleme zu geben:
Der erste Anruf wird jetzt nicht richtig ausgeführt. (Assertion-Verletzung ...) wird getroffen. Andere Fälle funktionieren gut.Einige alte Codes haben unnötige Positionen ausgegeben. Code entfernt, funktioniert wie erwartet. :) :)- Im Ernst, Haltepunkte schlagen beim zweiten Aufruf des Programms fehl (bei Verwendung der In-Memory-Kompilierung scheint das Dumping der Assembly in eine Datei die Haltepunkte wieder glücklich zu machen).
Beide Fälle funktionieren unter VS2008 korrekt. Der Hauptunterschied besteht darin, dass unter VS2010 die gesamte Anwendung für .NET 4 kompiliert und unter VS2008 mit .NET 2 kompiliert wird. Beide werden mit 64-Bit ausgeführt.
Update 7:
Wie bereits erwähnt, lief MDBG unter 64-Bit. Leider gibt es auch das Haltepunktproblem, bei dem es nicht erneut funktioniert, wenn ich das Programm erneut ausführe (dies bedeutet, dass es neu kompiliert wird, sodass nicht dieselbe Assembly, sondern immer noch dieselbe Quelle verwendet wird).
Update 8:
Ich habe auf der MS Connect-Website einen Fehler bezüglich des Haltepunktproblems gemeldet.
Update: Behoben
Update 9:
Nach langem Überlegen scheint der einzige Weg, den Debugger glücklich zu machen, SSA zu sein, sodass jeder Schritt isoliert und sequentiell sein kann. Ich muss diese Vorstellung jedoch noch beweisen. Aber es scheint logisch. Natürlich wird das Bereinigen von Temps von SSA das Debuggen unterbrechen, aber das ist leicht umzuschalten, und das Verlassen dieser Temps hat nicht viel Aufwand.
nop
schlägt das Steppen ohne das s fehl (ich werde dies erneut überprüfen, um sicherzugehen). Es ist ein Opfer, das ich wohl bringen müsste. Es ist nicht so, dass VS sogar ohne Administratorrechte ausgeführt werden kann :) Übrigens mit Reflection.Emit über das DLR (ein sehr gehacktes, früh verzweigtes).