专注收集记录技术开发学习笔记、技术难点、解决方案
网站信息搜索 >> 请输入关键词:
您当前的位置: 首页 > VSTS

[译]怎样用VisualStudio查看非托管代码 解决思路

发布时间:2011-06-20 18:40:40 文章来源:www.iduyao.cn 采编人员:星星草
[译]怎样用VisualStudio查看非托管代码 - 软件工程/管理 / 休闲广场
http://yizhu2000.cnblogs.com
[译]怎样用VisualStudio查看非托管代码 
(译者:这篇文章作者是一位美国的MVP,这是他的系列文章"Under the cover"的第一篇,文章的本意从最底层的角度来优化代码的性能,并作为阅读作者其他文章的技术基础,这种通过这样的做法虽然初看起来有些过分,但是对读者了解.Net许多底层运作是十分有益的) 

我们从使用visual studio进行非托管代码调试的基础开始,以便大家可以更容易的学习今后的例子,并让这篇文章作为我以后文章的基础,虽然我也使用windbg,但visual studio已经成为了一个功能强大的调试工具,对于简单的代码优化问题反而更容易使用 

当我们需要校调对性能要求很高的代码时,查看IL通常不是最好的做法,因为JIT优化器会默默的优化我们的代码,使用reflector或者ildasm你能很快发现release和debug模式下产生的IL代码几乎完全相同,那么是什么让release模式的代码运行起来如此迅速呢?这就是JIT优化的结果,通过查看managed代码(IL代码),我们没有办法看到这些优化,所以我们将通过native code(本地代码)来查找蛛丝马迹。 

必须说明我不提倡大家经常这样做,我不赞成过早的进行优化,你必须使你的代码先工作起来,你必须清楚的知道哪些代码是不值得优化的,当你的代码完成后再来找那些需要提速的地方,当你发现有的地方10% 的代码却使用了70%的时间的时候,再回过头去优化那10%的代码.同时你总是应该把判断的依据建立在对速度的实际测量上,而非仅仅是阅读代码,最后,其实数据结构的选择比底层的优化重要的多 

当然话又说回来,了解隐藏在.Net底层的秘密是非常有趣的,那就让我们开始设置visualstudio,并动手试验一个简单的例子 

首先我们需要一些试验代码 

  static void Main(string[] args) { 

  for (int i = 0; i < 10; i++) { 

  Console.WriteLine("Hello World!"); 

  } 

  } 

为了打开非托管代码调试,我们需要对visual studio进行设置.打开项目的属性并进入Debug Tab,选择该页上的“Enable unmanaged code debugging”复选框 

 

(注意,这个选项只对当前使用的配置有效,因此我们应该为我们使用的所有配置设置这个选项.)在循环的开始处插入一个断点,并运行程序,你将会像往常一样击中一个断点。这时你的屏幕应该看起来如图二(译者:原文缺图)如果你没有stack窗口,可以通过menu -> windows -> call stack (或者 ctrl + d c)将其呼出,打开call stack后,我们就可以通过右击鼠标,选择go to disassembly进入下面的代码
   

static void Main(string[] args) {
00000000 push ebp
00000001 mov ebp,esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 sub esp,38h
00000009 xor eax,eax
0000000b mov dword ptr [ebp-10h],eax
0000000e xor eax,eax
00000010 mov dword ptr [ebp-1Ch],eax
00000013 mov dword ptr [ebp-3Ch],ecx
00000016 cmp dword ptr ds:[00912DC8h],0
0000001d je 00000024
0000001f call 792B228E
00000024 xor esi,esi
00000026 xor edi,edi
00000028 nop
for (int i = 0; i < 10; i++) {
00000029 xor esi,esi
0000002b nop
0000002c jmp 0000003D
0000002e nop
Console.WriteLine("Hello World!");
0000002f mov ecx,dword ptr ds:[022B303Ch]
00000035 call 785D9074
0000003a nop
}
0000003b nop
for (int i = 0; i < 10; i++) {
0000003c inc esi
0000003d cmp esi,0Ah
00000040 setl al
00000043 movzx eax,al
00000046 mov edi,eax
00000048 test edi,edi
0000004a jne 0000002E
}
0000004c nop
0000004d lea esp,[ebp-0Ch]
00000050 pop ebx
00000051 pop esi
00000052 pop edi
00000053 pop



 我们正在查看的就是JIT为我们的代码产生的native code(本地代码),我们可以看到简单的循环在native code层次上怎么运行的,如果你从来没有研究过native code,这些本来很普通的代码可能看起来相当的奇怪,让我们来自己看看这里发生了什么 

00000029 xor esi,esi 

0000002b nop  

0000002c jmp 0000003D 

上面代码初始化我们在ESI中的计数器,ESI是一个索引寄存器,可以用来索引数组,你可以看到这里用了一个很古老的"把戏"来把计数器清0,代码没有使用把0值放入寄存器,而是让寄存器自己异或(xor)自己来达到清0的目的,接下来的一行Nop,意思是"没有操作",而他们的作用就和他们的名字一样,什么也不做,代码接下来立即跳转到3D.有时候像这样的跳转使得我们的代码不是自上而下的运行(就象许多高级语言比如c,vb,c#里面一样),如果跟着这个跳转进入这个循环的另外一个部分,就可以继续分析我们的代码 

0000003c inc esi  

0000003c后面第一个指令把ESI中的计数器加一(通过register窗口或者组合键ctrl+D R 你可以看到它的值),在第一次循环时代码会跳过这行,因为上面的跳转指令直接指向了0000003D


0000003d cmp esi,0Ah 

00000040 setl al  

00000043 movzx eax,al 

00000046 mov edi,eax 

00000048 test edi,edi 
友情提示:
信息收集于互联网,如果您发现错误或造成侵权,请及时通知本站更正或删除,具体联系方式见页面底部联系我们,谢谢。

其他相似内容:

热门推荐: