如何正确读取/解释原始 C# 堆栈跟踪? [英] How to read/interpret a raw C# stack trace correctly?
问题描述
我正在阅读来自 UWP 应用程序(C#,使用 .NET Native 编译)的一些崩溃报告,但我很难理解堆栈跟踪中使用的确切语法/格式.我尝试在互联网上寻找一些指南,但没有找到任何有用的东西.
I'm reading some crash reports from a UWP application (C#, compiled with .NET Native) and I'm having a hard time understanding the exact syntax/format used in the stack traces. I tried looking for some guides on the internet but I didn't come up with anything useful.
这里有几个例子:
1)
MyProject.ViewModels.SomeViewModel.<OnLogin>d__69.MoveNext()
OnLogin
是SomeViewModel
中的一个方法名,那么为什么它在尖括号内呢?"ClassName".<"MethodName>..."
是表示实例方法的常用方式吗?- 我知道 C# 编译器将
await
调用之间的每一块代码转换为匿名方法并使用延续来安排它们,所以我猜d__69
表示内部的异步延续当前的方法.- d"代表什么?
- 这些数字是随机的吗?我的意思是,该方法没有 69 个
await
调用,所以我猜这些数字不是连续的.是否可以从堆栈跟踪中的那个数字中找出原始方法中的确切部分? OnLogin
is the name of a method inSomeViewModel
, so why is it inside angular brackets? Is the"ClassName".<"MethodName>..."
the usual way to indicate an instance method?- I understand that the C# compiler turns every chunk of code between
await
calls into anonymous methods and schedules them using continuations, so I guess thatd__69
indicates an async continuation inside the current method.- What does the 'd' stand for?
- Are those numbers random? I mean, the method doesn't have 69
await
calls, so I guess those numbers aren't sequential. Is it possible to find out the exact part in the original method from that number in the stack trace?
2)
MyProject.UserControls.SomeControl.<.ctor>b__0_0
- 我知道
.ctor
代表对象构造函数,查看代码我发现b__0_0
代表在构造函数中添加的匿名事件处理程序,例如这:SomeEvent += (s, e) =>Foo();
.- b"代表什么?
- 为什么有两个数字带有下划线?其中哪一个是指匿名方法索引?我的意思是,它是第一个(所以它的索引是 0)但是这里有两个 0.如果是第二个,我会有
0_1
、1_0
或其他东西吗? - I know that
.ctor
stands for the object constructor, and looking at the code I found out thatb__0_0
stands for an anonymous event handler added inside the constructor, like this:SomeEvent += (s, e) => Foo();
.- What does the 'b' stand for?
- Why are there two numbers with an underscore? Which one of them refers to the anonymous method index? I mean, it's the first one (so its index is 0) but there are two 0s here. If it was the second, would I have had
0_1
,1_0
or something else?
3) 我有这个方法:
// 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); }
我有这个堆栈跟踪:
MyProject.Helpers.Extensions.TasksExtensions.<>c__3$1<System.__Canon>.<GetWatchedTask>b__3_0($Task$1<__Canon> task)
- 为什么第二个参数(令牌)没有出现在签名中?
- 为什么
$Task$1
类型用 '$' 字符编写?它是否像某种占位符/地面指示器(如在正则表达式中),以便它也可以在其他地方使用?(我的意思是,我看到$1
在我猜是方法返回类型中)- 如果第一部分是方法返回类型,为什么所有具有返回类型的方法都没有它?我有很多带有返回值的方法的堆栈跟踪,但它们没有相同的签名.
- Why doesn't the second parameter (the token) show up in the signature?
- Why is the type
$Task$1
written with the '$' character? Is it like some sort of placeholder/ground indicator (like in a regex) so that it can be used in other places too? (I mean, I see that$1<System.__Canon>
in what I guess is the method return type)- If that first part is the method return type, why isn't it there for all the methods that have a return type? I have lots of stack traces with method that do return a value, but they don't have that same signature.
4)
Windows.UI.Xaml.Media.SolidColorBrush..ctor($Color color)
- 为什么参数类型以$"字符开头?它代表什么?
5) 我还有另外一种方法:
5) I have this other method:
public static async Task<ObservableCollection<SomeCustomClass>> LoadItemGroups(String parentId)
还有这个堆栈跟踪:
MyProject.SQLiteDatabase.SQLiteManager.<>c__DisplayClass142_3.<LoadGroups>b__3()
- 那个
c__DisplayClass142_3
是什么?这是一种使用单一类型而不是三个单独的类(Task、ObservableCollection、SomeCustomClass)来指示返回类型的方法吗? - 再说一次,为什么我在这里有
b__3
,在其他堆栈跟踪中它使用格式d_xxx
来指示异步代码块? - What's that
c__DisplayClass142_3
? Is that a way to indicate the return type with a single type rather than the three separate classes (Task, ObservableCollection, SomeCustomClass)? - Again, why do I have
b__3
here, where in other stack traces it used the formatd_xxx
to indicate an async code chunk? - 它呈现了不同的情况(构造函数方法、泛型类型语法等),而不仅仅是询问与特定类型变量相关的某些默认关键字/符号的含义
- 它特别询问如何将给定的堆栈跟踪与原始方法签名进行比较,以及实现该目标的步骤
- 它在不同的上下文中展示了不同的例子,而不仅仅是问一个一般性的问题
- 顺便说一句,VS 调试器魔术名称"怎么能被认为是一个合适的问题标题?其他用户在查找 C# 堆栈跟踪符号含义时应该如何找到该问题?
抱歉问了很多问题,希望这篇文章也能帮助到其他 UWP C# 程序员.
Sorry for the many questions, I hope this post will help other UWP C# programmers too.
预先感谢您的帮助!
编辑:这个问题不应被认为是其他问题 因为:
EDIT: this question should not be considered a duplicate of this other questions because:
推荐答案
我打赌 Eric Lippert 会晚点来并给出更好的答案,但万一不会发生 - 这是我的看法,因为我也对这.我从 Eric Lippert 的 这个回答中得到的d"、c"和类似符号的含义.
I bet Eric Lippert will come later and give a better answer, but in case that won't happen - here is my take, because I also got interested in this. The meaning of "d", "c" and similar symbols I got from this answer by Eric Lippert.
1)
MyProject.ViewModels.SomeViewModel.
d__69.MoveNext() 这个比较简单.
OnLogin
是异步方法,此类方法由编译器重写为状态机.该状态机实现了IAsyncStateMachine
接口,该接口具有MoveNext
方法.因此,您的异步方法基本上变成了该状态机的一系列MoveNext
调用.这就是您在堆栈跟踪中看到MoveNext()
的原因.This one is relatively simple.
OnLogin
is async method, and such methods are rewritten by compiler into a state machine. This state machine implementsIAsyncStateMachine
interface which hasMoveNext
method. So your async method basically becomes a sequence ofMoveNext
invocations of that state machine. That is why you seeMoveNext()
in stack trace.MyProject.ViewModels.SomeViewModel.
是生成的状态机类的名称.因为此状态机与d__69 OnLogin
方法相关 - 它成为类型名称的一部分.d
是上面链接中的迭代器类".请注意,上面链接中的信息已有 7 年历史,并且在 asyncawait 实现之前,但我想状态机类似于迭代器(相同的MoveNext
方法,相同的原理) - 所以迭代器类"看起来不错.69 是一些唯一的数字计数器.我想这只是计数器,因为如果我只用两个异步方法编译 dll - 它们的状态机将是d__0
和d__1
.无法根据此信息推断出异步方法的哪一部分引发了异常.MyProject.ViewModels.SomeViewModel.<OnLogin>d__69
is the name of generated state machine class. Because this state machine is related toOnLogin
method - it becomes part of type name.d
is "iterator class" by the link above. Note that information from link above is 7 years old and is before asyncawait implementation, but I guess that state machine is similar to iterator (the sameMoveNext
method, same principle) - so "iterator class" looks fine. 69 is some unique number counter. I guess it's just counter, because if I compile dll with just two async methods - their state machines would bed__0
andd__1
. It's not possible to deduce which part of async method has thrown based on this info.2)
b
是匿名方法"(上面的链接).我做了一些实验,我认为第一个索引与使用匿名方法的方法有关,第二个索引似乎与使用它们的方法中的匿名方法索引有关.例如,假设您在构造函数中使用了 2 个匿名方法,在同一类中的方法Foo
中使用了 2 个匿名方法.然后:2)
b
is "anonymous method" (link above). I made some experiments and I think first index is related to the method in which anonymous method was used, and second index seems to be related to index of anonymous method inside that method in which they are used. For example suppose you use 2 anonymous methods in constructor and 2 anonymous methods in methodFoo
in the same class. Then: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) 这看起来很复杂.首先你问为什么第二个参数(令牌)没有出现在签名中".这很简单 - 因为有问题的方法代表匿名方法
task =>task.GetAwaiter().GetResult()
,而不是您的GetWatchedTask
方法.现在我无法用这个重现你的堆栈跟踪,但仍然有一些信息.首先,System.__Canon
是:3) This looks quite complicated. First you ask "Why doesn't the second parameter (the token) show up in the signature". That's simple - because method in question represents anonymous method
task => task.GetAwaiter().GetResult()
, not yourGetWatchedTask
method. Now I was not able to reproduce your stack trace with this one, but still some info. First,System.__Canon
is:用于实例化规范"的内部方法表通用实例化的方法表.用户永远不会看到名称__Canon",但它会在调试器堆栈跟踪中出现很多涉及泛型,因此故意保持简短以避免造成麻烦.
Internal methodtable used to instantiate the "canonical" methodtable for generic instantiations. The name "__Canon" will never been seen by users but it will appear a lot in debugger stack traces involving generics so it is kept deliberately short as to avoid being a nuisance.
对我来说看起来很神秘,但我想它代表了你在运行时的
T
.那么,<>c__3$1<System.__Canon>
就是<>c__3$1<T>
,是编译器生成的类名,其中"c"是匿名方法闭包类"(来自上面的链接).这样的类是在创建闭包时由编译器生成的,因此在匿名方法中捕获一些外部状态.捕获到的东西应该存放在某个地方,存放在这样的类中.Looks cryptic for me, but I guess it kind of represents your
T
in runtime. Then,<>c__3$1<System.__Canon>
is<>c__3$1<T>
and is a name of compiler generated class, where "c" is "anonymous method closure class" (from the link above). Such class is generated by compiler when you create a closure, so capture some external state in your anonymous method. What has been captured should be stored somewhere, and it is stored in such class.更进一步,
是上面那个匿名类中的一个方法.它代表你的b__3_0 task =>task.GetAwaiter().GetResult()
方法.第 2 点中的所有内容也适用于此.Going futher,
<GetWatchedTask>b__3_0
is a method in that anonymous class above. It represents yourtask => task.GetAwaiter().GetResult()
method. Everything from point 2 applies here as well.我不知道
$
是什么意思,也许它代表了类型参数的数量.所以也许Task$1
的意思是Task
而类似Tuple$2
的意思是 >元组
.I don't know the meaning of
$
, maybe it represents number of type parameters. So maybeTask$1<System.__Canon>
meansTask<T>
and something likeTuple$2<System.__Canon
would meanTuple<T1, T2>
.4) 不幸的是我不知道并且无法重现.
4) That I unfortunately don't know and was not able to reproduce.
5)
c__DisplayClass142_3
又是闭包类(见第 3 点).
是您在方法b__3() LoadGroups
中使用的匿名方法.所以这表明一些匿名方法是闭包(捕获的外部状态)并且在LoadGroups
方法中被调用.5)
c__DisplayClass142_3
is again closure class (see point 3).<LoadGroups>b__3()
is anonymous method you used in methodLoadGroups
. So that indicates some anonymous method which is closure (captured external state) and which was called inLoadGroups
method.这篇关于如何正确读取/解释原始 C# 堆栈跟踪?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!