今天是《net 高级调试》的第六篇文章。记得我刚接触 net 框架的时候,还是挺有信心的,对所谓的值类型和引用类型也能说出自己的见解,毕竟,自己一直在努力。当然这些见解都是书本上的,并没有做到眼见为实,所以总是有些东西说不清楚。今天,我们就好好的说说 c# 的类型,是从内存级别、从底层来说一下值类型、引用类型到底是什么,它们在内存中的形态,还有也说说数组的内存形态,如何内部布局的,以及我们如何查找由未捕捉的异常引起的程序崩溃。这些都是基础的,如果这些掌握不好,以后的高级调试的道路,也不好走。自从我过了这一关,很多东西理解起来,比较透彻 了,但是,还必须努力。当然了,第一次看视频或者看书,是很迷糊的,不知道如何操作,还是那句老话,一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。
如果在没有说明的情况下,所有代码的测试环境都是 net framewok 4.8,但是,有时候为了查看源码,可能需要使用 net core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。
调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:windows professional 10
调试工具:windbg preview(可以去microsoft store 去下载)
开发工具:visual studio 2022
net 版本:net framework 4.8
coreclr源码:源码下载
二、基础知识
1、对象检查
1.1、简介
高级调试的目标就是分析应用程序故障所形成的原因,既然是故障,大多数情况下是对象的某种损坏,这就需要我们深入的了解各种对象的审查方法。
2、各种检查方法
2.1、内存转储
这个方式非常底层,从内存地址上观察地址上的内容,常使用【dp】命令。除了【dp】命令,还有其他一些命令,比如:du,dw,db,da 等,如果想了解更多,可以查看 windbg 的帮助文档,命令是【.hh】。
2.2、对值类型的转储
对值类型的转储非常的简单,一般通过【dp】命令观察内存地址,从内存上提取内容,可以在结束处观察 esp 指针,当然,如果知道栈地址,我们也可以使用【dp】命令查看内容,效果和查看 esp 是一样的。
2.3、对引用类型的转储
如果我们想查看引用类型的内容,我们可以使用【!do】命令,查看应用类型的内容。
2.4、对数组的转储
c# 数组的内存布局,大概有两种形式,值类型和引用类型。
1)值类型数组。
结构:同步块索引,方法表,数组大小,数组元素1,数组元素2......
2)引用类型数组。
结构:同步块索引,方法表,数组大小,数组元素1,数组元素2......
| |
|------------------+ +--------------------|
| |
同步块索引、方法表、内容 同步块索引、方法表、内容
2.5、对异常的转储
有时候,程序存在未处理的异常导致的崩溃,在事后分析 dump 中要是能找到这个异常,对我们分析和解决问题会有很大的帮助。如果我们想查看异常的详情,我们可以使用【!pe(print exception)】命令,打印异常的详情,当然也可以使用【!do】命令查看更详细的异常内容。
三、调试过程
废话不多说,这一节是具体的调试操作的过程,又可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。
1、测试源码
1.1、example_6_1_1

1 namespace example_6_1_1 2 { 3 internal class program 4 { 5 static void main(string[] args) 6 { 7 var person = new person(); 8 9 console.readline(); 10 } 11 } 12 13 internal class person 14 { 15 public int age = 20; 16 17 public string name = "jack"; 18 } 19 }
1.2、example_6_1_2

1 namespace example_6_1_2 2 { 3 internal class program 4 { 5 static void main(string[] args) 6 { 7 debugger.break(); 8 9 int a = 10; 10 int b = 11; 11 int c = 12; 12 int d = 13; 13 } 14 } 15 }
1.3、example_6_1_3

1 namespace example_6_1_3 2 { 3 internal class program 4 { 5 static void main(string[] args) 6 { 7 int[] intarr = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; 8 9 string[] stringarr = { "1", "2", "3", "4", "5" }; 10 11 debugger.break(); 12 } 13 } 14 }
1.4、example_6_1_4

