MSIL Tutorial (上)

本文介绍了MSIL的基础概念及其编程实践,通过多个示例程序详细解释了如何使用MSIL进行基本的控制台应用开发,涵盖了字符串输出、数值运算、数组操作等核心功能。


MSIL 教程


原文地址:http://www.codeguru.com/Csharp/.NET/net_general/il/article.php/c4635#PrintString

介绍

MSIL是微软公司一系列的编译器的输出(如C#, VB, .NET, 等等),它也是一种语言, 我们可以用MSIL直接编写程序,但是太麻烦了:). 

ILDasm.exe是和.Net FrameWork SDK一起发布的一个工具程序, 用它我们可以查看任何可执行程序(exe和dll)的IL代码.    我们也可以使用ILAsm.exe把IL代码编译成可执行程序, 我们可以从类似于WINNT\Microsoft.NET\Framework\vn.nn.nn 的路径下找到它.

任何一个c++程序员在开始使用.Net进行开发时, 肯定会想知道.Net Framework在底层到底是怎样工作的. 通过对IL的学习,可以让使用C#的程序员有机会了解那些深埋于底层的工作机制, 加深理解. 实际上,我们没有必要直接用IL来写程序, 但是对于一些特殊的情况, 用ILDasm来看看某些功能是如何实现的是很有用的.

我认为学习IL最好的方法就是用它写几个小程序. 这也是为什么我在本书中提供了几个MSIL写的几个代码片段(实际上不是我手工写的,是C#编译器生成, 我只是进行了微小的改的, 并添加了大量的注释).增加这些代码片段有助于读者理解IL, 并在读者需要读IL的时候能够轻松读懂它.

 

一般信息

在IL中,所有的操作都是在堆栈上完成的. 当我们调用一个函数的时候, 首先要为函数参数和本地变量在堆栈上分配空间,并将他们的值加载到堆栈中. 然后函数代码开始运行,运行过程中,它可能会往堆栈写入数据或者操作堆栈中的数据或者从堆栈读取数据.
调用函数或调用MSIL的命令一般分为三步:
1. 把命令的操作数或者函数的参数送入堆栈中.
2. 执行MSIL命令或调用函数. 命令或函数从堆栈中读取操作数或参数,然后把结果在送入堆栈
3. 从堆栈中读取结果

其中第一和第三步是可选的, 譬如无返回值的函数就不需要去读结果.
堆栈中可以存放值类型和引用类型的引用, 引用类型实际上存放在托管堆中.

我们把那些用来把值压入堆栈的IL命令叫做ld...(load), 把那些用来把值从堆栈中取出来的IL命令叫做st...(store), 所以我们把压栈操作叫做loading, 把出栈操作叫做storing.


示例代码
这篇文章中包含了很多个用IL编写的控制台程序. 在用ILAsm把他们编译成可执行程序前,一定要保证ILAsm所在的路径已经包含在PATH中了. 我们可以用VS的文本编辑器来编辑这些代码.  在每个代码片段的最后我都加入了如下的代码:

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

这样我们在执行可执行程序时就能够看到程序的输出了.

下面是代码片段的列表:

  1. PrintString—把字符串输出到控制台.
  2. XequalN—把一个值赋给一个int变量,并把它输出到控制台.
  3. Operations—从控制台读入两个数并进行加减乘操作,然后将结果输出到控制台.
  4. Array1—声明一个int数值,给数值的每个元素赋值; 把数值的每个元素和数组的长度输出到控制台.
  5. Compare—输入两个数,并把较小的一个打印出来.
  6. Array2—在循环中填充数组并打印数组的元素.
  7. Unsafe—使用unsafe的指针访问数组元素.
  8. PInvoke—calls Win32 API.
  9. Classes—works with classes.
  10. Exception—handles exceptions.


我建议大家按照我列的顺序来阅读这些代码, 在每个代码片段的描述中,我将介绍一些新的IL命令的使用方法.


PrintString—把字符串输出到控制台.

PrintString 是一个用IL写的Hello, World 程序.

在代码中直接使用的IL指令包括:

  • .entrypoint—定义程序的入口(当程序启动时被.Net运行时调用的函数).
  • .maxstack—定义被函数使用的堆栈的最大深度. C#编译器会为每个函数设定一个精确的深度. 在示例中我把它设为8.

MSIL 命令:

  • ldstr string—将字符串压入堆栈
  • call function(parameters)—调用静态函数. 在调用这个函数之前应该把它的参数压入堆栈.
  • pop—将一个值从堆栈中弹出. 当我们不需要将这个值存到变量里的时候,调用这个命令.
  • ret—函数返回.

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

代码如下:

Code
.assembly PrintString {}

/**//*
    Console.WriteLine("Hello, World)"
*/


.method 
static public void main() il managed
{
    .entrypoint             
// this function is the application
                            
// entry point

    .maxstack 
8


    
// *****************************************************
    
// Console.WriteLine("Hello, World)";
    
// *****************************************************
    ldstr "Hello, World"        // load string onto stack

    
// Call static System.Console.Writeline function
    
// (function pops string from the stack)
    call   void [mscorlib]System.Console::WriteLine
                                 (
class System.String)


    
// *****************************************************
    ldstr "Press Enter to continue"
    call   
void [mscorlib]System.Console::WriteLine
                                 (
class System.String)


    
// Call the System.Console.Read function
    call int32 [mscorlib]System.Console::Read()

    
// The pop instruction removes the top element from the stack.
    
// (remove number returned by Read() function)
    pop
    
// *****************************************************

    ret
}




XequalN—把一个值赋给一个int变量,并把它输出到控制台

将整数或浮点数常量压入计算堆栈。

  • ldc.i4, ldc.i4.s : 将 4-byte int32 压入堆栈
  • ldc.i4.m1 : 将 -1 压入堆栈
  • ldc.i4.0 ... ldc.i4.8 : 将整数 0...8 压入堆栈
  • ldc.i8 : 将 8-byte int64 压入堆栈
  • ldc.r4 : 将 4-byte float32 压入堆栈
  • ldc.r8 : 将 8-byte float64 压入堆栈

stloc.n 从堆栈中取出最顶部的值,并把它保存到第n个变量中
ldloc.n 将第n个变量中的值压入堆栈

Code
.assembly XequalN {}

// int x;
// x = 7;
// Console.WriteLine(x);

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

    .maxstack 
8

    .locals init ([
0] int32 x)  // Allocate local variable

    
// *****************************************************
    
// x = 7;
    
// *****************************************************
    ldc.i4.7                    // load constant onto stack
    stloc.0                     // store value from stack to
                                
// var. 0

    
// *****************************************************
    
// Console.WriteLine(x);
    
// *****************************************************
    ldloc.0                     // load var.0 onto stack
    call void [mscorlib]System.Console::WriteLine(int32)

    ret
}




Operations—从控制台读入两个数并进行加减乘操作,然后将结果输出到控制台.

命令:

add—将两个数值相加.在调用这个命令之前, 应先将两个操作数送入堆栈. 该命令会从堆栈中将两个操作数弹出,然后将计算结果压入堆栈
sub—将两个数值相减
mul—将两个数相乘

Code
.assembly Operations {}
/**//*
// This program works as C# code:

            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,
           [
3string s)

    
// *****************************************************
    
// Console.WriteLine("Enter x:");
    
// *****************************************************
    ldstr      "Enter x:"       // load string onto stack
    call       void [mscorlib]System.Console::WriteLine(string)

    
// *****************************************************
    
// s = Console.ReadLine();
    
// *****************************************************
    call       string [mscorlib]System.Console::ReadLine()
    stloc.
3                     // store value to var. 3

    
// *****************************************************
    
// x = Int32.Parse(s);
    
// *****************************************************
    ldloc.3                     // load variable 3 onto stack

    
// Call System.Int32::Parse(string)
    
// Function pops string from stack and pushes to stack
    
// int32 value - result of parsing.
    call       int32 [mscorlib]System.Int32::Parse(string)

    stloc.
0                     // store value to var. 0



    
// *****************************************************
    
// Same operations with variable y
    
// *****************************************************
    ldstr      "Enter y:"
               
// load string
    call       void [mscorlib]System.Console::WriteLine(string)
               
// call
    call       string [mscorlib]System.Console::ReadLine()
               
// call
    stloc.3
               
// store to var. 3
    ldloc.3
               
// load var. 3
    call       int32 [mscorlib]System.Int32::Parse(string)
               
// call
    stloc.1
               
// store to var. 1

    
// *****************************************************
    
// z = x + y;
    
// *****************************************************
    ldloc.0             // load variable 0 onto stack
    ldloc.1             // load variable 1 onto stack

    
// pop two values from the stack, add them and push result
    
// onto stack
    add

    stloc.
2             // store to variable 2

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

    
// *****************************************************
    
// Console.Write(z);
    
// *****************************************************
    ldloc.2                    // load variable 2 onto stack
    call       void [mscorlib]System.Console::Write(int32)

    
// *****************************************************
    
// Console.WriteLine("");
    
// *****************************************************
    ldstr      ""                  // load string onto stack
    call       void [mscorlib]System.Console::WriteLine(string)

    
// Same operations with subtraction and multiplication 

    ret
}




Array1—声明一个int数值,给数值的每个元素赋值; 把数值的每个元素和数组的长度输出到控制台.

命令: 

  • newarr type创建Type类型的数组, 在调用这个命令之前要先把数组的大小压入堆栈. 把对数组对象的引用压入堆栈(数组实际上存在于托管堆上)

  • stelem.i4把一个32位的整型值赋给数组元素.在调用这个命令前需要将数组对象的引用, 索引值和要赋给数组元素的值都压入堆栈.

  • ldelema type把数组元素的地址压入堆栈. 在调用这个命令之前要将数组对象的引用以及索引值压入堆栈. 压入堆栈的地址可以用来对非静态函数进行调用.

  • ldlen将数组的长度压入堆栈. 调用这个命令之前要将数组对象的引用压入堆栈

  • ldloca.s variable将变量的地址压入堆栈

  • ldc.i4.s value将一个32位整型常量压入堆栈 (用于将比8大的的值压入堆栈).

  • conv.i4把从堆栈中取出的值转换成32位整型值,然后将结果压入堆栈
  • call instance function(arguments)调用非静态函数. 在调用非静态函数之前, 我们需要将对象的地址 (作为函数的第一个隐藏参数, 就像c++中那样)以及函数的参数压入堆栈. 在示例代码中我们使用ldememaldloca两个命令来将地址压入堆栈.

Code
.assembly Array1 {}

/**//*
// This program works as C# code:

int[] x = new int[5];
x[0] = 10;
x[1] = 20;

Console.WriteLine("x[0] = " + x[0].ToString());
Console.WriteLine("x[1] = " + x[1].ToString());

Console.WriteLine("Array length = " + x.Length.ToString());

*/


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

    .locals init ([
0] int32[] x,
                  [
1] int32 tmp)    // generated by compiler


    
// *****************************************************
    
// x = new int[5];
    
// *****************************************************
    ldc.i4.5                     // load constant onto stack

    
// create array and store reference onto stack
    newarr     [mscorlib]System.Int32

    
// Store (pop) value from the stack and place it to local
    
// variable 0.
    stloc.0

    
// *****************************************************
    
// x[0] = 10;
    
// *****************************************************
    ldloc.0           // Load local variable 0 onto stack (array)
    ldc.i4.0          // Load constant 0 to the stack     (index)
    ldc.i4.s   10     // Load constant 10 to the stack    (value)
    stelem.i4         // array[index] = value


    
// The same operations with element number 1


    
// ***************************************************
    
// Console.WriteLine("x[0] = " + x[0].ToString());
    
// ***************************************************
    ldstr      "x[0] = "            // load string onto stack
                
// STACK: "x[0] = "  (stack is shown from local
                
// variables)
    ldloc.0                         // load variable 0 onto stack
    ldc.i4.0                        // load constant 0 onto stack
                
// STACK: "x[0] = " -> x -> 0
    
// Load address of array element onto stack.
    ldelema    [mscorlib]System.Int32
                
// STACK: "x[0] = " -> pointer to Int32 instance
                
// 10
    
// Call non-static function System.Int32::ToString().
    call       instance string [mscorlib]System.Int32::ToString()
                
// STACK: "x[0] = " -> "10"
    
// call static System.String::Concat(string, string)
    call       string [mscorlib]System.String::Concat
                                               (
stringstring)
                
// STACK: "x[0] = 10"
    
// call static System.Console::WriteLine(string)
    call       void [mscorlib]System.Console::WriteLine(string)
                
// STACK: empty


    
// The same operations with element number 1 


    
// *****************************************************
    
// Console.WriteLine("Array length = " + x.Length.ToString());
    
// *****************************************************
    ldstr      "Array length = "
                
// load string onto stack
                
// STACK: "Array length = "
    ldloc.0
                
// load variable 0 to stack
                
// STACK: "Array length = " -> x
    ldlen
                
// push the length of array onto stack
                
// STACK: "Array length = " -> 5
    conv.i4
                
// Convert to int32, pushing int32 onto stack
                
// STACK: "Array length = " -> 5
    stloc.1
                
// store to local variable 1 (tmp)
                
// STACK: "Array length = "
    ldloca.s   tmp
                
// load address of variable tmp onto stack
                
// STACK: "Array length = " -> &tmp
    call       instance string [mscorlib]System.Int32::ToString()
                
// STACK: "Array length = " -> "5"
    call       string [mscorlib]System.String::Concat
                                       (
stringstring)
                
// STACK: "Array length = 5"
    call       void [mscorlib]System.Console::WriteLine(string)
                
// STACK: empty

    ret
}




 

