在使用VS進行托管應用程序的調試的時候,有的時候總感覺有些力不從心。譬如查看一個托管堆或者計算堆棧的時候,VS就不能勝任了。這個時候,Windbg+SOS擴展調試模塊就為我們提供了一個很好的解決方案。
我們看一段代碼:
class Program
{
static void Main(string[] args)
{
Program b = new Program();
b.test();
System.Console.ReadLine();
}
public void test()
{
int i = 67;
System.Console.WriteLine((char)i);
System.Console.WriteLine((char)67);
i = 1;
}
}
這是C#裡面的一個強制類型轉換,我們現在用windbg+SOS來分析下計算堆棧,以及強制類型轉換之後的JIT代碼。
在windbg裡面加載這個正在運行的程序,attach to this process,然後加載SOS擴展調試模塊:
0:003> .load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\SOS.dll
然後顯示當前的線程:
0:003> ~
0 Id: cf0.450 Suspend: 1 Teb: 7ffdf000 Unfrozen
1 Id: cf0.be8 Suspend: 1 Teb: 7ffdd000 Unfrozen
2 Id: cf0.168 Suspend: 1 Teb: 7ffdc000 Unfrozen
. 3 Id: cf0.7d0 Suspend: 1 Teb: 7ffde000 Unfrozen
切換到第0個線程:
0:003> ~0s
eax=0012f2e4 ebx=00000000 ecx=0012f400 edx=00000008 esi=0012f1f4 edi=00250688
eip=7c92eb94 esp=0012f194 ebp=0012f1b4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c92eb94 c3 ret
顯示test方法相關的地址:
0:000> !name2ee TestConcoleApp.exe TestConcoleApp.Program.test
Module: 00ab2c24 (TestConcoleApp.exe)
Token: 0x06000002
MethodDesc: 00ab2ff0
Name: TestConcoleApp.Program.test()
JITTED Code Address: 00d000f8
顯示這個方法被C#編譯器編譯之後的IL代碼:
0:000> !dumpil 00ab2ff0
ilAddr = 00402074
IL_0000: nop
IL_0001: ldc.i4.s 67
IL_0003: stloc.0
IL_0004: ldloc.0
IL_0005: conv.u2
IL_0006: call System.Console::WriteLine
IL_000b: nop
IL_000c: ldc.i4.s 67
IL_000e: call System.Console::WriteLine
IL_0013: nop
IL_0014: ldc.i4.1
IL_0015: stloc.0
IL_0016: ret
這裡,sandwi對conv.u2這條指令一直困惑良多。我也對這個問題困惑了好久,翻閱了很多資料也沒找到,後來准備在sscli的C#編譯器裡面找到答案,不過沒找到地方......
後來被證實,這條指令是C#編譯器為了類型安全,而生成的一條指令。作用在於把一個integer轉換稱為一個unsigned int16,然後前面補0成為int32壓入堆棧裡面去。
這是一個語言編譯器行為,為了證實這個想法,同時寫了一段同樣的VB代碼來證實我們的想法:
Module Module1
Sub Main()
Dim i As Integer
i = 67
System.Console.WriteLine(Chr(i))
System.Console.WriteLine(Chr(67))
System.Console.ReadLine()
End Sub
End Module
編譯之後的IL代碼也同樣支持上面的想法。
這裡,感謝微軟的張翼證實了我關於conv.u2的存在原因的猜想。但是,zhangyi說在test方法中的conv.u2指令在JIT生成的本地代碼中被優化掉了,我卻不同意這種看法:
0:000> !u 00d000f8
這條指令,是顯示JIT編譯了的test方法的本地代碼,根據
JITTED Code Address: 00d000f8
這一行得來的。顯示結果如下:
Normal JIT generated code
TestConcoleApp.Program.test()
push esi
push eax
mov dword ptr [esp],ecx
cmp dword ptr ds:[0AB2DD8h],0
je 00d0010b (跳到xor esi,esi這裡)
call mscorwks!CorLaunchApplication+0x108b4 (7a08e179)
xor esi,esi
nop
mov esi,43h
movzx ecx,si
call mscorlib_ni+0x2f8b9c (793b8b9c) (System.Console.WriteLine(Char), mdToken: 06000759)
nop
mov ecx,43h
call mscorlib_ni+0x2f8b9c (793b8b9c) (System.Console.WriteLine(Char), mdToken: 06000759)
nop
mov esi,1
pop ecx
pop esi
ret
這裡,movzx ecx,si這條指令就對應了IL代碼裡面的Conv.u2,把對應的int前面補0放入到ecx寄存器裡面去。
後記:關於動態調試托管代碼,我也是剛接觸不久,上面有不准確的地方,歡迎大家多多指正。