1 namespace example_6_1_4 2 { 3 internal class program 4 { 5 static void main(string[] args) 6 { 7 console.writeline($"请输入一个能够整除的数字:"); 8 var num = console.readline(); 9 10 var ret = 10 / convert.toint32(num); 11 } 12 } 13 }
2、眼见为实
项目的所有操作都是一样的,所以就在这里说明一下,但是每个测试例子,都需要重新启动,并加载相应的应用程序,加载方法都是一样的。流程如下:我们编译项目,打开 windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们需要使用【g】命令,继续运行程序,然后到达指定地点停止后,我们可以点击【break】按钮,就可以调试程序了。有时候可能需要切换到主线程,可以使用【~0s】命令。
2.1、内存转储
测试代码:example_6_1_1
我们首先去托管堆中查找一下 person 对象。【dp】命令的 p 是 pointer,指针的意思。
1 0:006> !dumpheap -type person 2 address mt size 3 031424c8 013d4dec 16 4 5 statistics: 6 mt count totalsize class name 7 013d4dec 1 16 example_6_1_1.person 8 total 1 objects
031424c8(person 对象的指针地址) 013d4dec(person 方法表的地址)
1 0:006> !dumpobj /d 031424c8 2 name: example_6_1_1.person 3 methodtable: 013d4dec 4 eeclass: 013d12f0 5 size: 16(0x10) bytes 6 file: e:\visual studio 2022\source\projects\....\example_6_1_1\bin\debug\example_6_1_1.exe 7 fields: 8 mt field offset type vt attr value name 9 706342a8 4000001 8 system.int32 1 instance 20 age 10 706324e4 4000002 4 system.string 0 instance 031424d8 name
上面两段代码,红色标注的就是 person 对象的方法表的地址,他们是一样的。我们有了对象的地址,对象的地址其实就是类型句柄的地址,也就是知道通过快索引的地址,只要减去 0x4就可以,说明一下,每个引用类型都包含【同步块索引】和【类型句柄】。
1 0:006> dp 031424c8-0x4 l4 2 031424c4 00000000 013d4dec 031424d8 00000014
00000000 就是同步块索引,后面的 013d4dec 就是方法表的地址,最后两项就是 person 具体的值。00000014 就是十进制的20,也就是 person 对象的 age 字段的值。031424d8 就是 person 对象的 name 的值。我们来验证。
1 0:006> !do 031424d8 2 name: system.string 3 methodtable: 706324e4 4 eeclass: 70737690 5 size: 22(0x16) bytes 6 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 string: jack 8 fields: 9 mt field offset type vt attr value name 10 706342a8 4000283 4 system.int32 1 instance 4 m_stringlength 11 70632c9c 4000284 8 system.char 1 instance 6a m_firstchar 12 706324e4 4000288 70 system.string 0 shared static empty 13 >> domain:value 01472260:notinit << 14 15 16 0:006> ? 00000014 17 evaluate expression: 20 = 00000014
红色标注的就是具体的值。我们可以继续使用【dp】命令,查看 name 的详情。
1 0:006> dp 031424d8 l4 2 031424d8 706324e4 00000004 0061006a 006b0063
031424d8(字符串 name 的地址) 706324e4(name 的方法表的地址) 00000004(字符串的长度) 0061006a 006b0063(这两项就是具体的值),字符串的长度,我们可以通过 name 的地址加上 0x4,就可以得到字符串的长度。
1 0:006> dp 031424d8+0x4 l4 2 031424dc 00000004 0061006a 006b0063 00000000
为什么是加上 0x4,因为我们在使用【!do】命令,查看 name 详情的时候,m_stringlength 的偏移量是4。
0061006a(看地址,我们是从低地址往高地址看),所以我们先看 006a,然后再查看 0061,006b0063这个地址也是一样的。我们可以使用【du】命令查看它的内容,u就是 unicode。006a 就是 j,0061就是a,0063就是 c,006b就是k。
1 0:006> du 031424d8+0x8 2 031424e0 "jack"
为什么加 0x8,因为偏移量是8。
2.2、对值类型的转储。
测试代码:example_6_1_2
我们首先找到栈地址,可以使用【!clrstack】命令,有了栈地址,我们可以使用【bp】命令在该地址上设置断点。
1 0:000> !clrstack 2 os thread id: 0x358c (0) 3 child sp ip call site 4 007af2e0 7566f262 [helpermethodframe: 007af2e0] system.diagnostics.debugger.breakinternal() 5 007af35c 7146f195 system.diagnostics.debugger.break() [f:\dd\ndp\clr\src\bcl\system\diagnostics\debugger.cs @ 91] 6 007af384 029e0879 example_6_1_2.program.main(system.string[]) [e:\visual studio 2022\source\projects\...\example_6_1_2\program.cs @ 9] 7 007af504 71daf036 [gcframe: 007af504]
红色标注的【029e0879】就是【program.main】方法的栈地址,然后我们设置断点,【g】继续运行。
1 0:000> bp 029e0879 2 0:000> g 3 breakpoint 0 hit 4 eax=00000000 ebx=007af434 ecx=72227ced edx=00b239b0 esi=00000000 edi=007af3b0 5 eip=029e0879 esp=007af384 ebp=007af398 iopl=0 nv up ei pl zr na pe nc 6 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 7 example_6_1_2!com+_entry_point <perf> (example_6_1_2+0x23d0879): 8 029e0879 90 nop
在断点处暂停,如图:
然后,使用【g】命令,继续运行。
1 0:000> g 2 breakpoint 1 hit 3 eax=00000000 ebx=007af434 ecx=72227ced edx=00b239b0 esi=00000000 edi=007af3b0 4 eip=029e0896 esp=007af384 ebp=007af398 iopl=0 nv up ei pl zr na pe nc 5 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 6 example_6_1_2!com+_entry_point <perf> (example_6_1_2+0x23d0896): 7 029e0896 90 nop
然后,我们使用【dp esp】命令查看详细,当然我们可以可以使用【dp 007af384】,dp 命令后跟的是栈地址,都是可以的。
1 0:000> dp esp 2 007af384 0000000d 0000000c 0000000b 0000000a 3 007af394 02a024bc 007af3a4 71daf036 00b239b0 4 007af3a4 007af3f8 71db22da 007af434 007af3e8 5 007af3b4 71ea23d0 007af504 71db2284 b7ed3afc 6 007af3c4 007af544 007af4c8 007af47c 71dbec59 7 007af3d4 007af434 00000000 b7ed3afc 007af3b0 8 007af3e4 007af4c8 007af574 71ea09b0 c64cea1c 9 007af3f4 00000001 007af460 71db859b 00000000
红色标注的就是变量赋的值。汇编代码如下:
1 0:000> !clrstack -l 2 os thread id: 0x358c (0) 3 child sp ip call site 4 007af384 029e0896 example_6_1_2.program.main(system.string[]) [e:\visual studio 2022\...\example_6_1_2\program.cs @ 15] 5 locals: 6 0x007af390 = 0x0000000a(a 变量的值,等号前面是栈地址) 7 0x007af38c = 0x0000000b(a 变量的值,等号前面是栈地址,等不 a 的栈地址减去 0x4) 8 0x007af388 = 0x0000000c(a 变量的值,等号前面是栈地址,等不 b 的栈地址减去 0x4) 9 0x007af384 = 0x0000000d(a 变量的值,等号前面是栈地址,等不 c 的栈地址减去 0x4) 10 11 007af504 71daf036 [gcframe: 007af504]
栈地址是由高到低分配的,0x007af384 是变量d 的地址,0x007af384+0x4=0x007af388,这个地址就是c 变量的地址,0x007af388+0x4=0x007af38c,0x007af38c这个地址就是 b 变量的地址,0x007af38c+0x4就是 a 变量的地址,也就是 0x007af390。
2.3、对引用类型的转储。
测试代码:example_6_1_1
1 0:000> !clrstack -l 2 os thread id: 0x38fc (0) 3 child sp ip call site 4 00aff25c 777110fc [inlinedcallframe: 00aff25c] 5 00aff258 70d99b71 domainneutralilstubclass.il_stub_pinvoke(microsoft.win32.safehandles.safefilehandle, byte*, int32, int32 byref, intptr) 6 7 ... 8 9 00aff358 0117088e example_6_1_1.program.main(system.string[]) [e:\visual studio 2022\...\example_6_1_1\program.cs @ 11] 10 locals: 11 0x00aff360 = 0x02bb24c8 12 13 00aff4dc 71daf036 [gcframe: 00aff4dc]
我们查看线程栈了,找到了局部变量。也找到了地址,我们通过【!do】或者【!dumpobj】查看person 变量。
1 0:000> !dumpobj /d 02bb24c8 2 name: example_6_1_1.person 3 methodtable: 00d14dec 4 eeclass: 00d112f0 5 size: 16(0x10) bytes 6 file: e:\visual studio 2022\source\projects\...\example_6_1_1\bin\debug\example_6_1_1.exe 7 fields: 8 mt field offset type vt attr value name 9 708f42a8 4000001 8 system.int32 1 instance 20 age 10 708f24e4 4000002 4 system.string 0 instance 02bb24d8 name(红色又是一个地址)
红色的可以在【!do】,我们就可以看到详情。
1 0:000> !dumpobj /d 02bb24d8 2 name: system.string 3 methodtable: 708f24e4 4 eeclass: 709f7690 5 size: 22(0x16) bytes 6 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 string: jack(字符串 name 变量的值) 8 fields: 9 mt field offset type vt attr value name 10 708f42a8 4000283 4 system.int32 1 instance 4 m_stringlength 11 708f2c9c 4000284 8 system.char 1 instance 6a m_firstchar 12 708f24e4 4000288 70 system.string 0 shared static empty 13 >> domain:value 00d6d780:notinit <<
1 0:000> dp 02bb24c8 l4 2 02bb24c8 00d14dec 02bb24d8 00000014 80000000
00d14dec 这个地址就是 person 对象的方法表的地址,02bb24d8 就是name 变量的地址。00000014 就是 age 变量的值。
2.4、对数组的转储。
测试代码:example_6_1_3
1)值类型数组
我们定义的数组:int[] intarr = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
我们先打印出线程栈。
1 0:000> !clrstack -l 2 os thread id: 0x2e54 (0) 3 child sp ip call site 4 00afeeec 7566f262 [helpermethodframe: 00afeeec] system.diagnostics.debugger.breakinternal() 5 00afef68 7146f195 system.diagnostics.debugger.break() [f:\dd\ndp\clr\src\bcl\system\diagnostics\debugger.cs @ 91] 6 7 00afef90 00ce0920 example_6_1_3.program.main(system.string[]) [e:\visual studio 2022\...\example_6_1_3\program.cs @ 13] 8 locals: 9 0x00afef9c = 0x02b82518(int 数组,值类型数组) 10 0x00afef98 = 0x02b82574(string 数组,引用类型的数组) 11 12 00aff118 71daf036 [gcframe: 00aff118]
我们可以点击栈地址后面的地址,就可以查看详情,相当于【!do】命令。
1 0:000> !dumpobj /d 02b82518 2 name: system.int32[] 3 methodtable: 708f426c 4 eeclass: 709f805c 5 size: 56(0x38) bytes 6 array: rank 1, number of elements 11, type int32 (print array) 7 fields: 8 none 9 0:000> !dumpobj /d 02b82574 10 name: system.string[] 11 methodtable: 708f2d74 12 eeclass: 709f7820 13 size: 32(0x20) bytes 14 array: rank 1, number of elements 5, type class (print array) 15 fields: 16 none
使用【dp】命令,查看结果。
1 0:000> dp 0x02b82518-0x4 l20(为什么要减去 0x4,因为当前对象的指针指向方法表,每个域是占用4个字节,l20 的l 是不区分大小写的) 2 02b82514 00000000 708f426c 0000000b 0000000a 3 02b82524 0000000b 0000000c 0000000d 0000000e 4 02b82534 0000000f 00000010 00000011 00000012 5 02b82544 00000013 00000014 00000000 708f4354 6 02b82554 00000000 00000000 00000000 00000000 7 02b82564 00000000 00000000 00c44dd8 00000000 8 02b82574 708f2d74 00000005 02b824c8 02b824d8 9 02b82584 02b824e8 02b824f8 02b82508 00000000
00000000 表示的同步块所有,为0就是什么数据都没有,708f426c 就是方法表的地址。0000000b 表示的数组长度,十进制是11,表示有11个元素。此项后面的就是数组元素了。
如果想可视化的查看,可以使用命令【!da -details】,内容太多,折叠了。

