一:背景
1. 讲故事
大家都知道所谓的.NET Native AOT即通过AOT编译器直接将C#代码编译成呆板码,大家也习惯用C/C++的编译过程来类比,都是静态编译本质上都差不多,这篇我们借助工具从宏观层面去看一看AOT的编译过程。
二:C/C++ 的编译过程
用gcc编译过c代码的朋友都知道,分别可以用 -E, -S, -c,-o 来显示编译的各个阶段,即:
- 预处理阶段:落地 define,include文件和代码。
- 编译阶段:将C转为汇编代码。
- 汇编阶段:汇编代码转为呆板码。
- 链接阶段:链接libc库及体系库,天生可实行文件。
画一张图如下:
这个天下上虽然说隔行如隔山,但隔行不隔理,有了这些知识,接下来就是按图索骥的对号入座即可。
三:AOT编译过程
在.NET中AOT编译器叫做ilc.exe,它是用C#代码写的,并且随.NET版本更新,比如我这里的 C:\Users\Administrator\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\8.0.8\tools\ilc.exe 。
对应的源码是在 D:\sources\runtime\src\coreclr\tools\aot 下。
另有一点要留意的是 ilc.exe 接收的是 MSIL 代码,而不是 C# 代码,有些朋友要问了 MSIL 何处来,自然是 dotnet publish 的时候先调用 Rolysn 来准备了,画个图如下:
接下来就是正式的ilc阶段。
1. 预处理阶段在哪里
这个阶段其实就对应着AOT的 依靠图构建和优化 ,当然C#这里比力复杂,包括的东西也比力多,比如:
- 构建依靠图
- Pinvoke,COM,Delegate 的IL代码二次处理
- ValueType 的 GetHashCode 和 Equals 天生。
- 对 反射的有限支持,提供了一些元数据。
- 摇树优化
为依靠图构建的全部物料,可以参考 obj\Debug\net8.0\win-x64\native 文件夹下的 Example_21_2.ilc.rsp。

感爱好的朋友可以重点研究下这个库下的代码以及 DependencyAnalyzer 类,截图如下:
 - /// <summary>
- /// Implement a dependency analysis framework. This works much like a Garbage Collector's mark algorithm
- /// in that it finds a set of nodes from an initial root set.
- ///
- /// However, in contrast to a typical GC in addition to simple edges from a node, there may also
- /// be conditional edges where a node has a dependency if some other specific node exists in the
- /// graph, and dynamic edges in which a node has a dependency if some other node exists in the graph,
- /// but what that other node might be is not known until it may exist in the graph.
- ///
- /// This analyzer also attempts to maintain a serialized state of why nodes are in the graph
- /// with strings describing the reason a given node was added to the graph. The degree of logging
- /// is configurable via the MarkStrategy
- ///
- /// </summary>
- public sealed class DependencyAnalyzer<MarkStrategy, DependencyContextType> : DependencyAnalyzerBase<DependencyContextType> where MarkStrategy : struct, IDependencyAnalysisMarkStrategy<DependencyContextType>
- {
- private MarkStrategy _marker = new MarkStrategy();
- private IComparer<DependencyNodeCore<DependencyContextType>> _resultSorter;
- private RandomInsertStack<DependencyNodeCore<DependencyContextType>> _markStack;
- private List<DependencyNodeCore<DependencyContextType>> _rootNodes = new List<DependencyNodeCore<DependencyContextType>>();
- }
复制代码 官方表明中写的挺有意思,这玩意就像 GC Mark 算法,看字段也是一个 深度优先算法。
有些朋友可能比力好奇,这个依靠树末了变成了什么样子,可以在 csproj 上配置 true 节点,然后通过 dotnet publish 就会天生一个 Example_21_2.map.xml 文件,打开即可看到范例和方法节点。

2. 编译阶段在哪里
C 的编译阶段是用来将C代码转成汇编代码,在 ILC 中叫做代码天生后端,在落地方案上支持两种。
对,你没看错,就是你熟悉的不能再熟悉的JIT编译器,AOT也在用它,毕竟这东西太成熟了,支持各大操作体系平台,对应的高层封装在 ILCompiler.RyuJit 库中,截图如下:
这东西如今主要用来天生 WebAssembly 代码,具体参见:https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM
3. 汇编阶段在哪里
在 C 中这个阶段主要是将 .s 变成 .o 文件,即 汇编代码 到 呆板码,假如往AOT上套的话,当属 ObjectWriter 类了,它要干的事情就是天生最终的 xxx.obj 文件。
4. 链接阶段在哪里
天生了 obj 之后,不管是 C 还是 C# 都 殊途同归 了,即调用 link.exe 将 VCRuntime运行时以及体系的.lib 库举行整体性归并,这个在 link.rsp 文件中可以窥之一二,截图如下:
图中有一个小留意点,此时的 obj 还没有 gc 代码,最终是在 link 阶段归并进去的。
三:如何眼见为实
为了研究这些过程,这里提供两款工具,一款叫 prefview,一款叫 procmon。
1. 如何观测编译流程
要想知道这个答案,找到一款符合的工具还是很容易知道的,在 Prefview 中有一个 Processes 视图,可以利用它观测 dotnet publish 命令实行后的历程启停情况,截图如下:
从卦中很明显看的出来是: dotnet.exe -> dotnet.exe (Roslyn) -> ilc.exe -> link.exe ,哈哈,这流程图是不是加深了对编译过程的理解哈。
2. 如何观测 obj 的天生
观测 obj 的天生,自然就是对 ilc.exe 的文件读写举行监控,对,可以用微软的 Procmon 工具,配置如下:
配置好之后,接下来就是使用 dotnet publish 来引诱 ilc.exe 出洞,然后 倾巢覆卵,截图如下:
接下来我们双击这一行观察 Stack 选项卡,可以很明显的看到是 ObjectWriter 所为,截图如下:
四:总结
研究这些东西还是比力贫困的,主要是官方github上对ilc的先容也比力有限,更多的还是需要研究源码,术业有专攻,作为一个调试师,更多精力还是耗在市场上的dump中吧。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |