龙空技术网

从 WinDbg 角度理解 .NET7 的AOT玩法

DotNET技术圈 1097

前言:

如今你们对“netframework470”大致比较看重,咱们都需要知道一些“netframework470”的相关知识。那么小编在网上汇集了一些对于“netframework470””的相关文章,希望大家能喜欢,看官们一起来学习一下吧!

一:背景 1.讲故事

前几天 B 站上有位朋友让我从高级调试的角度来解读下 .NET7 新出来的 AOT,毕竟这东西是新的,所以这一篇我就简单摸索一下。

二:AOT 的几个问题 1. 如何在 .NET7 中开启 AOT 功能

在 .NET7 中开启 AOT 非常方便,先来段测试代码。

internal class Program

{

static void Main(string[] args)

{

Console.WriteLine("hello world!");

Debugger.Break();

}

}

然后在项目配置上新增 <PublishAot>true</PublishAot> 节点,如下输出:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>

<OutputType>Exe</OutputType>

<TargetFramework>net7.0</TargetFramework>

<ImplicitUsings>enable</ImplicitUsings>

<able>enable</able>

<PublishAot>true</PublishAot>

</PropertyGroup>

</Project>

接下来在项目中右键选择 发布,选择一个输出地,这样一个 AOT 程序就完成了。

2. SOS 可以调试 AOT 程序吗

这是很多朋友关心的话题,我们都知道 SOS 是用来撬开 CoreCLR 的,只要能看到 CoreCLR.dll,那 SOS 就能用,接下来用 WinDbg 附加到 ConsoleApp2.exe 上,使用 lm 观察。

0:000> lm

start end module name

00007ff6`11680000 00007ff6`1196f000 ConsoleApp2 C (private pdb symbols) C:\test\ConsoleApp2.pdb

00007ffe`692b0000 00007ffe`692c3000 kernel_appcore (deferred)

00007ffe`6b3e0000 00007ffe`6b47d000 msvcp_win (deferred)

00007ffe`6b480000 00007ffe`6b4ff000 bcryptPrimitives (deferred)

00007ffe`6b660000 00007ffe`6b687000 bcrypt (deferred)

00007ffe`6b690000 00007ffe`6b6b2000 win32u (deferred)

00007ffe`6b720000 00007ffe`6b82a000 gdi32full (deferred)

00007ffe`6b830000 00007ffe`6b930000 ucrtbase (deferred)

00007ffe`6b9e0000 00007ffe`6bca7000 KERNELBASE (deferred)

00007ffe`6bcb0000 00007ffe`6bd5a000 ADVAPI32 (deferred)

00007ffe`6be50000 00007ffe`6be7a000 GDI32 (deferred)

00007ffe`6be80000 00007ffe`6bf1b000 sechost (deferred)

00007ffe`6c180000 00007ffe`6c2a3000 RPCRT4 (deferred)

00007ffe`6c440000 00007ffe`6c470000 IMM32 (deferred)

00007ffe`6c600000 00007ffe`6c729000 ole32 (deferred)

00007ffe`6c730000 00007ffe`6c7ce000 msvcrt (deferred)

00007ffe`6cc50000 00007ffe`6cfa4000 combase (deferred)

00007ffe`6d160000 00007ffe`6d300000 USER32 (deferred)

00007ffe`6d410000 00007ffe`6d4cd000 KERNEL32 (deferred)

00007ffe`6dc50000 00007ffe`6de44000 ntdll (pdb symbols) c:\mysymbols\ntdll.pdb\63E12347526A46144B98F8CF61CDED791\ntdll.pdb

从上面的输出中惊讶的发现,居然没有 clrjit.dll 和 coreclr.dll,前者没有很好理解,后者没有就很奇怪了。。。

既然没看到 coreclr.dll 这个动态链接库,那至少目前用 sos 肯定是无法调试的,即使你强制加载也会报错。

0:000> .load C:\Users\Administrator\.dotnet\sos64\sos.dll

0:000> !t

Failed to find runtime module (coreclr.dll or clr.dll or libcoreclr.so), 0x80004002

Extension commands need it in order to have something to do.

For more information see https://go.microsoft.com/fwlink/?linkid=2135652