1 0:000> !da -details 0x02b82518 2 name: system.int32[] 3 methodtable: 708f426c 4 eeclass: 709f805c 5 size: 56(0x38) bytes 6 array: rank 1, number of elements 11, type int32 7 element methodtable: 708f42a8 8 [0] 02b82520 9 name: system.int32 10 methodtable: 708f42a8 11 eeclass: 709f8014 12 size: 12(0xc) bytes 13 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 14 fields: 15 mt field offset type vt attr value name 16 708f42a8 40005a2 0 system.int32 1 instance 10 m_value 17 [1] 02b82524 18 name: system.int32 19 methodtable: 708f42a8 20 eeclass: 709f8014 21 size: 12(0xc) bytes 22 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 23 fields: 24 mt field offset type vt attr value name 25 708f42a8 40005a2 0 system.int32 1 instance 11 m_value 26 [2] 02b82528 27 name: system.int32 28 methodtable: 708f42a8 29 eeclass: 709f8014 30 size: 12(0xc) bytes 31 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 32 fields: 33 mt field offset type vt attr value name 34 708f42a8 40005a2 0 system.int32 1 instance 12 m_value 35 [3] 02b8252c 36 name: system.int32 37 methodtable: 708f42a8 38 eeclass: 709f8014 39 size: 12(0xc) bytes 40 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 41 fields: 42 mt field offset type vt attr value name 43 708f42a8 40005a2 0 system.int32 1 instance 13 m_value 44 [4] 02b82530 45 name: system.int32 46 methodtable: 708f42a8 47 eeclass: 709f8014 48 size: 12(0xc) bytes 49 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 50 fields: 51 mt field offset type vt attr value name 52 708f42a8 40005a2 0 system.int32 1 instance 14 m_value 53 [5] 02b82534 54 name: system.int32 55 methodtable: 708f42a8 56 eeclass: 709f8014 57 size: 12(0xc) bytes 58 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 59 fields: 60 mt field offset type vt attr value name 61 708f42a8 40005a2 0 system.int32 1 instance 15 m_value 62 [6] 02b82538 63 name: system.int32 64 methodtable: 708f42a8 65 eeclass: 709f8014 66 size: 12(0xc) bytes 67 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 68 fields: 69 mt field offset type vt attr value name 70 708f42a8 40005a2 0 system.int32 1 instance 16 m_value 71 [7] 02b8253c 72 name: system.int32 73 methodtable: 708f42a8 74 eeclass: 709f8014 75 size: 12(0xc) bytes 76 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 77 fields: 78 mt field offset type vt attr value name 79 708f42a8 40005a2 0 system.int32 1 instance 17 m_value 80 [8] 02b82540 81 name: system.int32 82 methodtable: 708f42a8 83 eeclass: 709f8014 84 size: 12(0xc) bytes 85 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 86 fields: 87 mt field offset type vt attr value name 88 708f42a8 40005a2 0 system.int32 1 instance 18 m_value 89 [9] 02b82544 90 name: system.int32 91 methodtable: 708f42a8 92 eeclass: 709f8014 93 size: 12(0xc) bytes 94 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 95 fields: 96 mt field offset type vt attr value name 97 708f42a8 40005a2 0 system.int32 1 instance 19 m_value 98 [10] 02b82548 99 name: system.int32 100 methodtable: 708f42a8 101 eeclass: 709f8014 102 size: 12(0xc) bytes 103 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 104 fields: 105 mt field offset type vt attr value name 106 708f42a8 40005a2 0 system.int32 1 instance 20 m_value
2)引用类型数组
我们定义的数组:string[] stringarr = { "1", "2", "3", "4", "5" };
1 0:000> !clrstack -l 2 os thread id: 0x2e54 (0) 3 child sp ip call site 4 00afeeec 7566f262 [helpermethodframe: 00afeeec] system.diagnostics.debugger.breakinternal() 5 00afef68 7146f195 system.diagnostics.debugger.break() [f:\dd\ndp\clr\src\bcl\system\diagnostics\debugger.cs @ 91] 6 7 00afef90 00ce0920 example_6_1_3.program.main(system.string[]) [e:\visual studio 2022\...\example_6_1_3\program.cs @ 13] 8 locals: 9 0x00afef9c = 0x02b82518 10 0x00afef98 = 0x02b82574(引用类型的数组) 11 12 00aff118 71daf036 [gcframe: 00aff118] 13 0:000> !dumpobj /d 02b82574 14 name: system.string[] 15 methodtable: 708f2d74 16 eeclass: 709f7820 17 size: 32(0x20) bytes 18 array: rank 1, number of elements 5, type class (print array) 19 fields: 20 none
我们同样使用【dp】命令,查看详情。
1 0:000> dp 02b82574-0x4 l20 2 02b82570 00000000(同步块索引) 708f2d74(方法表) 00000005(数组的长度是5) 02b824c8(具体的元素了,是一个地址,可以使用【!do】命令) 3 02b82580 02b824d8 02b824e8 02b824f8 02b82508 4 02b82590 00000000 7093dbc4 00000002 00000000 5 02b825a0 7093dbc4 00000001 00000000 709444c4 6 02b825b0 00000000 02b82768 00000000 00000018 7 02b825c0 00000008 00000001 00000006 00000000 8 02b825d0 70944500 00000000 02b82640 02b826d4 9 02b825e0 02b825f0 00000004 00000011 00000000
我们使用【!do】命令,查看元素详情。

