使用WinDbg获得托管方法的汇编代码
概述:有时候,我们需要查看一个托管方法的汇编指令是怎么样的。记得在大学的时候,我们使用gcc -s和objdump来获得一个c程序代码的汇编指令。但是对于.NET程序来说,我们肯定无法轻松地获得这些内容。因为所有的.NET程序都是编译成IL代码的,而只有在运行时才会被JIT编译成本机代码。因此,我们必须要在程序运行之后,再使用某种方式去“探得”汇编指令为何——除非我们可以让JIT在不运行程序的时候编译IL代码,老赵不知道该怎么做,可能需要朋友的提点。
http://www.evget.com/article/2009/6/2/11085.html
这是一个没有多大价值的小实验,对于大家了解.NET编程等方面几乎没有任何好处,尽管老赵一直强调“基础”,例如扎实的算法和数据结构能力,并且对一些必要的支持,例如操作系统,计算机体系结构,计算机网络有足够的了解,拥有“常识”,在需要的时候有足够的能力去深入了解便可;但是对于还有一些科目,例如“编译原理”,它虽然可以加强对于一个人对程序的理解,但是我也并不觉得这是一条“必经之路”。了解黑盒内部肯定是有好处的,但是是否值得学习还要进行权衡,至少要考虑(1)了解这些对于一个人究竟好处有多大,是否真那么关键;(2)同样了解这些知识,需要了解到多深,是否我们走的是了解这些的“必经之路”。同样,对于那种动辄一个问题就深入“IL”,“系统底层”的做法,老赵对此持保留态度1。当然,对于亲手进行一番尝试和探索的做法,我总是支持的,这表明了一种严谨的治学态度——但是,前提是我们并不是“以此为荣”而去搞这些(老赵也一直强调,谁说搞应用层的技术含量就比搞所谓“底层”要差了),在搞这些之前也已经有必要的根基。我们是为了探索而去研究,不是为了研究而去研究。
有时候,我们需要查看一个托管方法的汇编指令是怎么样的。记得在大学的时候,我们使用gcc -s和objdump来获得一个c程序代码的汇编指令。但是对于.NET程序来说,我们肯定无法轻松地获得这些内容。因为所有的.NET程序都是编译成IL代码的,而只有在运行时才会被JIT编译成本机代码。因此,我们必须要在程序运行之后,再使用某种方式去“探得”汇编指令为何——除非我们可以让JIT在不运行程序的时候编译IL代码,老赵不知道该怎么做,可能需要朋友的提点。
为了进行这个实验,我们先来写一些简单的示例代码:
namespace TestAsm
{
public static class TestClass
{
public static int TestMethod(int i)
{
return i;
}
} class Program
{
static void Main(string[] args)
{
Console.WriteLine("Before JIT.");
Console.ReadLine(); TestClass.TestMethod(1); Console.WriteLine("After JIT");
Console.ReadLine(); TestClass.TestMethod(1);
}
}
}
大家可以新建一个TestAsm项目,将以上代码复制粘贴,并使用Debug模式编译(避免TestMethod方法被内联,这会导致TestMethod永远不会被JIT)2,便可以得到一个TestAsm.exe,这就是我们的试验目标。可以看到代码中调用了两遍TestClass.TestMethod方法,并且分别在调用前使用Console.ReadLine中断,这使我们有了有机会使用WinDbg来进行一番探索。我们先进行一番准备工作:
- 运行TestAsm.exe,看到Before JIT字样(最好不要在VS里调试运行,因为这会加入VS的的调试模块——虽然这并不影响试验)。
- 打开WinDbg(假设您已经设好了Symbol Path),按F6(或File - Attach to a Process),选择TestAsm.exe并确定。
- 加载SOS(例如.load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll)。
现在我们就已经做好了准备。那么我们第一步是什么呢?自然是要找出TestClass.TestMethod方法的“位置”,于是先使用!name2ee命令获得TestClass类的信息:
0:003> !name2ee *!TestAsm.TestClass
Module: 70ca1000 (mscorlib.dll)
--------------------------------------
Module: 00942c5c (TestAsm.exe)
Token: 0x02000002
MethodTable: 0094306c
EEClass: 0094133c
Name: TestAsm.TestClass
“!name2ee *!TestAsm.TestClass”命令的含义是“遍历所有已加载模块,查找TestAsm.TestClass类型”。如果需要的话,您也可以使用“!name2ee modulename typename”的方式来查找指定模块中的指定类型。从输出中我们可以看到MethodTable的地址是0094306c。于是我们使用!dumpmt -md <address>命令来查看TestClass类型的方法描述符(Method Descriptor):
0:003> !dumpmt -md 0094306c
EEClass: 0094133c
Module: 00942c5c
Name: TestAsm.TestClass
mdToken: 02000002 (C:\...\TestAsm\bin\Debug\TestAsm.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 5
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
70e66a70 70ce4934 PreJIT System.Object.ToString()
70e66a90 70ce493c PreJIT System.Object.Equals(System.Object)
70e66b00 70ce496c PreJIT System.Object.GetHashCode()
70ed72f0 70ce4990 PreJIT System.Object.Finalize()
0094c040 00943060 NONE TestAsm.TestClass.TestMethod(Int32)
且看TestMethod的JIT栏的状态:“NONE”,这意味着这个方法还没有经过JIT的编译,如果我们此时通过!u <address>命令来查看方法的汇编指令就会看到:
0:003> !u 0094c040
Unmanaged code
0094c040 e8755d9571 call mscorwks!PrecodeFixupThunk (722a1dba)
0094c045 5e pop esi
0094c046 0000 add byte ptr [eax],al
0094c048 60 pushad
0094c049 30940000000000 xor byte ptr [eax+eax],dl
0094c050 0000 add byte ptr [eax],al
0094c052 0000 add byte ptr [eax],al
0094c054 0000 add byte ptr [eax],al
0094c056 0000 add byte ptr [eax],al
0094c058 0000 add byte ptr [eax],al
这段代码的目的是将方法执行过程导向到JIT进行编译,再执行编译后的本机代码。由于JIT还没有发生,因此我们还无法获得TestMethod方法的汇编指令。
于是我们在WinDbg里按F5(或Debug - Go)让程序继续执行。此时您可以去控制台按下回车,这样就会执行TestMethod方法,接着控制台上会显示After JIT字样,并再一次中断。这样我们可以回到WinDbg按下Ctrl+Break(或Debug - Break)重新进入调试。我们重新查看TestClass的Descriptor,就会发现:
0:003> !dumpmt -md 0094306c
EEClass: 0094133c
Module: 00942c5c
Name: TestAsm.TestClass
mdToken: 02000002 (C:\...\TestAsm\bin\Debug\TestAsm.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 5
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
70e66a70 70ce4934 PreJIT System.Object.ToString()
70e66a90 70ce493c PreJIT System.Object.Equals(System.Object)
70e66b00 70ce496c PreJIT System.Object.GetHashCode()
70ed72f0 70ce4990 PreJIT System.Object.Finalize()
01a100d8 00943060 JIT TestAsm.TestClass.TestMethod(Int32)
从JIT栏中可以看出,TestMethod方法的已经经过了JIT,而它的Entry地址也与刚才不同,因为再次调用方法时,已经不需要经过JIT了。现在我们便可继续!u来查看TestMethod的汇编指令:
0:003> !u 01a100d8
Normal JIT generated code
TestAsm.TestClass.TestMethod(Int32)
Begin 01a100d8, size 2d
>>> 01a100d8 55 push ebp
01a100d9 8bec mov ebp,esp
01a100db 83ec08 sub esp,8
01a100de 894dfc mov dword ptr [ebp-4],ecx
01a100e1 833d142e940000 cmp dword ptr ds:[942E14h],0
01a100e8 7405 je 01a100ef
01a100ea e892a3ae70 call mscorwks!JIT_DbgIsJustMyCode (724fa481)
01a100ef 33d2 xor edx,edx
01a100f1 8955f8 mov dword ptr [ebp-8],edx
01a100f4 90 nop
01a100f5 8b45fc mov eax,dword ptr [ebp-4]
01a100f8 8945f8 mov dword ptr [ebp-8],eax
01a100fb 90 nop
01a100fc eb00 jmp 01a100fe
01a100fe 8b45f8 mov eax,dword ptr [ebp-8]
01a10101 8be5 mov esp,ebp
01a10103 5d pop ebp
01a10104 c3 ret
关于上面的这段汇编代码,大家可以不去深究,因为这是使用Debug模式编译下的结果,其中的指令会包含一些调试信息(如call mscorwks!JIT_DbgIsJustMyCode)。现在我们也可以看出在JIT前后,一个方法入口点的变化。那么您是否会思考,那么TestMethod在被调用的时候,它的入口点的改变,是如何让调用方得知的呢?难道JIT之后,所有调用TestMethod的方法,其汇编指令还要有所变化吗?为此,我们可以再关注一下Program.Main方法的汇编指令:
0:003> !name2ee *!TestAsm.Program
Module: 70ca1000 (mscorlib.dll)
--------------------------------------
Module: 00942c5c (TestAsm.exe)
Token: 0x02000003
MethodTable: 0094300c
EEClass: 009412d8
Name: TestAsm.Program
0:003> !dumpmt -md 0094300c
EEClass: 009412d8
Module: 00942c5c
Name: TestAsm.Program
mdToken: 02000003 (C:\...\TestAsm\bin\Debug\TestAsm.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
70e66a70 70ce4934 PreJIT System.Object.ToString()
70e66a90 70ce493c PreJIT System.Object.Equals(System.Object)
70e66b00 70ce496c PreJIT System.Object.GetHashCode()
70ed72f0 70ce4990 PreJIT System.Object.Finalize()
0094c015 00943004 NONE TestAsm.Program..ctor()
01a10070 00942ff8 JIT TestAsm.Program.Main(System.String[])
0:003> !u 01a10070
Normal JIT generated code
TestAsm.Program.Main(System.String[])
Begin 01a10070, size 57
>>> 01a10070 55 push ebp
01a10071 8bec mov ebp,esp
01a10073 50 push eax
01a10074 894dfc mov dword ptr [ebp-4],ecx
01a10077 833d142e940000 cmp dword ptr ds:[942E14h],0
01a1007e 7405 je 01a10085
01a10080 e8fca3ae70 call mscorwks!JIT_DbgIsJustMyCode (724fa481)
01a10085 90 nop
01a10086 8b0d3020bd02 mov ecx,dword ptr ds:[2BD2030h] ("Before JIT.")
*** WARNING: Unable to verify checksum for C:\Windows\assembly\...\mscorlib.ni.dll
01a1008c e84737966f call mscorlib_ni+0x6d37d8 (713737d8) (...)
01a10091 90 nop
01a10092 e8f141966f call mscorlib_ni+0x6d4288 (71374288) (...)
01a10097 90 nop
01a10098 b901000000 mov ecx,1
01a1009d ff1568309400 call dword ptr ds:[943068h] (...TestMethod(Int32), ...)
01a100a3 90 nop
01a100a4 8b0d3420bd02 mov ecx,dword ptr ds:[2BD2034h] ("After JIT")
01a100aa e82937966f call mscorlib_ni+0x6d37d8 (713737d8) (...)
01a100af 90 nop
01a100b0 e8d341966f call mscorlib_ni+0x6d4288 (71374288) (...)
01a100b5 90 nop
01a100b6 b901000000 mov ecx,1
01a100bb ff1568309400 call dword ptr ds:[943068h] (...TestMethod(Int32), ...)
01a100c1 90 nop
01a100c2 90 nop
01a100c3 8be5 mov esp,ebp
01a100c5 5d pop ebp
01a100c6 c3 ret
请注意最后标红的两个地址“943068h”,它并不是call指令的目标,而是表示call指令的目标是“该地址所存dword的址”。于是我们通过dd <address>命令查看该地址的值:
0:003> dd 943068h
00943068 01a100d8 00000000 0000000c 00040011
00943078 00000004 70f10508 00942c5c 009430a4
00943088 0094133c 00000000 00000000 70e66a70
00943098 70e66a90 70e66b00 70ed72f0 00000080
还记得01a100d8这个地址吗?向上翻翻,您会发现这就是JIT之后TestMethod方法的入口点。可以料得,在JIT之前,dd 943068h的结果是0094c040,因为这就是TestMethod在JIT之前的入口点。在TestMethod第一次被调用时,call指令会进入JIT,而第二次调用以后,call指令便可以直接访问方法的汇编指令了。
其实,如果要查看汇编指令,更简单的方法可能是在VS中设置断点,然后通过“Go to Disassmbly”来查看汇编代码。不过有时候我们却无法借助VS,例如在《浅谈尾递归的优化方式》一文中,我们的试验目标是通过IL编译得来的(因为C#编译器不会生成IL指令tail.)。这时候,我们就需要出动WinDbg了。当然,您也可以对进程进行dump之后,使用WinDbg来“Open Crash Dump”再进行分析——不过如果你要查看某个方法的汇编指令,还是要确保它已经经过了JIT。而本文没有使用dump的方式进行调试,也是因为想要演示一下JIT前后的改变3。
注1:事实上经老赵观察发现,动辄喜欢用IL解释的人,大都是因为他理解得不够;而对问题充分理解之后,往往也就不需要用IL,或长篇IL代码了。就像CLR via C#,中间有多少是用IL说明问题的呢?而且真正的好书,好的教学方式,都是尽可能避免用低抽象的内容来说明问题的,因为重在“分析”,而不是使用的手段本身。手段本身应该尽可能的简化。因此MIT已经使用Python替代Scheme进行教学了,而很多大学操作系统课程也用了Java。
注2:使用[MethodImpl(MethodImplOptions.NoInlining)]对方法进行标记之后,JIT时应该也不会被内联,可以一试。
注3:如果事先使用ngen.exe对程序集进行处理,则托管方法就会变成PreJIT状态,在调试时便可以直接查看其汇编指令。
使用WinDbg获得托管方法的汇编代码的更多相关文章
- 浅析VS2010反汇编 VS 反汇编方法及常用汇编指令介绍 VS2015使用技巧 调试-反汇编 查看C语言代码对应的汇编代码
浅析VS2010反汇编 2015年07月25日 21:53:11 阅读数:4374 第一篇 1. 如何进行反汇编 在调试的环境下,我们可以很方便地通过反汇编窗口查看程序生成的反汇编信息.如下图所示. ...
- 转: 借助GitHub托管你的项目代码
转自:http://www.cnblogs.com/edisonchou/p/5990875.html 备注: 原贴关于github使用说明,非常详细易懂.建议看原帖. 借助GitHub托管你的项目代 ...
- GCC 嵌入汇编代码
The format of basic inline assembly is very much straight forward. Its basic form is 基本汇编嵌入格式如下: asm ...
- GCC生成的汇编代码
假设我们写了一个C代码文件 code.c包含下面代码: int accum = 0; int sum(int x, int y){ int t = x + y; accum += t; return ...
- 理解ATL中的一些汇编代码(通过Thunk技术来调用类成员函数)
我们知道ATL(活动模板库)是一套很小巧高效的COM开发库,它本身的核心文件其实没几个,COM相关的(主要是atlbase.h, atlcom.h),另外还有一个窗口相关的(atlwin.h), 所以 ...
- VS2005混合编译ARM汇编代码-转
原文地址:http://blog.csdn.net/annelcf/article/details/5468093 公司HW team有人希望可以给他们写一个在WinCE上,单独读写DDR的工具,以方 ...
- 32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式
32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式 一丶RadAsm的配置和使用 用了怎么长时间的命令行方式,我们发现了几个问题 1.没有代码提醒功能 2.编写代码很慢,记不住各 ...
- 【JVM】-NO.110.JVM.1 -【hsdis jitwatch 生成查看汇编代码】
Style:Mac Series:Java Since:2018-09-10 End:2018-09-10 Total Hours:1 Degree Of Diffculty:5 Degree Of ...
- 使用hsdis查看jit生成的汇编代码
http://blog.csdn.net/unei66/article/details/26477629 JVM 有 HotSpot引擎可以对热代码路径进行有效的 JIT优化,大幅度提升计算密集代码 ...
随机推荐
- UVa 12304 (6个二维几何问题合集) 2D Geometry 110 in 1!
这个题能1A纯属运气,要是WA掉,可真不知道该怎么去调了. 题意: 这是完全独立的6个子问题.代码中是根据字符串的长度来区分问题编号的. 给出三角形三点坐标,求外接圆圆心和半径. 给出三角形三点坐标, ...
- string.Format 里面包含 javascript方法参数的时候 单引号变成双引号的问题解决方法
解决方法如下 StringBuilder sb = new StringBuilder(); var str =@"<label><input type='checkbox ...
- 流媒体相关知识介绍 及其 RTP 应用
一.流媒体简介 随着Internet的日益普及,在网络上传输的数据已经不再局限于文字和图形,而是逐渐向声音和视频等多媒体格式过渡.目前在网络上传输音频/视频(Audio/Video,简称A/V)等多媒 ...
- 众神看过来:IE11下鼠标中键(滚轮)导致的一个似乎无法解决的问题?!
最近在做asp.net mvc项目时遇到一个关于超链接的问题.很是纠结. 问题描述 有一个公司列表展示页.在用鼠标中键(注意了是滚轮)以下简称中键,点击编辑(超链接)的时候在该条数据的下面直接加在一个 ...
- zoj 1967 Fiber Network/poj 2570
题意就是 给你 n个点 m条边 每条边有些公司支持 问 a点到b点的路径有哪些公司可以支持 这里是一条路径中要每段路上都要有该公司支持 才算合格的一个公司// floyd 加 位运算// 将每个字符当 ...
- 写的cursor demo仅作记录
declare @objectID int; declare objcur cursor for object_id from m_object open objcur fetch next from ...
- Corn Fields(POJ 3254状压dp)
题意: n*m网格1能放0不能放 放的格子不能相邻 求一共多少种可放的方案. 分析: dp[i][j]第i行可行状态j的的最大方案数,枚举当前行和前一行的所有状态转移就行了(不放牛也算一种情况) #i ...
- HDU 5778 abs (BestCoder Round #85 C)素数筛+暴力
分析:y是一个无平方因子数的平方,所以可以从sqrt(x)向上向下枚举找到第一个无平方因子比较大小 大家可能觉得这样找过去暴力,但实际上无平方因子的分布式非常密集的,相关题目,可以参考 CDOJ:无平 ...
- U盘安装Centos5.3
一.制作 U 盘启动引导盘 1. 插上 U 盘,打开 UltraISO 软件,打开CentOS-5.3-i386-bin-DVD.iso 文件: 2.点启动--写入硬盘镜像,在硬盘驱动器里面选择你的 ...
- 【转载】epoll的使用
select,poll,epoll简介 select select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理.这样所带来的缺点是: 1 单个进程可监视的fd数量被限制 2 需要维 ...