Advanced .Net Debugging 3:基本调试任务(上)
一、简介这是我的《Advanced .Net Debugging》这个系列的第三篇文章。这个系列的每篇文章写的周期都要很长,由于每篇文章都是原书的一章内容(太长的就会分开写)。再者说,原书写的有点早,有些内容还是需要修正的,调试每个案例,这都是需要时间的。今天这篇文章的标题虽然叫做“基本调试任务”,但是这章的内容还是挺多的。我本来想用一篇文章把这个章节写完,我发现是不可能的,于是就分“上“和”下”用两篇来写。既然,我们要调试我们的 .Net 应用步伐,那必须把握一些调试本领、方法和工具。我们习惯了使用 Visual Studio IDE 的调试本领,比如:单步调试、下断点、过程调试等,但是,有些时候,VS 是使用不了的。那我们也必须学习如何使用 Windbg 的命令,在没有 VS IDE 的情况下,如何调试我们的步伐,如何设置断点、恢复执行、停止执行、退出调试回话,如何为 JIT 编译的方法设置断点,如何为没有被 JIT 编译的方法设置断点,为泛型方法设置断点等等。如果我们想成为一名合格步伐员,这些调试本领都是必须要把握的。
如果在没有说明的情况下,所有代码的测试情况都是 Net 8.0,如果有变动,我会在项目章节里举行说明。好了,废话不多说,开始我们今天的调试工作。
调试情况我需要举行说明,以防各人不清晰,详细情况我已经罗列出来。
操作体系:Windows Professional 10
调试工具:Windbg Preview(Debugger Client:1.2306.1401.0,Debugger engine:10.0.25877.1004)和 NTSD(10.0.22621.2428 AMD64)
下载地址:可以去Microsoft Store 去下载
开辟工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
Net 版本:.Net 8.0
CoreCLR源码:源码下载
说明一下,这个系列内容安排有些变动,我把底子知识和眼见为实放在了一起,讲什么内容,立刻就将讲的内容做一个眼见为实验证,如许做更便于各人理解,我认为如许会更好一些,不用在文章里来回跑了。
命令行调试器要想乐成使用,必须先安装 MSVC,想要相识详情,可以去微软的官网:https://learn.microsoft.com/zh-cn/cpp/build/building-on-the-command-line?view=msvc-170,如果我们使用的 Visual Studio 2022,本身也有命令行工具,我们就可以直接使用。安装如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227142242246-387452828.png
二、调试源码
废话不多说,本节是调试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。
2.1、ExampleCore_3_1_1
1 namespace ExampleCore_3_1_1
2 {
3 internal class Program
4 {
5 static void Main(string[] args)
6 {
7 Console.WriteLine("Welcome to Advanced .Net Debugging!");
8 Console.Read();
9 }
10 }
11 }
2.2、ExampleCore_3_1_2
1 using System.Diagnostics;
2
3 namespace ExampleCore_3_1_2
4 {
5 internal class Program
6 {
7 static void Main(string[] args)
8 {
9 Console.WriteLine("第一次执行,并开始中断执行!");
10 Debugger.Break();
11 Console.WriteLine("第二次执行,并开始中断执行!");
12 Debugger.Break();
13 Console.WriteLine("第三次执行,并开始中断执行!");
14 Debugger.Break();
15
16 Console.WriteLine("恢复执行调试完毕!");
17 Console.ReadLine();
18 }
19 }
20 }
2.3、ExampleCore_3_1_3
1 using System.Diagnostics;
2
3 namespace ExampleCore_3_1_3
4 {
5 internal class Program
6 {
7 static void Main(string[] args)
8 {
9 Sum1(10);
10 Debugger.Break();
11
12 int i = 10;
13 int j = 20;
14
15 var sum = Sum1(i);
16 Console.WriteLine($"sum={sum},i={i},j={j}");
17
18 Console.ReadLine();
19 }
20
21 private static int Sum1(int a)
22 {
23 var i = a;
24 var j = 11;
25 int sum = Sum2(i, j);
26
27 return sum;
28 }
29
30 private static int Sum2(int a, int b)
31 {
32 var i = a;
33 var j = b;
34 var k = 13;
35
36 var sum = Sum3(i, j, k);
37 return sum;
38 }
39
40 private static int Sum3(int i, int j, int k)
41 {
42 return i + j + k;
43 }
44 }
45 }
2.4、ExampleCore_3_1_4
1 namespace ExampleCore_3_1_4
2 {
3 internal class Program
4 {
5 static void Main(string[] args)
6 {
7 //第一次调用函数
8 Console.WriteLine("Press any key(1st instance function)");
9 Console.ReadKey();
10 BreakPoint bp = new BreakPoint();
11 bp.AddAndPrint(10, 5);
12
13 //第二次调用函数
14 Console.WriteLine("Press any key(2nd instance function)");
15 Console.ReadKey();
16 bp = new BreakPoint();
17 bp.AddAndPrint(100, 50);
18 }
19 }
20
21 internal class BreakPoint
22 {
23 public void AddAndPrint(int a, int b)
24 {
25 int res = a + b;
26 Console.WriteLine("Adding {0}+{1}={2}", a, b, res);
27 }
28 }
29 }
2.5、ExampleCore_3_1_5
1 using System.Diagnostics;
2
3 namespace ExampleCore_3_1_5
4 {
5 internal class Program
6 {
7 static void Main(string[] args)
8 {
9 Debugger.Break();
10
11 var mylist = new MyList<int>();
12
13 mylist.Add(10);
14
15 Console.ReadLine();
16 }
17 }
18
19 public class MyList<T>
20 {
21 public T[] arr = new T;
22
23 public void Add(T t)
24 {
25 arr = t;
26 }
27 }
28 }
三、底子知识和眼见为实
3.1、调试器以及调试目的
A、知识介绍
在任何调试中都包含两个组件:调试器和调试目的。
调试器:是一个引擎,我们必须通过这个引擎和调试目的举行交互。所有与调试目的之间的交互操作(如:设置断点、观察状态等),都可以通过调试器的命令完成,而调试器将在调试目的的情况中执行这些命令。
调试目的:一样平常指我们编写的步伐,对于 .Net步伐员来说就是,大概是要调试的步伐。
它们之间的关系,有一张图可以更好的表现他们之间的关系。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227140059969-2116993412.png
B、眼见为实
在【眼见为实】这个章节里,有些调试动作是一样的,我就不每个节点都写了。我就写在这里了。首先编译好自己的项目,根据自己的喜好,可以切换到编译项目目次下,也可以直接输入项目所在目次,接着就可以举行项目调试了。我使用的命令行工具是【Developer Command Prompt for VS 2022】。
1)、使用NTSD调试器
调试源码:ExampleCore_3_1_1
我们命令行工具中输入命令:ntsd,打开新窗口。效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227143150495-935905410.png
NTSD 的新窗口。
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227143202832-1551729910.png
如果没有指定任何参数,只能显示一组可用的选项。我们将我们的项目完备路径和项目名称作为输入参数。执行【ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\ExampleCore_3_1_1.exe】,弹出新窗口,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227143839660-607445368.png
新的 NTSD 窗口,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227143911937-1052896330.png
上面这个截图分三个部分:第一部分是符号文件搜索路径,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227145323505-2058715900.png
第二部分:加载的所有模块,表示应用步伐所需要的模块都已经加载完毕。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227151010861-926037240.png
第三部分:停止指令异常。每当调试器启动一个进程大概调试器附加到一个进程的时候,调试器都会注入一个停止指令,这条指令将使调试目的停止运行。断点指令的作用:使用户与调试器和调试目的举行交互。这里是 int 3 停止。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227151506157-279995008.png
调试器的命令提示符是:X:Y>,X 表示当前正在被调试的运动目的(在大多数调试器中,这个值为 0),Y 表示导致调试器停止的线程 ID。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227153705317-942654637.png
2)、使用 NTSD 附加进程
调试源码:ExampleCore_3_1_1
当在调试器下启动有问题的引用步伐的时候,这种调试方式很有作用。如果是引用步伐已经运行起来了,那我们该如何调试呢?我们可以通过给【ntsd】命令,加上 -p 命令,就可以附加进程了。比如:你写的一个 Web 服务已经乐成运行起来了。随着时间的推移,这个 Web 服务开始表现出一些怪怪的行为,你盼望当步伐具有这总奇怪行为的时候对它举行调试,附加调试就可以大展拳脚了。
-p 参数告诉调试器盼望调试一个正在运行的进程。对于这个参数后,再跟上要调试进程的 Id 就可以了。
执行命令【ntsd -p 14624】,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227160208793-1839633245.png 打开新的调试器窗口,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227160322449-319978612.png
下面很有许多内容,就不显示了。
3)、使用 TList.exe 显示进程 Id。
调试源码:ExampleCore_3_1_1
如果我们想获取一个进程的 id,可以有许多方法,我们可以使用 Windows 调试工具会合的 tlist.exe,tlist.exe 会输出所有运行的进程名称和 ID。启动我们的 ExampleCore_3_1_1.exe,在控制台输出:Welcome to Advanced .Net Debugging!,我们打开命令工具【Developer Command Prompt for vs 2022】,输出命令 tlist,显示如下:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227155612632-1532485346.png 我们进程的信息如下:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227155732450-572865163.png
4)、使用 Windbg 调试
调试源码:ExampleCore_3_1_1
编译好我们的项目,打开【Windbg Preview】调试器。依次点击【文件】--->【Launch executable】加载我们的项目文件:ExampleCore_3_1_1.exe,选择【打开】按钮,乐成加载并进入调试器界面。
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227161407240-2041725150.png
点击【文件】按钮,切换界面。
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227161421718-769232999.png
点击【Launch executable】按钮打开新窗口。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227161533587-2035161234.png
进入调试器界面,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227161626064-1910624979.png
我们就可以在下方的命令框中输入命令,调试步伐了。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227161820107-1471138646.png
以上是在调试器中启动有问题的引用步伐的流程,如果想使用【Windbg Preview】附加进程该怎么办呢?其实,也很简单,编译项目,打开调试器,依次点击【文件】--->【Attach to process】,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227162750500-1492405487.png
打开选择进程的窗口,在右侧。
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240227163030655-2072246657.png
之后就是进入调试器界面,使用方法就一样了。
3.2、符号
A、知识介绍
符号文件:是一种辅助数据,它包含了对引用步伐代码的一些标注信息,这些信息在调试过程中非常有用。如果没有这些辅助数据,那获得的信息只有引用步伐的二进制文件了。对二进制代码举行调试是非常困难的,由于你无法看到代码中的函数名、数据结构名等。符号文件的扩展名通常是 .pdb。
在符号文件中包含许多重要的信息,比方:行号和局部变量的名称等,它能极大的提高调试的服从。
符号文件有两种类型:私有符号文件(Private)和公有符号文件(Public)。
私有符号文件:是大多数开辟人员在日常工作中使用的符号文件,此中包含了调试会话中所需要的所有符号信息。私有符号文件一样平常是和我们编译的步伐存放在一起的,调试器在开始调试的时候,会自动加载他们。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228104834682-1600104959.png
公有符号文件:这类型的符号文件只是有选择的包含了一些符号信息,这会使调试工作困难一点。比如:在 Microsoft 符号服务器上存储了一些公有符号文件。每当将调试器指向 Microsoft 符号服务器时,都可以下载这些符号文件,并在调试会话中使用它们。
之所以有【私有符号文件】和【公有符号文件】之分,主要是为了保护知识产权。私有符号中包含大量底层技术信息,就很容易对应用步伐举行逆向工程。公有符号就不存在如许的问题,既可以调试,又不会泄露核心技术信息。
B、眼见为实
.sympath(+) 命令的使用,加号表示不会替换,而是追加。
调试源码:ExampleCore_3_1_1
编译好我们的项目,打开【Windbg Preview】工具,依次打开【文件】--->【Launch executable】,加载我们的项目文件:ExampleCore_3_1_1.exe,进入调试器。直接执行【.sympath】命令,就可以看到当前的符号文件信息。
1 0:000> .sympath
2 Symbol search path is: srv*
3 Expanded Symbol search path is: cache*;SRV*https://msdl.microsoft.com/download/symbols
4
5 ************* Path validation summary **************
6 Response Time (ms) Location
7 Deferred srv* 【.sympath】命令可以设置符号文件路径。在命令后跟上详细的符号文件所在的地址。
1 0:007> .sympath E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\
2 Symbol search path is: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\
3 Expanded Symbol search path is: e:\visual studio 2022\source\projects\advanceddebug.netframework.test\examplecore_3_1_1\bin\debug\net8.0\
4
5 ************* Path validation summary **************
6 Response Time (ms) Location
7 OK E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\ 虽然,我们设置新的符号文件的路径,但是并不会从这个路径中加载任何符号。如果想要加载符号信息,必须执行【.reload】命令。我们还是设置回去吧,防止以后有错误。
1 0:007> .sympath srv*
2 Symbol search path is: srv*
3 Expanded Symbol search path is: cache*;SRV*https://msdl.microsoft.com/download/symbols
4
5 ************* Path validation summary **************
6 Response Time (ms) Location
7 Deferred srv* 如果我们使用【.sympath】命令设置错了符号文件地址,使用【.reload】也无法加载符号文件,我们可以使用【.symfix】命令,修复问题就可以了。
1 0:007> .symfix
2 DBGHELP: Symbol Search Path: cache*;SRV*https://msdl.microsoft.com/download/symbols
3 SYMSRV:BYINDEX: 0x17
4 C:\ProgramData\Dbg\sym
5 ntdll.pdb
6 63E12347526A46144B98F8CF61CDED791
7 SYMSRV:PATH: C:\ProgramData\Dbg\sym\ntdll.pdb\63E12347526A46144B98F8CF61CDED791\ntdll.pdb
8 SYMSRV:RESULT: 0x00000000
9 DBGHELP: ntdll - public symbols
10 C:\ProgramData\Dbg\sym\ntdll.pdb\63E12347526A46144B98F8CF61CDED791\ntdll.pdb
11 SYMSRV:BYINDEX: 0x18
12 C:\ProgramData\Dbg\sym
13 kernel32.pdb
14 85A257DB4B7B82F2E19AD96AB7BB116A1
15 SYMSRV:PATH: C:\ProgramData\Dbg\sym\kernel32.pdb\85A257DB4B7B82F2E19AD96AB7BB116A1\kernel32.pdb
16 SYMSRV:RESULT: 0x00000000
17 DBGHELP: KERNEL32 - public symbols
18 C:\ProgramData\Dbg\sym\kernel32.pdb\85A257DB4B7B82F2E19AD96AB7BB116A1\kernel32.pdb 这里有这么多输出,是由于我执行了【!sym noisy】命令,开启了显示符号加载的详细信息,如果不想显示,可以使用【!sym quiet 】命令。
1 0:007> !sym quiet
2 quiet mode - symbol prompts on
3 0:007> .symfix 【.symfix】命令后面可以跟一个参数,就是当地路径,用来缓存下载的符号文件,就不用调试器每次去下载雷同的符号了,可以直接从当地加载。在默认情况下,如果没有指定当地路径缓存,那么调试器将使用调试软件包安装的路径下的 sym 文件夹。
3.3、控制调试目的的执行
在任何调试会话中,能够控制调试目的的执行是非常重要的。我们可以设置断点,然后恢复步伐的执行知道断点处,在此可以查看应用步伐的状态,单步跟踪到函数内部,然后在恢复执行等。
3.3.1、停止执行
调试器停止步伐的方式有许多种,我举三种最常用的停止执行的方式。
1)、如果我们使用的命令行调试器,可以使用【ctrol+c】组合键手动方式停止调试目的的执行,比方:调试死锁问题。
2)、给我们的应用步伐设置断点来停止调试目的的执行。通过设置断点,可以很方便的使调试器在执行流程的恣意位置上停止执行。
3)、抛出异常可以使调试器停止执行。
3.3.2、恢复执行
A、知识介绍
当调试器停止执行时(可能是触发了断点大概其他的变乱),可以使用【g】命令回复调试器的执行。如果【g】命令不带任何参数,只是回复调试目的的执行,直到下一次发生某个调试变乱。
B、眼见为实
1)、使用【g】命令恢复执行。
调试源码:ExampleCore_3_1_2
编译好我们的项目,打开我们的命令工具,输入命令【ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】,打开调试器新窗口。
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228135558921-262508828.png 打开新的调试器窗口,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228135846378-1597905994.png
触发初始断点是调试器的默认行为,也是调试人员开始分析应用步伐的最早时机。此时,调试目的会停止执行并等候输入命令。此刻,我们可以输入【g】命令,恢复执行,调试器输出如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228140350822-877002392.png
2)、禁用初始和退出断点
调试源码:ExampleCore_3_1_2
如果不盼望调试器在初始启动时停止步伐的执行,可以在启动调试器时使用 -g 命令开关,每当调试器退出时,调试器也将停止执行,可以使用 -G(大写)命令开关,避免在进程竣事时触发终极的断点。
编译好我们的项目,打开我们的命令行工具,使用【ntsd -g E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】命令,启动调试器。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228142533712-1316581155.png 打开新的调试器窗口,这次已经输出“第一次执行,并开始停止执行!”,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229103946599-1679144902.png
如果我们使用 -G 命令开关,执行命令【ntsd -G E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】,调试器在初始会停止执行。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228143311179-1111542205.png
如果没有使用 -G 命令开关,我们还需要输入命令才气退出。
3.3.3、单步调试代码
我们使用过 VS IDE 的调试功能,快捷键有:F10,F11,F9等,调试器也为我们提供了类似的命令。但是,需要注意:如果在调试托管代码时使用非托管调试器,那么通常是对 JIT 编译器产生的机器代码举行单步调试。
A、知识介绍
1)、p 命令
p(step):命令其实就是 VS 中的 f10 快捷键,单步执行,遇到函数也是当成一条指令执行,不会进入函数体。
2)、t 命令
t(trace):命令其实就是 VS 的 f11 快捷键,它是一种进入函数的单步执行调试。
3)、pc 命令
pc(Step to Next Call) 就是不停运行直到遇到 call 为止,不会进入函数体,call 是一个函数调用,汇编指令。
4)、tc 命令
tc(Trace to Next Call) 和 pc 不同的是,tc 会进入方法体,直到遇到 call 为止。
5)、pt 命令
pt(Step to Next Return) 如果有方法会进入方法内部递归处理,遇到下一个 ret 为止。
6)、tt 命令
tt(Trace to Next Return) 会进入函数体直到遇到 ret 为止。递归的意思。
B、眼见为实
这一节的演示,使用【Windbg Preview】,我没有使用的是【NSTD】调试器,其他他们是一样的,只不外一个是由界面的,一个是没界面的。有些操作是一样的,我就写在这里了。
编译好我们的项目,打开【Windbg Preview】,依次点击【文件】--->【Launch executable】,加载我们的项目文件:ExampleCore_3_1_3.exe,进入调试器。界面的内容太多,我们可以使用【.cls】命令,清空调试器的界面。我们再使用【g】命令,继续运行调试器,我们现在查看一下托管代码的调用栈,执行命令【!clrstack】。
0:000> g
ModLoad: 00007ff9`454c0000 00007ff9`454f0000 C:\Windows\System32\IMM32.DLL
ModLoad: 00007ff8`8d8e0000 00007ff8`8d938000 C:\Program Files\dotnet\host\fxr\8.0.0\hostfxr.dll
ModLoad: 00007ff8`812d0000 00007ff8`81334000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\hostpolicy.dll
ModLoad: 00007ff8`80de0000 00007ff8`812cb000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\coreclr.dll
ModLoad: 00007ff9`454f0000 00007ff9`45619000 C:\Windows\System32\ole32.dll
ModLoad: 00007ff9`44b10000 00007ff9`44e64000 C:\Windows\System32\combase.dll
ModLoad: 00007ff9`45d60000 00007ff9`45e35000 C:\Windows\System32\OLEAUT32.dll
ModLoad: 00007ff9`444f0000 00007ff9`4456f000 C:\Windows\System32\bcryptPrimitives.dll
(3994.1028): Unknown exception - code 04242420 (first chance)
ModLoad: 00007ff8`7fb10000 00007ff8`807a8000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Private.CoreLib.dll
ModLoad: 00007ff8`7f950000 00007ff8`7fb08000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\clrjit.dll
ModLoad: 00007ff9`44120000 00007ff9`44133000 C:\Windows\System32\kernel.appcore.dll
ModLoad: 0000021a`398c0000 0000021a`398c8000 E:\Visual Studio 2022\.\ExampleCore_3_1_3\bin\Debug\net8.0\ExampleCore_3_1_3.dll
ModLoad: 0000021a`398d0000 0000021a`398de000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Runtime.dll
ModLoad: 00007ff8`7f920000 00007ff8`7f948000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Console.dll
(3994.1028): Break instruction exception - code 80000003 (first chance)
KERNELBASE!wil::details::DebugBreak+0x2:
00007ff9`44799202 cc int 3
0:000> !clrstack
OS Thread Id: 0x1028 (0)
Child SP IP Call Site
00000020B9B7E6A8 00007ff944799202 System.Diagnostics.Debugger.BreakInternal()
00000020B9B7E7B0 00007ff87ff360aa System.Diagnostics.Debugger.Break()
00000020B9B7E7E0 <strong>00007ff8213f197f</strong> ExampleCore_3_1_3.Program.Main(System.String[]) 我们找到了红色标注的【Program.Main()】方法的地址:00007ff8213f197f,有了这个地址,我们就可以对这个地址下一个断点。
1 0:000> bp 00007ff8213f197f 设置好断点后,我们就可以使用【g】命令,继续运行调试器。
1)、p、pc、pt 命令的使用
调试源码:ExampleCore_3_1_3
我们设置好了断点,就可以开始我们的调试工作了。继续使用【g】运行调试器,调试器会在【Debugger.Break()】这行代码暂停,效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229113222486-1440786997.png 我们可以使用【p】命令,单步执行,到了第15行代码,会直接跳过而执行,不会进入方法。当然,这个过程要执行多次【p】命令。
1 0:000> p
2 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x50:
3 00007ff8`213f1980 c745fc0a000000mov dword ptr ,0Ah ss:00000020`b9b7e84c=00000000
4 0:000> p
5 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x57:
6 00007ff8`213f1987 c745f814000000mov dword ptr ,14h ss:00000020`b9b7e848=00000000
7 0:000> p
8 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x5e:
9 00007ff8`213f198e 8b4dfc mov ecx,dword ptr ss:00000020`b9b7e84c=0000000a
10 0:000> p
11 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x61:
12 00007ff8`213f1991 ff1531520a00 call qword ptr ds:00007ff8`21496bc8=00007ff8213f1a20
13 0:000> p
14 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x67:
15 00007ff8`213f1997 8945c0 mov dword ptr ,eax ss:00000020`b9b7e810=00000000
16 0:000> p
17 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x6a:
18 00007ff8`213f199a 8b4dc0 mov ecx,dword ptr ss:00000020`b9b7e810=00000022
19 0:000> p
20 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x6d:
21 00007ff8`213f199d 894df4 mov dword ptr ,ecx ss:00000020`b9b7e844=00000000
22 0:000> p
23 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x70:
24 00007ff8`213f19a0 488d4dc8 lea rcx, 【pc】命令调试:
前面的操作一样,查看堆栈,设置断点,开始运行,到断点出暂停。
【pc】命令很简单,我们直接输入【pc】,代码直接会运行到【var sum = Sum1(i)】,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229114347590-929214285.png
中间的代码是直接跳过的。
【pt】命令调试:
前面的操作一样,查看堆栈,设置断点,开始运行,到断点出暂停。
我又增加了一些断点,断点如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229133915324-1864732111.png 接着如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229133945337-511061235.png
执行【pt】命令的过程如下,执行【g】命令,到【Debugger.Break()】如许代码处停止执行。
1 0:000> g
2 Breakpoint 0 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x4f:
4 00007ff8`2101197f 90 nop 效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229134545576-1310667562.png
继续执行【pt】命令,会在【var sum = Sum1(i)】这行带出停止执行。
1 0:000> g
2 Breakpoint 1 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x5e:
4 00007ff8`20af198e 8b4dfc mov ecx,dword ptr ss:000000ef`fb17e6ac=0000000a 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229134816116-217669792.png
继续执行【pt】命令,会进入【Sum1()】方法内部,在断点处停止执行。
1 0:000> pt
2 Breakpoint 2 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum1+0x24:
4 00007ff8`20af1a84 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229135118807-1768668891.png
继续执行【pt】命令,会到【int sum = Sum2(i, j)】这行代码停止执行。
1 0:000> pt
2 Breakpoint 8 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum1+0x32:
4 00007ff8`20af1a92 8b4dfc mov ecx,dword ptr ss:000000ef`fb17e62c=0000000a 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229135342930-483992752.png
继续执行【pt】命令,会进入【Sum2()】方法内部。
1 0:000> pt
2 Breakpoint 4 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum2+0x2c:
4 00007ff8`20af1afc 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229135536106-1070052055.png
继续执行【pt】命令,会到【var sum=Sum3(i,j,k)】这行代码处停止执行。
1 0:000> pt
2 Breakpoint 5 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum2+0x40:
4 00007ff8`20af1b10 8b4dfc mov ecx,dword ptr ss:000000ef`fb17e5dc=0000000a 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229135829093-1551975325.png
继续执行【pt】命令,会进入【Sum3()】方法内部。
1 0:000> pt
2 Breakpoint 13 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum3+0x27:
4 00007ff8`20af1b77 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229140045792-1232736246.png
继续执行【pt】命令,执行到43行代码处停止执行。
1 0:000> pt
2 Breakpoint 7 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum3+0x35:
4 00007ff8`20af1b85 8b45fc mov eax,dword ptr ss:000000ef`fb17e58c=00000022 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229140300397-2034262719.png
末了我们执行一个【pt】命令,也就是【Sum3()】方法竣事,遇到【ret】,调试器停止执行。
1 0:000> pt
2 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum3+0x3d:
3 00007ff8`20af1b8d c3 ret 这里我增加了许多断点,是为了测试是否会进入方法内部。执行【pt】命令,如果有方法调用会进入方法内部,知道遇到【ret】为止。
2)、t、tc、tt 命令的使用
调试源码:ExampleCore_3_1_3
【t】命令使用:
我们进入调试器,设置断点,使用【g】命令运行调试器。调试器会在 Program.Main() 方法的【Debugger.Break()】这行代码停止执行。
1 0:000> g
2 Breakpoint 0 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x4f:
4 00007ff8`20dd197f 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229143104891-1507841026.png
继续运行【t】命令,单步执行,遇到【Sum1(i)】方法,就会进入方法内部举行单步调试。
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229143558329-260330574.png
【t】命令很简单,就像 VS 的 F11快捷键一样,按一下执行一条命令。
【tc】命令使用:
当我们在调试器中使用【bp】命令设置好断点后,就可以看是测试命令了。
1 0:000> g
2 Breakpoint 0 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x4f:
4 00007ff8`20e0197f 90 nop 执行【g】命令,调试器会在 Program.Main() 方法的【Debugger.Break()】这行代码出停止执行。执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229144059643-737807187.png
我们继续执行【tc】命令,它会到【var sum = Sum1(i)】这行代码处停止执行,由于调用 Sum1方法是通过【call】指令的。
1 0:000> tc
2 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x61:
3 00007ff8`20e01991 ff1531520a00 call qword ptr ds:00007ff8`20ea6bc8=00007ff820e01a60 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229144944581-751302105.png
再次执行【tc】命令,调试器会在Sum1方法内的【int sum = Sum2(i, j)】这行代码处停止执行。
1 0:000> tc
2 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum1+0x38:
3 00007ff8`20e01a98 ff1542510a00 call qword ptr ds:00007ff8`20ea6be0=00007ff820e01ad0 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229145401204-1342236799.png
就不继续了,下一个停止执行点是Sum2方法【var sum = Sum3(i, j, k)】这行代码,这个命令很简单。
【tt】命令使用:
当我们在调试器中设置到断点后,就可以开始调试了,测试我们的命令了。
我们使用【g】命令运行调试器,调试器会在Program.Main方法的【Debugger.Break()】这行代码处停止执行。
1 0:000> g
2 Breakpoint 0 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x4f:
4 00007ff8`20e0197f 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229150039734-1975739131.png
继续执行【tt】命令,会进入Sum1方法内部。
1 0:000> tt
2 Breakpoint 2 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum1+0x24:
4 00007ff8`20e01a84 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229150321551-1501295224.png
再次继续执行【tt】命令,会进入Sum2方法内部。
1 0:000> tt
2 Breakpoint 4 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum2+0x2c:
4 00007ff8`20e01afc 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229150504708-1811472808.png
再次继续执行【tt】命令,会进入Sum3方法内部。
1 0:000> tt
2 Breakpoint 6 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum3+0x27:
4 00007ff8`20e01b77 90 nop 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240229150648762-1691695302.png
当我们再次运行【tt】命令,调试器会在【43】行停止执行。再次执行【tt】命令,遇到Sum3方法的返回命令【ret】则为止。
1 0:000> tt
2 Breakpoint 7 hit
3 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum3+0x35:
4 00007ff8`20e01b85 8b45fc mov eax,dword ptr ss:00000045`d7f7e52c=00000022
5 0:000> tt
6 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Sum3+0x3d:
7 00007ff8`20e01b8d c3 ret 这个命令也不复杂,各人逐步体会吧。
3.3.4、退出调试回话
在执行完一个调试会话后,可以有许多方式退出调试回话,这里演示主要是以命令行调试器为主。
A、知识介绍
1)、q(quit):竣事调试会话+调试步伐退出
调试会话竣事,应用步伐也会退出。
2)、qd(quit and detach):竣事调试会话+调试步伐继续运行
调试会话竣事,应用步伐保持运行态,不会退出。
B、眼见为实
这里调试我使用的是【ntsd】,没有使用【Windbg Preview】,使用是一样的。
1)、q 命令退出
调试源码:ExampleCore_3_1_2
编译好我们的项目,执行命令【ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】加载调试器。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228164954972-1404592866.png 开启新的调试器窗口,我们可以输入【q】命令,查看结果。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228165227357-1278070999.png
按回车,调试器也关闭了,步伐也关了。
2)、qd 命令退出
调试源码:ExampleCore_3_1_2
编译好我们的项目,通过命令【ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】加载调试器。如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228165435422-431382224.png 打开新的调试器窗口,输入命令【qd】,按回车,如图:
https://img2024.cnblogs.com/blog/1048776/202402/1048776-20240228165718686-1647819114.png
效果很明显,不用多说了。
3.4、加载托管代码调试的扩展命令
3.4.1、加载 SOS 调试器扩展
A、知识介绍
SOS 调试器的扩展 DLL 与步伐使用的 CLR 版本是是相干的。因此,在发布每个 CLR 主版本的同时,都会发布一个新版本的 SOS 调试扩展。以确保这个 DLL 可以使用该版本 CLR 的新功能。SOS 扩展作为运行时的一部分发布的,它的路径位于:%systemRoot%Microsoft.Net\Framework\\sos.dll。
在非托管调试器中可以使用两类命令,一类是:元命令,另一类是:扩展命令。
元命令:指在调试器引擎中内置的命令,当使用该命令的时候,必须在命令前加上英文点号。如:.cls。如果想要列出所有的元命令,可以使用【.help】命令。
扩展命令:指在调试器引擎之外的独立的 dll 中实现的,这些 DLL 也被称为调试器扩展。在使用扩展命令的时候,命令前面加上前缀“!”。如:!clrstack。
无论是【NTSD】还是【Windbg Preview】,现在会自动加载 SOS.DLL,以前的老版本需要使用【.load】加载 SOS.DLL。
B、眼见为实
使用【.load】加载 SOS.DLL
1 0:000> .load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\sos.dll
3.4.2、加载 SOSEX 调试器扩展
这个调试器扩展很好用,但是也很可惜,它只支持 Net Framework 版本。在最新的 .Net 版本是抛弃的,不能使用了。如果想查看调试过程,可以查看我的另外一个系列【Net 高级调试】中有一篇文章,地址:https://www.cnblogs.com/PatrickLiu/p/17788840.html
3.5、控制 CLR 的调试
如果我们想在托管代码调试的过程总输出各种信息(比方:SOS 命令的输出),我们可以加载一个辅助 DLL,称为:mscordacwks.dll。加载【mscordacwks.dll】的路径取决于倍加再到进程中【mscorwks.dll】的路径。在【实时调试】中一样平常没有问题,两个 dll 版本是同等的。如果是【事后调试】,可能会出现版本不同等的情况,我们可以使用元命令:cordll 来解决。
比如:.cordll -lp c:\x\y\z,如许就能告诉调试器从 c:\x\y\z 目次下加载 mscordacwks.dll。
3.6、设置断点
设置断点的目的就是为了告诉目的步伐在执行到了断点处停止执行。断点可以使用开辟人员分析步伐在执行流中的状态,并且找到出现问题的根本缘故原由。在非托管代码中设置断点很容易,由于我们知道了代码的位置,于是就可以使用【bp】命令在代码位置处设置断点了。
3.6.1、在非托管代码中设置断点。
这次我们使用 notepad.exe 做这个调试,由于它是非托管步伐,代码已经编译成机器码了,代码地址有了,我们就可以使用【bp】命令直接设置断点了。
眼见为实:
1)、使用【NTSD】调试
我们先打开【notepad.exe】应用步伐,然后,执行【tlist】命令,获取 notepad 应用步伐的 id,效果如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301131145236-363906790.png
执行命令【ntsd -p 9580】,附加 notepad 应用步伐的进程,打开调试器。
1 D:\Program Files\Microsoft Visual Studio\2022\Community>ntsd -p 9580 执行效果如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301131454891-1006619254.png
打开调试器窗口,如下:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301131634883-1517952292.png
这个时候,我们打开的 notepad 是不能操作的,由于调试器已经停止执行了。
执行【X notepad!*Save*】命令,查找 Notepad 的包含 Save 关键字的方法。
1 0:001> X notepad!*Save*
2 00007ff6`510e86a0 notepad!ShowOpenSaveDialog (void)
3 00007ff6`510ec5cc notepad!InitLegacyOpenSaveEncodingComboBox (void)
4 00007ff6`510ffd54 notepad!TraceFileSaveStart (void __cdecl TraceFileSaveStart(void))
5 00007ff6`510f047c notepad!SaveGlobals (void __cdecl SaveGlobals(void))
6 00007ff6`510f0314 notepad!RegGetIntSaveDefault (unsigned long __cdecl .....)
7 00007ff6`510ec7a0 notepad!NpLegacySaveDialogHookProc (unsigned __int64 __cdecl ....))
8 00007ff6`511013a8 notepad!FileSaveDialog_GetSelectedEnterpriseId (FileSaveDialog_GetSelectedEnterpriseId)
9 00007ff6`510e8b8c notepad!InvokeLegacySaveDialog (long __cdecl InvokeLegacySaveDialog(unsigned short const *,...)
10 00007ff6`510fef58 notepad!RestartHandler::TryAutosaveOpenedDocument (public: bool __cdecl RestartHandler::TryAutosaveOpenedDocument(void))
11 <strong>00007ff6`510ee780</strong> <strong>notepad!SaveFile</strong> (bool __cdecl SaveFile(struct HWND__ *,.....))
12 00007ff6`510ea2e8 notepad!CheckSave (int __cdecl CheckSave(void))
13 00007ff6`511123bc notepad!fInSaveAsDlg = <no type information>
14 00007ff6`510ea124 notepad!CheckSaveTaskDlgBox (int __cdecl CheckSaveTaskDlgBox(unsigned short const *))
15 00007ff6`510ffdd4 notepad!TraceFileSaveComplete (void __cdecl TraceFileSaveComplete(struct _NP_FileInfo *,int))
16 00007ff6`510e8d90 notepad!InvokeSaveDialog (long __cdecl InvokeSaveDialog(struct HWND__ *,...))
17 00007ff6`511105c0 notepad!szSaveCaption = <no type information>
18 00007ff6`5110508c notepad!_imp_load_GetSaveFileNameW (__imp_load_GetSaveFileNameW)
19 00007ff6`51111640 notepad!g_ftSaveAs = <no type information>
20 00007ff6`51107570 notepad!CLSID_FileSaveDialog = <no type information>
21 00007ff6`510ff258 notepad!RestartHandler::TryRestoreAutosavedDocument (public: bool __cdecl...)
22 00007ff6`511150b0 notepad!_imp_GetSaveFileNameW = <no type information> 代码中有些【...】如许的省略号,表示内容太长,省略了。
红色标注的就是我们找到了 notepad 生存功能的方法名称和地址。我们直接执行【bp notepad!SaveFile】命令大概【bp 00007ff6`510ea2e8】命令,都可以在 SaveFile 方法上下断点。
1 0:001> bp notepad!SaveFile 下完断点后,我们【g】继续执行。但是,此时调试器的光标在闪动,我们打开的 notepad 窗口也可以使用了。效果如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301133031032-1204458966.png
我们在 notepad 窗口中随意写一些文字,点击【文件】-->【生存】,就会触发断点。效果如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301134154728-887501507.png
此时的 notepad 应用步伐的窗口是不能使用的,由于在断点出已经停止执行了。
我们继续使用【g】命令,继续调试器的运行,notepad 才可以正常使用,文件也生存乐成。
2)、使用【Windbg Preview】调试。
我们先打开一个 notepad.exe 应用步伐。然后再打开【Windbg Preview】,依次点击【文件】--->【Attach to process】,在窗口右侧【进程列表】框中选择 notepad 进程,点击【附加】,进入调试器。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301135720986-1865766030.png 我们使用【X notepad!*Save*】命令,查找 notepad 的生存数据的方法。
1 0:002> X notepad!*Save*
2 00007ff6`510e86a0 notepad!ShowOpenSaveDialog (void)
3 00007ff6`510ec5cc notepad!InitLegacyOpenSaveEncodingComboBox (void)
4 00007ff6`510ffd54 notepad!TraceFileSaveStart (void __cdecl TraceFileSaveStart(void))
5 00007ff6`510f047c notepad!SaveGlobals (void __cdecl SaveGlobals(void))
6 00007ff6`510f0314 notepad!RegGetIntSaveDefault (unsigned long __cdecl ...)
7 00007ff6`510ec7a0 notepad!NpLegacySaveDialogHookProc (unsigned __int64 __cdecl ...)
8 00007ff6`511013a8 notepad!FileSaveDialog_GetSelectedEnterpriseId (FileSaveDialog_GetSelectedEnterpriseId)
9 00007ff6`510e8b8c notepad!InvokeLegacySaveDialog (long __cdecl ...)
10 00007ff6`510fef58 notepad!RestartHandler::TryAutosaveOpenedDocument (public: bool __cdecl RestartHandler::TryAutosaveOpenedDocument(void))
11 <strong>00007ff6`510ee780 notepad!SaveFile</strong> (bool __cdecl SaveFile(struct HWND__ *,class ...))
12 00007ff6`510ea2e8 notepad!CheckSave (int __cdecl CheckSave(void))
13 00007ff6`511123bc notepad!fInSaveAsDlg = <no type information>
14 00007ff6`510ea124 notepad!CheckSaveTaskDlgBox (int __cdecl CheckSaveTaskDlgBox(unsigned short const *))
15 00007ff6`510ffdd4 notepad!TraceFileSaveComplete (void __cdecl TraceFileSaveComplete(struct _NP_FileInfo *,int))
16 00007ff6`510e8d90 notepad!InvokeSaveDialog (long __cdecl InvokeSaveDialog(struct HWND__ *...))
17 00007ff6`511105c0 notepad!szSaveCaption = <no type information>
18 00007ff6`5110508c notepad!_imp_load_GetSaveFileNameW (__imp_load_GetSaveFileNameW)
19 00007ff6`51111640 notepad!g_ftSaveAs = <no type information>
20 00007ff6`51107570 notepad!CLSID_FileSaveDialog = <no type information>
21 00007ff6`510ff258 notepad!RestartHandler::TryRestoreAutosavedDocument (public: bool __cdecl...)
22 00007ff6`511150b0 notepad!_imp_GetSaveFileNameW = <no type information> 红色标注的就是我们要找的方法名和地址。此时,notepad的窗口是不可以使用的。使用【bp 00007ff6`510ee780】命令下断点。
1 0:002> bp 00007ff6`510ee780 继续【g】,运行调试器,我们操作 notepad窗口,随意输入文字,然后点击【文件】--->【生存】,调试器运行,在 SaveFile 方法的断点出停止执行,notepad 窗口也不能使用了。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301141101399-1921804701.png
我们使用【g】命令,继续运行,notepad 生存乐成。
3.6.2、在 JIT 编译的托管函数上下断点
A、知识介绍
非托管方法设置断点很容易,由于代码都已经被编译了,代码的地址就是已知的。但是,托管代码要举行两次编译才气运行。我们想要给代码设置断点,必须先找到代码的位置。这一节我们讨论已经编译的函数如何设置断点,既然已经编译了,说明代码的地址就是可以直接找到的,设置断点就很容易了。
JIT 编译器编译了一个函数并将其放在内存中。如果我们知道了 JIT 编译器生存机器代码的位置,我们就可以使用调试器命令【bp】设置断点了。
B、眼见为实
调试任务:在第二次调用 AddAndPrint 方法的时候设置断点。为什么选择第二次,第一次已经执行过了,说明已经编译了。第二次就是使用编译的机器码。
1)、使用【NTSD】调试
调试源码:ExampleCore_3_1_4
执行【ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_4\bin\Debug\net8.0\ExampleCore_3_1_4.exe】命令,开启调试器。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301143907995-997668572.png 打开【ntsd】调试器窗口。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301144044724-917284050.png
我们使用【g】命令,运行调试器,直到调试器显示【Press any key(1st instance function)】暂停,等候输入。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301144244645-825034136.png
我们按下恣意键,步伐继续执行,直到调试器输出【Press any key(2nd instance function)】。此时,我们按下【ctrl+c】进入调试器的停止模式。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301145246690-833096023.png
现在,我们可以使用【!name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint】命令找到方法的是否编译的信息。
1 0:002> !name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint
2 Module: 00007ffccc24e0a0
3 Assembly: ExampleCore_3_1_4.dll
4 Token: 0000000006000003
5 MethodDesc:00007ffccc279398
6 Name: ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)
7 <strong>JITTED Code Address: 00007ffccc1c1a90</strong> 红色标注的说明代码已经编译了,地址是:00007ffccc1c1a90,如果不信,我们可以使用【u】命令确认一下。
1 0:002> !U 00007ffccc1c1a90
2 <strong>Normal JIT generated code
</strong> 3 <strong>ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)
</strong> 4 ilAddr is 000002B7069720AC pImport is 000001A39F836140
5 Begin 00007FFCCC1C1A90, size b7
6 >>> 00007ffc`cc1c1a90 55 push rbp
7 00007ffc`cc1c1a91 4883ec40 sub rsp,40h
8 00007ffc`cc1c1a95 488d6c2440 lea rbp,
9 00007ffc`cc1c1a9a c5d857e4 vxorpsxmm4,xmm4,xmm4
10 00007ffc`cc1c1a9e c5f97f65e0 vmovdqa xmmword ptr ,xmm4
11 00007ffc`cc1c1aa3 c5f97f65f0 vmovdqa xmmword ptr ,xmm4
12 00007ffc`cc1c1aa8 48894d10 mov qword ptr ,rcx
13 00007ffc`cc1c1aac 895518 mov dword ptr ,edx
14 00007ffc`cc1c1aaf 44894520 mov dword ptr ,r8d
15 00007ffc`cc1c1ab3 833d6ec8080000cmp dword ptr ,0
16 00007ffc`cc1c1aba 7405 je 00007ffc`cc1c1ac1
17 00007ffc`cc1c1abc e84fefc75f call coreclr!JIT_DbgIsJustMyCode (00007ffd`2be40a10)
18 00007ffc`cc1c1ac1 90 nop
19 00007ffc`cc1c1ac2 8b4d18 mov ecx,dword ptr
20 00007ffc`cc1c1ac5 034d20 add ecx,dword ptr
21 00007ffc`cc1c1ac8 894dfc mov dword ptr ,ecx
22 00007ffc`cc1c1acb 48b9881113ccfc7f0000 mov rcx,7FFCCC131188h (MT: System.Int32)
23 00007ffc`cc1c1ad5 e8469ab55f call coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffd`2bd1b520)
24 00007ffc`cc1c1ada 488945f0 mov qword ptr ,rax
25 00007ffc`cc1c1ade 488b4df0 mov rcx,qword ptr
26 00007ffc`cc1c1ae2 8b4518 mov eax,dword ptr
27 00007ffc`cc1c1ae5 894108 mov dword ptr ,eax
28 00007ffc`cc1c1ae8 48b9881113ccfc7f0000 mov rcx,7FFCCC131188h (MT: System.Int32)
29 00007ffc`cc1c1af2 e8299ab55f call coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffd`2bd1b520)
30 00007ffc`cc1c1af7 488945e8 mov qword ptr ,rax
31 00007ffc`cc1c1afb 488b4de8 mov rcx,qword ptr
32 00007ffc`cc1c1aff 8b4520 mov eax,dword ptr
33 00007ffc`cc1c1b02 894108 mov dword ptr ,eax
34 00007ffc`cc1c1b05 48b9881113ccfc7f0000 mov rcx,7FFCCC131188h (MT: System.Int32)
35 00007ffc`cc1c1b0f e80c9ab55f call coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffd`2bd1b520)
36 00007ffc`cc1c1b14 488945e0 mov qword ptr ,rax
37 00007ffc`cc1c1b18 4c8b4de0 mov r9,qword ptr
38 00007ffc`cc1c1b1c 8b55fc mov edx,dword ptr
39 00007ffc`cc1c1b1f 41895108 mov dword ptr ,edx
40 00007ffc`cc1c1b23 4c8b4de0 mov r9,qword ptr
41 00007ffc`cc1c1b27 488b55f0 mov rdx,qword ptr
42 00007ffc`cc1c1b2b 4c8b45e8 mov r8,qword ptr
43 00007ffc`cc1c1b2f 48b9880a8d9bf7020000 mov rcx,2F79B8D0A88h ("Adding {0}+{1}={2}")
44 00007ffc`cc1c1b39 ff15d12c0d00 call qword ptr
45 00007ffc`cc1c1b3f 90 nop
46 00007ffc`cc1c1b40 90 nop
47 00007ffc`cc1c1b41 4883c440 add rsp,40h
48 00007ffc`cc1c1b45 5d pop rbp
49 00007ffc`cc1c1b46 c3 ret 在反汇编代码的第一部分很清晰的表明方法的名称,并且是 JIT 生成的。第四行【Begin 00007FFCCC1C1A90, size b7】表示方法的起始地址和生成代码的大小。
设置断点,执行命令【 bp 00007ffccc1c1a90】。
1 0:002> bp 00007ffccc1c1a90 断点设置乐成后,但是我在运行调试的时候出错,还没有找到缘故原由和解决办法,如果有知道缘故原由的,不吝见教。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301152214892-2037216545.png
2)、使用【Windbg Preview】调试
调试源码:ExampleCore_3_1_4
编译好我们的项目,打开【Windbg Preview】,依次点击【文件】--->【Launch executable】,加载我们的项目文件:ExampleCore_3_1_4.exe,进入到调试器。
先执行【g】命令,运行调试器。等我们的控制台输出:Press any key(1st instance function),我们在按恣意键继续。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301160803651-44336299.png 控制台步伐如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301160848813-262585491.png
我们按【回车键】,如下:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301160957498-1225707613.png
这时,我们回到【Windbg Preview】调试器中,调试窗口是如许的,如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301161117976-472189426.png
我们点击【Break】按钮,让调试器进入停止模式。
1 (39a0.944): Break instruction exception - code 80000003 (first chance)
2 ntdll!DbgBreakPoint:
3 00007ffd`f89ee880 cc int 3 我们使用【!name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint】命令,查看方法的信息。
0:001> !name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint
Module: 00007ffcccd0e0a0
Assembly: ExampleCore_3_1_4.dll
Token: 0000000006000003
MethodDesc:00007ffcccd39398
Name: ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)
JITTED Code Address: 00007ffcccc81c40 JITTED 表示已经是编译过的,编译的地址是:00007ffcccc81c40。当然,我们可以使用【!U 00007ffcccc81c40】命令查看汇编代码。
1 0:001> !U 00007ffcccc81c40
2 <strong>Normal JIT generated code (说明是 JIT 生成的)
</strong> 3 <strong>ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)(方法的名称,说明我们获取的地址是对的)
</strong> 4 ilAddr is 00000217571620AC pImport is 00000214BF4B0480
5 <strong>Begin 00007FFCCCC81C40, size b7(代码的开始地址和生成代码的大小)
</strong> 6
7 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_4\Program.cs @ 24:
8 >>> 00007ffc`ccc81c40 55 push rbp
9 00007ffc`ccc81c41 4883ec40 sub rsp,40h
10 00007ffc`ccc81c45 488d6c2440 lea rbp,
11 00007ffc`ccc81c4a c5d857e4 vxorpsxmm4,xmm4,xmm4
12 00007ffc`ccc81c4e c5f97f65e0 vmovdqa xmmword ptr ,xmm4
13 00007ffc`ccc81c53 c5f97f65f0 vmovdqa xmmword ptr ,xmm4
14 00007ffc`ccc81c58 48894d10 mov qword ptr ,rcx
15 00007ffc`ccc81c5c 895518 mov dword ptr ,edx
16 00007ffc`ccc81c5f 44894520 mov dword ptr ,r8d
17 00007ffc`ccc81c63 833dbec6080000cmp dword ptr ,0
18 00007ffc`ccc81c6a 7405 je 00007ffc`ccc81c71
19 00007ffc`ccc81c6c e89fedc95f call coreclr!JIT_DbgIsJustMyCode (00007ffd`2c920a10)
20 00007ffc`ccc81c71 90 nop
21
22 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_4\Program.cs @ 25:
23 00007ffc`ccc81c72 8b4d18 mov ecx,dword ptr
24 00007ffc`ccc81c75 034d20 add ecx,dword ptr
25 00007ffc`ccc81c78 894dfc mov dword ptr ,ecx
26
27 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_4\Program.cs @ 26:
28 00007ffc`ccc81c7b 48b98811bfccfc7f0000 mov rcx,7FFCCCBF1188h (MT: System.Int32)
29 00007ffc`ccc81c85 e89698b75f call coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffd`2c7fb520)
30 00007ffc`ccc81c8a 488945f0 mov qword ptr ,rax
31 00007ffc`ccc81c8e 488b4df0 mov rcx,qword ptr
32 00007ffc`ccc81c92 8b4518 mov eax,dword ptr
33 00007ffc`ccc81c95 894108 mov dword ptr ,eax
34 00007ffc`ccc81c98 48b98811bfccfc7f0000 mov rcx,7FFCCCBF1188h (MT: System.Int32)
35 00007ffc`ccc81ca2 e87998b75f call coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffd`2c7fb520)
36 00007ffc`ccc81ca7 488945e8 mov qword ptr ,rax
37 00007ffc`ccc81cab 488b4de8 mov rcx,qword ptr
38 00007ffc`ccc81caf 8b4520 mov eax,dword ptr
39 00007ffc`ccc81cb2 894108 mov dword ptr ,eax
40 00007ffc`ccc81cb5 48b98811bfccfc7f0000 mov rcx,7FFCCCBF1188h (MT: System.Int32)
41 00007ffc`ccc81cbf e85c98b75f call coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffd`2c7fb520)
42 00007ffc`ccc81cc4 488945e0 mov qword ptr ,rax
43 00007ffc`ccc81cc8 4c8b4de0 mov r9,qword ptr
44 00007ffc`ccc81ccc 8b55fc mov edx,dword ptr
45 00007ffc`ccc81ccf 41895108 mov dword ptr ,edx
46 00007ffc`ccc81cd3 4c8b4de0 mov r9,qword ptr
47 00007ffc`ccc81cd7 488b55f0 mov rdx,qword ptr
48 00007ffc`ccc81cdb 4c8b45e8 mov r8,qword ptr
49 00007ffc`ccc81cdf 48b9880a82ed57020000 mov rcx,257ED820A88h ("Adding {0}+{1}={2}")
50 00007ffc`ccc81ce9 ff15212b0d00 call qword ptr
51 00007ffc`ccc81cef 90 nop
52
53 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_4\Program.cs @ 27:
54 00007ffc`ccc81cf0 90 nop
55 00007ffc`ccc81cf1 4883c440 add rsp,40h
56 00007ffc`ccc81cf5 5d pop rbp
57 00007ffc`ccc81cf6 c3 ret 这里比【NTSD】好看的多,不多说了。
我们使用【bp 00007ffcccc81c40】命令,设置断点。
1 0:001> bp 00007ffcccc81c40
2
3 0:001> g
4 Breakpoint 0 hit
5 <strong>ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint:
</strong>6 00007ffc`ccc81c40 55 push rbp 我们进入 AddAndPrint 方法的断点出了,我们就可以使用【p】大概【t】命令就行调试了。
3.6.3、在还没有被 JIT 编译的托管函数上下断点
A、知识介绍
非托管方法设置断点很容易,由于代码都已经被编译了,代码的地址就是已知的。但是,托管代码要举行两次编译才气运行。我们想要给代码设置断点,必须先找到代码的位置。这一节我们讨论在未编译的函数上如何设置断点,我们就不能使用【bp】命令,需要使用另外一个命令【bpmd】,它能自动找出被 JIT 编译子女码正确地址,并且,可以仅根据完备的方法名来设置断点。
【bpmd】命令可以用来在还没有被 JIT编译的代码上设置断点,它设置的是一个延迟断点,设置断点时位置是未知的,只有在将来某个变乱发生时,才会真正的设置断点。【bpmd】命令是通过注册内部的 CLR JIT 编译关照来实现延迟断点的。当调试器收到 JIT 编译关照时,它会检查这个关照是否和现有的某一个延迟断点相干,如果相干,那么就会在函数执行之前就会使断点生效。而且,【bpmd】命令还会接受模块加载关照,这就意味着在设置断点时乃至可以不需要加载步伐集。当步伐集加载的时候,这个命令会再次得到关照,并检查是否有某个延迟断点位于这个模块中,如果有,便会激活这个断点。
B、眼见为实
1)、使用【NTSD】调试
调试源码:ExampleCore_3_1_4
调试任务:在 AddAndPrint 方法第一次执行前设置断点。
执行命令【D:\Program Files\Microsoft Visual Studio\2022\Community>ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_4\bin\Debug\net8.0\ExampleCore_3_1_4.exe】打开调试器窗口。
我们直接【g】运行调试器,看到调试器中输出:Press any key(1st instance function)
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301172140083-62108072.png 我们使用【ctrl+c】进入调试器停止模式。我们使用【!name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint】命令,查看方法是否已经编译。
1 0:009> !name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint
2 Module: 00007ffccdfde0a0
3 Assembly: ExampleCore_3_1_4.dll
4 Token: 0000000006000003
5 MethodDesc:00007ffcce009398
6 Name: ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)
7 Not JITTED yet. Use !bpmd -md 00007FFCCE009398 to break on run. 我们使用【!bpmd -md 00007FFCCE009398】命令设置断点。
1 0:009> !bpmd -md 00007FFCCE009398
2 MethodDesc = 00007FFCCE009398
3 Adding pending breakpoints... 我们断点设置乐成。但是我在运行调试的时候出错,还没有找到缘故原由和解决办法,如果有知道缘故原由的,不吝见教。
1 0:008> g
2 g(3308.f8): CLR notification exception - code e0444143 (first chance)
3 JITTED ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)
4 Setting breakpoint: bp 00007FFCCDF51A90
5 <strong>Unable to insert breakpoint 0 at 00007ffc`cdf51a90, Win32 error 0n998
</strong> 6 "内存位置访问无效。"
7 <strong>The breakpoint was set with BP.If you want breakpoints
</strong> 8 <strong>to track module load/unload state you must use BU.
</strong> 9 bp0 at 00007ffc`cdf51a90 failed
10 WaitForEvent failed, Win32 error 0n998
11 内存位置访问无效。
12 KERNELBASE!RaiseException+0x69:
13 00007ffd`f5fb3e49 0f1f440000 nop dword ptr
2)、使用【Windbg Preview】调试
调试源码:ExampleCore_3_1_4
调试任务:在 AddAndPrint 方法第一次执行前设置断点。
编译好我们的项目,打开【Windbg Preview】,依次点击【文件】--->【Launch executable】,加载我们的项目文件:ExampleCore_3_1_4.exe,进入调试器。
我们使用【g】命令,继续运行,我们的控制台步伐输出:Press any key(1st instance function),这是第一次输出,AddAndPrint 方法还没有执行,也就还没有编译。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240301170022239-1489598275.png 我们点击【Break】按钮,停止执行,我们先证明 AddAndPrint 这个方法还没有编译,执行【!name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint】命令。
1 0:001> !name2ee ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint
2 Module: 00007ffccdfce0a0
3 Assembly: ExampleCore_3_1_4.dll
4 Token: 0000000006000003
5 MethodDesc:<strong>00007ffccdff9398(这个就是方法描述符)
</strong>6 Name: ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)
7 <strong>Not JITTED yet</strong>. Use !bpmd -md 00007FFCCDFF9398 to break on run. Not JITTED yet:表示未编译。【Use !bpmd -md 00007FFCCDFF9398 to break on run.】这句话是说可以通过使用【bpmd】命令和方法描述符来设置一个断点。
1 0:001> !bpmd -md 00007ffccdff9398
2 MethodDesc = 00007FFCCDFF9398
3 Adding pending breakpoints... 当我们使用【g】命令继续执行,并在控制台应用步伐中按下【回车键】,调试器输出如下:
1 0:001> g
2 (21e8.31c8): <strong>CLR notification exception - code e0444143 (first chance)
</strong>3 JITTED ExampleCore_3_1_4!ExampleCore_3_1_4.BreakPoint.AddAndPrint(Int32, Int32)
4 Setting breakpoint: bp 00007FFCCDF41C40
5 Breakpoint 0 hit
6 <strong>ExampleCore_3_1_4!</strong><strong>ExampleCore_3_1_4.BreakPoint.AddAndPrint</strong>:
7 00007ffc`cdf41c40 55 push rbp 需要注意的是 notification 部分的输出,调试器已经接受到了 CLR 关照异常(e0444143),JIT 编译方法的时候,重新设置了断点在地址:00007FFCCDF41C40,并且乐成在断点处停止执行。
3.6.4、在预编译的步伐会合设置断点
.NET 代码也需要在进程的上下文中执行。JIT 编译器将步伐集的 IL 代码编译为机器代码,每当 .NET 代码访问同一段代码时,CLR 首先检查它是否已经被编译了,如果是,则重用已编译的代码。当然,当进程竣事了,JIT 编译器生成的所有机器代码也会随之消失。当下一次需要执行步伐集时,JIT 编译器再重新对雷同的代码举行编译。
预编译步伐集是与某个步伐集对应的非托管映像,此中全部的代码已经全部被编译为机器代码。如果 CLR 需要执行这个步伐会合的代码,并且这个步伐集在机器上有一个非托管的映像,就会直接跳过 JIT 编译步骤,并直接从这个非托管映像中加载机器代码。
需要说明一点,这本书写的有点早,那个时候只有 .NET Framework 平台,NGEN 也是针对 .NET Framework 平台的。我这个系列是针对 .NET 8,也就是跨平台的版本,所以是不能直接使用 NGEN 生成预编译的步伐集的。如果想生成跨平台的预编译步伐集,需要使用 CrossGen。
NET 6 引入了 CrossGen2,它是已被删除的 CrossGen 的后继版本。 CrossGen 和 CrossGen2 是用于提供预先 (AOT) 编译的工具,可改进应用的启动时间。 CrossGen2 是用 C# (而不是 C++)编写的,可执行之前的版本无法实现的分析和优化。 如果想相识 CrossGen2,可以去微软官网:https://devblogs.microsoft.com/dotnet/conversation-about-crossgen2/
3.6.5、在泛型方法上设置断点
A、知识介绍
如果我们想对泛型类型的方法下断点,最首要的任务就是找到泛型类型的名称和方法的名称,找到之后,我们就可以下断点了。找泛型类型的名称和方法的名称有两种办法,第一种是通过命令,第二种是我们可以使用 ILSpy 找到。
B、眼见为实
我们想要在泛型类型的方法上下断点,首要的任务是找到泛型类型的名称和方法的名称,这是关键。
1)、使用【NTSD】调试
a、我们通过 Windbg 和 SOS 的命令找到类型的名称。
编译好我们的项目,打开【Visual Studio 2022 Developer Command Prompt】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.exe】,打开调试器。
使用【g】命令,运行调试器。
1 0:000> g
2 ModLoad: 00007ffe`90a90000 00007ffe`90ac0000 C:\Windows\System32\IMM32.DLL
3 ModLoad: 00007ffe`4f1d0000 00007ffe`4f229000 C:\Program Files\dotnet\host\fxr\8.0.2\hostfxr.dll
4 ModLoad: 00007ffe`3b840000 00007ffe`3b8a4000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\hostpolicy.dll
5 ModLoad: 00007ffe`28ab0000 00007ffe`28f98000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\coreclr.dll
6 ModLoad: 00007ffe`914d0000 00007ffe`915f9000 C:\Windows\System32\ole32.dll
7 ModLoad: 00007ffe`91f30000 00007ffe`92284000 C:\Windows\System32\combase.dll
8 ModLoad: 00007ffe`918a0000 00007ffe`91975000 C:\Windows\System32\OLEAUT32.dll
9 ModLoad: 00007ffe`902c0000 00007ffe`9033f000 C:\Windows\System32\bcryptPrimitives.dll
10 (3b74.3fac): Unknown exception - code 04242420 (first chance)
11 ModLoad: 00007ffe`27be0000 00007ffe`2886c000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
12 ModLoad: 00007ffe`27a20000 00007ffe`27bd9000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\clrjit.dll
13 ModLoad: 00007ffe`8f900000 00007ffe`8f913000 C:\Windows\System32\kernel.appcore.dll
14 ModLoad: 000001ba`47fc0000 000001ba`47fc8000 E:\Visual Studio 2022\...\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.dll
15 ModLoad: 000001ba`47fd0000 000001ba`47fde000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Runtime.dll
16 ModLoad: 00007ffe`75c60000 00007ffe`75c88000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Console.dll
17 (3b74.3fac): Break instruction exception - code 80000003 (first chance) 按【ctrl+c】组合键进入停止模式。
输入【!dumpdomain】命令查看应用步伐域详情,该命令会列出每个应用步伐域中加载的所有步伐集和模块。
1 0:000> !dumpdomain
2 --------------------------------------
3 System Domain: 00007ffe28f460d0
4 LowFrequencyHeap: 00007FFE28F465A8
5 HighFrequencyHeap:00007FFE28F46638
6 StubHeap: 00007FFE28F466C8
7 Stage: OPEN
8 Name: None
9 --------------------------------------
10 Domain 1: 000001ba465a1ff0
11 LowFrequencyHeap: 00007FFE28F465A8
12 HighFrequencyHeap:00007FFE28F46638
13 StubHeap: 00007FFE28F466C8
14 Stage: OPEN
15 Name: clrhost
16 Assembly: 000001ba46564d20
17 ClassLoader: 000001BA46564DB0
18 Module
19 00007ffdc8f44000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
20
21 <strong>Assembly: 000001ba465506e0
</strong>22 ClassLoader: 000001BA46550FC0
23 <strong>Module
</strong>24 <strong>00007ffdc912e0a0</strong> E:\Visual Studio 2022\...\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.dll
25
26 Assembly: 000001ba465507e0
27 ClassLoader: 000001BA46550870
28 Module
29 00007ffdc912fbc8 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Runtime.dll
30
31 Assembly: 000001ba47f97460
32 ClassLoader: 000001BA47F97DE0
33 Module
34 00007ffdc91597f0 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Console.dll 我们找到了模块,就可以将模块中所有的类型输出来,可以使用【!dumpmodule -mt 】命令。
1 0:000> !dumpmodule -mt 00007ffdc912e0a0
2 Name: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.dll
3 Attributes: PEFile
4 TransientFlags: 00209011
5 Assembly: 000001ba465506e0
6 BaseAddress: 000001BA47FC0000
7 PEFile: 000001BA4654FCD0
8 ModuleId: 00007FFDC912E458
9 ModuleIndex: 0000000000000001
10 LoaderHeap: 00007FFE28F46598
11 TypeDefToMethodTableMap: 00007FFDC9134320
12 TypeRefToMethodTableMap: 00007FFDC9134340
13 MethodDefToDescMap: 00007FFDC9134470
14 FieldDefToDescMap: 00007FFDC9134498
15 MemberRefToDescMap: 00007FFDC91343D0
16 FileReferencesMap: 0000000000000000
17 AssemblyReferencesMap: 00007FFDC91344B8
18 MetaData start address:000001BA47FC20A8 (1612 bytes)
19
20 Types defined in this module
21
22 MT TypeDef Name
23 ------------------------------------------------------------------------------
24 00007ffdc91500e8 0x02000002 ExampleCore_3_1_5.Program
25 <strong>00007ffdc91593e0</strong> <strong>0x02000003</strong> <strong>ExampleCore_3_1_5.MyList`</strong><strong>1</strong>
26
27 Types referenced in this module
28
29 MT TypeRef Name
30 ------------------------------------------------------------------------------
31 00007ffdc8fd5fa8 0x0200000d System.Object
32 00007ffdc9159700 0x02000010 System.Diagnostics.Debugger
33 00007ffdc915ab08 0x02000011 System.Console 红色标注的就是我们要查找泛型类型真实的名称。有了类型,我们继续可以使用【!dumpmt -md 00007ffdc91593e0】命令,输出它所有方法。
1 0:000> !dumpmt -md 00007ffdc91593e0
2 EEClass: 00007FFDC9161F48
3 Module: 00007FFDC912E0A0
4 Name: ExampleCore_3_1_5.MyList`1
5 mdToken: 0000000002000003
6 File: E:\Visual Studio 2022\...\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.dll
7 BaseSize: 0x18
8 ComponentSize: 0x0
9 DynamicStatics:false
10 ContainsPointers true
11 Slots in VTable: 6
12 Number of IFaces in IFaceMap: 0
13 --------------------------------------
14 MethodDesc Table
15 Entry MethodDesc JIT Name
16 00007FFDC8FE0048 00007FFDC8FD5F38 NONE System.Object.Finalize()
17 00007FFDC8FE0060 00007FFDC8FD5F48 NONE System.Object.ToString()
18 00007FFDC8FE0078 00007FFDC8FD5F58 NONE System.Object.Equals(System.Object)
19 00007FFDC8FE00C0 00007FFDC8FD5F98 NONE System.Object.GetHashCode()
20 00007FFDC914B948 00007FFDC91593B8 NONE ExampleCore_3_1_5.MyList`1..ctor()
21 00007FFDC914B930 00007FFDC91593A8 NONE <strong>ExampleCore_3_1_5.MyList`1.Add(!0)</strong> 红色标记就是我们要查找的 Add 方法,有了方法的地址,我们就可以使用【!bpmd ExampleCore_3_1_5 ExampleCore_3_1_5.MyList`1.Add】命令为其下断点了。
1 0:000> !bpmd ExampleCore_3_1_5 ExampleCore_3_1_5.MyList`1.Add
2 MethodDesc = 00007FFDC91593A8
3 Adding pending breakpoints... 断点设置乐成。但是我在运行调试的时候出错,还没有找到缘故原由和解决办法,如果有知道缘故原由的,不吝见教。
1 0:000> g
2 (3b74.3fac): CLR notification exception - code e0444143 (first chance)
3 JITTED ExampleCore_3_1_5!ExampleCore_3_1_5.MyList`1[].Add(Int32)
4 Setting breakpoint: bp 00007FFDC90A1A50 ].Add(Int32)]
5 Unable to insert breakpoint 0 at 00007ffd`c90a1a50, Win32 error 0n998
6 "内存位置访问无效。"
7 The breakpoint was set with BP.If you want breakpoints
8 to track module load/unload state you must use BU.
9 bp0 at 00007ffd`c90a1a50 failed
10 WaitForEvent failed, Win32 error 0n998
11 内存位置访问无效。
12 KERNELBASE!RaiseException+0x69:
13 00007ffe`8fb03e49 0f1f440000 nop dword ptr
b、我们可以使用 ILSpy 大概 SnPay 来查找泛型类型的名称和方法的名称。
和使用【Windbg Preview】这节的内容一样。
2)、使用【Windbg Preview】调试
调试源码:ExampleCore_3_1_5
a、我们通过 Windbg 和 SOS 的命令找到类型的名称。
编译步伐集后,泛型类型一定在这个步伐集的模块中。然后我们再在这个模块中打印出所有的类型,就可以找到这个类型了。
编译好我们的项目,打开【Windbg Preview】,依次点击【文件】--->【Launch executable】,加载我们的项目文件:ExampleCore_3_1_5.exe。进入到调试器后,我们使用【g】命令运行调试器,调试器会在 Program 类型的 Main 方法的【Debugger.Break()】这个行代码停止执行。我们点击【break】按钮,进入调试模式。
我们现在这个步伐会合查找模块信息,我们可以使用【!dumpdomain】命令。
1 0:000> !dumpdomain
2 --------------------------------------
3 System Domain: 00007ffe226360d0
4 LowFrequencyHeap: 00007FFE226365A8
5 HighFrequencyHeap:00007FFE22636638
6 StubHeap: 00007FFE226366C8
7 Stage: OPEN
8 Name: None
9 --------------------------------------
10 Domain 1: 000001a1469ddc40
11 LowFrequencyHeap: 00007FFE226365A8
12 HighFrequencyHeap:00007FFE22636638
13 StubHeap: 00007FFE226366C8
14 Stage: OPEN
15 Name: clrhost
16 Assembly: 000001a146a2e010
17 ClassLoader: 000001A146A2E0A0
18 Module
19 00007ffdc2634000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
20
21 <strong>Assembly: 000001a1484c2bf0
</strong>22 ClassLoader: 000001A1484C2C80
23 <strong>Module
</strong>24 <strong>00007ffdc281e0a0 E:\Visual Studio 2022\Source\...\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.dll
</strong>25
26 Assembly: 000001a146976520
27 ClassLoader: 000001A1469765B0
28 Module
29 00007ffdc281fbc8 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Runtime.dll 00007ffdc281e0a0 这个地址就是我们步伐集(ExampleCore_3_1_5.dll)的模块地址。我们找到了模块,就可以将模块中所有的类型输出来,可以使用【!dumpmodule -mt 】命令。
1 0:000> !dumpmodule -mt 00007ffdc281e0a0
2 Name: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.dll
3 Attributes: PEFile
4 TransientFlags: 00209011
5 Assembly: 000001a1484c2bf0
6 BaseAddress: 000001A1468F0000
7 PEAssembly: 000001A14696F6A0
8 ModuleId: 00007FFDC281E458
9 ModuleIndex: 0000000000000001
10 LoaderHeap: 00007FFE22636598
11 TypeDefToMethodTableMap: 00007FFDC2824320
12 TypeRefToMethodTableMap: 00007FFDC2824340
13 MethodDefToDescMap: 00007FFDC2824470
14 FieldDefToDescMap: 00007FFDC2824498
15 MemberRefToDescMap: 00007FFDC28243D0
16 FileReferencesMap: 0000000000000000
17 AssemblyReferencesMap: 00007FFDC28244B8
18 MetaData start address:000001A1468F20A8 (1612 bytes)
19
20 Types defined in this module
21
22 MT TypeDef Name
23 ------------------------------------------------------------------------------
24 00007ffdc28400e8 0x02000002 ExampleCore_3_1_5.Program
25 <strong>00007ffdc28493e0 0x02000003</strong> <strong>ExampleCore_3_1_5.MyList`</strong>1
26
27 Types referenced in this module
28
29 MT TypeRef Name
30 ------------------------------------------------------------------------------
31 00007ffdc26c5fa8 0x0200000d System.Object
32 00007ffdc2849700 0x02000010 System.Diagnostics.Debugger
33 00007ffdc284ab08 0x02000011 System.Console ExampleCore_3_1_5.MyList`1 就是泛型类型编译后的名称。红色标注的就是我们要查找泛型类型真实的名称。有了类型,我们继续可以使用【!dumpmt -md 00007ffdc28493e0】命令,输出它所有方法。
1 0:000> !dumpmt -md 00007ffdc28493e0
2 EEClass: 00007ffdc2851f48
3 Module: 00007ffdc281e0a0
4 Name: ExampleCore_3_1_5.MyList`1
5 mdToken: 0000000002000003
6 File: E:\Visual Studio 2022\...\ExampleCore_3_1_5\bin\Debug\net8.0\ExampleCore_3_1_5.dll
7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
8 BaseSize: 0x18
9 ComponentSize: 0x0
10 DynamicStatics: false
11 ContainsPointers: true
12 Slots in VTable: 6
13 Number of IFaces in IFaceMap: 0
14 --------------------------------------
15 MethodDesc Table
16 Entry MethodDesc JIT Name
17 00007FFDC26D0048 00007ffdc26c5f38 NONE System.Object.Finalize()
18 00007FFDC26D0060 00007ffdc26c5f48 NONE System.Object.ToString()
19 00007FFDC26D0078 00007ffdc26c5f58 NONE System.Object.Equals(System.Object)
20 00007FFDC26D00C0 00007ffdc26c5f98 NONE System.Object.GetHashCode()
21 00007FFDC283B948 00007ffdc28493b8 NONE ExampleCore_3_1_5.MyList`1..ctor()
22 00007FFDC283B930 00007ffdc28493a8 NONE <strong>ExampleCore_3_1_5.MyList`1.Add(!0)</strong>
红色标记就是我们要查找的 Add 方法,有了方法的地址,我们就可以使用【bpmd】命令为其下断点了。
1 0:000> !bpmd ExampleCore_3_1_5 ExampleCore_3_1_5.MyList`1.Add
2 MethodDesc = 00007FFDC28493A8
3 Adding pending breakpoints... 断点设置乐成后,我们使用【g】命令,步伐继续运行,就可以在断点处暂停。
1 0:000> g
2 (2c88.2734): CLR notification exception - code e0444143 (first chance)
3 JITTED ExampleCore_3_1_5!ExampleCore_3_1_5.MyList`1[].Add(Int32)
4 Setting breakpoint: bp 00007FFDC2791A50 ].Add(Int32)]
5 Breakpoint 0 hit
6 <strong>ExampleCore_3_1_5!ExampleCore_3_1_5.MyList<int>.Add:
</strong>7 00007ffd`c2791a50 55 push rbp 断点效果如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240304151351216-2104784208.png
b、我们可以使用 ILSpy 大概 SnPay 来查找泛型类型的名称和方法的名称。
我们可以使用 ILSpy 大概 snSpy 查看泛型类型和方法的名称。我们打开【ILSpy】工具,加载我们的 ExampleCore_3_1_5.dll 文件。再左侧,依次点击【Metadata】--->【Tables】--->【TypeDef】,在右侧就能看到这个步伐会合定义的所有的类型名称。如图:
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240304152057568-1115717489.png
我们知道了类型的名称,然后就是查找方法的名称。也很简单。
https://img2024.cnblogs.com/blog/1048776/202403/1048776-20240304152305841-2123427176.png
现在我们知道了泛型类型的名称和方法的名称,就可以直接设置断点了。
编译好我们的项目,打开【Windbg Preview】,依次点击【文件】--->【Launch executable】,加载我们的项目文件:ExampleCore_3_1_5.exe。进入到调试器后,我们使用【g】命令运行调试器,调试器会在 Program 类型的 Main 方法的【Debugger.Break()】这个行代码停止执行。我们点击【break】按钮,进入调试模式。
我们执行命令【!bpmd ExampleCore_3_1_5 ExampleCore_3_1_5.MyList`1.Add】,就可以直接下断点了。
1 0:000> !bpmd ExampleCore_3_1_5 ExampleCore_3_1_5.MyList`1.Add
2 MethodDesc = 00007FFDC2DE93A8
3 Adding pending breakpoints... 断点设置乐成后,我们使用【g】命令,步伐继续运行,就可以在断点处暂停。
1 0:000> g
2 (10b4.42c4): CLR notification exception - code e0444143 (first chance)
3 JITTED ExampleCore_3_1_5!ExampleCore_3_1_5.MyList`1[].Add(Int32)
4 Setting breakpoint: bp 00007FFDC2D31A50 ].Add(Int32)]
5 Breakpoint 0 hit
6 <strong>ExampleCore_3_1_5!ExampleCore_3_1_5.MyList<int>.Add:
</strong>7 00007ffd`c2d31a50 55 push rbp 断点设置乐成,我们也完成我们的任务。
四、总结
这篇文章终于写完了,是这篇文章的“上”篇写完了,“下”篇还没有开始呢,这篇文章写作周期也不短,内容实在多。Net 高级调试这条路,也刚刚起步,还有许多要学的地方。皇天不负有心人,积极,不辜负自己,我相信付出就有回报,再者说,学习的过程,有时候,虽然很痛苦,但是,学有所成,学有所懂,这个开心的感觉还是不可言喻的。不忘初心,继续积极。做自己喜好做的,开心就好。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]