VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > C#教程 >
  • CLR探索系列Windows+SOS动态调试分析托管代码

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  在使用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:WINDOWSMicrosoft.NETFrameworkv2.0.50727SOS.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寄存器里面去。

  后记:关于动态调试托管代码,我也是刚接触不久,上面有不准确的地方,欢迎大家多多指正。

 

 

  显示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寄存器里面去。

  后记:关于动态调试托管代码,我也是刚接触不久,上面有不准确的地方,欢迎大家多多指正。

 



相关教程