Compare输入两个数,并把较小的一个打印出来.

命令:

  • bge.s label如果value1的值大于等于value2的值, 跳转到 label处执行. 在调用这个命令之前value1value2必需提前压入堆栈.

  • br.s label跳转到label处继续执行.

  • box value type将值类型转换成对象类型, 并将对象的引用压入堆栈

这段代码中的装箱操作是由于这个条语句引起的: Console.WriteLine("{0:d}", z);

如果写成如下形式,就不会有装箱操作了: Console.WriteLine(z.ToString());

Code
.assembly Compare {}
/**//*
            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);

            if ( x < y )
                z = x;
            else
                z = y;

            Console.WriteLine("{0:d}", z);
*/


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

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

    
// *****************************************************
    
// Console.WriteLine("Enter x:");
    
// *****************************************************
    ldstr      "Enter x:"               // load string onto stack
    call  void [mscorlib]System.Console::WriteLine(string)

    
// *****************************************************
    
// s = Console.ReadLine();
    
// *****************************************************
    call       string [mscorlib]System.Console::ReadLine()
    stloc.
3                             // store to var. 3

    
// *****************************************************
    
// x = Int32.Parse(s);
    
// *****************************************************
    ldloc.3                             // load var. 3 onto stack
    call       int32 [mscorlib]System.Int32::Parse(string)
    stloc.