到这里我的个人结论是:目前SOS无法对这类程序进行调试,如果大家用在生产上出现各种内存暴涨,CPU爆高问题,就要当心了。

3. AOT 真的没有 CoreCLR 吗

其实仔细想一想,这是不可能的,C# 的出发点就是作为一门托管语言而存在,再怎么发展也不会忘记这个初衷,所谓不忘初心,方得始终。

我们回过头看下 ConsoleApp.exe 这个程序,有没有发现,它居然有 3M 大小。

聪明的朋友应该猜到了,对,就是把 CoreCLR 打包到 exe 中了,这个太牛了,那怎么验证呢?可以用 IDA 打开一下。

从图中可以清晰的看到各种 gc_heap 相关的函数,这也验证了为什么一个简简单单的 ConsoleApp.exe 有这么大Size的原因。

4. 真的无法调试 AOT 程序吗

在 Windows 平台上就没有 WinDbg 不能调试的程序,所以 AOT 程序自然不在话下,毕竟按托管不行,大不了按非托管调试,这里我们举一个 GC.Collect() 的源码调试吧。

一段简单的测试代码。

internal class Program

{

static void Main(string[] args)

{

Debugger.Break();

GC.Collect();

}

}

下断点

熟悉 GC 的朋友应该知道我只需用 bp coreclr!WKS::GCHeap::GarbageCollect 下一个断点就可以了,但刚才我也说了,内存中并没有 coreclr 模块,下面的 x 写法肯定会报错。

0:000> x coreclr!WKS::GCHeap::GarbageCollect

^ Couldn't resolve 'x coreclr'

那怎么下呢?先输个 k 观察下调用栈有没有什么新发现。

0:000> k

# Child-SP RetAddr Call Site

00 00000011`5e52f628 00007ff6`7f288c5a ConsoleApp2!RhDebugBreak+0x2 [D:\a\_work\1\s\src\coreclr\nativeaot\Runtime\MiscHelpers.cpp @ 45]

01 00000011`5e52f630 00007ff6`7f2f0e28 ConsoleApp2!S_P_CoreLib_System_Diagnostics_Debugger__Break+0x3a [/_/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 17]

02 00000011`5e52f6c0 00007ff6`7f1fe37e ConsoleApp2!ConsoleApp2__Module___StartupCodeMain+0x118

03 00000011`5e52f720 00007ff6`7f1f9540 ConsoleApp2!wmain+0xae [D:\a\_work\1\s\src\coreclr\nativeaot\Bootstrap\main.cpp @ 205]

04 (Inline Function) --------`-------- ConsoleApp2!invoke_main+0x22 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90]

05 00000011`5e52f770 00007ffe`6d426fd4 ConsoleApp2!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]

06 00000011`5e52f7b0 00007ffe`6dc9cec1 KERNEL32!BaseThreadInitThunk+0x14

07 00000011`5e52f7e0 00000000`00000000 ntdll!RtlUserThreadStart+0x21

我去,int 3 函数也换了,成了 ConsoleApp2!RhDebugBreak+0x2,不过也能看出来,应该将 coreclr 改成 ConsoleApp2 即可,输出如下:

0:000> bp ConsoleApp2!WKS::GCHeap::GarbageCollect

breakpoint 0 redefined

0:000> g

Breakpoint 0 hit

ConsoleApp2!WKS::GCHeap::GarbageCollect:

00007ff6`7f1a9410 48894c2408 mov qword ptr [rsp+8],rcx ss:00000011`5e52f5f0=0000000000000000

源码也看的清清楚楚,路径也是在 gc 目录下。如下图所示:

4. AOT 的实现源码在哪里

观察刚才的线程栈中的 D:\a\_work\1\s\src\coreclr\nativeaot\Bootstrap\main.cpp 可以发现,新增了一个名为 nativeaot 的目录,这在 .NET 6 的 coreclr 源码中是没有的。

如果有感兴趣的朋友,可以研究下源码。

三:总结

总的来说,AOT 目前还是一个雏形阶段,大家慎用吧,一旦出了问题,可不好事后调试哦,希望后续加强对 SOS 的支持。

标签: #netframework470 #net打包exe