WinDbg查看C#程序方法汇编代码
一. 测试代码
namespace Demo { class Program { static void Main(string[] args) { //声明 int number; string str; MyObject myObjet; //初始化 number = 666; str = "Hello World"; myObjet = new MyObject(); } } public class MyObject { public int x; public int y; public int z; } }
由于之前的测试都是使用 x64 的程序,见于我们平时的使用的代码都是 x8 6的我们就使用 32 位编辑的程序进行调试,使用 .Net 4.0 的版本。

现在我们开始调试吧!
二. 使用Visual Studio获得反汇编
如果我们要简单的获得程序的汇编代码,我们在VS中就可以实现,在任意的位置打上断点运行,如下图:

反汇编代码为:
--- D:\博客园文章\如何用WinDbg查看CSharp指定函数的汇编代码\Demo\Demo\Program.cs ------------------- { 017C4160 push ebp 017C4161 mov ebp,esp 017C4163 push edi 017C4164 push esi 017C4165 push ebx 017C4166 sub esp,40h 017C4169 mov esi,ecx 017C416B lea edi,[ebp-4Ch] 017C416E mov ecx,10h 017C4173 xor eax,eax 017C4175 rep stos dword ptr es:[edi] 017C4177 mov ecx,esi 017C4179 mov dword ptr [ebp-3Ch],ecx 017C417C cmp dword ptr ds:[14ECE38h],0 017C4183 je 017C418A 017C4185 call 72B9FCD0 017C418A xor edx,edx 017C418C mov dword ptr [ebp-44h],edx 017C418F xor edx,edx 017C4191 mov dword ptr [ebp-48h],edx 017C4194 xor edx,edx 017C4196 mov dword ptr [ebp-40h],edx 017C4199 nop //声明 int number; string str; MyObject myObjet; //初始化 number = 666; 017C419A mov dword ptr [ebp-40h],29Ah str = "Hello World"; 017C41A1 mov eax,dword ptr ds:[41923ACh] 017C41A7 mov dword ptr [ebp-44h],eax myObjet = new MyObject(); 017C41AA mov ecx,5D911D0h 017C41AF call 011430F4 017C41B4 mov dword ptr [ebp-4Ch],eax 017C41B7 mov ecx,dword ptr [ebp-4Ch] 017C41BA call 017C3708 017C41BF mov eax,dword ptr [ebp-4Ch] 017C41C2 mov dword ptr [ebp-48h],eax }
同时我们可以看到一些注释的对应代码:

接着进入正题,我们用 WinDbg 来调试查看一下汇编代码!
(重要★★★)三. WinDbg 调试函数方法反汇编流程
1. 由于要调试 x86 的32位程序,所以使用 x86 的 WinDbg 版本。目前最新版本的 WinDbg 可以从微软的官网上下载到,这里调试使用的版本为:

2. 使用 WinDbg 打开位于工程下的 Demo.exe 程序,选择 " File " -> "Open Executable "(或者按 Ctrl + E),如下图:

选择要打开的 Demo.exe:

3. 先配置符号路径并加载符号,然后使用 " .reload " 命令

没有提示任何的错误。然后我们加载 Demo 的符号,将 Debug 目录下面的 Demo.pdb 复制到符号目录路径下面,如下:

加载 Demo 的模块和符号,输入 ld Demo

它提示不能验证文件的检验和,我们不要去管这个提示就可以了。
4. 接着,输入命令 " sxe ld clrjit ",让 WinDbg 在载入 clrjit.dll 后中断。这一点非常重要,有以下两点好处:
- 可以在 clrjit.dll 载入之后中断下来,以便可以在 Main 函数之前设置断点;
- 可以防止载入 sos.dll 后出现神奇的失效的情况;
如下图:

这边 sxe 的语法是:
- sxe ld [要载入的模块名称]
- sxe ld:[要载入的模块名称]
- sxe ld [要载入的模块名称.dll]
- sxe ld:[要载入的模块名称.dll]
上面的 4 个都可以,效果是一样的。
作用:可以在加载对应模块的时候下断点。
5. 执行命令 g,继续执行程序,程序会在加载完 clrjit.dll 后中断下来:

6. 使用 " .loadby sos clr " 来载入 sos.dll
7. 用 " .chain " 来查看一下 sos.dll 是否被正确载入,如下图:

8. 由于我们知道我们程序主函数入口点的签名,所以我们直接下断点 " !bpmd Demo Demo.Program.Main" ,如下图:

注:这边在以前的 WinDbg 中是可以用模块的全名,如 " !bpmd Demo.exe Demo.Program.Main ",但是在我当前使用的最新版本的 WinDbg 中提示为

提示模块名错误。
9. 在下完断点之后,再次使用 g 来继续运行程序,让程序在进入 Main 函数时被中断:

可以看到第一行汇编代码 nop,它的虚拟地址是 00ea0873。
10. 使用 " !u 00ea0873 " 显示从这个地址开始的所有汇编代码,如下:

当前所在行的前部有 " >>> " 进行了标记。除了有一些乱码之外,别的都和 VS 中的反汇编一致,有的地方还有一些人性化的提示,如 " Hello World " 的堆地址:

" 854DE4h(MT:Demo.MyObject)" 是 854DE4h 是 MyObject 的方发表地址:

等等。
下面是 WinDbg的反汇编:
Normal JIT generated code Demo.Program.Main(System.String[]) Begin 00ea0848, size 5d D:\²©¿ÍÔ°ÎÄÕÂ\ÈçºÎÓÃWinDbg²é¿´CSharpÖ¸¶¨º¯ÊýµÄ»ã±à´úÂë\Demo\Demo\Program.cs @ 12: 00ea0848 55 push ebp 00ea0849 8bec mov ebp,esp 00ea084b 83ec14 sub esp,14h 00ea084e 33c0 xor eax,eax 00ea0850 8945ec mov dword ptr [ebp-14h],eax 00ea0853 894dfc mov dword ptr [ebp-4],ecx 00ea0856 833df042850000 cmp dword ptr ds:[8542F0h],0 00ea085d 7405 je 00ea0864 00ea085f e86cf4cf71 call clr!CreateHistoryReader+0x749d0 (72b9fcd0) (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE) 00ea0864 33d2 xor edx,edx 00ea0866 8955f4 mov dword ptr [ebp-0Ch],edx 00ea0869 33d2 xor edx,edx 00ea086b 8955f0 mov dword ptr [ebp-10h],edx 00ea086e 33d2 xor edx,edx 00ea0870 8955f8 mov dword ptr [ebp-8],edx >>> 00ea0873 90 nop D:\²©¿ÍÔ°ÎÄÕÂ\ÈçºÎÓÃWinDbg²é¿´CSharpÖ¸¶¨º¯ÊýµÄ»ã±à´úÂë\Demo\Demo\Program.cs @ 19: 00ea0874 c745f89a020000 mov dword ptr [ebp-8],29Ah D:\²©¿ÍÔ°ÎÄÕÂ\ÈçºÎÓÃWinDbg²é¿´CSharpÖ¸¶¨º¯ÊýµÄ»ã±à´úÂë\Demo\Demo\Program.cs @ 20: 00ea087b 8b0544238303 mov eax,dword ptr ds:[3832344h] ("Hello World") 00ea0881 8945f4 mov dword ptr [ebp-0Ch],eax D:\²©¿ÍÔ°ÎÄÕÂ\ÈçºÎÓÃWinDbg²é¿´CSharpÖ¸¶¨º¯ÊýµÄ»ã±à´úÂë\Demo\Demo\Program.cs @ 21: 00ea0884 b9e44d8500 mov ecx,854DE4h (MT: Demo.MyObject) 00ea0889 e866289aff call 008430f4 (JitHelp: CORINFO_HELP_NEWSFAST) 00ea088e 8945ec mov dword ptr [ebp-14h],eax 00ea0891 8b4dec mov ecx,dword ptr [ebp-14h] 00ea0894 ff15044e8500 call dword ptr ds:[854E04h] (Demo.MyObject..ctor(), mdToken: 06000003) 00ea089a 8b45ec mov eax,dword ptr [ebp-14h] 00ea089d 8945f0 mov dword ptr [ebp-10h],eax D:\²©¿ÍÔ°ÎÄÕÂ\ÈçºÎÓÃWinDbg²é¿´CSharpÖ¸¶¨º¯ÊýµÄ»ã±à´úÂë\Demo\Demo\Program.cs @ 22: 00ea08a0 90 nop 00ea08a1 8be5 mov esp,ebp 00ea08a3 5d pop ebp 00ea08a4 c3 ret
11. 当然我们在 Main 函数中断的时候,也可以通过打开 " 反汇编 " 窗口进行查看,如下图:
菜单选择 " View " -> " Disassembly ",当然也可以使用快捷键 " Alt + 7 ":

打开 " 反汇编 " 窗口,当前断下的位置为紫色底显示:

最好还是使用 " !u "命令的汇编,因为会有一些友好提示,比较好分析。
12. 最后,通过命令查看一下主函数中的参数和局部变量的初始化情况,输入 " !clrstack -a ",如下图:

看到了 PARAMETERS 是 Main 函数的参数,而 LOCALS 是函数中的三个临时的局部变量,但是它们都没有被初始化,都指向 0x00000000(null)。
13. 通过使用 p 执行下一条汇编指令(单步),并且配合 " !clrstack -a " 或者 "!clrstack -l " 可以一直单步,直到三个局部变量(LOCALS)有值,如下图:

14. 分析:第一个 0x005aefa0 = 0x0000029a 是

int number = 666;
而后面的分别是 string 和 MyObject 的堆的地址。使用 " !dumpobj 0x02832354 " 和 " !dumpobj 0x02832378 " 来查看一下:
第一个就是
string str = “Hello World”;

而第二个就是 MyObject ,有三个 Int32 类型的字段,如下图:

三. 遇到的问题
在有的文章上说,使用 " !clrstack -i -a " 可以看到临时变量的名称的符号信息,但是我这边在调试的时候一直提示错误,这个是怎么回事能?

不知道是不是符号服务器配置或者是WinDbg有问题,看之后能不能解决再贴在本篇下面。

浙公网安备 33010602011771号