补充知识点:opcode

在前面我们已经知道了,由于计算机只认识0和1,所以,源代码“NOP”是无法直接运行的。当Assembler遇到“NOP”的时候,为了生成让计算机能运行的“东西”(暂且这样称呼吧),就会以十六进制数“0x90”来代替它。

在这里,“0x90”就是“OpCode ”,而“NOP”则是“助记符(mnemonic )”。

OpCode的全称

OpCode就是Operation Code,意即操作码的意思。

一个OpCode只对应一个助记符吗?

示例:OpCode && mnemonic
OpCode mnemonic
0x90 NOP
0x90 XCHG AX, AX
0x90 XCHG EAX, EAX

Ldloc:ld=load 加载,loc=local variable局部变量  st=stored 存储,fld=field字段

作者:蔡学镛

2003年09月

.NET CLR 和 Java VM 都是堆叠式虚拟机(Stack-Based VM),也就是说,它们的指令集(Instruction Set)都是采用堆叠运算的方式:执行时的数据都是先放在堆叠中,再进行运算。 Java VM 有约200个指令(Instruction),每个指令都是 1 byte 的 opcode(操作码),后面接不等数目的参数;.NET CLR 有超过 220 个指令,但是有些指令使用相同的 opcode,所以 opcode 的数目比指令数略少。 特别注意,.NET 的 opcode 长度并不固定,大部分的 opcode 长度是 1 byte,少部分是 2 byte。

本文章以一个实际的例子,让你了解堆叠式 VM 的运作原理,并对 .NET IL(Intermediate Language)有最基本的领略。

下面是一个简单的 C# 源代码 :

using System;

public class Test {
public static void Main(String[] args) {
int i=1;
int j=2;
int k=3;
int answer = i+j+k;
Console.WriteLine("i+j+k="+answer);
}
}

将此源代码编译之后,可以得到一个 EXE 文件。 我們可以透過 ILDASM. EXE 来反组译 EXE 以观察 IL。 我将 Main() 的 IL 反组译条列如下,这里共有十八道 IL 指令,有的指令(例如 ldstr 与 box)后面需要接参数,有的指令(例如 ldc.i4.1 与 add)后面不需要接参数。

ldc.i4.1
stloc.0
ldc.i4.2
stloc.1
ldc.i4.3
stloc.2
ldloc.0
ldloc.1
add
ldloc.2
add
stloc.3
ldstr "i+j+k="
ldloc.3
box [mscorlib]System.Int32
call string [mscorlib]System.String::Concat(object, object)
call void [mscorlib]System.Console::WriteLine(string)
ret

此程序运行时,关键的内存有三种,分别是:

  • **Managed Heap:**这是动态配置(Dynamic Allocation)的内存,由 Garbage Collector(GC)在执行时自动管理,整个 Process 共享一个 Managed Heap。
  • **Call Stack:**这是由 .NET CLR 在执行时自动管理的内存,每个 Thread 都有自己专属的 Call Stack。 每呼叫一次 method,就会使得 Call Stack 上多了一个 Record Frame;呼叫完毕之后,此 Record Frame 会被丢弃。 一般来说,Record Frame 内纪录着 method 参数(Parameter)、返回地址(Return Address)、以及区域变量(Local Variable)。 Java VM 和 .NET CLR 都是使用 0, 1, 2... 编号的方式来识别区域变量。
  • **Evaluation Stack:**这是由 .NET CLR 在执行时自动管理的内存,每个 Thread 都有自己专属的 Evaluation Stack。 前面所谓的堆叠式虚拟机,指的就是这个堆叠。

后面有一连串的示意图,用来解说在执行时此三种内存的变化。 首先,在进入 Main() 之后,尚未执行任何指令之前,内存的状况如图 1 所示

接着要执行第一道指令 ldc.i4.1。 此指令的意思是:在 Evaluation Stack 置入一个 4 byte 的常数,其值为 1。 运行完此道命令之后,内存的变化如图 2 所示:

图 2

接着要执行第二道指令 stloc.0。 此指令的意思是:从 Evaluation Stack 取出一个值,放到第 0 号变量(V0)中。 这里的第 0 号变量其实就是源代码中的 i。 运行完此道命令之后,内存的变化如图 3 所示:

图 3

后面的第三道指令和第五道指令雷同于第一道指令,且第四道指令和第六道指令雷同于第二道指令。 为了节省篇幅,我不在此一一赘述。 提醒大家第 1 号变量(V1)其实就是源代码中的 j,且第 2 号变量(V2)其实就是源码中的 k。 图 4~7 分别是执行完第三~六道指令之后,内存的变化图:

接着要执行第七道指令 ldloc.0 以及第八道指令 ldloc.1:分别将 V0(也就是 i)和 V1(也就是 j)的值放到 Evaluation Stack,这是相加前的准备动作。 图 8 和图 9 分别是执行完第七、第八道指令之后,内存的变化图:

接着要执行第九道指令 add。 此指令的意思是:从 Evaluation Stack 取出两个值(也就是 i 和 j),相加之后将结果放回 Evaluation Stack 中。 运行完此道命令之后,内存的变化如图 10 所示:

接着要执行第十道指令 ldloc.2。 此指令的意思是:分别将 V2(也就是 k)的值放到 Evaluation Stack,这是相加前的准备动作。 运行完此道命令之后,内存的变化如图 11 所示:

接着要执行第十一道指令 add。 从 Evaluation Stack 取出两个值,相加之后将结果放回 Evaluation Stack 中,此为 i+j+k 的值。 运行完此道命令之后,内存的变化如图 12 所示:

接着要执行第十二道指令 stloc.3。 从 Evaluation Stack 取出一个值,放到第 3 号变量(V3)中。 这里的第3号变量其实就是源代码中的 answer。 运行完此道命令之后,内存的变化如图 13 所示:

接着要执行第十三道指令 ldstr "i+j+k="。 此指令的意思是:将 "i+j+k=" 的 Reference 放进 Evaluation Stack。 运行完此道命令之后,内存的变化如图 14 所示:

接着要执行第十四道指令 ldloc.3。 将 V3 的值放进 Evaluation Stack。 运行完此道命令之后,内存的变化如图 15 所示:

接着要执行第十五道指令 box [mscorlib]System.Int32。 此指令的意思是:从 Evaluation Stack 中取出一个值,将此 Value Type 包装(box)成为 Reference Type。 运行完此道命令之后,内存的变化如图 16 所示:

接着要执行第十六道指令 call string [mscorlib] System.String::Concat(object, object)。 此指令的意思是:从 Evaluation Stack 中取出两个值,此二值皆为 Reference Type,下面的值当作第一个参数,上面的值当作第二个参数,呼叫 mscorlib.dll 所提供的 System.String.Concat() method 来将此二参数进行字串接合(String Concatenation),将接合出来的新字串放在 Managed Heap,将其 Reference 放进 Evaluation Stack。 值得注意的是:由于 System.String.Concat() 是 static method,所以此处使用的指令是 call,而非 callvirt(呼叫虚拟)。 运行完此道命令之后,内存的变化如图 17 所示:

请注意:此时 Managed Heap 中的 Int32(6) 以及 String("i+j+k=") 已经不再被参考到,所以变成垃圾,等待 GC 的回收。

接着要执行第十七道指令 call void [mscorlib] System.Console::WriteLine(string)。 此指令的意思是:从 Evaluation Stack 中取出一个值,此值为 Reference Type,将此值当作参数,呼叫 mscorlib.dll 所提供的 System.Console.WriteLine() method 来将此字符串显示在 Console 窗口上。 System.Console.WriteLine() 也是 static method。 运行完此道命令之后,内存的变化如图 18 所示:

接着要执行第十八道指令 ret。 此指令的意思是:结束此次呼叫(也就是 Main 的呼叫)。 此时会检查 Evaluation Stack 内剩下的资料,由于 Main() 宣告不需要传出值(void),所以 Evaluation Stack 内必须是空的,本范例符合这样的情况,所以此时可以顺利结束此次呼叫。 而 Main 的呼叫一结束,程序也随之结束。 运行完此道命令之后(且在程序结束前),内存的变化如图 19 所示:

过此范例,读者应该可以对于 IL 有最基本的认识。 对 IL 感兴趣的读者应该自行阅读 Serge Lidin 所著的《Inside Microsoft .NET IL Assembler》(Microsoft Press 出版)。 我认为:熟知 IL 每道指令的作用,是 .NET 程序员必备的知识。. NET 程序员可以不会用 IL Assembly 写程序,但是至少要看得懂 ILDASM 反组译出来的 IL 组合码。

