web-dev-qa-db-fra.com

Comment lire / interpréter correctement une trace de pile C # brute?

Je lis certains rapports d'erreur d'une application UWP (C #, compilés avec .NET Native) et j'ai du mal à comprendre la syntaxe/le format exact utilisé dans les traces de pile. J'ai essayé de chercher des guides sur Internet mais je n'ai rien trouvé d'utile.

Voici quelques exemples:

1)

MyProject.ViewModels.SomeViewModel.<OnLogin>d__69.MoveNext()
  • OnLogin est le nom d'une méthode dans SomeViewModel, alors pourquoi est-elle à l'intérieur angular crochets? Le "ClassName".<"MethodName>..." est-il la manière habituelle d'indiquer une méthode d'instance?
  • Je comprends que le compilateur C # transforme chaque morceau de code entre les appels await en méthodes anonymes et les planifie à l'aide de continuations, donc je suppose que d__69 Indique une continuation asynchrone à l'intérieur de la méthode actuelle.
    • Que signifie le "d"?
    • Ces chiffres sont-ils aléatoires? Je veux dire, la méthode n'a pas 69 await appels, donc je suppose que ces chiffres ne sont pas séquentiels. Est-il possible de trouver la partie exacte de la méthode d'origine à partir de ce numéro dans la trace de la pile?
  • Quelle est cette méthode MoveNext() à la fin? De quel type est-il appelé?

2)

MyProject.UserControls.SomeControl.<.ctor>b__0_0
  • Je sais que .ctor Représente le constructeur d'objet, et en regardant le code, j'ai découvert que b__0_0 Représente un gestionnaire d'événements anonyme ajouté à l'intérieur du constructeur, comme ceci: SomeEvent += (s, e) => Foo();.
    • Que signifie le "b"?
    • Pourquoi y a-t-il deux nombres avec un trait de soulignement? Lequel d'entre eux fait référence à l'index de méthode anonyme? Je veux dire, c'est le premier (donc son indice est 0) mais il y a deux 0 ici. Si c'était le deuxième, aurais-je eu 0_1, 1_0 Ou autre chose?

3) J'ai cette méthode:

// Returns a Task that immediately throws when awaited, as soon as the token is cancelled
public static Task<T> GetWatchedTask<T>(this Task<T> awaitableTask, CancellationToken token)
{
    return awaitableTask.ContinueWith(task => task.GetAwaiter().GetResult(), token);
}

Et j'ai cette trace de pile:

MyProject.Helpers.Extensions.TasksExtensions.<>c__3$1<System.__Canon>.<GetWatchedTask>b__3_0($Task$1<__Canon> task)
  • Pourquoi le deuxième paramètre (le jeton) n'apparaît-il pas dans la signature?
  • Pourquoi le type $Task$1 Est-il écrit avec le caractère '$'? Est-ce comme une sorte d'indicateur d'espace réservé/au sol (comme dans une expression régulière) afin qu'il puisse également être utilisé dans d'autres endroits? (Je veux dire, je vois que $1<System.__Canon> Dans ce que je suppose être le type de retour de méthode)
    • Si cette première partie est le type de retour de méthode, pourquoi n'est-elle pas là pour toutes les méthodes qui ont un type de retour? J'ai beaucoup de traces de pile avec une méthode qui renvoie une valeur, mais elles n'ont pas la même signature.
  • Que signifie tout cela .<>c__3$1<System.__Canon>? À partir de $1 Et ainsi de suite, je suppose que ce serait le type de retour Task<T>, Mais quelle est cette partie b__3_0, Car je n'ai pas d'appels asynchrones ni de gestionnaires d'événements? Est-ce que cela signifie quelque chose de différent dans ce cas?

4)

Windows.UI.Xaml.Media.SolidColorBrush..ctor($Color color)
  • Pourquoi le type de paramètre commence-t-il par le caractère "$"? Qu'est ce que cela signifie?

5) J'ai cette autre méthode:

public static async Task<ObservableCollection<SomeCustomClass>> LoadItemGroups(String parentId)

Et cette trace de pile:

MyProject.SQLiteDatabase.SQLiteManager.<>c__DisplayClass142_3.<LoadGroups>b__3()
  • Qu'est-ce que c__DisplayClass142_3? Est-ce une façon d'indiquer le type de retour avec un seul type plutôt que les trois classes distinctes (Task, ObservableCollection, SomeCustomClass)?
  • Encore une fois, pourquoi ai-je b__3 Ici, alors que dans d'autres traces de pile, il a utilisé le format d_xxx Pour indiquer un bloc de code asynchrone?

Désolé pour les nombreuses questions, j'espère que ce message aidera également d'autres programmeurs UWP C #.

Merci d'avance pour votre aide!