1 0:000> !do 02b824c8 2 name: system.string 3 methodtable: 708f24e4 4 eeclass: 709f7690 5 size: 16(0x10) bytes 6 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 string: 1 8 fields: 9 mt field offset type vt attr value name 10 708f42a8 4000283 4 system.int32 1 instance 1 m_stringlength 11 708f2c9c 4000284 8 system.char 1 instance 31 m_firstchar 12 708f24e4 4000288 70 system.string 0 shared static empty 13 >> domain:value 00dbd7b8:notinit << 14 0:000> !do 02b824d8 15 name: system.string 16 methodtable: 708f24e4 17 eeclass: 709f7690 18 size: 16(0x10) bytes 19 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 20 string: 2 21 fields: 22 mt field offset type vt attr value name 23 708f42a8 4000283 4 system.int32 1 instance 1 m_stringlength 24 708f2c9c 4000284 8 system.char 1 instance 32 m_firstchar 25 708f24e4 4000288 70 system.string 0 shared static empty 26 >> domain:value 00dbd7b8:notinit <<
当然,我们也可以使用【!da -details】命令。

1 0:000> !da -details 02b82574 2 name: system.string[] 3 methodtable: 708f2d74 4 eeclass: 709f7820 5 size: 32(0x20) bytes 6 array: rank 1, number of elements 5, type class 7 element methodtable: 708f24e4 8 [0] 02b824c8 9 name: system.string 10 methodtable: 708f24e4 11 eeclass: 709f7690 12 size: 16(0x10) bytes 13 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 14 string: 1 15 fields: 16 mt field offset type vt attr value name 17 708f42a8 4000283 4 system.int32 1 instance 1 m_stringlength 18 708f2c9c 4000284 8 system.char 1 instance 31 m_firstchar 19 708f24e4 4000288 70 system.string 0 shared static empty 20 >> domain:value 00dbd7b8:notinit << 21 [1] 02b824d8 22 name: system.string 23 methodtable: 708f24e4 24 eeclass: 709f7690 25 size: 16(0x10) bytes 26 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 27 string: 2 28 fields: 29 mt field offset type vt attr value name 30 708f42a8 4000283 4 system.int32 1 instance 1 m_stringlength 31 708f2c9c 4000284 8 system.char 1 instance 32 m_firstchar 32 708f24e4 4000288 70 system.string 0 shared static empty 33 >> domain:value 00dbd7b8:notinit << 34 [2] 02b824e8 35 name: system.string 36 methodtable: 708f24e4 37 eeclass: 709f7690 38 size: 16(0x10) bytes 39 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 40 string: 3 41 fields: 42 mt field offset type vt attr value name 43 708f42a8 4000283 4 system.int32 1 instance 1 m_stringlength 44 708f2c9c 4000284 8 system.char 1 instance 33 m_firstchar 45 708f24e4 4000288 70 system.string 0 shared static empty 46 >> domain:value 00dbd7b8:notinit << 47 [3] 02b824f8 48 name: system.string 49 methodtable: 708f24e4 50 eeclass: 709f7690 51 size: 16(0x10) bytes 52 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 53 string: 4 54 fields: 55 mt field offset type vt attr value name 56 708f42a8 4000283 4 system.int32 1 instance 1 m_stringlength 57 708f2c9c 4000284 8 system.char 1 instance 34 m_firstchar 58 708f24e4 4000288 70 system.string 0 shared static empty 59 >> domain:value 00dbd7b8:notinit << 60 [4] 02b82508 61 name: system.string 62 methodtable: 708f24e4 63 eeclass: 709f7690 64 size: 16(0x10) bytes 65 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 66 string: 5 67 fields: 68 mt field offset type vt attr value name 69 708f42a8 4000283 4 system.int32 1 instance 1 m_stringlength 70 708f2c9c 4000284 8 system.char 1 instance 35 m_firstchar 71 708f24e4 4000288 70 system.string 0 shared static empty 72 >> domain:value 00dbd7b8:notinit <<
2.5、异常的转储
测试代码:example_4_1_2
我们必须先要下载 procdump 程序,这个程序分两个版本,32 位和64 位的,使用它注册 aedebug。打开 procdump 文件所在的目录,就可以执行下面的命令了。
操作步骤:
1)下载 procdump
下载地址:https://learn.microsoft.com/zh-cn/sysinternals/downloads/procdump
2)创建保存dump的目录,不创建会出错。
f:\test\testdump
3)注册 aedbug。
打开 cmd 窗口,执行如下的命令:procdump -ma -i f:\test\testdump