0                             // store to var. 0

    
// The same operations for y 

    
// *****************************************************
    
// branch
    
// if ( x >= y ) goto L_GR;
    
// *****************************************************
    ldloc.0                     // load x onto stack (value 1)
    ldloc.1                     // load y onto stack (value 2)
    bge.s  L_GR                 // goto L_GR if value1 is greater
                                
// than or equal to value2

    
// *****************************************************
    
// z = x
    
// *****************************************************
    ldloc.0                     // load variable 0 onto stack
    stloc.2                     // store to variable 2

    br.s       L_CONTINUE       
// goto L_CONTINUE

L_GR:

    
// *****************************************************
    
// z = y
    
// *****************************************************
    ldloc.1             // load variable 1 onto stack
    stloc.2             // store to variable 2

L_CONTINUE:

    
// *****************************************************
    
// Console.WriteLine("{0:d}", z);
    
// NOTE: this line causes boxing.
    
// *****************************************************
    ldstr      "{0:d}"  // load string onto stack
    ldloc.2             // load variable 2 to stack (z)
    box       [mscorlib]System.Int32   // convert Int32 to Object
    call  void [mscorlib]System.Console::WriteLine(stringobject)

    ret
}




 

Array2在循环中填充数组并打印数组的元素.

命令:

  • blt.s label如果value1的值小于value2的值,那么跳转到label处继续执行. 在调用这个命令前要将value1value2压入堆栈.

  • ldelem.i4将数组元素压入堆栈. 调用之前要将数组对象的引用以及数组元素的索引压入堆栈.

  • ldarga.s argument将函数参数的地址压入堆栈

