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 状态。
如果堆栈顶部是非托管的帮助程序代码,则种子有助于 CLR 确定开始堆栈审核的位置;否则,将忽略种子。 必须为异步审核提供种子。 如果执行同步审核,则不需要种子。
仅当 COR_PRF_SNAPSHOT_CONTEXT 标志传入 infoFlags
参数时,context
参数才有效。
contextSize
[in] CONTEXT
结构的大小,由 context
参数引用。
注解
为 thread
传递 null 会生成当前线程的快照。 仅当目标线程在此时挂起时,才能对其他线程执行快照操作。
当探查器需要审核堆栈时,它会调用 DoStackSnapshot
。 在 CLR 从该调用返回之前,它将调用 StackSnapshotCallback
几次,一次是针对堆栈上的每个托管帧(或运行非托管帧)调用的。 如果遇到非托管帧,则必须自行对它们进行审核。
审核堆栈的顺序与帧推送到堆栈上的顺序相反:首先遍历的是叶(最后推送的)帧,最后遍历主要(首先推送的)帧。
有关如何对探查器进行编程以审核托管堆栈的详细信息,请参阅 .NET Framework 2.0 中的探查器堆栈审核:基础知识及更多。
堆栈审核可以是同步的,也可以是异步的,如以下各部分所述。
同步的堆栈审核
同步的堆栈审核包括遍历当前线程的堆栈以响应回调。 它不需要进行种子设定或挂起。
当你在为了响应 CLR 调用你的探查器的一个 ICorProfilerCallback(或 ICorProfilerCallback2)方法时进行同步调用,你可以调用 DoStackSnapshot
来审核当前线程的堆栈。 当你想要在通知(例如 ICorProfilerCallback::ObjectAllocated)中查看堆栈的外观时,这非常有用。 只需从 ICorProfilerCallback
方法内调用 DoStackSnapshot
即可,将在 context
和 thread
参数中传递 NULL。
异步的堆栈审核
异步的堆栈审核需要审核不同线程的堆栈,或审核当前线程的堆栈,而不是响应回调,但是是通过劫持当前线程的指令指针进行的。 如果堆栈顶部是非托管代码,该代码不属于平台调用 (PInvoke) 或 COM 调用,而是 CLR 自身中的帮助程序代码,则异步审核需要种子。 例如,执行实时 (JIT) 编译或垃圾回收的代码都是帮助器代码。
你可以通过直接挂起目标线程并自己审核它的堆栈来获取种子,直到找到最顶层的托管帧。 目标线程挂起后,获取目标线程的当前寄存器上下文。 接下来,通过调用 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
来审核该目标线程的堆栈,现在这将是安全的。
要求
平台:请参阅系统要求。
头文件: CorProf.idl、CorProf.h
库:CorGuids.lib
.NET Framework 版本:自 2.0 起可用