4)运行程序,保存dump
故意输入0,系统崩溃。
5)运行 windbg
我们输出所有线程,使用【!t】命令。
1 0:000> !t 2 threadcount: 2 3 unstartedthread: 0 4 backgroundthread: 1 5 pendingthread: 0 6 deadthread: 0 7 hosted runtime: no 8 lock 9 id osid threadobj state gc mode gc alloc context domain count apt exception 10 0 1 2048 0081da18 2a020 preemptive 0284f400:00000000 00817c30 0 mta system.dividebyzeroexception 028450c0 11 5 2 334c 0082db60 2b220 preemptive 00000000:00000000 00817c30 0 mta (finalizer)
红色标注,说明主线程发生了异常。继续使用【!pe[exception]】命令查看异常详情。
1 :000> !pe 2 exception object: 028450c0 3 exception type: system.dividebyzeroexception 4 message: 尝试除以零。 5 innerexception: <none> 6 stacktrace (generated): 7 sp ip function 8 006fefe0 026408a5 example_6_1_4!example_6_1_4.program.main(system.string[])+0x5d 9 10 stacktracestring: <none> 11 hresult: 80020012
我们可以继续使用【!do】命令查看异常对象。
1 0:000> !dumpobj /d 028450c0 2 name: system.dividebyzeroexception 3 methodtable: 708fd01c 4 eeclass: 70ad84fc 5 size: 84(0x54) bytes 6 file: c:\windows\microsoft.net\assembly\gac_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 fields: 8 mt field offset type vt attr value name 9 708f24e4 40002a4 4 system.string 0 instance 0284ce64 _classname 10 708f6f28 40002a5 8 ...ection.methodbase 0 instance 00000000 _exceptionmethod 11 708f24e4 40002a6 c system.string 0 instance 00000000 _exceptionmethodstring 12 708f24e4 40002a7 10 system.string 0 instance 0284b5d8 _message 13 7093f8fc 40002a8 14 ...tions.idictionary 0 instance 00000000 _data 14 708f25d4 40002a9 18 system.exception 0 instance 00000000 _innerexception 15 708f24e4 40002aa 1c system.string 0 instance 00000000 _helpurl 16 708f2734 40002ab 20 system.object 0 instance 0284b670 _stacktrace 17 708f2734 40002ac 24 system.object 0 instance 0284b6a0 _watsonbuckets 18 708f24e4 40002ad 28 system.string 0 instance 00000000 _stacktracestring 19 708f24e4 40002ae 2c system.string 0 instance 00000000 _remotestacktracestring 20 708f42a8 40002af 3c system.int32 1 instance 0 _remotestackindex 21 708f2734 40002b0 30 system.object 0 instance 00000000 _dynamicmethods 22 708f42a8 40002b1 40 system.int32 1 instance -2147352558 _hresult 23 708f24e4 40002b2 34 system.string 0 instance 00000000 _source 24 708f7b18 40002b3 44 system.intptr 1 instance 6fe8a4 _xptrs 25 708f42a8 40002b4 48 system.int32 1 instance -1073741676 _xcode 26 70903adc 40002b5 4c system.uintptr 1 instance 0 _ipforwatsonbuckets 27 709362d0 40002b6 38 ...ializationmanager 0 instance 0284b654 _safeserializationmanager 28 708f2734 40002a3 84 system.object 0 shared static s_edilock 29 >> domain:value 00817c30:notinit <<
四、总结
终于写完了,为什么说是终于,因为写这一篇文章,不是一天完成的。今天介绍的是值类型、应用类型的区别,另外在加上数组和异常崩溃的解决思路,现在终于做到知其一也知其二了,但是这个过程不好熬。为什么不好熬呢,基础差,没有汇编基础,什么栈帧,栈地址,都没有什么概念,没有别的办法,努力补充起来,底层的东西,一遍肯定是不行的。好了,不说了,不忘初心,继续努力,希望老天不要辜负努力的人。
发表评论