[你必须知道的.NET]第十三回:从Hello, world开始认识IL

转自:http://www.cnblogs.com/anytao/archive/2007/07/22/must_net_13.html

本文将介绍以下内容:

  • 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编码,至于其工作原理我们期望在系列的后续文章中做以交代,我们查看其截图为:

[你必须知道的.NET]第十三回:从Hello, world开始认识IL_第1张图片

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

3.1 MANIFEST清单分析

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

[你必须知道的.NET]第十三回:从Hello, world开始认识IL_第2张图片

从这段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位字节的反序(如下图所示),用于唯一的确定程序集;还包括其他信息如语言文化等。  

[你必须知道的.NET]第十三回:从Hello, world开始认识IL_第3张图片

  • 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代码,详细的分析就以注释来展开吧。

[你必须知道的.NET]第十三回:从Hello, world开始认识IL_第4张图片

4. 结论

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


你可能感兴趣的:(.net,String,编译器,alignment,代码分析,反编译工具)