MSIL解析一(转)

转自:http://www.cnblogs.com/Yahong111/archive/2007/08/15/857140.html

在网上发现了一个非常好的MSIL教程,可惜是英文版的,于是就翻译了一下,与大家共享,
原文http://www.codeguru.com/Csharp/.NET/net_general/il/article.php/c4635,初次翻译文章,请大家指正。

介绍

微软中间语言 (MSIL) 是一种语言,是许多编译器(C#,VB.NET等)的输出. ILDasm (中间语言反汇编器)程序和.Net Framework SDK(FrameworkSDK\Bin\ildasm.exe)打包在一起,让用户以人可阅读的格式查看MSIL代码。通过该工具,我们可以打开任何.net可执行文件(exe或dll)并查看其MSIL代码。

ILAsm 程序(中间语言编译器)从MSIL语言生成可执行文件。我们可以在WINNT \ Microsoft.NET \Framework \vn.nn.nn目录中找到这个程序。

许多Visual C++程序员开始.net开发是因为他们对.NET框架的底层发生了一些什么感兴趣。学习MSIL给了用户理解某些对C#程序员或VB.NET程序员来说是透明的东西的机会。通晓MSIL给.NET程序员更多的能力。我们从不需要直接用MSIL编写程序,但是在某些情况下是非常有用的,我们可以用ILDasm打开程序的MSIL代码,查看它到底做了一些什么。

一个Doc格式的MSIL参考对.NET开发人员来说比较有用,它也许可以在Framework SDK目录下找到:

  • FrameworkSDK\Tool Developers Guide\docs\Partition II Metadata.doc (元数据定义和术语). 在这个文件中,我发现了所有MSIL指令的说明,例如.entrypoint.locals等.
  • FrameworkSDK\Tool Developers Guide\docs\Partition III CIL.doc (CIL命令集)包含了一个MSIL命令的完整列表。

在工作中,我也用到了一个MSDN的ILDAsm教程,一篇2001年5月由John Robbins发表在MSDN杂志的优秀的文章:"ILDASM is Your New Best Friend"。

我想学习一门语言最好的途径就是用它写一些程序,所以我决定写一些小的MSIL程序。实际上,我们有写这些代码——是C#编译器生成的,我只是做一了一些小的更改,并加了许多注释以描述MSIL是如何工作的。

通过阅读附在本文的例子可以帮助.NET程序员理解中间语言,帮助其在需要的时候更易读懂MSIL代码。


一般信息

在MSIL中,所有的操作都在栈上完成。当调用一个函数的时候,其参数和局部变量都被分配到栈上。函数的代码从该栈开始,把一些值压入栈,对这些值进行一些操作,从栈上取出值。

执行MSIL名利和函数由3个步骤完成:

1.      把命令操作数和函数参数压入栈。

2.      执行命令或者调用函数。命令或函数从栈中取出他们的操作数(参数)并把他们压入结果栈 (返回值)。

3.      从栈中读取结果值。

步骤1~3是可选的,例如,void函数不会压入一个结果值到栈。

栈包含值类型对象和引用类型对象的引用。引用类型对象本身保存在堆中。

用来把一个值压入栈中的MSIL命令是ld... (装载),用来从栈中取出值的命令是st... (存储),因为值都存在变量中。我们可以把入栈操作叫做装载,出栈操作叫做存储。


示例项目

本文附上的代码中包含了许多用MSIL写的控制台程序. 如果需要编译他们,请确定ILAsm程序可以通过PATH访问。每个项目都是一个Visual Studio解决方案,IL源文件可以用VS的文本编辑器打开,Build命令运行ILAsm 程序在项目所在目录生成exe文件,run命令执行该文件。在每个程序的末尾,我加了几行代码,他们可以用C#来写:

 Console.WriteLine("Press Enter to continue");
 Console.Read();

这样,当从Windows Explorer运行的时候,就可以看到程序的输出。

下面是所含项目的列表:

1.      打印字符串—打印字符传到控制台。

2.      赋值—给一个int变量赋值并把它打印到控制台。

3.      运算—从控制台读取2个数字,惊醒+,-和乘的操作,并显示结果。

4.      数组— 分配一个int类型的数组,给他的元素赋值,打印其元素和数组的长度。

5.      比较— 输入2个数字并打印出最小的那个。

6.      数组2— 用循环填充数组元素并打印某些元素。

7.      不安全代码— 使用unsafe指针访问数组元素。

8.      PInvoke— 调用Win32 API。

9.      类— 和类一起工作。

10.   异常— 异常处理。

我假设你以在这所说的顺序阅读这些项目。在下面的项目描述中,我用程序来解释每一条MSIL命令,并给出一些代码片段。


打印字符串

PrintString 就是MSIL版的 Hello, World 

在代码中用到的MSIL指令如下:

l          .entrypoint— 定义程序的入口点(该函数在程序启动的时候由.NET 运行库调用)

l          .maxstack— 定义函数代码所用堆栈的最大深度。C#编译器可以对每个函数设置准确的值, 在例子中,我把他设为8。

用到的MSIL命令如下:

  • ldstr string—把一个字符串常量装入堆栈。
  • call function(parameters)—调用静态函数。函数的参数必须在函数调用前装入堆栈。
  • pop— 取出栈顶的值。当我们不需要把值存入变量时使用。
  • ret— 从一个函数中返回。

调用静态函数很简单。我们把函数的参数压入堆栈,调用函数,然后从堆栈中读取函数的返回值(如果是非void函数)。Console.WriteLine 就是一个这样的函数。

下面是代码:

.assembly PrintString {}
/*
    Console.WriteLine("Hello, World)"
*/
.method static public void main() il managed
{
    .entrypoint             // 该函数是程序的入口
    .maxstack 8
    // *****************************************************
    // Console.WriteLine("Hello, World)";
    // *****************************************************
    ldstr "Hello, World"        // 把字符串压入堆栈
    // 调用静态的System.Console.Writeline函数
    // (函数移除栈顶的字符串)
    call   void [mscorlib]System.Console::WriteLine
                                 (class System.String)
    // *****************************************************
    ldstr "Press Enter to continue"
    call   void [mscorlib]System.Console::WriteLine
                                 (class System.String)
    // 调用 System.Console.Read 函数
    call int32 [mscorlib]System.Console::Read()
    // pop 指令移除栈顶元素
    // (移除由Read()函数返回的数字
    pop
    // *****************************************************
    ret
}

赋值

该程序给一个变量赋与int值并把它打印到控制台窗口。

命令:

  • ldc.i4.n—把一个 32位的常量(n从0到8)装入堆栈
  • stloc.n— 把一个从堆栈中返回的值存入第n(n从0到8)个局部变量

代码:

.assembly XequalN {}
// int x;
// x = 7;
// Console.WriteLine(x);
.method static public void main() il managed
{
    .entrypoint
    .maxstack 8
    .locals init ([0] int32 x)  // 分配一个局部变量
    // *****************************************************
    // x = 7;
    // *****************************************************
    ldc.i4.7                    // 把常量装入堆栈
    stloc.0                     // 把堆栈中的值存入第0个变量
    // *****************************************************
    // Console.WriteLine(x);
    // *****************************************************
    ldloc.0                     // 把第0个变量转入堆栈
    call void [mscorlib]System.Console::WriteLine(int32)
    ret
}

数据运算

本程序从控制台读取2个数字,对它们进行简单的运算,然后显示结果。

命令:

  • add—2个值相加。命令的参数必须在调用前装入堆栈,该函数从堆栈中移除参数并把运算后的结果压入堆栈。
  • sub— 2个值相减。
  • mul— 2个值相乘。

代码片段:

.assembly Operations {}
/*
// 程序的C#代码:
            int x, y, z;
            string s;

            Console.WriteLine("Enter x:");
            s = Console.ReadLine();
            x = Int32.Parse(s);

            Console.WriteLine("Enter y:");
            s = Console.ReadLine();
            y = Int32.Parse(s);

            z = x + y;
            Console.Write("x + y = ");
            Console.Write(z);
            Console.WriteLine("");

            z = x - y;
            Console.Write("x - y = ");
            Console.Write(z);
            Console.WriteLine("");

            z = x * y;
            Console.Write("x * y = ");
            Console.Write(z);
            Console.WriteLine("");
*/

.method static public void main() il managed
{
    .entrypoint
    .maxstack 8

    .locals init ([0] int32 x,
           [1] int32 y,
           [2] int32 z,
           [3] string s)

    // *****************************************************
    // Console.WriteLine("Enter x:");
    // *****************************************************
    ldstr      "Enter x:"       // 把字符装入堆栈
call       void [mscorlib]System.Console::WriteLine(string)

    // *****************************************************
    // s = Console.ReadLine();
    // *****************************************************
    call       string [mscorlib]System.Console::ReadLine()
    stloc.3                     // 把值存入第3个变量

    // *****************************************************
    // x = Int32.Parse(s);
    // *****************************************************
    ldloc.3                     // 把第3个变量装入堆栈

    // 调用 System.Int32::Parse(string)函数
    // 把字符串从堆栈中移除并把解析的结果——int值压入堆栈
    call       int32 [mscorlib]System.Int32::Parse(string)

    stloc.0                     // 把值存入第0个变量

    // *****************************************************
    // 和变量y的一些运算
    // *****************************************************
    ldstr      "Enter y:"
               // 装入字符串
    call       void [mscorlib]System.Console::WriteLine(string)
               // 调用
    call       string [mscorlib]System.Console::ReadLine()
               // 调用
    stloc.3
               //把值存入第3个变量
    ldloc.3
               //把第3个变量装入堆栈
    call       int32 [mscorlib]System.Int32::Parse(string)
               // 调用
    stloc.1
               //把值存入第1个变量

    // *****************************************************
    // z = x + y;
    // *****************************************************
    ldloc.0             //把第0个变量装入堆栈
    ldloc.1             //把第1个变量装入堆栈

    // 把这2个值从堆栈中移除,把结果压入堆栈
add

    stloc.2             //把值存入第2个变量

    // *****************************************************
    // Console.Write("x + y = ");
    // *****************************************************
    ldstr      "x + y = "          // load string onto stack
    call       void [mscorlib]System.Console::Write(string)

    // *****************************************************
    // Console.Write(z);
    // *****************************************************
    ldloc.2                    //把第2个变量装入堆栈
    call       void [mscorlib]System.Console::Write(int32)

    // *****************************************************
    // Console.WriteLine("");
    // *****************************************************
    ldstr      ""                  //装入字符串
    call       void [mscorlib]System.Console::WriteLine(string)

    //相减和相乘运算过程与上面相同

    ret
}
时间: 2024-10-19 01:56:43

MSIL解析一(转)的相关文章

深入解析.NET框架

一.AOP框架        Encase 是C#编写开发的为.NET平台提供的AOP框架.Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式.这种部署方面(aspects)的方法帮助缺少经验的开发人员提高开发效率. NKalore是一款编程语言,它扩展了C#允许在.net平台使用AOP.NKalore的语法简单.直观,它的编译器是基于Mono C#编译器(MCS).NKalore目前只能在命令行或#Develop内部使用.NKalore兼容公

解析Exception和C#处理Exception的常用方法总结

在.NET中,异常是指成员没有完成它的名称宣称可以完成的行动.在异常的机制中,异常和某件事情的发生频率无关. 异常处理四要素包括:一个表示异常详细信息的类类型:一个向调用者引发异常类实例的成员:调用者的一段调用异常成员的代码块:调用者的一段处理将要发生异常的代码块.异常类类型包括:基类:System.Exception:系统级异常:System.SystemException:应用程序级异常:System.ApplicationException. (一).在.NET中有如下的异常类: (1).

Dotnet文件格式解析

0x0.序 解析过程并没有介绍对pe结构的相关解析过程,网上此类相关资料很多可自行查阅,本文只介绍了网上资料较少的从pe结构的可选头中的数据目录表中获取dotnet目录的rva和size,到完全解析dotnet文件格式特有数据结构的部分. 了解dotnet文件格式你可能需要一款名为CFF Explorer的工具:你也可能在很多时候需要查阅书籍<Expert .NET 2.0 IL Assembler>,该书籍的中文版本名为<.NET探秘MSIL权威指南>.简要的文件格式图,可以参考

OSS.Core基于Dapper封装(表达式解析+Emit)仓储层的构思及实现

最近趁着不忙,在构思一个搭建一个开源的完整项目,至于原因以及整个项目框架后边文章我再说明.既然要起一个完整的项目,那么数据仓储访问就必不可少,这篇文章我主要介绍这个新项目(OSS.Core)中我对仓储层的简单思考和实现过程(当前项目还处在搭建阶段),主要集中在以下几个方面: 1. 数据仓储层的需求 2. ORM框架选择 3. OSS.Core仓储层设计实现 4. 调用示例 下边的实现部分中可能需要你对.NET的 泛型,委托,扩展,表达式等有一个基础了解.正是因为这些语言特性,方便我们对操作共性的

C++, Java和C#的编译过程解析

转自:http://www.developingthefuture.net/compilation-process-and-jit-compiler/ 我们知道计算机不能直接理解高级语言,它只能理解机器语言,所以我们必须要把高级语言翻译成机器语言,这样计算机才能执行高级语言编写的程序, 非托管环境的编译过程(C/C++) 纯C/C++的程序通常运行在一个非托管环境中,类是由头文件(.h)和实现文件(.cpp)组成,每个类形成了一个单独的编译单元,当我们编译程序时,几个基本组件会把我们的源代码翻译

【错误】未能找到类型或命名空间名称XXXX和未能解析引用的程序

“未能找到类型或命名空间名称XXXX”,以往遇到这种情况第一时间想到就是没有引用需要的dll.但今天我反复检查了好几次,还是没有解决问题.我注意到除了错误信息,还有几个警告信息“未能解析引用的程序集XXXXXXVersion=1.0.0.0, Culture=neutral, processorArchitecture=MSIL”,因为它对不在当前目标框架“.NETFramework,Version=v4.0,Profile=Client”中的“System.Web, Version=4.0.0

C++工程编译之“error LNK2001: 无法解析的外部符号”

今天一整天都在折腾“error LNK2001: 无法解析的外部符号”,就在头疼不已的时候,总算是找到问题原因了:各个动态链接库的编译方式必须统一才行,要不然很容易对库函数的引用产生冲突.简单来说就是,如果使用的第三方函数库编译方式采用/MD,那么主工程也应该使用/MD.我使用了libevent,而主工程默认采用/MT,所以需要忽略一大堆的函数库,我还纳闷呢,怎么会这么奇怪!!今天总算是解决了长久以来的困惑了. 下面引用一篇文章的描述:[Z]VC运行库版本不同导致链接.LIB静态库时发生重复定义

防止恶意解析——禁止通过IP直接访问网站

一.什么是恶意解析 一般情况下,要使域名能访问到网站需要两步,第一步,将域名解析到网站所在的主机,第二步,在web服务器中将域名与相应的网站绑定.但是,如果通过主机IP能直接访问某网站,那么把域名解析到这个IP也将能访问到该网站,而无需在主机上绑定,也就是说任何人将任何域名解析到这个IP就能访问到这个网站.可能您并不介意通过别人的域名访问到您的网站,但是如果这个域名是未备案域名呢?一旦被查出,封IP.拔线甚至罚款的后果都是需要您来承担的.某些别有用心的人,通过将未备案域名解析到别人的主机上,使其

.NET深入解析LINQ框架(五:IQueryable、IQueryProvider接口详解)

阅读目录: 1.环路执行对象模型.碎片化执行模型(假递归式调用) 2.N层对象执行模型(纵横向对比链式扩展方法) 3.LINQ查询表达式和链式查询方法其实都是空壳子 4.详细的对象结构图(对象的执行原理) 5.IQueryable<T>与IQueryProvider一对一的关系能否改成一对多的关系 6.完整的自定义查询 1]. 环路执行对象模型.碎片化执行模型(假递归式调用) 这个主题扯的可能有点远,但是它关系着整个LINQ框架的设计结构,至少在我还没有搞懂LINQ的本意之前,在我脑海里一直频