MSIL学习笔记(03):第一个IL代码的分析

前情回顾

MSIL学习笔记(01):ilasm和ildasm

MSIL学习笔记(02):简化ildasm生成的代码

两篇笔记记录了使用使用Ildasm反编译了一个简单的程序,并且在保证程序可以正常编译执行的情况下,清理了一些不太重要的内容。


为了方便阅读,还是把C#源文件和对应的IL代码先贴出来

C#源文件

using System;

namespace ILSample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello");
}
}
}

反编译的IL代码(简化版)

.assembly ILSample
{

}
.class private auto ansi beforefieldinit ILSample.Program
extends [mscorlib]System.Object
{
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr "Hello"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
}
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
}
}

作为一个.NET程序员,根据以往对IL的接触,里边的“ldstr\call\ret\ldarg”基本可以猜出来个123,反倒是一堆关键字修饰符看不明白

所以先问自己几个问题,然后再找出来答案

问题1:那个必不可少的.assembly ILSample{}起什么作用?

问题2:.class中 auto 关键字什么意思?

问题3:.class中 ansi 关键字什么意思?

问题4:.class中 beforefieldinit 关键字什么意思?

问题5:.method中 .entrypoint 关键字什么意思?

问题6:.method中 hidebysig 关键字什么意思?

问题7:.method中 specialname 关键字什么意思?

问题8:.method中 rtspecialname 关键字什么意思?

问题9:每个指令前边的 IL_00XX: 是神马东西

问题10:.maxstack 表示什么

-------------------------

关于上面问题的答案 只是可能的答案

问题1:那个必不可少的.assembly ILSample{}起什么作用?

这么简单一个语句,我的直觉是声明这个文件包含程序集清单。理由1是.NET程序集可以包含在多个文件中,必定有一个文件(我随便起个名字叫程序集的主文件)保存有程序集的信息,比如名称、描述、版本……最最起码,这个文件应该表述一下"我就是这个程序集的主文件"。.assembly XXX{} 起的应该就是这个作用。理由2是当初删除了.assembly ILSample{}之后,是可以正常编译的,但是无法正常执行,提示的错误如下

既然可以编译,就说明允许这样存在(以),不能执行是缺少程序集清单。

为了验证我的想法 根据《如何:生成多文件程序集》自己做了一个多文件程序集,在主文件中存在的.assembly XXX{},在附属文件中的确是不存在的。

围绕这个问题的新发现是.file .moXXXXXXX

问题2:.class中 auto 关键字什么意思?

StructLayout特性》《.net托管环境下struct实例字段的内存布局(Layout)和大小(Size)

问题3:.class中 ansi 关键字什么意思?
问题4:.class中 beforefieldinit 关键字什么意思?

这个关键字表示让运行时决定什么时候调用静态构造函数,也可能在调用函数的开始处,也可能恰恰在第一次访问静态成员或者第一次实例化之前。

总之是运行时决定的,只要在你用到这个类之前搞定就好了

如果没有这个关键字,则运行时恰恰在第一次访问静态成员或者第一次实例化之前调用静态构造函数。

至于C#编译的IL什么情况下有这个关键字什么时候没有关键字,判断起来也很容易,如果有显式的静态构造函数,就没有这个关键字,如果没有显式的构造函数,就有这个关键字。比如下面两个类:

 1     public class Test1
2 {
3 public static int i;
4 static Test1()//有显式的构造函数
5 {
6 i = 3;
7 }
8 }
9 public class Test2//没有显式的构造函数
10 {
11 public static int i = 3;
12 }

生成的IL

.class public auto ansi Test1
extends [mscorlib]System.Object
{
.method private hidebysig specialname rtspecialname static void .cctor() cil managed
{
.maxstack 8
L_0000: ldc.i4.3
L_0001: stsfld int32 _108.IL.Test1::i
L_0006: ret
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib]System.Object::.ctor()
L_0006: ret
}
.field private static int32 i
}


class public auto ansi beforefieldinit Test2
extends [mscorlib]System.Object
{
.method private hidebysig specialname rtspecialname static void .cctor() cil managed
{
.maxstack 8
L_0000: ldc.i4.3
L_0001: stsfld int32 _108.IL.Test2::i
L_0006: ret
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib]System.Object::.ctor()
L_0006: ret
}
.field private static int32 i
}

关于beforefieldinit还有很多有意思的内容,可以参考下面这些园子里高手的文章

关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释

[你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器

对beforefieldinit的理解

静态构造函数的执行时机


问题5:.method中 .entrypoint 关键字什么意思?

.entrypoint这个比较简单,就是程序的入口点,如果是.EXE的程序集有且只能有一个,如果是.DLL的程序集,当然可以没有入口点。

C#的Main()函数,就被编译器加上了.entrypoint,IL编译器是不认Main()的,只认.entrypoint,所以在IL中,你可以随便让一个函数

当作入口。没必要非是Main()。

 

 问题6:.method中 hidebysig 关键字什么意思?

 基本相当于C#中的new关键字

问题7、问题8::.method中 specialname  rtspecialname 关键字什么意思?

目前也没搞很清楚,大概就是表示这个方法比较特殊

大概构造函数之类的需要加上这两个修饰

问题9:每个指令前边的 IL_00XX: 是神马东西

IL_XXXX:其实就是语句的标号,这里的这些标号是ILDASM生成的,就程序执行来说并没有意义。

我们也可以根据自己的情况来写标号,比如"BEGIN: END:"等等

也可以通过标号让程序来跳转,C#不也是有这个功能的么?

接下来的问题就是IL_XXXX后便XXXX这几个数字了,是按什么编号的?其实很明显感觉是相对于方法入口的地址偏移。

为了证实这一点,我也没想到什么好办法,随便写了一个C#程序,反编译后为

IL_0000:  ldstr      "dddddddddddddddd" //72 
IL_0005: call void [mscorlib]System.Console::WriteLine(string) //28
IL_000a: ldstr "ffffffffffffffff" //72
IL_000f: call void [mscorlib]System.Console::WriteLine(string) //28
IL_0014: ldstr "dddddddddddddddd" //72
IL_0019: call void [mscorlib]System.Console::WriteLine(string) //28
IL_001e: ldc.i4.3 //19
IL_001f: stloc.0 //0A
IL_0020: ldc.i4.4 //1A
IL_0021: stloc.1 //0B
IL_0022: ldloc.0 //06
IL_0023: ldloc.1 //07
IL_0024: add //58
IL_0025: stloc.2 //0C
IL_0026: ldloc.2 //08
IL_0027: call void [mscorlib]System.Console::WriteLine(int32) //28
IL_002c: ret //2A

用UltraEdit打开了.EXE文件。

找到这段IL代码表示二进制代码(OpCode),查找的方法当然要找到每条指令对应的OpCode,

比如0x72代表ldstr;0x28代表call;0x19代表ldc.i4.3  更多需要参考《MSIL指令\操作\Opcode对照表

按照内存的顺序将列下来,就很容易看出来代码地址偏移的关系。

 

问题10:.maxstack 表示什么

最后一个问题 留到下篇吧。。。

posted @ 2012-01-15 04:28  小墨的童鞋  阅读(2172)  评论(1编辑  收藏  举报