Stellen Sie sicher, dass Ihre .NET-Sprache im Debugger korrekt ausgeführt wird


135

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 IgnoreSymbolStoreSequencePointsnicht angewendet.

  1. Rufen Sie baz mit null arg auf, es funktioniert korrekt. (null? x) gefolgt von x.
  2. Rufen Sie baz mit Cons arg auf, es funktioniert korrekt. (null? x) dann (Paar? x) dann (Auto x).
  3. 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):

  1. Rufen Sie baz mit null arg auf, es funktioniert korrekt. (null? x) gefolgt von x.
  2. Rufen Sie baz mit Cons arg an, es schlägt fehl. (null? x) dann (Paar? x).
  3. 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 IgnoreSymbolStoreSequencePointsnicht angewendet wird.

Fazit

Es kann sein, dass der VS-Debugger einfach nur fehlerhaft ist :(

Verweise:

  1. Debuggen einer CLR / .NET-Sprache

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 brXXXAnweisung benötigen Sie einen Sequenzpunkt (falls nicht gültig, auch bekannt als '#line hidden', geben Sie a aus nop).
  • brXXXGeben Sie vor jeder Anweisung eine '#line hidden' und a aus nop.

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 ein nop.

Dies verwendet den Modus, in dem IgnoreSymbolStoreSequencePointsnicht 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 nopnach ret. Ich verstehe das Problem nicht wirklich. Wie kann eine nopPausenü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:

  1. 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. :) :)
  2. 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.


Alle Ideen sind willkommen, ich werde sie ausprobieren und die Ergebnisse an die Frage anhängen. Versuchen Sie als erstes 2 mdbg und schreiben Sie den gleichen Code in C # und vergleichen Sie.
Leppie

6
Was ist die Frage?
George Stocker

9
Ich denke, seine Frage lautet: "Warum geht meine Sprache nicht richtig?"
McKay

1
Wenn Sie peverify nicht bestehen, können Sie nicht in Situationen mit teilweiser Vertrauenswürdigkeit ausgeführt werden, z. B. von einem zugeordneten Laufwerk oder in vielen Plugin-Hosting-Setups. Wie generieren Sie Ihre IL, Reflection.Emit oder über Text + Ilasm? In beiden Fällen können Sie die .line 16707566,16707566: 0,0 '' immer selbst eingeben, damit Sie sich keine Sorgen machen müssen, dass sie nach der Rückkehr ein Nop setzen.
Hippiehunter

@ Hippiehunter: Danke. Leider nopschlä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).
Leppie

Antworten:


26

Ich bin Ingenieur im Visual Studio Debugger-Team.

Korrigieren Sie mich, wenn ich falsch liege, aber es scheint, dass das einzige verbleibende Problem darin besteht, dass beim Wechsel von PDBs zum dynamischen .NET 4-Kompilierungssymbolformat einige Haltepunkte übersehen werden.

Wir würden wahrscheinlich einen Repro benötigen, um das Problem genau zu diagnostizieren. Hier sind jedoch einige Hinweise, die helfen könnten.

  1. VS (2008+) kann als Nicht-Administrator ausgeführt werden
  2. Werden beim zweiten Mal Symbole geladen? Sie können testen, indem Sie einbrechen (durch Ausnahme oder durch Aufrufen von System.Diagnostic.Debugger.Break ()).
  3. Gibt es unter der Annahme, dass Symbole geladen werden, einen Repro, den Sie uns senden könnten?
  4. Der wahrscheinliche Unterschied besteht darin, dass sich das Symbolformat für dynamisch kompilierten Code zwischen .NET 2 (PDB-Stream) und .NET 4 (IL DB, glaube ich, haben sie es genannt?) Zu 100% unterscheidet.
  5. Das Geräusch des Nops ist ungefähr richtig. Siehe Regeln zum Generieren impliziter Sequenzpunkte weiter unten.
  6. Sie müssen nicht wirklich Dinge in verschiedenen Zeilen ausgeben. Standardmäßig führt VS "Symbolanweisungen" aus, wobei Sie als Compiler-Writer definieren können, was "Symbolanweisung" bedeutet. Wenn Sie also möchten, dass jeder Ausdruck ein separates Element in der Symboldatei ist, funktioniert dies einwandfrei.

Die JIT erstellt einen impliziten Sequenzpunkt basierend auf den folgenden Regeln: 1. IL nop-Anweisungen 2. IL-Stapel leere Punkte 3. Die IL-Anweisung unmittelbar nach einer Aufrufanweisung

Wenn sich herausstellt, dass wir einen Repro benötigen, um Ihr Problem zu lösen, können Sie einen Verbindungsfehler melden und Dateien sicher über dieses Medium hochladen.

Aktualisieren:

Wir empfehlen anderen Benutzern, bei denen dieses Problem auftritt, die Entwicklervorschau von Dev11 unter http://www.microsoft.com/download/en/details.aspx?displaylang=de&id=27543 zu testen und Feedback zu geben. (Muss auf 4.5 abzielen)

Update 2:

Leppie hat den Fix überprüft, der für ihn in der Beta-Version von Dev11 funktioniert, die unter http://www.microsoft.com/visualstudio/11/en-us/downloads verfügbar ist, wie im Verbindungsfehler https://connect.microsoft angegeben. de / VisualStudio / feedback / details / 684089 / .

Vielen Dank,

Luke


Vielen Dank. Ich werde versuchen, bei Bedarf Repro zu machen.
Leppie

Was die Bestätigung betrifft, ja, Sie haben Recht, aber ich würde es trotzdem vorziehen, die Schrittprobleme zu lösen, ohne die Überprüfbarkeit zu beeinträchtigen. Aber wie gesagt, ich bin bereit, das zu opfern, wenn ich möglicherweise beweisen könnte, dass es niemals eine Laufzeit werfen wird VerificationException.
Leppie

Punkt 6 habe ich für diese Frage einfach "zeilenbasiert" gemacht. Der Debugger tritt tatsächlich korrekt in / out / over-Ausdrücke ein, so wie ich es beabsichtige :) Das einzige Problem bei der Vorgehensweise besteht darin, Haltepunkte für die 'inneren' Ausdrücke festzulegen, wenn ein 'äußerer' Ausdruck sie abdeckt. Der Debugger in VS möchte in der Regel den äußersten Ausdruck als Haltepunkt verwenden.
Leppie

Ich glaube, ich habe das Breakpoint-Problem isoliert. Während der Ausführung unter CLR2 scheinen die Haltepunkte beim erneuten Ausführen des Codes neu bewertet zu werden, wenn auch neuer Code. In CLR4 bleiben die Haltepunkte nur beim ursprünglichen Code. Ich habe eine kleine Screen des CLR4 Verhalten @ gemacht screencast.com/t/eiSilNzL5Nr . Beachten Sie, dass sich der Typname zwischen den Aufrufen ändert.
Leppie

Ich habe einen Fehler beim Verbinden gemeldet. Der Repro ist ganz einfach :) connect.microsoft.com/VisualStudio/feedback/details/684089
Leppie

3

Ich bin Ingenieur im SharpDevelop Debugger-Team :-)

Hast du das Problem gelöst?

Haben Sie versucht, es in SharpDevelop zu debuggen? Wenn es in .NET einen Fehler gibt, frage ich mich, ob wir eine Problemumgehung implementieren müssen. Mir ist dieses Problem nicht bekannt.

Haben Sie versucht, es in ILSpy zu debuggen? Besonders ohne Debug-Symbole. Es würde C # -Code debuggen, aber es würde uns sagen, ob die IL-Anweisungen gut debuggbar sind. (Beachten Sie, dass der ILSpy-Debugger Beta ist)

Kurznotizen zum ursprünglichen IL-Code:

  • .line 19,19: 6,15 '' kommt zweimal vor?
  • .line 20,20: 7,14 '' startet nicht am impliziten Sequenzpunkt (Stapel ist nicht leer). ich bin besorgt
  • .line 20,20: 7,14 '' enthält den Code für "car x" (gut) sowie "#f nooo x" (schlecht?)
  • in Bezug auf die nop nach ret. Was ist mit stloc, ldloc, ret? Ich denke, C # verwendet diesen Trick, um ret zu einem bestimmten Sequenzpunkt zu machen.

David


+1 Danke für das Feedback, als ersten Punkt, unglückliche Nebenwirkung vom Codegenerator, scheint aber harmlos. Zweiter Punkt, das ist genau das, was ich brauche, aber ich sehe Ihren Punkt, werde ihn untersuchen. Ich bin mir nicht sicher, was du mit dem letzten Punkt meinst.
Leppie

Der dritte Punkt war, dass .line 22,22: 7,40 '' vor IL_0020 stehen sollte. Oder es sollte etwas vor IL_0020 sein, sonst zählt der Code immer noch als .line 20,20: 7,14 ''. Der vierte Punkt ist "ret, nop" und könnte durch "sloc, .line, ldloc, ret" ersetzt werden. Ich habe das Muster schon einmal gesehen, vielleicht war es überflüssig, aber vielleicht hatte es einen Grund.
Dsrbecky

Ich kann den stloc nicht machen, ldloc vor ret, da ich den Schwanzruf verlieren werde. Ah, ich verstehe, Sie haben sich auf die ursprüngliche Ausgabe bezogen?
Leppie
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.