调用堆栈中的 InlinedCallFrame

发布于 2024-12-19 08:27:03 字数 758 浏览 4 评论 0原文

有时在托管调用堆栈中,尽管有任何方法调用,我都会得到 InlinedCallFrame。这究竟意味着什么?

0024e6dc 6fe1e38f Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)  
0024e6fc 6fa64c29 Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)  
0024e700 000a1104 [InlinedCallFrame: 0024e700]   
0024e8d8 6e378d5e System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)  
0024e974 6e3789c7 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)  
0024e9c8 6e378811 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)  

Sometimes in managed calls stack, inspite any method call I get InlinedCallFrame. What does this exactly mean?

0024e6dc 6fe1e38f Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)  
0024e6fc 6fa64c29 Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)  
0024e700 000a1104 [InlinedCallFrame: 0024e700]   
0024e8d8 6e378d5e System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)  
0024e974 6e3789c7 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)  
0024e9c8 6e378811 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)  

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

紙鸢 2024-12-26 08:27:04

tl;dr - 这是一个深层的实现细节 - 典型的 .NET 开发人员可以忽略它。它是运行时留在堆栈上以帮助堆栈行走的对象。

上下文:

在计算的早期,没有异常处理,也没有垃圾收集。所以对于堆栈的可步行性并没有硬性要求。因此,一般来说,栈是不可行走的。

从技术上讲,调试器需要它们。调试器神奇地实现了这一目标。我将在这里跳过实际机制。

为了支持异常、垃圾收集和调试,CLR 必须确保所有托管帧都是可步行的。为此,JIT 生成所描述的代码,以便堆栈遍历器知道如何展开各个托管帧。

有时,托管代码需要调用一些用 C++ 编写的代码。这可能是因为我们正在执行 PInvoke,但也可能是其他各种原因,例如,当我们需要执行 GC 时,或者我们正在执行反射等时。在这些情况下,堆栈上会包含一些 C++ 代码它,并且不能保证 C++ 代码生成可步行的堆栈帧,因此我们遇到了问题。

解决方案

这就是我们拥有这些 Frame 对象的原因。这些 Frame 对象旨在解决该问题。

这个命名有点不幸。我们有栈帧和 Frame 对象。请注意 Frame 中的大写 F,这就是我们区分它们的方式。

Frame 对象在堆栈上分配,它是一个链表,因此每个 Frame 都可以有一个父 Frame(如果没有,则为 FRAME_TOP),并且可以通过线程静态。它们一起形成一个与实际堆栈并排的单独链表。

有了这两个,现在我们可以并排行走它们,并按地址将它们交错。请记住,当我们进行函数调用时,堆栈指针值会减少。因此,从被调用者到调用者,堆栈指针应该始终增加,因此我们确切地知道如何交错它们。更有趣的是,这些 Frame 对象负责跳过 C++ 帧,因此我们确切地知道如何遍历托管帧的整个堆栈。

警告 - 不要尝试通过在构造函数上设置断点来调试框架链的创建。一些 Frame 对象通过程序集存根或 jitted 代码直接放置在堆栈上。

自 x64 以来,平台应用程序二进制接口的设计使得堆栈始终是可步行的,至少对于 Windows 而言是这样。因此,技术上不再需要帮助堆栈遍历器。但由于必须支持 x86,而且我们可以跳过本机帧,这是一件好事,因此保留了这个单独的 Frame 对象链。

从技术上讲,!clrstack 将它们像其他行一样显示为一行,这有点令人困惑。该行不代表函数调用。相反,它是一个恰好位于堆栈上的特殊对象,就像您的参数或局部变量一样。对于 .NET 运行时开发人员来说,调试堆栈遍历器中出现的问题可能很有用,但对于编写 .NET 代码的开发人员来说几乎毫无用处。

欢迎在 此处了解有关 CLR 堆栈遍历的更多信息

tl;dr - It is a deep implementation detail - it can be ignored by a typical .NET developer. It is an object that the runtime leaves on the stack to help with stack walking.

Context:

In the early days of computing, there is no exception handling and there is no garbage collection. So there isn't a hard requirement for stacks to be walkable. Therefore, in general, the stack is not walkable.

Technically, the debuggers need them. The debuggers pulled off magic to achieve it. I will skip the actual mechanism here.

To support exception, garbage collection, and debugging, the CLR has to make sure that all managed frames are walkable. To do that, the JIT generates code that is described so that the stack walker knows how to unwind the individual managed frames.

Once in a while, managed code needs to call into some code written in C++. It could be because we are doing a PInvoke, but it could also be various other reasons, for example, when we need to do a GC, or we are doing reflection, etc. In those cases, the stack will have some C++ code on it, and there is no guarantee that c++ code produces stack frames that are walkable, so we have a problem.

Solution:

That's why we have these Frame objects. These Frame objects are meant to solve that problem.

The naming is a bit unfortunate. We have stack frames and we have Frame objects. Note the capital F in Frame, that is how we differentiate them.

A Frame object is allocated on the stack, it is a linked list so every Frame can have a parent Frame (or FRAME_TOP if we don't have one), and the top Frame can be accessed through a thread static. Together, these form a separate linked list that is side-by-side with the actual stack.

With the two, now we can walk them side by side, and interleave them by their addresses. Remember stack pointer values decrease as we make function calls. So from the callee to caller, the stack pointer should always increase, so we know exactly how to interleave them. More interestingly, these Frame objects are responsible for skipping through the c++ frames, so we know exactly how to walk the whole stack for managed frames.

Caveat - do not attempt to debug frame chain creation by setting breakpoints on constructors. Some Frame objects are directly placed on the stack by assembly stubs or jitted code.

Ever since x64, the platform application binary interface is designed so that the stack is always walkable, at least for Windows. So helping the stack walker is technically no longer needed. But since x86 has to be supported, and it is a nice thing to have that we can skip through the native frames, this separate chain of Frame objects stayed.

Technically, it is a bit confusing that !clrstack show them there as a row just like the others. That line does NOT represent a function call. Instead, it is a special object that happens to be on the stack, just like your arguments or locals. It might be useful for a .NET runtime developer to debug what's gone wrong in the stack walker, but it is almost useless for developers writing .NET code.

Feel free to learn more about CLR stack walking here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文