首页 > 编程语言 >AOT漫谈专题(第四篇): C#程序如何编译成Native代码

AOT漫谈专题(第四篇): C#程序如何编译成Native代码

时间:2024-10-15 12:13:30浏览次数:8  
标签:exe ilc C# 编译成 编译 AOT dotnet 代码

一:背景

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中吧。

图片名称

标签:exe,ilc,C#,编译成,编译,AOT,dotnet,代码
From: https://www.cnblogs.com/huangxincheng/p/18467144

相关文章

  • Docker 学习笔记
    目录快速入门:镜像与容器:dockerrun命令:选项详解:Docker基础:基础命令:演示:数据卷:案例1-利用Nginx容器部署静态资源:案例2-MySQL容器数据挂载:自定义镜像:简介:Dockerfile:镜像构建:网络:项目部署:DockerCompose:快速入门:镜像与容器:​ 当利用Docker安装应用时,Docker会自动搜索并下载应用镜......
  • 【路径规划】一种考虑拥塞的改进路径规划算法[CCPF-RRT*](Matlab代码实现)
    ......
  • dynamsoft_barcode_reader_bundle Python 10.4.2000
    RevolutionizingInventoryManagementinWarehouseswithDronesandBarcodeScanningTechnologydynamsoft_barcode_readerAsbusinessesscaleandsupplychainsbecomemorecomplex,inventorymanagementhasemergedasacriticalchallengeforwarehouseopera......
  • linux---centos7.9给/扩容
    linux给/扩容1.添加新磁盘(例如我添加新的磁盘为1TB=1000GB)随后重启机器------------必须要重启虚拟机才能识别!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#lsblk可以看到重启后识别出了sdb磁盘1T[root@localhost~]#lsblkNAMEMAJ:MINRMSIZEROTYPEMOUNTPOINTsda8:005......
  • Ribbon-Loadbalancer自定义负载均衡策略:本地优先+偏向服务器优先
    Ribbon核心顶层抽象packagecom.netflix.loadbalancer;publicinterfaceIRule{Serverchoose(Objectvar1);voidsetLoadBalancer(ILoadBalancervar1);ILoadBalancergetLoadBalancer();}继承IRule实现choose方法默认实现我们这里说明现有的集......
  • 解决SecureFX的中文乱码问题
    SecureFX的乱码截图 一般出现乱码问题,看起来会很烦,所以,我们要干掉它。解决步骤:1,在SecureFX中,选择“选项”-“全局选项”,打开对话框,不同的版本可能会显示略有不同,但我们的目的是找到配置文件的路径,如下图中,该路径就是配置文件夹的路径,复制该路径,使用文件夹打开。  或者......
  • 清除openstack导出的qcow2格式的Windows16镜像的管理员密码
    由于公司使用的openstack版本太老,无法使用cloudbase-init传递元数据修改win16镜像的管理员密码,所以琢磨其它办法,搞了一个星期。原理:使用kpartx挂载镜像,然后使用chntpw清空密码,并修改cloudbase-init配置文件里的重置密码选项。准备环境系统:centos7.5磁盘80G(转换win16镜像由qcow......
  • Educational Codeforces Round 170 (Rated for Div. 2) C. New Games
    题意转化找一些相邻的数(其中相邻定义为递增序下任意相邻两数差\(\leq1\))求相邻数中,不同数字有\(k\)种,取到数字个数的最大值算法容易想到按顺序排列观察到有点像滑动窗口,考虑用队列维护一个出现不同数字次数为\(k\)的区间,再计算代码来自转载地址voidsolv......
  • cvpr注意事项和注册流程(2025版)(20241015更新还未开放注册)
    本文章基于现有网上没有cvpr详细版本的一步一步的注册流程进行编写,用于指导自己和方便他人进行注册。接下来将从CVPR2025的重要节点、变更事项、注册流程进行说明重要节点CVPR2025变更的重要事项Duetothedramaticincreaseinthenumberofsubmissionsandthedeterio......
  • ef8解决sql2008R2不支持fetch命令问题:optionsBuilder.UseSqlServer(sConn, options =>
    netcoreer8.sql2012支持fetch命令,sql2008r2不支持fetch命令,调用ef的skip(100).take(10)时,会报错:ERROR[.NETTPWorker](D:\Design\CSharp\海宏发票查重报销管理\iPublic\公共\BaseApiController.cs:114)-资料维护.Pages.CommBase.FaPiaoTypeApiController.query::'OFFSET......