一:背景

1. 讲故事

大家都知道所谓的.NET Native AOT即通过AOT编译器直接将C#代码编译成机器码,大家也习惯用C/C++的编译过程来类比,都是静态编译本质上都差不多,这篇我们借助工具从宏观层面去看一看AOT的编译过程。

二:C/C++ 的编译过程

用gcc编译过c代码的朋友都知道,分别可以用 -E, -S, -c,-o 来显示编译的各个阶段,即:

  1. 预处理阶段:落地 define,include文件和代码。
  2. 编译阶段:将C转为汇编代码。
  3. 汇编阶段:汇编代码转为机器码。
  4. 链接阶段:链接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#这里比较复杂,包括的东西也比较多,比如:

  1. 构建依赖图
  2. Pinvoke,COM,Delegate 的IL代码二次处理
  3. ValueType 的 GetHashCode 和 Equals 生成。
  4. 对 反射的有限支持,提供了一些元数据。
  5. 摇树优化

为依赖图构建的所有物料,可以参考 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 上配置 <IlcGenerateMapFile>true</IlcGenerateMapFile> 节点,然后通过 dotnet publish 就会生成一个 Example_21_2.map.xml 文件,打开即可看到类型和方法节点。

2. 编译阶段在哪里

C 的编译阶段是用来将C代码转成汇编代码,在 ILC 中叫做代码生成后端,在落地方案上支持两种。

  1. RyuJIT

对,你没看错,就是你熟悉的不能再熟悉的JIT编译器,AOT也在用它,毕竟这东西太成熟了,支持各大操作系统平台,对应的高层封装在 ILCompiler.RyuJit 库中,截图如下:

  1. LLVM

这东西目前主要用来生成 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中吧。

AOT漫谈专题(第四篇): C#程序如何编译成Native代码的更多相关文章

  1. 用python写个简单的小程序,编译成exe跑在win10上

    每天的工作其实很无聊,早知道应该去IT公司闯荡的.最近的工作内容是每逢一个整点,从早7点到晚11点,去查一次客流数据,整理到表格中,上交给素未蒙面的上线,由他呈交领导查阅. 人的精力毕竟是有限的,所以 ...

  2. PerfView专题 (第四篇):如何寻找 C# 中程序集泄漏

    一:背景 前两篇我们都聊到了非托管内存泄漏,一个是 HeapAlloc ,一个是 VirtualAlloc,除了这两种泄漏之外还存在其他渠道的内存泄漏,比如程序集泄漏,这一篇我们就来聊一聊. 二: 程 ...

  3. asp.net signalR 专题—— 第四篇 模拟RPC模式的Hub操作

    在之前的文章中,我们使用的都是持久连接,但是使用持久连接的话,这种模拟socket的形式使用起来还是很不方便的,比如只有一个唯一的 OnReceived方法来处理业务逻辑,如下图: protected ...

  4. linux设备驱动第四篇:从如何定位oops的代码行谈驱动调试方法

    上一篇我们大概聊了如何写一个简单的字符设备驱动,我们不是神,写代码肯定会出现问题,我们需要在编写代码的过程中不断调试.在普通的c应用程序中,我们经常使用printf来输出信息,或者使用gdb来调试程序 ...

  5. 嵌入式linux下wifi网卡的使用(四)——应用程序sub_supplicant编译

    有readme先看看readme看看有没有编译的方法 里面告诉我们安装时可能会依赖某些库事实证明会依赖openssl库,之前也使用过openssl 这个文件中有个defualtconfig,先用它做. ...

  6. python学习之路基础篇(第四篇)

    一.课程内容回顾 1.python基础 2.基本数据类型  (str|list|dict|tuple) 3.将字符串“老男人”转换成utf-8 s = "老男人" ret = by ...

  7. Spring Native 项目,把 Spring 项目编译成原生程序!

    Spring Native 是什么 优点 缺点 原生镜像(native image)和常规 JVM 程序的区别 前置条件:GraalVM 支持的语言 关键特性 GraalVM 下的 Java 微服务 ...

  8. Linux环境下Android JNI程序的编译

    尊重原创作者,转载请注明出处: http://blog.csdn.net/gemmem/article/details/8993493 在android开发中,有时候需要编写一些C/C++代码,这时候 ...

  9. 四十年前的 6502 CPU 指令翻译成 JS 代码会是怎样

    去年折腾的一个东西,之前 blog 里也写过,不过那时边琢磨边写,所以比较杂乱,现在简单完整地讲解一下. 前言 当时看到一本虚拟机相关的书,正好又在想 JS 混淆相关的事,无意中冒出个问题:能不能把某 ...

  10. 微信小程序裁剪图片成圆形

    代码地址如下:http://www.demodashi.com/demo/14453.html 前言 最近在开发小程序,产品经理提了一个需求,要求微信小程序换头像,用户剪裁图片必须是圆形,也在gith ...

随机推荐

  1. 【Shiro】06 自定义Realm授权实现

    创建一个激活的用户类: public class ActiverUser { private User user; private List<String> roleList; priva ...

  2. AI的发展需要有应用和落地场景 —— 李开复:传统公司看不懂技术,大模型落地B端阻碍多

    引自:https://baijiahao.baidu.com/s?id=1801826206644007472&wfr=spider&for=pc "我们投了七八家机器人企业 ...

  3. 神州笔记本 win11 节能模式 供电不足 自动关机

    刚刚买了一个神州笔记本没几天,用着用着就出现问题了. 本人使用电脑有个极为不好的习惯,那就是会一次性打开特别多的应用,然后不关,一直留着,这个习惯虽然不好但也是一直没有啥问题的,不过最近换了个新的笔记 ...

  4. 台式机电脑散热之狂想曲——主机机箱散热的另类方法(纯空想中ing)

    本博文是一篇狂想曲,之所以叫狂想曲是因为本文只是博主在无聊时突发奇想,而且仅停留于想的阶段,所以本文内容不用太过认真. 事情是这样的,博主有一台式机,有事没事的就喜欢宅在宿舍里面,有时候还能偶然用这台 ...

  5. 2023.4.12.汇报.pptx

    6月份汇报想法    8月份写论文 ==========================================================

  6. java集合专题之Collection接口

    1.背景 集合是java中非常重要的技术点,也是面试经常问到的技术点.... 2.集合体系 单列集合 双列集合,key value集合 如果出去面试,这体系应该可以背出来,才算合格 3.常用方法 代码 ...

  7. Centos7安装mysql8.0教程

    1.背景 centos7安装mysql8.0 2.安装步骤 步骤一:安装准备工作 1.查看是否有安装过mysql rpm -qa | grep -i mysql 2.删除mysql yum -y re ...

  8. Java基础之占位符

  9. 【CDQ分治】三元环

    三元环 HDU - 7439 思路 考虑 \(3\) 个点的有向图,要么成环,要么有一个点入度为 \(2\) ,假设第 个点的入度为 \(d_i\),答案为 \(C_n^3-\sum\limits_{ ...

  10. [KEYENCE Programming Contest 2023 Autumn(AtCoder Beginner Contest 325) E

    KEYENCE Programming Contest 2023 Autumn(AtCoder Beginner Contest 325) - AtCoder E E - Our clients, p ...