Rendre votre langage .NET pas correctement dans le débogueur

135

Premièrement, je m'excuse de la longueur de cette question.

Je suis l'auteur d' IronScheme . Récemment, j'ai travaillé dur pour émettre des informations de débogage correctes, afin de pouvoir utiliser le débogueur .NET «natif».

Bien que cela ait été en partie réussi, je rencontre des problèmes de démarrage.

Le premier problème est lié au pas.

Comme Scheme est un langage d'expression, tout a tendance à être entouré de parenthèses, contrairement aux principaux langages .NET qui semblent être basés sur des instructions (ou des lignes).

Le code d'origine (Scheme) ressemble à:

(define (baz x)
  (cond
    [(null? x) 
      x]
    [(pair? x) 
      (car x)]
    [else
      (assertion-violation #f "nooo" x)]))

J'ai volontairement disposé chaque expression sur une nouvelle ligne.

Le code émis se transforme en C # (via ILSpy) ressemble à ceci:

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));
}

Comme vous pouvez le voir, assez simple.

Remarque: Si le code était transformé en une expression conditionnelle (? :) en C #, le tout ne serait qu'une étape de débogage, gardez cela à l'esprit.

Voici la sortie IL avec les numéros de source et de ligne:

  .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'

Remarque: pour empêcher le débogueur de simplement mettre en évidence la méthode entière, je fais le point d'entrée de la méthode sur une seule colonne de large.

Comme vous pouvez le voir, chaque expression correspond correctement à une ligne.

Maintenant, le problème avec le pas à pas (testé sur VS2010, mais même problème / similaire sur VS2008):

Ceux-ci ne sont IgnoreSymbolStoreSequencePointspas appliqués.

  1. Appelez baz avec un argument nul, cela fonctionne correctement. (nul? x) suivi de x.
  2. Appelez baz avec Cons arg, cela fonctionne correctement. (nul? x) puis (paire? x) puis (voiture x).
  3. Appelez baz avec un autre argument, cela échoue. (nul? x) puis (paire? x) puis (voiture x) puis (assertion-violation ...).

Lors de l'application IgnoreSymbolStoreSequencePoints(comme recommandé):

  1. Appelez baz avec un argument nul, cela fonctionne correctement. (nul? x) suivi de x.
  2. Appelez baz avec Contre arg, ça échoue. (nul? x) puis (paire? x).
  3. Appelez baz avec un autre argument, cela échoue. (nul? x) puis (paire? x) puis (voiture x) puis (assertion-violation ...).

Je trouve également dans ce mode que certaines lignes (non représentées ici) ne sont pas correctement mises en évidence, elles sont décalées de 1.

Voici quelques idées quelles pourraient en être les causes:

  • Tailcalls confond le débogueur
  • Le chevauchement d'emplacements (non affichés ici) confond le débogueur (il le fait très bien lors de la définition d'un point d'arrêt)
  • ????

Le deuxième problème, mais également sérieux, est que le débogueur ne parvient pas à casser / atteindre les points d'arrêt dans certains cas.

Le seul endroit où je peux faire en sorte que le débogueur se brise correctement (et de manière cohérente) est au point d'entrée de la méthode.

La situation s'améliore un peu lorsqu'elle IgnoreSymbolStoreSequencePointsn'est pas appliquée.

Conclusion

Il se peut que le débogueur VS soit tout simplement bogué :(

Références:

  1. Rendre un langage CLR / .NET déboguable

Mise à jour 1:

Mdbg ne fonctionne pas pour les assemblys 64 bits. Donc c'est fini. Je n'ai plus de machines 32 bits pour le tester. Mise à jour: je suis sûr que ce n'est pas un gros problème, est-ce que quelqu'un a une solution? Edit: Oui, idiot, lancez simplement mdbg sous l'invite de commande x64 :)

Mise à jour 2:

J'ai créé une application C # et essayé de disséquer les informations de ligne.

Mes découvertes:

  • Après toute brXXXinstruction, vous devez avoir un point de séquence (s'il n'est pas valide aka '#line hidden', émettez un nop).
  • Avant toute brXXXinstruction, émettez un '#line hidden' et un nop.

L'application de cela ne résout cependant pas les problèmes (seul?).

Mais en ajoutant ce qui suit, donne le résultat souhaité :)

  • Ensuite ret, émettez un '#line hidden' et un nop.

Ceci utilise le mode où IgnoreSymbolStoreSequencePointsn'est pas appliqué. Lorsqu'elle est appliquée, certaines étapes sont toujours ignorées :(

Voici la sortie IL lorsque ci-dessus a été appliquée:

  .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'

Mise à jour 3:

Problème avec le 'semi-fix' ci-dessus. Peverify signale des erreurs sur toutes les méthodes en raison de l' nopaprès ret. Je ne comprends pas vraiment le problème. Comment une nopvérification peut-elle interrompre après un ret. C'est comme du code mort (sauf que ce n'est même PAS du code) ... Eh bien, l'expérimentation continue.

Mise à jour 4:

De retour à la maison maintenant, supprimé le code «invérifiable», fonctionnant sur VS2008 et les choses sont bien pires. Peut-être qu'exécuter du code non vérifiable pour un bon débogage pourrait être la réponse. En mode «release», toutes les sorties seraient toujours vérifiables.

Mise à jour 5:

J'ai maintenant décidé que mon idée ci-dessus est la seule option viable pour le moment. Bien que le code généré ne soit pas vérifiable, je n'en ai pas encore trouvé VerificationException. Je ne sais pas quel sera l'impact sur l'utilisateur final de ce scénario.

En prime, mon deuxième problème a également été résolu. :)

Voici un petit screencast de ce que j'ai fini avec. Il atteint les points d'arrêt, fait un pas correct (entrée / sortie / dépassement), etc. Dans l'ensemble, l'effet désiré.

Cependant, je n'accepte toujours pas cela comme moyen de le faire. Cela me semble trop piraté. Avoir une confirmation sur le vrai problème serait bien.

Mise à jour 6:

Je viens d'avoir le changement pour tester le code sur VS2010, il semble y avoir des problèmes:

  1. Le premier appel ne se déroule désormais pas correctement. (assertion-violation ...) est touché. D'autres cas fonctionnent bien. Certains anciens codes émettaient des positions inutiles. Suppression du code, fonctionne comme prévu. :)
  2. Plus sérieusement, les points d'arrêt échouent à la deuxième invocation du programme (en utilisant la compilation en mémoire, le vidage de l'assembly dans un fichier semble rendre les points d'arrêt heureux à nouveau).

Ces deux cas fonctionnent correctement sous VS2008. La principale différence est que sous VS2010, l'application entière est compilée pour .NET 4 et sous VS2008, compile vers .NET 2. Les deux exécutant 64 bits.

Mise à jour 7:

Comme mentionné, j'ai mdbg fonctionnant sous 64 bits. Malheureusement, il a également le problème de point d'arrêt où il ne parvient pas à se briser si je réexécute le programme (cela implique qu'il est recompilé, donc pas en utilisant le même assembly, mais toujours en utilisant la même source).

Mise à jour 8:

J'ai déposé un bogue sur le site MS Connect concernant le problème du point d'arrêt.

Mise à jour: fixe

Mise à jour 9:

Après une longue réflexion, le seul moyen de rendre le débogueur heureux semble être de faire SSA, de sorte que chaque étape peut être isolée et séquentielle. Je n'ai pas encore prouvé cette notion. Mais cela semble logique. De toute évidence, le nettoyage des temps de SSA interrompra le débogage, mais cela est facile à basculer, et les laisser n'a pas beaucoup de frais généraux.

leppie
la source
Toutes les idées sont les bienvenues, je vais les essayer et ajouter les résultats à la question. 2 premières idées, essayez mdbg et écrivez le même code en C # et comparez.
leppie
6
Quelle est la question?
George Stocker
9
Je pense que sa question va dans le sens de "pourquoi ma langue ne fonctionne-t-elle pas correctement?"
McKay
1
Si vous ne passez pas peverify, vous ne pourrez pas exécuter dans des situations de confiance partielle, comme à partir d'un lecteur mappé ou dans de nombreuses configurations d'hébergement de plugins. Comment générez-vous votre IL, Reflect.Emit ou via text + ilasm? Dans les deux cas, vous pouvez toujours mettre vous-même la ligne.
Hippiehunter
@Hippiehunter: Merci. Malheureusement, sans le nops, le pas à pas échoue (je le vérifierai à nouveau pour être sûr). C'est un sacrifice que je devrais faire. Ce n'est pas comme si VS pouvait même fonctionner sans droits d'administrateur :) Btw en utilisant Reflection.Emit via le DLR (un premier branchement très piraté).
leppie

Réponses:

26

Je suis ingénieur dans l'équipe de débogage de Visual Studio.

Corrigez-moi si je me trompe, mais il semble que le seul problème qui reste est que lors du passage des PDB au format de symbole de compilation dynamique .NET 4, certains points d'arrêt sont manqués.

Nous aurions probablement besoin d'une repro pour diagnostiquer exactement le problème, mais voici quelques notes qui pourraient vous aider.

  1. VS (2008+) peut fonctionner en tant que non-administrateur
  2. Des symboles se chargent-ils du tout la deuxième fois? Vous pouvez tester par effraction (via une exception ou appeler System.Diagnostic.Debugger.Break ())
  3. En supposant que les symboles se chargent, y a-t-il un repro que vous pourriez nous envoyer?
  4. La différence probable est que le format de symbole pour le code compilé de manière dynamique est 100% différent entre .NET 2 (flux PDB) et .NET 4 (IL DB je pense qu'ils l'ont appelé?)
  5. Le son du 'nop est à peu près correct. Consultez les règles de génération de points de séquence implicites ci-dessous.
  6. Vous n'avez pas vraiment besoin d'émettre des choses sur des lignes différentes. Par défaut, VS effectuera des étapes «symboles-instructions» où, en tant que rédacteur du compilateur, vous définissez ce que signifie «symbole-instruction». Donc, si vous voulez que chaque expression soit une chose distincte dans le fichier de symboles, cela fonctionnera très bien.

Le JIT crée un point de séquence implicite basé sur les règles suivantes: 1. Instructions IL nop 2. Points vides de pile IL 3. L'instruction IL immédiatement après une instruction d'appel

S'il s'avère que nous avons besoin d'une repro pour résoudre votre problème, vous pouvez signaler un bogue de connexion et télécharger des fichiers en toute sécurité via ce support.

Mettre à jour:

Nous encourageons les autres utilisateurs rencontrant ce problème à essayer l'aperçu développeur de Dev11 à l' adresse http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27543 et à commenter avec vos commentaires. (Doit cibler 4,5)

Mise à jour 2:

Leppie a vérifié que le correctif fonctionnait pour lui sur la version bêta de Dev11 disponible à http://www.microsoft.com/visualstudio/11/en-us/downloads comme indiqué dans le bogue de connexion https://connect.microsoft. com / VisualStudio / commentaires / détails / 684089 / .

Merci,

Luke

Luke Kim
la source
Merci beaucoup. J'essaierai de faire des repro, si besoin.
leppie
En ce qui concerne la confirmation, oui vous avez raison, mais je préférerais quand même résoudre les problèmes de progression sans casser la vérifiabilité. Mais comme je l'ai dit, je suis prêt à sacrifier cela si je pouvais prouver qu'il ne lancera jamais d'exécution VerificationException.
leppie
Quant au point 6, je l'ai simplement fait "en ligne" pour cette question. Le débogueur entre en fait correctement dans / out / over expressions comme je l'ai prévu pour fonctionner :) Le seul problème avec l'approche est de définir des points d'arrêt sur les expressions «internes» si une expression «externe» la couvre; le débogueur dans VS a tendance à vouloir créer l'expression la plus externe comme point d'arrêt.
leppie
Je pense avoir isolé le problème du point d'arrêt. Lors de l'exécution sous CLR2, les points d'arrêt semblent être réévalués lors de la réexécution du code, bien que nouveau code. Sur CLR4, les points d'arrêt ne «collent» qu'au code d'origine. J'ai fait un petit screencast du comportement du CLR4 @ screencast.com/t/eiSilNzL5Nr . Notez que le nom du type change entre les appels.
leppie
J'ai déposé un bug sur la connexion. La repro est assez simple :) connect.microsoft.com/VisualStudio/feedback/details/684089
leppie
3

Je suis ingénieur dans l'équipe SharpDevelop Debugger :-)

Avez-vous résolu le problème?

Avez-vous essayé de le déboguer dans SharpDevelop? S'il y a un bogue dans .NET, je me demande si nous devons implémenter une solution de contournement. Je ne suis pas au courant de ce problème.

Avez-vous essayé de le déboguer dans ILSpy? Surtout sans symboles de débogage. Cela déboguerait le code C #, mais cela nous indiquerait si les instructions IL sont bien déboguables. (Notez que le débogueur ILSpy est en version bêta)

Notes rapides sur le code IL d'origine:

  • .line 19,19: 6,15 '' apparaît deux fois?
  • .line 20,20: 7,14 '' ne démarre pas au point de séquence implicite (la pile n'est pas vide). je suis inquiet
  • .line 20,20: 7,14 '' inclut le code pour "car x" (bon) ainsi que "#f nooo x" (mauvais?)
  • concernant le nop après ret. Et pour stloc, ldloc, ret? Je pense que C # utilise cette astuce pour faire de ret un point de séquence distinct.

David

dsrbecky
la source
+1 Merci pour les commentaires, comme premier point, effet secondaire malheureux du générateur de code, mais semble inoffensif. Deuxième point, c'est exactement ce dont j'ai besoin, mais je vois votre point, je vais l'examiner. Je ne sais pas ce que vous entendez par le dernier point.
leppie
Le troisième point était que .line 22,22: 7,40 '' devrait être avant IL_0020. Ou il devrait y avoir quelque chose avant IL_0020, sinon le code compte toujours comme .line 20,20: 7,14 ''. Le quatrième point est "ret, nop" pourrait être remplacé par "sloc, .line, ldloc, ret". J'ai déjà vu le modèle, peut-être était-il redondant, mais peut-être avait-il une raison.
dsrbecky
Je ne peux pas faire le stloc, ldloc avant ret, car je perdrai l'appel de queue. Ah je vous comprends, vous faisiez référence à la sortie originale?
leppie le