.大内高手专栏: NET中间语言(IL)的更多相关文章

  1. .NET语言的编译过程:中间语言(IL)和即时编译器(JIT)

    .NET语言的编译分为两个阶段.首先高级语言被编译成一种称作IL的中间语言,与高级语言相比,IL更像是机器语言,然而,IL却包含一些抽象概念(比如:类.异常),这也是这种语言被称为中间语言的原因.IL ...

  2. 何为中间语言IL?

    一直以来,对于.NET与C#之间的关系我都存在着疑惑,为此,今天专门仔细看了一下以前最容易忽略掉的书本“前言”部分,予以澄清:) 首先,c#的结构和方法论反映了.NET的基础方法论,在很多情况下,c# ...

  3. 《你必须知道的.NET》读书笔记:从Hello World认识IL

    通用的语言基础是.NET运行的基础,当我们对程序运行的结果有异议的时候,如何透过本质看表面,需要我们从底层来入手探索,这时候,IL便是我们必须知道的基础. 一.IL基础概念 1.1 什么是IL? IL ...

  4. 读懂IL代码就这么简单 (一)

    一前言 感谢 @冰麟轻武 指出文章的错误之处,现已更正 对于IL代码没了解之前总感觉很神奇,初一看完全不知所云,只听高手们说,了解IL代码你能更加清楚的知道你的代码是如何运行相互调用的,此言一出不明觉 ...

  5. IL指令大全

    IL是.NET框架中中间语言(Intermediate Language)的缩写.使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执 ...

  6. 读懂IL代码就这么简单

    原文地址:http://www.cnblogs.com/zery/p/3366175.html 一前言 感谢 @冰麟轻武 指出文章的错误之处,现已更正 对于IL代码没了解之前总感觉很神奇,初一看完全不 ...

  7. 谈谈托管代码、IL、CLR、ISAPI?

    什么是托管代码?       托管代码是可以使用20多种支持Microsoft .NET Framework的高级语言编写的代码,这些语言包括:C#, J#, Microsoft Visual Bas ...

  8. IL(Intermediate Language)

    释义: IL是.NET框架中中间语言(Intermediate Language)的缩写.使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU ...

  9. 初识.Net IL

    1.IL基本资料 1.IL概述 IL是.NET框架中中间语言(Intermediate Language)的缩写.使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出 ...

随机推荐

  1. ddos攻击是什么,如何防御

    DDoS(Distributed Denial of Service,分布式拒绝服务) 定义: 主要通过大量合法的请求占用大量网络资源,从而使合法用户无法得到服务的响应,是目前最强大.最难防御的攻击之 ...

  2. 【Android】安卓四大组件之内容提供者

    [Android]安卓四大组件之内容提供者 1.关于内容提供者 1.1 什么是内容提供者 内容提供者就是contentProvider,作用有如下: 给多个应用提供数据 类似一个接口 可以和多个应用分 ...

  3. 操作系统的发展史(并发与并行)<异步与同步>《进程与程序》[非堵塞与堵塞]

    目录 一:一:手工操作 -- 穿孔卡片 1.简介 二:手工操作方式两个特点: 三:批处理 -- 磁带存储 1.联机批处理系统 2.脱机批处理系统 3.多道程序系统 4.多道批处理系统 四:总结发展史 ...

  4. linux中三剑客之一grep命令

    目录 一:grep语法格式: 二:参数: 三:正则表达式 1.linux正则表达式 2.普通正则表达式 四:正则与grep实战案例实战: grep简介: linux 三剑客之一,文本过滤器(根据文本内 ...

  5. ES入门三部曲:索引操作,映射操作,文档操作

    ES入门三部曲:索引操作,映射操作,文档操作 一.索引操作 1.创建索引库 #语法 PUT /索引名称 { "settings": { "属性名": " ...

  6. JavaScripts调用摄像头【MediaDevices.getUserMedia()】

    h5调用摄像头(允许自定义界面)[MediaDevices.getUserMedia()] <!DOCTYPE html> <html lang="en"> ...

  7. PyTorch 1.4 中文文档校对活动正式启动 | ApacheCN

    一如既往,PyTorch 1.4 中文文档校对活动启动了! 认领须知 请您勇敢地去翻译和改进翻译.虽然我们追求卓越,但我们并不要求您做到十全十美,因此请不要担心因为翻译上犯错--在大部分情况下,我们的 ...

  8. RealFormer: 残差式 Attention 层的Transformer 模型

    原创作者 | 疯狂的Max 01 背景及动机 Transformer是目前NLP预训练模型的基础模型框架,对Transformer模型结构的改进是当前NLP领域主流的研究方向. Transformer ...

  9. js演示面向对象

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. storyboard文件的认识

    - 作用:描述软件界面 - 程序启动的简单过程     - 程序一启动,就会加载`Main.storyboard`文件     - 会创建箭头所指的控制器,并且显示控制器所管理的软件界面 - 配置程序 ...