从 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();
}
}
然后在项目配置上新增 <PublishAot>true</PublishAot>
节点,如下输出:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<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 的支持。
从 WinDbg 角度理解 .NET7 的AOT玩法的更多相关文章
- 转:如何学习SQL(第二部分:从关系角度理解SQL)
转自:http://blog.163.com/mig3719@126/blog/static/285720652010950825538/ 6. 从关系角度理解SQL 6.1. 关系和表 众所周知,我 ...
- 从tcp原理角度理解Broken pipe和Connection reset by peer的区别
从tcp原理角度理解Broken pipe和Connection reset by peer的区别 http://lovestblog.cn/blog/2014/05/20/tcp-broken-pi ...
- 从npm 角度理解 mvn 的 pom.xml
从npm 角度理解 mvn 的 pom.xml pom -- project object model. 用于描述项目的配置: 基础说明 依赖 如何构建运行 类似 node.js 的 package. ...
- ES7前端异步玩法:async/await理解 js原生API妙用(一)
ES7前端异步玩法:async/await理解 在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是“异步”的意思,a ...
- 从极大似然估计的角度理解深度学习中loss函数
从极大似然估计的角度理解深度学习中loss函数 为了理解这一概念,首先回顾下最大似然估计的概念: 最大似然估计常用于利用已知的样本结果,反推最有可能导致这一结果产生的参数值,往往模型结果已经确定,用于 ...
- 以环形角度理解php数组索引
array_slice ( array $array , int $offset [, int $length = NULL [, bool $preserve_keys = false ]] ) : ...
- 从相亲的角度理解 K8S 的 Node Affinity, Taints 与 Tolerations
这是昨天晚上阅读园子里的2篇 k8s 博文时产生的想法,在随笔中记录一下. 这2篇博文是 K8S调度之节点亲和性 与 K8S调度之Taints and Tolerations . 如果我们把 node ...
- 从源码角度理解Java设计模式——装饰者模式
一.饰器者模式介绍 装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活. 适用场景:动态的给一个对象添加或者撤销功能. 优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个 ...
- IL角度理解C#中字段,属性与方法的区别
IL角度理解C#中字段,属性与方法的区别 1.字段,属性与方法的区别 字段的本质是变量,直接在类或者结构体中声明.类或者结构体中会有实例字段,静态字段等(静态字段可实现内存共享功能,比如数学上的pi就 ...
- IL角度理解for 与foreach的区别——迭代器模式
IL角度理解for 与foreach的区别--迭代器模式 目录 IL角度理解for 与foreach的区别--迭代器模式 1 最常用的设计模式 1.1 背景 1.2 摘要 2 遍历元素 3 删除元素 ...
随机推荐
- dotnet 设计规范 · 数组定义
✓ 建议在公开的 API 使用集合而不是数组.集合可以提供更多的信息. X 不建议设置数组类型的字段为只读.虽然用户不能修改字段,但是可以修改字段里面的元素.如果需要一个只读的集合,建议定义为只读集合 ...
- dotnet 设计规范 · 抽象类
X 不要定义 public 或 protected internal 访问的构造函数.默认 C# 语言不提供抽象类的公开构造函数方法. 如果一个构造函数定义为公开,只有在开发者需要创建这个类的实例的时 ...
- window环境导入odbc数据源
<ODBC指南>中只介绍了window环境如何配置odbc数据源,但是没有介绍如何导入数据源驱动,这里做个补充. 在没有导入数据源驱动之前,按照文档操作是查不到kingbaseES的odb ...
- JDK自带javap命令反编译class文件和Jad反编译class文件(推荐使用jad)
一.前言 我们在日常学习中,对一个java代码有问题,不知道jvm内部怎么进行解析的时候:有个伟大壮举就是反编译,这样就可以看到jvm内部怎么进行对这个java文件解析的!我们可以使用JDK自带的ja ...
- .NET 部署Https(SSL)通过代码方式
在上一个文章中,传送门,给大家介绍了怎么在配置文件中使用 Kestrel 部署 Https,正好今天有小伙伴稳问到:可以通过代码的方式实现 Kestrel 的 Https 的部署吗?答案是肯定的,我们 ...
- 9. 第八篇 kube-controller-manager安装及验证
文章转载自:https://mp.weixin.qq.com/s?__biz=MzI1MDgwNzQ1MQ==&mid=2247483826&idx=1&sn=88f0cef6 ...
- CentOS7.9 yum方式安装redis最新版
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm yum --enablerepo=remi ins ...
- Redis一键安装脚本
#! /usr/bin/env bash # redis 6.0.3 源码安装 # 用法: bash -x install-redis-single.sh 6.0.3 version=$1 usage ...
- 7.prometheus监控多个MySQL实例
mysqld_exporter集中部署 集中部署,就是说我们将所有的mysqld_exporter部署在同一台服务器上,在这台服务器上对mysqld_exporter进行统一的管理,下面介绍一下集中部 ...
- Elasticsearch:使用 GeoIP 丰富来自内部专用 IP 地址
转载自:https://blog.csdn.net/UbuntuTouch/article/details/108614271 对于公共 IP,可以创建表来指定 IP 属于哪个城市的特定范围.但是,互 ...