在这段代码中我们可以看到, MSILFor循环是使用 label实现的.

Code
.assembly Array2 {}
/**//*

            int[] px = new int[100];
            int i;

            for ( i = 1; i < 100; i++ )
            {
                px[i] = i + 1;
            }

            ShowNumber(px[5]);
            ShowNumber(px[10]);


        static void ShowNumber(int n)
        {
            Console.WriteLine(n.ToString());
        }
*/


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

    .locals init ([
0] int32[] px,
                  [
1] int32 i)

    
// *****************************************************
    
// x = new int[100]
    
// *****************************************************
    ldc.i4.s   100                      // load constant onto
                                        
// stack
    newarr     [mscorlib]System.Int32   // allocate Int32
    stloc.0                             // store to variable 0

    
// *****************************************************
    
// i = 1
    
// *****************************************************
    ldc.i4.1                    // load constant onto stack
    stloc.1                     // store to variable 1

    br.s       CHECK_COUNTER    
// goto CHECK_COUNTER

START_LOOP:
    
// *****************************************************
    
// px[i] = i + 1;
    
// *****************************************************
    ldloc.0                     // load variable 0 to stack
                                
// STACK: px
    ldloc.1                     // load variable 1 to stack
                                
// STACK; px -> i
    ldloc.1                     // load variable 1 to stack
                                
