• 从Hello, world开始认识IL <第一篇>


    IL代码分析方法

    Hello, world历史

    .NET学习方法论

    1.引言

    1988年Brian W.Kernighan和Dennis M.Ritchie合著了软件史上的经典巨著《The C programming Language》,我推荐所有的程序人都有机会重温这本历史上的经典之作。从那时起,Hello, world示例就作为了几乎所有实践型程序设计书籍的开篇代码,一直延续至今,除了表达对巨人与历史的尊重,本文也以Hello, world示例作为我们扣开IL语言的起点,开始我们循序渐进的IL认识之旅。

    2.从Hello, world开始

    首先,当然是展示我们的Hello, world代码,开始一段有益的分享。

    using System;
    using System.Data;

    public class HelloWorld
    {
         public static void Main()
         {
             Console.WriteLine("Hello, world.");
         }
    }

    这段代码执行了最简单的过程,向陌生的世界打了一个招呼,那么运行在高级语言背后真相又是什么呢,下面开始我们基于上述示例的IL代码分析。 

    3.IL体验中心

    对编译后的可执行文件HelloWorld.exe应用ILDasm.exe反编译工具,还原HelloWorld的为文本MSIL编码,至于其工作原理我们期望在系列的后续文章中做以交代,我们查看其截图为:

    由上图可知,编译后的IL结构中,包含了MANIFEST和HelloWorld类,其中MANIFEST是个附加信息列表,主要包含了程序集的一些属性,例如程序集名称、版本号、哈希算法、程序集模块等,以及对外部引用程序集的引用项;而HelloWorld类则是我们下面介绍的主角。

    3.1 MANIFEST清单分析

    打开MANIFEST清单,我们可以看到

    959x344

    从这段IL代码中,我们的分析如下:

    .assembly指令用于定义编译目标或者加载外部库。在IL清单中可见,.assembly extern mscorlib表示外部加载了外部核心库mscorlib,而.assembly HelloWorld则表示了定义的编译目标。值得注意的是,.assembly将只显示程序中实际应用到的程序集列表,而对于加入using引用的程序集,如果并未在程序中引用,则编译器会忽略多加载的程序集,例如System.Data将被忽略,这样就有效避免了过度加载引起的代码膨胀。

    我们知道mscorlib.dll程序集定义managed code依赖的核心数据类型,属于必须加载项。 例如接下来要分析的.ctor指令表示构造函数,从代码中我们知道没有为HelloWord类提供任何显示的构造函数,因此可以肯定其继承自基类System.Object,而这个System.Object就包含在mscorlib程序集中。

    在外部指令中还会指明了引用版本(.ver);应用程序实际公钥标记(.publickeytoken),公钥Token是SHA1哈希码的低8位字节的反序(如下图所示),用于唯一的确定程序集;还包括其他信息如语言文化等。

    HelloWorld程序集中包括了.hash algorithm指令,表示实现安全性所使用的哈希算法,系统缺省为0x00008004,表明为SHA1算法;.ver则表示了HelloWorld程序集的版本号;

    程序集由模块组成, .module为程序集指令,表明定义的模块的元数据,以指定当前模块。

    其他的指令还有:imagebase为影像基地址;.file alignment为文件对齐数值;.subsystem为连接系统类型,0x0003表示从控制台运行;.corflags为设置运行库头文件标志,默认为1;这些指令不是我们研究的重点,详细的信息请参考MSDN相关信息。

    3.2 HelloWorld类分析

    首先是HelloWorld类,代码为:

    .class public auto ansi beforefieldinit HelloWorld
           extends [mscorlib]System.Object
    {
    } // end of class HelloWorld

    .class表明了HelloWorld是一个public类,该类继承自外部程序集mscorlib的System.Object类。

    public为访问控制权限,这点很容易理解。

    auto表明程序加载时内存的布局是由CLR决定的,而不是程序本身

    ansi属性则为了在没有被管理和被管理代码间实现无缝转换。没有被管理的代码,指的是没有运行在CLR运行库之上的代码,例如原来的C,C++代码等。

    beforefieldinit属性为HelloWorld提供了一个附加信息,用于标记运行库可以在任何时候执行类型构造函数方法,只要该方法在第一次访问其静态字段之前执行即可。如果没有beforefieldinit则运行库必须在某个精确时间执行类型构造函数方法,从而影响性能优化,详细的情况可以参与MSDN相关内容。

    然后是.ctor方法,代码为:

    .method public hidebysig specialname rtspecialname
             instance void  .ctor() cil managed
    {
       // 代码大小       7 (0x7)
       .maxstack  8
       IL_0000:  ldarg.0
       IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
       IL_0006:  ret
    } // end of method HelloWorld::.ctor

    cil managed 说明方法体中为IL代码,指示编译器编译为托管代码。

    .maxstack表明执行构造函数.ctor期间的评估堆栈(Evaluation Stack)可容纳数据项的最大个数。关于评估堆栈,其用于保存方法所需变量的值,并在方法执行结束时清空,或者存储一个返回值。

    IL_0000,是一个标记代码行开头,一般来说,IL_之前的部分为变量的声明和初始化。

    ldarg.0 表示装载第一个成员参数,在实例方法中指的是当前实例的引用,该引用将用于在基类构造函数中调用。

    call指令一般用于调用静态方法,因为静态方法是在编译期指定的,而在此调用的是构造函数.ctor()也是在编译期指定的;而另一个指令callvirt则表示调用实例方法,它的调用过程有异于call,函数的调用是在运行时确定的,首先会检查被调用函数是否为虚函数,如果不是就直接调用,如果是则向下检查子类是否有重写,如果有就调用重写实现,如果没有还调用原来的函数,依次类推直到找到最新的重写实现。

    ret表示执行完毕,返回。

    最后是Main方法,代码为:

    .method public hidebysig static void  Main() cil managed
    {
       .entrypoint
       // 代码大小       11 (0xb)
       .maxstack  8
       IL_0000:  ldstr      "Hello, world."
       IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
       IL_000a:  ret
    } // end of method HelloWorld::Main

    .entrypoint指令表明了CLR加载程序HelloWorld.exe时,是首先从.entrypoint方法开始执行的,也就是表明Main方法将作为程序的入口函数。每个托管程序必须有并且只有一个入口点。这区别于将Main函数作为程序入口标志。

    ldstr指令表示将字符串压栈,"Hello, world."字符串将被移到stack顶部。CLR通过从元数据表中获得文字常量来构造string对象,值得注意的是,在此构造string对象并未出现在《第五回:深入浅出关键字---把new说透》中提到的newobj指令,对于这一点的解释我们将在下一回中做简要分析。

    hidebysig属性用于表示如果当前类作为父类时,类中的方法不会被子类继承,因此HelloWorld子类中不会看到Main方法。

    接下来的一点补充:

    关于注释,IL代码中的注释和C#等高级语言的注释相同,其实编译器在编译IL代码时已经将所有的注释去掉,所以任何对程序的注释在IL代码中是看不见的。 

    3.3 回归简洁

    去粗取精,我们的IL代码可以简化,下面的代码是基于上面的分析,并去处不重要的信息,以更简洁的方式来展现的HelloWorld版IL代码,详细的分析就以注释来展开吧。

    4.结论

    结束本文,我们从一个点的角度和IL来了一次接触,除了了解几个重要的指令含义,更重要的是已经走进了IL的世界。通过一站式的扫描HelloWorld的IL编码,我们还不足以从全局来了解IL,不过第一次的亲密接触至少让我们太陌生,而且随着系列文章的深入我们将逐渐建立起这种认知,从而提高我们掌握了解.NET底层的有效工具。本系列也将在后续的文章中,逐渐建立起这种使用工具的方法,敬请关注。

  • 相关阅读:
    linux mono环境
    【百度杯】ctf夺旗大战,16万元奖励池等你拿
    【渗透技术】渗透测试技术分析_TomCat
    成功率“99%”!截止目前史上最强大电信诈骗术
    【sql注入】浅谈sql注入中的Post注入
    通过Weeman+Ettercap配合拿下路由器管理权限
    【sql注入教程】mysql注入直接getshell
    【云盘资料】Sql注入从菜鸟到高手系列教程
    【安全开发】浅谈JSP安全开发之XSS
    Python黑帽编程2.1 Python编程哲学
  • 原文地址:https://www.cnblogs.com/mingxuantongxue/p/3822303.html
Copyright © 2020-2023  润新知