[~ # ~] modifier [~ # ~] : cette question devrait pas être considérée comme un doublon de - ces autres questions car:

  • Il présente différents cas (méthodes constructeurs, syntaxe des types génériques, etc.) au lieu de simplement demander la signification de certains mots clés/symboles par défaut liés à un certain type de variables
  • Il demande spécifiquement comment comparer une trace de pile donnée à une signature de méthode d'origine, et les étapes à suivre pour y parvenir
  • Il présente différents exemples dans différents contextes, au lieu de simplement poser une question générale
  • Et au fait, comment les "noms magiques du débogueur VS" peuvent-ils être considérés comme un titre de question approprié? Comment un autre utilisateur était-il censé trouver cette question lors de la recherche de la signification des symboles de traces de pile C #?
26
Sergio0694

Je parie qu'Eric Lippert viendra plus tard et donnera une meilleure réponse, mais au cas où cela ne se produirait pas - voici mon point de vue, car je me suis également intéressé à cela. La signification de "d", "c" et des symboles similaires que j'ai obtenus de this réponse par Eric Lippert.

1) MyProject.ViewModels.SomeViewModel.<OnLogin>d__69.MoveNext()

Celui-ci est relativement simple. OnLogin est une méthode asynchrone, et ces méthodes sont réécrites par le compilateur dans une machine à états. Cette machine d'état implémente l'interface IAsyncStateMachine qui a la méthode MoveNext. Ainsi, votre méthode asynchrone devient fondamentalement une séquence d'invocations MoveNext de cette machine d'état. C'est pourquoi vous voyez MoveNext() dans la trace de la pile.

MyProject.ViewModels.SomeViewModel.<OnLogin>d__69 Est le nom de la classe de machine d'état générée. Étant donné que cette machine d'état est liée à la méthode OnLogin - elle fait partie du nom de type. d est "classe itérateur" par le lien ci-dessus. Notez que les informations du lien ci-dessus datent de 7 ans et sont avant async\attendent l'implémentation, mais je suppose que la machine d'état est similaire à l'itérateur (la même méthode MoveNext, même principe) - donc "classe itérateur" semble bien . 69 est un certain nombre\compteur unique. Je suppose que c'est juste contre, parce que si je compile une DLL avec seulement deux méthodes asynchrones - leurs machines d'état seraient d__0 Et d__1. Il n'est pas possible de déduire quelle partie de la méthode async a été lancée sur la base de ces informations.

2) b est une "méthode anonyme" (lien ci-dessus). J'ai fait quelques expériences et je pense que le premier index est lié à la méthode dans laquelle la méthode anonyme a été utilisée, et le deuxième index semble être lié à l'index de la méthode anonyme à l'intérieur de la méthode dans laquelle ils sont utilisés. Par exemple, supposons que vous utilisez 2 méthodes anonymes dans le constructeur et 2 méthodes anonymes dans la méthode Foo dans la même classe. Ensuite:

public Test() {
    Handler += (s, e) => Foo(); // this will be `b__0_0` because it's first in this method
    Handler += (s, e) => Bar(); // this will be `b__0_1` because it's second
}

static void Foo() {
    Action a = () => Console.WriteLine("test"); // this is `b__1_0`, 1 refers to it being in another method, not in constructor. 
    // if we use anonymous method in `Bar()` - it will have this index 2
    a();
    Action b = () => Console.WriteLine("test2"); // this is `b__1_1`
    b();
}

3) Cela semble assez compliqué. Vous demandez d'abord "Pourquoi le deuxième paramètre (le jeton) n'apparaît-il pas dans la signature". C'est simple - parce que la méthode en question représente la méthode anonyme task => task.GetAwaiter().GetResult(), pas votre méthode GetWatchedTask. Maintenant, je ne pouvais pas reproduire votre trace de pile avec celui-ci, mais encore quelques informations. Tout d'abord, System.__Canon Est:

Table de méthode interne utilisée pour instancier la table de méthode "canonique" pour les instanciations génériques. Le nom "__Canon" ne sera jamais vu par les utilisateurs mais il apparaîtra beaucoup dans les traces de pile de débogueur impliquant des génériques, il est donc délibérément court pour éviter d'être une nuisance.

Cela me semble énigmatique, mais je suppose que cela représente en quelque sorte votre T lors de l'exécution. Ensuite, <>c__3$1<System.__Canon> Est <>c__3$1<T> Et est un nom de classe générée par le compilateur, où "c" est "classe de fermeture de méthode anonyme" (à partir du lien ci-dessus). Une telle classe est générée par le compilateur lorsque vous créez une fermeture, donc capturez un état externe dans votre méthode anonyme. Ce qui a été capturé doit être stocké quelque part, et il est stocké dans une telle classe.

Pour aller plus loin, <GetWatchedTask>b__3_0 Est une méthode de la classe anonyme ci-dessus. Il représente votre méthode task => task.GetAwaiter().GetResult(). Tout à partir du point 2 s'applique également ici.

Je ne connais pas la signification de $, Peut-être qu'il représente le nombre de paramètres de type. Donc peut-être que Task$1<System.__Canon> Signifie Task<T> Et quelque chose comme Tuple$2<System.__Canon Signifierait Tuple<T1, T2>.

4) que je ne sais malheureusement pas et que je n'ai pas pu reproduire.

5) c__DisplayClass142_3 Est à nouveau une classe de fermeture (voir point 3). <LoadGroups>b__3() est une méthode anonyme que vous avez utilisée dans la méthode LoadGroups. Cela indique donc une méthode anonyme qui est la fermeture (état externe capturé) et qui a été appelée dans la méthode LoadGroups.

13
Evk