.NET 2.0中的堆栈遍历执行须知
创始人
2024-04-25 14:51:36
0

堆栈遍历

本文面向的是对构建用于检查托管应用程序的分析器感兴趣的读者。我将描述如何编写分析器,以在 .NET Framework 的公共语言运行库 (CLR) 中遍历托管堆栈。我将尽力保持轻松的心情,因为主题本身的进展有时会非常艰难。

在 CLR 的 2.0 版本中分析 API 有一个名为 DoStackSnapshot 的新方法,它允许分析器遍历正在分析的应用程序的调用堆栈。CLR 的 1.1 版通过进程内调试接口提供了类似功能。但使用 DoStackSnapshot 遍历调用堆栈更容易、更准确且更稳定。DoStackSnapshot 方法使用的堆栈遍历器与垃圾收集器、安全系统、异常系统等使用的堆栈遍历器相同。因此,您知道它必须运转正常。

访问完整的堆栈跟踪可使分析器用户在发生值得关注的事件时,对应用程序运行情况有一个全面的了解。根据应用程序及用户想要分析的内容,您可以假设用户在分配对象、加载类、引发异常时需要调用堆栈。即使所获得的是应用程序事件以外事件(例如计时器事件)的调用堆栈,也仍然会引起采样分析器的关注。如果您看到谁调用了包含热点的函数,则查看代码形式的热点将会变得更加有启迪作用。

我将侧重于通过 DoStackSnapshot API 获取堆栈跟踪。获取堆栈跟踪的另一方法是通过构建影子堆栈:可挂接 FunctionEnter 和 FunctionLeave,以保存当前线程的托管调用堆栈的副本。如果您在应用程序执行期间始终需要堆栈信息,如果您不介意在每次执行托管调用及返回时运行分析器的代码所产生的性能成本,则影子堆栈构建将会非常有用。如果很少需要报告堆栈(例如,为了响应事件),则 DoStackSnapshot 将是极佳的方法。即使采样分析器每隔几毫秒便拍一次堆栈快照,其频率也要比构建影子堆栈低。因此,DoStackSnapshot 非常适合采样分析器。

谨慎地进行堆栈遍历

如果您希望能够在需要时随时获取调用堆栈,这将非常有用。但是与能力随之而来的还有责任。分析器用户不会希望堆栈遍历在运行时导致访问违例 (AV) 或死锁。作为分析器编写者,您必须谨慎行使您的权力。我将讨论如何使用 DoStackSnapshot,以及如何小心地执行此操作。如您所见,您想利用此方法执行的操作越多,操作就越难以正确执行。

让我们看一下我们的主题。以下是分析器调用的内容(可在 Corprof.idl 的 ICorProfilerInfo2 接口中找到):

  1. HRESULT DoStackSnapshot(   
  2.   [in] ThreadID thread,   
  3.   [in] StackSnapshotCallback *callback,   
  4.   [in] ULONG32 infoFlags,   
  5.   [in] void *clientData,   
  6.   [in, size_is(contextSize), length_is(contextSize)] BYTE context[],   
  7.   [in] ULONG32 contextSize);   

下列代码是 CLR 在分析器上调用的内容(也可在 Corprof.idl 中找到)。向上例的 callback 参数中的此函数实现传递指针。

  1. typedef HRESULT __stdcall StackSnapshotCallback(   
  2.   FunctionID funcId,   
  3.   UINT_PTR ip,   
  4.   COR_PRF_FRAME_INFO frameInfo,   
  5.   ULONG32 contextSize,   
  6.   BYTE context[],   
  7.   void *clientData);   

这像是一块三明治。在分析器想要遍历堆栈时,调用 DoStackSnapshot。在 CLR 从该调用返回之前,它调用 StackSnapshotCallback 函数多次,即,为堆栈上的每一个托管帧或每一组非托管帧调用一次该函数。图 1 显示了此三明治结构。

分析期间的调用“三明治” 

堆栈遍历: 分析期间的调用“三明治”

正如您从我的注释中所看到的,CLR 会将这些帧告知给您,但告知的顺序与这些帧被推入到堆栈中的顺序正好相反。即,最先告知叶节点帧(被最后推入),最后告知主节点帧(被最先推入)。

这些函数的所有参数有何意义?我不准备对它们进行逐一讨论,但我将从 DoStackSnapshot 开始讨论其中的一部分(我将利用一小部分时间讨论余下部分)。infoFlags 值来自 Corprof.idl 中的 COR_PRF_SNAPSHOT_INFO 枚举,它允许您控制 CLR 是否为您提供它所报告的帧的寄存器上下文。您可为 clientData 指定您所需要的任何值,并且 CLR 将在 StackSnapshotCallback 调用中返回该值。

在 StackSnapshotCallback 中,CLR 使用 funcId 参数向您传递当前遍历的帧的 FunctionID 值。如果当前帧是一组非托管帧,则该值为 0(我稍后会加以介绍)。如果 funcId 值是一个非零值,则您可向其他方法(例如 GetFunctionInfo2 和 GetCodeInfo2)传递 funcId 和 frameInfo,以获得有关该函数的更多信息。您可在堆栈遍历过程中立即获得此函数信息,或者保存 funcId 值并在以后获取函数信息,以减少对运行中的应用程序的影响。如果您是在以后获取函数信息,请记住 frameInfo 值仅在为您提供的回调内有效。尽管可以保存 funcId 值以供以后使用,但是切勿保存 frameInfo 值以备日后使用。

当您从 StackSnapshotCallback 返回时,通常会返回 S_OK,并且 CLR 将继续遍历堆栈。如果需要的话,也可返回 S_FALSE,这将停止堆栈遍历。然后,DoStackSnapshot 调用会返回 CORPROF_E_STACKSNAPSHOT_ABORTED。

【编辑推荐】

  1. ASP.NET 2.0页面状态持续:ViewState与控件
  2. ASP.NET 2.0数据绑定控件的自定义集合
  3. 列表控件示例:HeadlineList
  4. ASP.NET 2.0数据绑定机制:生成控件
  5. ASP.NET 2.0数据绑定的发展简述

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...