概述:使用 Windbg 调试 .NET 文件,虽然使用Windbg是必须的技能,但是非必要情况下可以使用 dnSpy调试。

[toc]

调试示例

以 [[【.Net】index|C#]] 如下所示的代码为例:

namespace DnrspEngine.Hooks.System
{
    class Win32Native
 	{
        [StructLayout(LayoutKind.Sequential)]
        internal class SECURITY_ATTRIBUTES
        {
        }
         
        internal static SafeFileHandle MySafeCreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile)
        {
        }
 
        internal static SafeFileHandle PxySafeCreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile)
        {
        }
 	}
}

命名空间 DnrspEngine.Hooks.System 有一个类 Win32Native。

调试 .NET 程序前,需要加载 SOS 扩展,SOS (Son of Strike) 是 .NET 调试的扩展,它提供了很多用于调试 .NET 应用程序的命令。首先,你需要加载 SOS 扩展:

.loadby sos clr

如果 clr 加载不成功,则需要尝试以下命令查看原因并解决(相关参考:SOS.DLL在windbg里加载错误 - 活着的虫子 - 博客园):

!sym noisy
.symfix 路径
.cordll -ve -u -l

或者在 clrjit 加载成功后再尝试加载 sos 和 clr,一般都是这个原因(相关参考:windbg的.loadby sos clr第一次总不成功,必须在加载clrjit以后才能运行,为何?-CSDN社区):

sxe ld:clrjit

0x01 查看函数地址

!name2ee 查看类信息,如果不知道有哪些类,可以使用 DumpHeap命令](#Dumpheap),如果要查看具体的对象具有的方法但是又没有源代码时,可以使用 dnSpy 这类的反编译工具。

# 查看类的属性
!name2ee mscorelib.dll!Microsoft.Win32.Win32Native
!name2ee System.dll!System.Diagnostics.Process
 
# 输出如下所示
0:026> !name2ee mscorlib.dll!Microsoft.Win32.Win32Native
Module:      70931000
Assembly:    mscorlib.dll
Token:       0200000e
MethodTable: 709acdcc
EEClass:     70a67be8
Name:        Microsoft.Win32.Win32Native
 
#查看 MethodTable
!DumpMT -md 709acdcc
 
#查看模块
!DumpModule /d 70931000
# 查看具体的函数
!name2ee mscorlib.dll Win32Native.SafeCreateFile // 需要知道函数名
!name2ee system.dll System.Diagnostics.Process.Start
!name2ee system.dll System.Diagnostics.Process.StartWithCreateProcess
	!name2ee system.dll System.Diagnostics.Process.StartWithShellExecuteEx
!name2ee mscorlib.dll System.AppDomain.CreateDomain
 
# 输出如下所示
0:026> !name2ee mscorlib.dll Win32Native.SafeCreateFile
Module:      70931000
Assembly:    mscorlib.dll
Token:       06000042
MethodDesc:  70b9db00
Name:        Microsoft.Win32.Win32Native.SafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)
JITTED Code Address: 70d044a0

输出分析,以我当前要使用的字段为主:

Module: 模块句柄

MethodTable:方法表。给出了我们对应的类的方法表的地址

!DumpHeap

!DumpHeap 命令用于查看当前调试的进程的堆上的对象,略微局限

  1. -stat:只输出堆上所有类型对象的统计摘要,包括它们的计数、大小和类型名称。这个选项非常有用,可以快速了解堆上对象的分布情况。
  2. -nostrings:排除字符串的输出。当与 -stat 一起使用时,这个选项可以使得输出更加简洁,专注于非字符串类型的对象。
  3. -gen X:仅输出属于指定代的对象。在 .NET 中,对象根据它们的生命周期被分配到不同的代(Generation),这个选项允许你查看特定代的对象。
  4. -min X 和 -max X:忽略小于或大于指定字节大小的对象。这两个选项可以帮助你过滤掉不感兴趣的小对象或大对象。
  5. -mt MethodTable:仅列出具有指定 MethodTable 的对象。MethodTable 是 .NET 中用于描述类型的内部结构。
  6. -type type:仅列出类型名为指定子字符串的对象。这个选项允许你按类型名称过滤输出。
  7. -live:仅输出仍然存活的对象,即那些还没有被垃圾回收器标记为可回收的对象。
  8. -dead:仅输出已死亡的对象,这些对象将在下一次完全垃圾回收(Full GC)中被回收。
  9. -cache:将对象保存在内部缓存中以供以后使用,这有助于加快后续扫描堆的速度。
  10. -lx:只打印每个堆中的指定数量的项,而不是所有对象。这可以帮助你限制输出的数量。
  11. -short:仅打印出对象的地址。这个选项通常与其他命令(如 .foreach)组合使用,用于自动化处理或进一步分析。

0x02 查看方法有哪些

!dumpmt 查看方法有哪些

!dumpmt -md %MethodTable% # 查看方法有哪些
---------------------------------------
!dumpmt -md 00007ffd79054540
 
# 输出如下所示:
0:024> !dumpmt -md 00007ffd79054540
EEClass:         00007ffd792da338
Module:          00007ffd79051810
Name:            DnrspEngine.Hooks.System.Win32Native
mdToken:         000000000200000b
File:            DnrspEngine, Version=1.0.0.1031, Culture=neutral, PublicKeyToken=null
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 7
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007ffdd5c957a0 00007ffdd579c7a0 PreJIT System.Object.ToString()
00007ffdd5d0d2b0 00007ffdd5935560 PreJIT System.Object.Equals(System.Object)
00007ffdd5cfaba0 00007ffdd5935588 PreJIT System.Object.GetHashCode()
00007ffdd5d0aac0 00007ffdd5935590 PreJIT System.Object.Finalize()
00007ffd79159448 00007ffd79054538   NONE DnrspEngine.Hooks.System.Win32Native..ctor()
00007ffd792e1440 00007ffd79054518    JIT DnrspEngine.Hooks.System.Win32Native.MySafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)
00007ffd792e1810 00007ffd79054528    JIT DnrspEngine.Hooks.System.Win32Native.PxySafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)

输出分析:

可以看到输出内容是比较多的,并且输出了方法表示以及各个方法对应的地址。这里就可以对函数下断点了,以 DnrspEngine.Hooks.System.Win32Native.MySafeCreateFile 为例 (注意只有 JIT 的函数才可以下断点)

0x03 添加断点

!bpmd -md 00007ffd79054518
 
# 输出如下所示:
0:024> !bpmd -md 00007ffd79054518
MethodDesc = 00007ffd79054518
Setting breakpoint: bp 00007FFD792E1440 [DnrspEngine.Hooks.System.Win32Native.MySafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)]

分析:

可以看到断点设置信息,已经函数参数等。等待函数执行就可以触发断点。

0x04 查看堆栈

!clrstack 
 
# 查看所有线程的堆栈
!dumpstack
#或者
~*e!dumpstack
 

添加初始断点

0:000> sxe ld:clrjit
 
0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll
0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
0:000> .loadby sos clr  (等同于上面两条命令)

查找某个函数

# 如下所示,查找AppDomain.CreateDomain
!name2ee *!AppDoamin.CreateDomain

0x06 其他命令补充

  1. 帮助命令

    !help
  2. 清屏

    !cls
  3. 查看当前托管线程已执行时间

    !runaway
  4. 查看当前线程池情况

    !threadpool