.NET高级调试 - 3.8线程操作
简介
高级调试过程中,与线程与线程栈是打交道特别多的。因此如何查看线程与线程栈就显得至关重要了
查看线程
!Threads
使用 !t/!Threads 命令获取所有托管线程
含义 | |
---|---|
ThreadCount | 线程数量 |
UnstartedThread | 线程创建,但未开始的 |
BackgroundThread | 后台线程数量 |
PendingThread | 阻塞的线程数量 |
DeadThread | 线程已经执行完,到被线程池回收的这个过程。线程被称为Dead Thread。此时OSId会被销毁 |
列名 | 详细说明 |
---|---|
null | windbg自定义的线程Id,包含了托管线程和非托管线程。我们可以看到,0之后紧接着就是5.说明有4个非托管线程被创建 |
Id | 托管线程Id,就是CurrentManagedThreadId |
OSId | 操作系统的Id,CLR团队曾经想将托管线程Id与OSId 设为多对一的关系。并未成功。现在是一对一的关系 |
ThreadOBJ | CLR层面的Thread信息,可以用dp观察其中的内容 |
State | CLR层面的线程状态 |
GC Mode | CLR当前的线程状态,是抢占模式还是协作模式,主要是判断是否有操控托管堆的权限 |
GC Alloc Context | GC会在这个上下文区间内分配对象,类似于缓冲区 |
Domain | 当前线程所属的应用程序域,默认情况为Domain1 |
Lock Count | 当前线程持有的托管锁个数 |
Apt | 当前COM套件模式,分为STA与MTA |
Exception | 当前线程的异常信息,如果抛出异常并未处理的话 |
Threads命令包含了一组开关如下
- !t -live
只输出处于活跃状态的线程的信息
2. !t -special
额外输出所有“特殊”线程,如垃圾回收线程,线程池线程
查看非托管调用栈
k
WinDbg是非托管调试器,自带的命令k只能查看非托管调用栈。因此看不到托管部分。
会输出如下警告信息:
WARNING:Frame IP not in any known module. Following frames may be wrong.
我并没有复现,可能是版本的windbg有调整。但是红圈部分也表示了。这一段代码并没有pdb文件,所以只显示一个Entry_point
查看托管调用栈
!ClrStack
因此要查看托管代码的调用栈,要使用SOS拓展的ClrStack命令。
ClrStack命令显示了托管调用栈的所有栈帧,并有如下几个开关
-
!Clrstack -l
相当于local,用于显示局部变量信息 -
!Clrstack -p
相当于parameters将显示调用栈上每个托管代码帧的所有参数 -
!Clrstack -a
相当于all,把当前线程上的局部变量和参数全部显示出来
Clrstack命令如果在非托管代码线程上运行,会显示一个错误信息
!dso
有时候我们会发现有很多变量是no data. 尤其是64位程序。
我们可以辅助使用!dso命令来显示出线程调用栈的所有对象
同时查看托管/非托管调用栈
!DumpStack
Clrstack命令只输出托管代码调用栈,k命令只出书非托管调用栈。要同时输出,可以使用dumpstack命令。
使用-EE开关还表示只显示托管函数,这与ClrStack一模一样。只是多显示了方法描述符指针
可能并没有你想象的这么好用,因为输出的信息太多了。反而不利于判断。
遍历所有线程的调用栈
!EEStack
有时候,我们需要获得进程中所有托管线程的调用栈,不想重复使用0s,1s来切换线程。
我们可以使用EEStack命令,来遍历所有托管线程的调用栈,非托管线程不遍历
-
!EEStack -short
这个开关只输出"感兴趣"的调用栈,比如当前线程持有一个锁,线程被劫持以执行一个垃圾收集操作,线程正在执行。 -
!EEStack -EE
直接传递给DumpStack命令,并只显示托管代码调用栈
~*e xxx
e代表在指定线程后 追加一个指令。
比如 ~*e !clrstack 代表遍历所有托管线程的调用栈
~*e k 代表遍历所有非托管线程的调用栈