// STACK: px -> i -> i
    ldc.i4.1                    // load constant to stack
                                
// STACK: px -> i -> i -> 1.
    add                         // add last two values
                                
// STACK: px -> i -> i+1
                                
//        (array,index,value)
    stelem.i4                   // store value to array element:
                                
// array[index] = value
                                
// STACK: empty
    
// *****************************************************
    
// i = i + 1
    
// *****************************************************
    ldloc.1                     // load variable 1 onto stack
    ldc.i4.1                    // load constant onto stack
    add                         // add
    stloc.1                     // store to variable 1

CHECK_COUNTER:
    
// *****************************************************
    
// if i < 100 goto start f loop
    
// *****************************************************
    ldloc.1                     // load variable 1 onto stack
    ldc.i4.s   100              // load constant onto stack
    blt.s      START_LOOP       // if value1 < value2 go to
                                
// START_LOOP


    
// *****************************************************
    
// ShowNumber(px[5]
    
// *****************************************************
    ldloc.0                     // load variable 0 onto stack
                                
// (array)
    ldc.i4.5                    // load constant onto stack
                                
// (index)
    ldelem.i4                   // load array element to stack
    call       void ShowNumber(int32)   // call ShowNumber

    
// *****************************************************
    
// ShowNumber(px[10]
    
// *****************************************************
    ldloc.0
    ldc.i4.s   
10
    ldelem.i4
    call       
void ShowNumber(int32)

    ret
}


.method 
static public void  ShowNumber(int32 n) il managed
{
  .maxstack  
1

  ldarga.s   n          
// load to stack address of argument n

  call       instance 
string [mscorlib]System.Int32::ToString()
  call       
void [mscorlib]System.Console::WriteLine(string)

  ret
}





 

转载于:https://www.cnblogs.com/guothree2003/articles/1233332.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值