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

一:背景

1.讲故事

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

二:AOT 的几个问题

1. 如何在 .NET7 中开启 AOT 功能

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


    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("hello world!");
            Debugger.Break();
        }
    }

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



    
        Exe
        net7.0
        enable
        enable
        true
    


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

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

2. SOS 可以调试 AOT 程序吗

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


0:000> lm
start             end                 module name
00007ff611680000 00007ff61196f000   ConsoleApp2 C (private pdb symbols)  C:testConsoleApp2.pdb
00007ffe692b0000 00007ffe692c3000   kernel_appcore   (deferred)             
00007ffe6b3e0000 00007ffe6b47d000   msvcp_win   (deferred)             
00007ffe6b480000 00007ffe6b4ff000   bcryptPrimitives   (deferred)             
00007ffe6b660000 00007ffe6b687000   bcrypt     (deferred)             
00007ffe6b690000 00007ffe6b6b2000   win32u     (deferred)             
00007ffe6b720000 00007ffe6b82a000   gdi32full   (deferred)             
00007ffe6b830000 00007ffe6b930000   ucrtbase   (deferred)             
00007ffe6b9e0000 00007ffe6bca7000   KERNELBASE   (deferred)             
00007ffe6bcb0000 00007ffe6bd5a000   ADVAPI32   (deferred)             
00007ffe6be50000 00007ffe6be7a000   GDI32      (deferred)             
00007ffe6be80000 00007ffe6bf1b000   sechost    (deferred)             
00007ffe6c180000 00007ffe6c2a3000   RPCRT4     (deferred)             
00007ffe6c440000 00007ffe6c470000   IMM32      (deferred)             
00007ffe6c600000 00007ffe6c729000   ole32      (deferred)             
00007ffe6c730000 00007ffe6c7ce000   msvcrt     (deferred)             
00007ffe6cc50000 00007ffe6cfa4000   combase    (deferred)             
00007ffe6d160000 00007ffe6d300000   USER32     (deferred)             
00007ffe6d410000 00007ffe6d4cd000   KERNEL32   (deferred)             
00007ffe6dc50000 00007ffe6de44000   ntdll      (pdb symbols)          c:mysymbolsntdll.pdb63E12347526A46144B98F8CF61CDED791ntdll.pdb

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

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


0:000> .load  C:UsersAdministrator.dotnetsos64sos.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 大小。

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

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

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

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

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

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

  1. 一段简单的测试代码。

    internal class Program
    {
        static void Main(string[] args)
        {
            Debugger.Break();

            GC.Collect();
        }
    }

  1. 下断点

熟悉 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 000000115e52f628 00007ff67f288c5a     ConsoleApp2!RhDebugBreak+0x2 [D:a_work1ssrccoreclrnativeaotRuntimeMiscHelpers.cpp @ 45] 
01 000000115e52f630 00007ff67f2f0e28     ConsoleApp2!S_P_CoreLib_System_Diagnostics_Debugger__Break+0x3a [/_/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 17] 
02 000000115e52f6c0 00007ff67f1fe37e     ConsoleApp2!ConsoleApp2__Module___StartupCodeMain+0x118
03 000000115e52f720 00007ff67f1f9540     ConsoleApp2!wmain+0xae [D:a_work1ssrccoreclrnativeaotBootstrapmain.cpp @ 205] 
04 (Inline Function) --------`--------     ConsoleApp2!invoke_main+0x22 [D:a_work1ssrcvctoolscrtvcstartupsrcstartupexe_common.inl @ 90] 
05 000000115e52f770 00007ffe6d426fd4     ConsoleApp2!__scrt_common_main_seh+0x10c [D:a_work1ssrcvctoolscrtvcstartupsrcstartupexe_common.inl @ 288] 
06 000000115e52f7b0 00007ffe6dc9cec1     KERNEL32!BaseThreadInitThunk+0x14
07 000000115e52f7e0 0000000000000000     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:
00007ff67f1a9410 48894c2408      mov     qword ptr [rsp+8],rcx ss:000000115e52f5f0=0000000000000000

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

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

4. AOT 的实现源码在哪里

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

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

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

三:总结

总的来说,AOT 目前还是一个雏形阶段,大家慎用吧,一旦出了问题,可不好事后调试哦,希望后续加强对 SOS 的支持。
从 WinDbg 角度理解 .NET7 的AOT玩法插图5

文章来源于互联网:从 WinDbg 角度理解 .NET7 的AOT玩法

THE END
分享
二维码