CLR中执行的托管代码分为Jitted 代码和ngened代码。区Jitted代码是动态编译生成的而ngened代码是预编译生成的。本文将讲述使用Windbg在这两种不同的代码中设置断点的方法。本文将使用如下的例子进行说明:
using System;
public class Test {
public static void Main() {
Console.WriteLine("Test");
}
}
编译上面的代码生成test.exe
Jitted代码(Test.Main)
同一个托管方法的Jitted代码地址并不固定,所以在托管方法中设置断点较麻烦。托管方法对应的CLR数据结构是MethodDesc。MethodDesc中有一个变量DWORD_PTR m_CodeOrIL储存Jitted代码地址(编译后)或IL相对地址(编译后)。JIT在编译一个方法后会改变该变量的值。下面是设置断点的步骤:
(1) 对mscorjit(JIT Engine)设置模块加载断点:“sxe ld:mscorjit.dll”。然后输入输入g命令。
(2) 载入sos,“.loadby sos mscorwks”。
(3) 查找Test.Main的MethodDesc:“!name2ee test.exe Test.Main”.
MethodDesc: 975070
Name: [DEFAULT] Void test.Main()
(4) 在该MethodDesc的m_CodeOrIL域(MethodDesc的地址加4)上设置一个内存断点:
ba w4 975070+0x04 "bp poi(975070+0x04);g"
上面的断点的意思是在m_CodeOrIL被改变后在m_CodeOrIL中存储的地址上设置代码断点并继续执行。
(5) Debugger会停在Test.Main的入口处。输入!clrstack:
ESP EIP
0012f6ac 06cb0058 [DEFAULT] Void test.Main()
at [+0x0] [+0x0] g:"t.cs:7
0012f9b0 791da717 [FRAME: GCFrame]
0012fa94 791da717 [FRAME: GCFrame]
Ngened代码
如果执行的方法是预编译过(关于细节请参阅关于ngen的文章),它的地址将是固定的,所以只需要找出该地址一次。假设我们想调试Console.WriteLine,可以使用如下的步骤来设置断点:
(1) windbg test.exe
(2) 输入“g”命令。Debugger会停在进程结束(process termination)断点处。
(3) 载入sos,“.loadby sos mscorwks”。
(4) 输入“!name2ee mscorlib.dll System.Console.WriteLine”。Debugger会显示Console.WriteLine的所有Overloads。我们感兴趣的是:
MethodDesc: 79bb96b8
Name: [DEFAULT] Void System.Console.WriteLine(String)
(5) 显示该方法的信息:
!DumpMD 79bb96b8
Method Name : [DEFAULT] Void System.Console.WriteLine(String)
MethodTable 79bb92a0
Module: 79b66000
mdToken: 0600051a (c:"windows.0"microsoft.net"framework"v1.1.4322"mscorlib.dll)
Flags : 8010
Method VA : 799f91b8
(6) windbg test.exe
(7) 对mscorlib(Console.WriteLine所在模块)设置模块加载断点。“sxe ld:mscorlib.dll”.
(8) 输入“g”命令两次。注意对于预编译的Assembly,CLR会先载入IL Assembly,然后在载入Ngened Image。这是为什么要“g”两次的原因。在Debugger中应当有如下的输出:
ModLoad: 79780000 79980000 c:"windows.0"microsoft.net"framework"v1.1.4322"mscorlib.dll
ModLoad: 79980000 79ca6000
c:"windows"assembly"nativeimages1_v1.1.4322"mscorlib"1.0.5000.0__b77a5c561934e089_a6cf3080"mscorlib.dll
(9) 在Ngened Image被加载后,可以直接在以前得到的地址上设置断点:“bp 799f91b8”
“g”。Debugger会停在Console.WriteLine的入口处。输入!clrstack:
Thread 0
ESP EIP
0012f6a4 799f91b8 [DEFAULT] Void System.Console.WriteLine(String)
0012f6a8 06cb0067 [DEFAULT] Void Test.Main()
at [+0xf] [+0xa] g:"t.cs:8
0012f9b0 791da717 [FRAME: GCFrame]
0012fa94 791da717 [FRAME: GCFrame]
Windbg对CLR Whidbey的支持更强。K会直接显示托管方法名。Bp可以直接支持托管方法:bp mscorlib_dll!System.Console。WriteLine
2004年4月28日 9:24 - (阅读:2567;评论:6)
反馈
# 回复: 在托管代码中设置断点(WINDBG) 2004-4-28 12:30 Flier Lu
太好了,呵呵,前两天还在琢磨着怎么写个插件实现 bp 命令直接给函数下断点呢 :D
# 回复: 在托管代码中设置断点(WINDBG) 2004-5-1 12:45 chiv_BJ
问一下,你有Shared source CLI Essential, by Stutz David,
这本书 电子版吗?
# re: 在托管代码中设置断点(WINDBG) 2004-9-13 15:19 twinsant
Is mscorlib_dll!System.Console.WriteLine managed code?
I did think it is native code.
# re: 在托管代码中设置断点(WINDBG) 2004-9-14 11:51 Peng Gang
It is prejitted code for mscorlib.dll (native). Note the format was changed, so you might see something like:
mscorlib_ni!System.Console.WriteLine
# re: 在托管代码中设置断点(WINDBG) 2005-4-25 2:00 尖锐湿疣
Thank YOu!
# 使用Windbg和SoS扩展调试分析.NET程序 2006-12-23 0:30 自由、创新、研究、探索……
在博客堂的不是我舍不得-HighCPUinGC(都是 =惹的祸,为啥不用StringBuilder呢?)、不是我舍不得-.NET里面的OutOfMemory看到很多人在问如何分析...