ICorProfilerInfo2::DoStackSnapshot 方法

审核堆栈中指定线程的托管帧,并通过回调向探查器发送信息。

HRESULT DoStackSnapshot(
    [in] ThreadID thread,
    [in] StackSnapshotCallback *callback,
    [in] ULONG32 infoFlags,
    [in] void *clientData,
    [in, size_is(contextSize), length_is(contextSize)] BYTE context[],
    [in] ULONG32 contextSize);

参数

  • thread
    [in] 目标线程的 ID。

    向 thread 传入 Null 会生成当前线程的快照。 如果传递其他线程的 ThreadID,则公共语言运行时 (CLR) 会挂起该线程、执行快照并继续。

  • callback
    [in] 一个指向 StackSnapshotCallback 方法的实现的指针,CLR 调用该方法以向探查器提供有关每个托管帧和每个非托管帧运行的信息。

    StackSnapshotCallback 方法由探查器编写器来实现。

  • infoFlags
    [in] 一个 COR_PRF_SNAPSHOT_INFO 枚举值,该值指定要通过 StackSnapshotCallback 为每个帧传回的数据量。

  • clientData
    [in] 一个指向客户端数据的指针,客户端数据直接传递给 StackSnapshotCallback 回调函数。

  • context
    [in] 一个指向 Win32 CONTEXT 结构的指针,用来设置堆栈审核的种子。 该 Win32 CONTEXT 结构包含 CPU 寄存器的值,并表示 CPU 在特定时刻的状态。

    如果堆栈顶部是非托管 helper 代码,该种子可以帮助 CLR 确定开始堆栈审核的位置;否则,将忽略该种子。 必须为异步审核提供种子。 如果您正在执行同步审核,则不需要种子。

    仅当将 COR_PRF_SNAPSHOT_CONTEXT 标志传入 infoFlags 参数中时,context 参数才有效。

  • contextSize
    [in] CONTEXT 结构的大小,由 context 参数引用。

备注

为 thread 传递 Null 会生成当前线程的快照。 只有目标线程挂起时,才能创建其他线程的快照。

当探查器要审核堆栈时,它会调用 DoStackSnapshot。 在 CLR 从该调用中返回之前,它会多次调用 StackSnapshotCallback,为堆栈中的每个托管帧(或每个非托管帧的运行)各调用一次。 遇到非托管帧时,您必须亲自对其进行审核。

审核堆栈的顺序与帧入栈的顺序相反:首先是页帧(最后入栈),最后是主帧(最先入栈)。

有关如何对探查器进行编程以审核托管堆栈的更多信息,请参见 MSDN Library 中的 Profiler Stack Walking in the .NET Framework 2.0: Basics and Beyond(.NET Framework 2.0 中的探查器堆栈审核:基础和超越)。

堆栈审核可以是同步或异步的,如以下各节中所述。

同步堆栈审核

同步堆栈审核涉及为了响应回调而对当前线程的堆栈进行审核。 它不要求设置种子或挂起。

在响应 CLR 对探查器的其中一个 ICorProfilerCallback(或 ICorProfilerCallback2)方法的调用时您会进行同步调用,您将调用 DoStackSnapshot 以审核当前线程的堆栈。 如果您要查看堆栈在通知时的状态(如 ICorProfilerCallback::ObjectAllocated),这将非常有用。 只需从 ICorProfilerCallback 方法内调用 DoStackSnapshot,在 context 和 thread 参数中传递 Null。

异步堆栈审核

异步堆栈审核要求审核不同线程的堆栈或审核当前线程的堆栈,不作为对回调的响应,而通过截取当前线程的指令指针来实现。 如果堆栈顶部是不属于平台调用 (PInvoke) 或 COM 调用的非托管代码(但却是 CLR 中的 helper 代码),则异步审核需要种子。 例如,执行实时 (JIT) 编译或垃圾回收的代码就是 helper 代码。

直接挂起目标线程并亲自对其堆栈进行审核,直到找到最顶端的托管帧,便可获得种子。 目标线程挂起后,获取目标线程的当前寄存器上下文。 然后,通过调用 ICorProfilerInfo::GetFunctionFromIP 确定该寄存器上下文是否指向非托管代码 — 如果返回的 FunctionID 等于零,则帧为非托管代码。 现在,审核堆栈直至到达第一个托管帧,然后基于该帧的寄存器上下文来计算种子上下文。

用种子上下文调用 DoStackSnapshot,开始异步堆栈审核。 如果不提供种子,DoStackSnapshot 可能会跳过堆栈顶部的托管帧,因此将提供不完整的堆栈审核。 如果提供种子,它必须指向 JIT 编译的代码或本机映像生成器 (Ngen.exe) 生成的代码;否则,DoStackSnapshot 将返回失败代码 CORPROF_E_STACKSNAPSHOT_UNMANAGED_CTX。

如果不遵循以下原则,异步堆栈审核很容易引起死锁或访问冲突:

  • 直接挂起线程时,请记住只有从未运行托管代码的线程才能挂起另一个线程。

  • 始终在 ICorProfilerCallback::ThreadDestroyed 回调中阻塞,直到该线程的堆栈审核完成为止。

  • 当探查器调入可能触发垃圾回收的 CLR 函数时,不要持有锁。 也就是说,如果拥有的线程可能进行触发垃圾回收的调用,请不要持有锁。

如果从已创建探查器,以便可以遍历单独目标线程的堆栈的线程中调用 DoStackSnapshot,则还存在死锁风险。 第一次创建的该线程会进入某些 ICorProfilerInfo* 方法(包括 DoStackSnapshot),CLR 将在每个线程执行该线程上的特定于 CLR 的初始化。 如果您的探查器已挂起您正在尝试检查其堆栈的目标线程,并且该目标线程正好拥有一个执行此每线程初始化所需的锁,则将会发生死锁。 要避免此死锁,请从探查器创建的线程进行到 DoStackSnapshot 的初始调用,以遍历单个目标线程,但首先不挂起目标线程。 此初始调用确保在不死锁的情况下,完成每个线程初始化。 如果 DoStackSnapshot 成功,并且报告了至少一个框架,则此后探查器创建的线程将安全挂起任何目标线程并调用 DoStackSnapshot,以审核该目标线程的堆栈。

要求

**平台:**请参见 .NET Framework 系统要求

**头文件:**CorProf.idl、CorProf.h

**库:**CorGuids.lib

**.NET Framework 版本:**4、3.5 SP1、3.5、3.0 SP1、3.0、2.0 SP1、2.0

请参见

参考

ICorProfilerInfo 接口

ICorProfilerInfo2 接口