AOT漫谈(第一篇): 如何调试CAOT程序

AOT漫谈(第一篇): 如何调试CAOT程序

文章图片

AOT漫谈(第一篇): 如何调试CAOT程序

文章图片

AOT漫谈(第一篇): 如何调试CAOT程序

文章图片

一:背景 1. 讲故事上个月接到了二个C# AOT程序的故障分析 , 发现如今的C# AOT程序也开始在各个领域开枝散叶了 , 这是一件非常好的事情 , 本着对这类程序有一个专业的维修态度 , 开一个系列好好聊一聊吧 , 当然我这里是漫谈 , 文章没有先后顺序 , 先从如何调试开始吧 。
二:如何调试AOT程序 1. 一个简单的例子现在的VS非常强大 , 新建模板的时候就有一个 Enable native AOT publish 选项 , 勾完之后就会自动的在项目的 csproj 中添加配置节 <PublishAot>true</PublishAot>, 截图如下:

勾选的好处就是可以在编码阶段就能感知到AOT不允许的东西 , 方便及时告警 , 生成完之后参考代码如下:

static void Main(string[
args)
{
var i = 10;
var j = 20;
var sum = i + j;
Console.WriteLine($\"{i+{j={sum\");
Console.ReadLine();


代码有了之后 , 可以借助dotnet cli的 publish 命令直接发布成nativeaot程序 , 这里先使用 Debug 模式 , 这样就生成好了一个完整的程序 , 参考如下:
dotnet publish -r win-x64 -c Debug -o D:\\testdump
当然你也可以带上 PublishAot=true 参数 , 即:
dotnet publish -r win-x64 -c Debug /p:PublishAot=true -o D:\\testdump
2. 使用VS调试用 VS调试非常简单 , 直接将 ConsoleApp1.exe 拖到 VS 中即可 , 对 , 就是拖到VS中 , 然后在源码的相应位置下个断点 , 接下来在 ConsoleApp1.exe 上右键 -> Debug -> Start New Instance 即可启动调试 , 截图如下:
大家看到命中断点了也不要高兴的太早 , VS只适合调试Debug模式发布出来的程序 , 而实际情况大家更多的是以Release模式发布的 , 这种模式下用 VS 就不能很好的调试了 。
为了验证 , 我们简单的修改一下 Debug 改成 Release 模式 , 参考如下:

PS D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1> dotnet publish -r win-x64 -c Release -o D:\\testdump
正在确定要还原的项目…
所有项目均是最新的 , 无法还原 。
ConsoleApp1 -> D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\bin\\Release\et8.0\\win-x64\\ConsoleApp1.dll
ConsoleApp1 -> D:\\testdump\\

有可用的工作负载更新 。 有关详细信息 , 请运行 `dotnet workload list` 。

程序运行以来之后 , 可以看到那些 i , j , sum 都不见了踪影 , 无语了 , 截图如下:
3. WinDbg调试水到绝境是风景 , 人到绝境是重生 。 当你绝望的时候一定要知道有WinDbg这种通杀一切之工具的存在 , 所以这次我们要祭出 WinDbg 。
那用 windbg 如何调试呢?非常简单 , 直接对 Main 方法下断点即可 , 毕竟AOT程序在 ilc 阶段就已经编译成完整的 机器码 , 参考命令如下:
bp ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main
这个命令的格式如下:
bp 模块名!模块名_命名空间_类名__方法名
【AOT漫谈(第一篇): 如何调试CAOT程序】参考汇编代码如下:

0:000> uf ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main
ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 9
:
9 00007ff6`e4836d20 4883ec48 sub rsp48h
9 00007ff6`e4836d24 0f57e4 xorps xmm4xmm4
9 00007ff6`e4836d27 0f29642420 movaps xmmword ptr [rsp+20h
xmm4
9 00007ff6`e4836d2c 0f29642430 movaps xmmword ptr [rsp+30h
xmm4
9 00007ff6`e4836d31 33c0 xor eaxeax
9 00007ff6`e4836d33 4889442440 mov qword ptr [rsp+40h
rax
10 00007ff6`e4836d38 488d4c2420 lea rcx[rsp+20h

10 00007ff6`e4836d3d ba02000000 mov edx2
10 00007ff6`e4836d42 41b803000000 mov r8d3
10 00007ff6`e4836d48 e823f10200 call ConsoleApp1!S_P_CoreLib_System_Runtime_CompilerServices_DefaultInterpolatedStringHandler___ctor (00007ff6`e4865e70)
10 00007ff6`e4836d4d 488d4c2420 lea rcx[rsp+20h

10 00007ff6`e4836d52 ba0a000000 mov edx0Ah
10 00007ff6`e4836d57 e8a48e0500 call ConsoleApp1!S_P_CoreLib_System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__AppendFormatted<Int32> (00007ff6`e488fc00)
10 00007ff6`e4836d5c 8b4c2430 mov ecxdword ptr [rsp+30h

10 00007ff6`e4836d60 8b542440 mov edxdword ptr [rsp+40h

10 00007ff6`e4836d64 3bca cmp ecxedx
10 00007ff6`e4836d66 0f87c9000000 ja ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x115 (00007ff6`e4836e35) Branch

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x4c [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836d6c 488b442438 mov raxqword ptr [rsp+38h

10 00007ff6`e4836d71 448bc1 mov r8decx
10 00007ff6`e4836d74 4a8d0440 lea rax[rax+r8*2

10 00007ff6`e4836d78 2bd1 sub edxecx
10 00007ff6`e4836d7a 83fa01 cmp edx1
10 00007ff6`e4836d7d 721d jb ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x7c (00007ff6`e4836d9c) Branch

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x5f [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836d7f 488d0d4a180600 lea rcx[ConsoleApp1!_Str___206014A4266C2082B9433011FB2991059F972D570A8B3C976E5B2B8DFCFBFC8D (00007ff6`e48985d0)

10 00007ff6`e4836d86 4883c10c add rcx0Ch
10 00007ff6`e4836d8a 0fb711 movzx edxword ptr [rcx

10 00007ff6`e4836d8d 668910 mov word ptr [rax
dx
10 00007ff6`e4836d90 8b4c2430 mov ecxdword ptr [rsp+30h

10 00007ff6`e4836d94 ffc1 inc ecx
10 00007ff6`e4836d96 894c2430 mov dword ptr [rsp+30h
ecx
10 00007ff6`e4836d9a eb11 jmp ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x8d (00007ff6`e4836dad) Branch

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x7c [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836d9c 488d4c2420 lea rcx[rsp+20h

10 00007ff6`e4836da1 488d1528180600 lea rdx[ConsoleApp1!_Str___206014A4266C2082B9433011FB2991059F972D570A8B3C976E5B2B8DFCFBFC8D (00007ff6`e48985d0)

10 00007ff6`e4836da8 e893f20200 call ConsoleApp1!S_P_CoreLib_System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__GrowThenCopyString (00007ff6`e4866040)

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x8d [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836dad 488d4c2420 lea rcx[rsp+20h

10 00007ff6`e4836db2 ba14000000 mov edx14h
10 00007ff6`e4836db7 e8448e0500 call ConsoleApp1!S_P_CoreLib_System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__AppendFormatted<Int32> (00007ff6`e488fc00)
10 00007ff6`e4836dbc 8b4c2430 mov ecxdword ptr [rsp+30h

10 00007ff6`e4836dc0 8b542440 mov edxdword ptr [rsp+40h

10 00007ff6`e4836dc4 3bca cmp ecxedx
10 00007ff6`e4836dc6 776d ja ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0x115 (00007ff6`e4836e35) Branch

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0xa8 [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836dc8 488b442438 mov raxqword ptr [rsp+38h

10 00007ff6`e4836dcd 448bc1 mov r8decx
10 00007ff6`e4836dd0 4a8d0440 lea rax[rax+r8*2

10 00007ff6`e4836dd4 2bd1 sub edxecx
10 00007ff6`e4836dd6 83fa01 cmp edx1
10 00007ff6`e4836dd9 721d jb ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0xd8 (00007ff6`e4836df8) Branch

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0xbb [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836ddb 488d0dee1f0600 lea rcx[ConsoleApp1!_Str___E7DAC261E841E53EB65AC8C2A0E56544DF49C46D71E8002D7764F92C66C4C868 (00007ff6`e4898dd0)

10 00007ff6`e4836de2 4883c10c add rcx0Ch
10 00007ff6`e4836de6 0fb711 movzx edxword ptr [rcx

10 00007ff6`e4836de9 668910 mov word ptr [rax
dx
10 00007ff6`e4836dec 8b4c2430 mov ecxdword ptr [rsp+30h

10 00007ff6`e4836df0 ffc1 inc ecx
10 00007ff6`e4836df2 894c2430 mov dword ptr [rsp+30h
ecx
10 00007ff6`e4836df6 eb11 jmp ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0xe9 (00007ff6`e4836e09) Branch

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0xd8 [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836df8 488d4c2420 lea rcx[rsp+20h

10 00007ff6`e4836dfd 488d15cc1f0600 lea rdx[ConsoleApp1!_Str___E7DAC261E841E53EB65AC8C2A0E56544DF49C46D71E8002D7764F92C66C4C868 (00007ff6`e4898dd0)

10 00007ff6`e4836e04 e837f20200 call ConsoleApp1!S_P_CoreLib_System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__GrowThenCopyString (00007ff6`e4866040)

ConsoleApp1!ConsoleApp1_ConsoleApp1_Program__Main+0xe9 [D:\\skyfly\\18.20241010\\src\\Example\\ConsoleApp1\\Program.cs @ 10
:
10 00007ff6`e4836e09 488d4c2420 lea rcx[rsp+20h

10 00007ff6`e4836e0e ba1e000000 mov edx1Eh
10 00007ff6`e4836e13 e8e88d0500 call ConsoleApp1!S_P_CoreLib_System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__AppendFormatted<Int32> (00007ff6`e488fc00)
10 00007ff6`e4836e18 488d4c2420 lea rcx[rsp+20h

10 00007ff6`e4836e1d e8def00200 call ConsoleApp1!S_P_CoreLib_System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__ToStringAndClear (00007ff6`e4865f00)
10 00007ff6`e4836e22 488bc8 mov rcxrax
10 00007ff6`e4836e25 e856080000 call ConsoleApp1!System_Console_System_Console__WriteLine_12 (00007ff6`e4837680)
13 00007ff6`e4836e2a e831080000 call ConsoleApp1!System_Console_System_Console__ReadLine (00007ff6`e4837660)
14 00007ff6`e4836e2f 90 nop
14 00007ff6`e4836e30 4883c448 add rsp48h
14 00007ff6`e4836e34 c3 ret

仔细观察上面的汇编代码 , 你会发现 i , j 根本就没有作为栈变量使用 , 而是直接放到了寄存器中传给了 AppendFormatted 方法 。
这里要提醒一点就是 DefaultInterpolatedStringHandler 是 C# 中 string 拼接的一种底层优化实现 , 所以最后代码被优化成了 Console.WriteLine($\"{10+{20={30\"); 这样的句子 , 这也是 Release 的威力所在 。
当然 WinDbg 的功能不限于此 , 后面还有更多牛叉的功能 , 在系列文章后面再展开吧 。
三:总结工欲善其事必先利其器 , 对未知知识的探索必然需要一个好的工具 , 希望本篇给大家带来一些灵感吧 。

    推荐阅读