[你必须知道的.NET]第二十四回:认识元数据和IL(上)
发布日期:2009.02.24 作者:Anytao
© 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处。
说在,开篇之前 |
很早就有说说Metadata(元数据)和IL(中间语言)的想法了,一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀,虽然来得没有《第一回:恩怨情仇:is和as》那么迅速,但是Metadata和IL却是绝对重量级的内容,值得我们在任何时间关注,本文就是开始。 |
你可曾想到,我们的C#代码,编译之后究竟为何物?你可曾认知,我们的可执行程序,运行之时的轨迹究竟为哪般?那么,本文通过对Metadata(元数据)和IL(Intermediate Language, 中间语言)的认识开始,来逐步给出答案。在这个探索轨迹上,元数据、IL、程序集、程序域、JIT、虚分派、方法表和托管堆这些形形色色的神秘嘉宾将在某个时刻不期而遇,作为你必须知道的.NET 系列2.0版本的一部分,本文首先从认识元数据和IL这两位重量级选手开始,而其他的嘉宾也将很快登场。
2 初次接触
在事实上,编译之后的cs代码被组织为两种基本的元素:元数据(Metadata)和IL。我们可以以最简单的方式来了解程序集(*.dll)或可执行文件(*.exe)中包含的Metadata和IL的秘密,这种方式就是我们常说的反编译,打开ILDasm并加载实现准备的程序集,我们可以看到托管PE文件的相关内容:
详细的结构信息和IL代码分析,可以参见[你必须知道的.NET]第3章 “一切从IL开始”的介绍,在此就不做太多的分析。另外,我们可以通过执行“View/MetaInfo/Show!”或者Ctrl+M快捷键来获取该程序集所使用的MetaData信息列表:
其中该程序集使用的元数据主要有:Module、TypeRef、TypeDef、Method、Param、MemberRef、CostomAttribute、Assembly、AssemblyRef等,同时还包括#Strings、#GUID、#Blob、#US堆等。
当然,关于ILDasm工具,还有很多好玩的使用方式来满足我们探索IL代码的好奇心,例如:
ildasm Anytao.Insidenet.MetadataIL.exe /output:my.il,将反编译结果导出为il代码格式,生成一个my.il包含了所有的IL代码和一个my.res包含了所有的资源文件。
ildasm Anytao.Insidenet.MetadataIL.exe /text,将反编译结果以Console形式输出。
当然我们还是推荐以GUI形式来查看IL细节,组织结构良好的Class View:
ildasm Anytao.Insidenet.MetadataIL.exe
下面首先给出参与编译的相关代码文件,然后再展开我们对Metadata和IL的讨论:
// Release : code01, 2009/02/12
// Author : Anytao, http://www.anytao.com
// List : One.cs
public class One
{
public int ID { get; set; }
}// Release : code02, 2009/02/12
// Author : Anytao, http://www.anytao.com
// List : Two.cs
public class Two
{
public string SayHello()
{
return "Hello, world.";
}
}// Release : code03, 2009/02/12
// Author : Anytao, http://www.anytao.com
// List : Program.cs
class Program
{
static void Main(string[] args)
{
int id = 1;
One one = new One();
one.ID = id;
Two two = new Two();
Console.WriteLine(two.SayHello());
}
}
接着,我们对上述程序的编译执行过程进行一点探索,以命令行编译器来演化其大致的编译过程,以此进一步了解托管模块,程序集和可执行文件之间的关系:
打开Visual Studio 2008 Command Prompt,并定位到cs代码所在文件夹,编译One.cs为托管模块,执行命令:
csc /t:module One.cs
执行之后,将生成名为One.netmodule文件;
继续执行,将多个模块打包为程序集
执行之后,将生成名为Two.dll文件;
最后,编译Main函数和Two.dll为可执行文件
最终将得到本文开始时所加载的用于反编译的程序集文件Anytao.Insidenet.MetadataIL.exe,在该执行命令中对几个指示符开关做点说明:
- /out:Anytao.Insidenet.MetadataIL.exe,表示输出的可执行文件,及其名称
- /t:exe,表示输出的文件类型为CUI(控制台界面程序)程序;而/t:winexe,表示输出为GUI(图形界面程序)程序
- /r:Two.dll,表示引用刚刚生产的Two.dll程序集
- /r:mscorlib.dll,表示因为外部程序集mscorlib.dll,因为我们的程序中使用了Console静态方法,而该方法则被定义在mscorlib.dll中。mscorlib.dll是如此的重要,我们将在本文之后的某些时候再次与mscorlib.dll握手,那时在对其进行一个详细的分析,敬请期待。
在cmd中的执行过程可以参考:
通过分步执行的方式我们对csc编译器的执行过程有个基本的了解,也同时从侧面认识了每次在Visual Studio中执行“Build“或者“ReBuild”的缩影。综上分析,我们可以简单的看到:
Note:在Visual Studio中,编译是分模块进行的,编译结果保存在obj目录中,最后再合并为可执行文件于bin目录,同时默认情况下,编译过程是增量式的,仅编译发生修改的模块,我将在后文给出较为详细的过程。
同时,我们还可以收获以下几个基本的结论:
cs代码编译之后将生成元数据和IL,并组成托管模块(Module)的基本单元。
多个托管模块组成程序集,其实还包括一定的资源文件,只是没有在此体现。
程序集或者可执行文件是逻辑组织的基本单元,符合基本的Windows PE文件格式,可以被x86或者x64Windows直接加载执行。
3 继续深入
一个或者多个模块,再加上资源文件就形成了程序集(Assembly),作为逻辑组织的基本单元,
事实上,此图仅仅从粗粒度对程序集的基本组成有个大致的了解,实际上程序集中包含了复杂的结构和要素,例如PE Signature、Managed Resources、Strong Name Signature Hash,而其中最核心的要素则体现在上图。
- 程序集清单(MANIFEST)包含了程序集的自描述信息,主要包含AssemblyDef、FileDef、ManifestResourceDef和ExportedTypeDef,在反编译选项中MANIFEST包含了详细的内容。在《你必须知道的.NET》3.1节 “从Hello,world开始认识IL”对其有过详细的描述,此不赘述。
- PE文件头,标准Windows PE头文件(PE32或PE32+),PE文件的基本信息,例如文件类型,创建时间,本地CPU信息等。
- CLR头,包含CLR版本、模块元数据、资源等信息。
- 资源文件。
执行View/Statisctics菜单,可以打开相关的统计信息:
File size : 5632
PE header size : 512 (496 used) ( 9.09%)
PE additional info : 1691 (30.02%)
Num.of PE sections : 3
CLR header size : 72 ( 1.28%)
CLR meta-data size : 2212 (39.28%)
CLR additional info : 0 ( 0.00%)
CLR method headers : 52 ( 0.92%)
Managed code : 287 ( 5.10%)
Data : 2048 (36.36%)
Unaccounted : -1242 (-22.05%) Num.of PE sections : 3
.text - 3072
.rsrc - 1536
.reloc - 512 CLR meta-data size : 2212
Module - 1 (10 bytes)
TypeDef - 4 (56 bytes) 0 interfaces, 0 explicit layout
TypeRef - 25 (150 bytes)
MethodDef - 8 (112 bytes) 0 abstract, 0 native, 8 bodies
FieldDef - 1 (6 bytes) 0 constant
MemberRef - 29 (174 bytes)
ParamDef - 2 (12 bytes)
CustomAttribute- 16 (96 bytes)
StandAloneSig - 4 (8 bytes)
PropertyMap - 1 (4 bytes)
Property - 1 (6 bytes)
MethodSemantic- 2 (12 bytes)
Assembly - 1 (22 bytes)
AssemblyRef - 1 (20 bytes)
Strings - 920 bytes
Blobs - 328 bytes
UserStrings - 68 bytes
Guids - 16 bytes
Uncategorized - 192 bytes CLR method headers : 52
Num.of method bodies - 8
Num.of fat headers - 4
Num.of tiny headers - 4 Managed code : 287
Ave method size - 35
我们将在后篇《深入程序集和模块》中对PE头,CLR头和资源文件进行详细论述。
- IL代码被组织为
.class public auto ansi beforefieldinit Anytao.Insidenet.MetadataIL.Two
extends [mscorlib]System.Object
{
.method public hidebysig instance string
SayHello() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldstr "Hello, world."
IL_0006: stloc.0
IL_0007: br.s IL_0009 IL_0009: ldloc.0
IL_000a: ret
} // end of method Two::SayHello .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
} // end of method Two::.ctor } // end of class Anytao.Insidenet.MetadataIL.Two
包装在类似于汇编模样的外衣下,我看依稀可见class, System.Object, method, public, string这些面向对象高级语言中的熟悉面孔,不同的只是多了很多benforefieldinit(参考:[你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器), ret, maxstack, ldstr, stloc这些陌生的指令。然而IL并非一个怪胎,而正是基于其本身面向对象的汇编式风格,才造就了IL代码成为名副其实的“中间语言”的重任。通过IL代码,CLR就可在编译时由JIT编译转换为Native Code,我们将在下节继续分析这个过程的来龙去脉。
欲知后事如何,且听下文继续:-) |
|
2009/02/24 | http://anytao.cnblogs.com/ |http://www.cnblogs.com/anytao/archive/2009/02/24/must_net_24.html
本文以“现状”提供且没有任何担保,同时也没有授予任何权利。 | This posting is provided "AS IS" with no warranties, and confers no rights.
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
参考文献
[你必须知道的.NET]第二十四回:认识元数据和IL(上)的更多相关文章
- [你必须知道的.NET]第二十六回:认识元数据和IL(下)
发布日期:2009.03.04 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 书接上回: 第二十四回:认识元数据和IL(上), ...
- [你必须知道的.NET]第二十五回:认识元数据和IL(中)
发布日期:2009.02.25 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 书接上回[第二十四回:认识元数据和IL(上)], ...
- [你必须知道的.NET]第二十九回:.NET十年(上)
发布日期:2009.05.08 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. /// <summary> /// 本文部分内容,已 ...
- [你必须知道的.NET]第二十八回:说说Name这回事儿
发布日期:2009.3.18 作者:Anytao © 2009 Anytao.com ,原创作品,转贴请注明作者和出处. 1 缘起 老赵在谈表达式树的缓存(2):由表达式树生成字符串中提到,在描述Ty ...
- [你必须知道的.NET]第二十二回:字符串驻留(上)---带着问题思考
发布日期:2008.8.27 作者:Anytao © 2008 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 走钢丝的人,在刺激中体验快感.带着问题思考,在问题 ...
- [你必须知道的.NET]第二十回:学习方法论
说在,开篇之前 本文,源自我回答刚毕业朋友关于.NET学习疑惑的回复邮件. 本文,其实早计划在<你必须知道的.NET>写作之初的后记部分,但是因为个中原因未能如愿,算是补上本书的遗憾之一. ...
- [你必须知道的.NET]第十九回:对象创建始末(下)
本文将介绍以下内容: 对象的创建过程 内存分配分析 内存布局研究 接上回[第十八回:对象创建始末(上)],继续对对象创建话题的讨论>>> 2.2 托管堆的内存分配机制 引用类型的实例 ...
- [你必须知道的.NET]第十八回:对象创建始末(上)
本文将介绍以下内容: 对象的创建过程 内存分配分析 内存布局研究 1. 引言 了解.NET的内存管理机制,首先应该从内存分配开始,也就是对象的创建环节.对象的创建,是个复杂的过程,主要包括内存分配和初 ...
- [你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器
发布日期:2008.11.2 作者:Anytao © 2008 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 今天Artech兄在<关于Type Init ...
随机推荐
- bzoj1426: 收集邮票(期望)
推错半天式子T T 设f[i]为买了i种卡,期望再买几张有n种卡 设g[i]为买了i种卡,期望再花多少钱有n种卡 可以把当前买卡的价格看作1,则以后买的所有卡片要增加1元,于是要加上f[i]和f[i+ ...
- 【bzoj4484】【jsoi2015】最小表示
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 432 Solved: 223[Submit][Status][Discuss] Descriptio ...
- 手脱ASPack v2.12
1.PEID查壳提示为: ASPack 2.12 -> Alexey Solodovnikov 2.载入OD,程序入口点是一个pushad,在他的下一行就可以进行ESP定律,下硬件访问断点然后s ...
- 基于javaWeb阶段下的Servlet总结
1. Servlet概述 Servlet是用Java语言编写的服务端的程序,采用request--response模式提供Web服务,并且支持标准ServletAPI,Servlet就一个运行在w ...
- Linux下UDP一发一收通信
实现在Linux环境下的UDP通信测试. 注释了while循环,将代码规范化. udpserver.c代码: /******************************************** ...
- Hibernate学习(5)- session的get与load方法对比
1.共同点:get和load都是根据Id单条查询获取对象 org.hibernate.Session.load(Class<User> theClass, Serializable id) ...
- JNI实现JAVA和C++互相调用
SDK.h #ifndef SDK_H #define SDK_H #include "AsyncProxy.h" #include "Module.h" #i ...
- 子序列 sub sequence问题,例:最长公共子序列,[LeetCode] Distinct Subsequences(求子序列个数)
引言 子序列和子字符串或者连续子集的不同之处在于,子序列不需要是原序列上连续的值. 对于子序列的题目,大多数需要用到DP的思想,因此,状态转移是关键. 这里摘录两个常见子序列问题及其解法. 例题1, ...
- UVA 12520 Square Garden
https://vjudge.net/problem/UVA-12520 题意:n*n网格中染色m个格子,染色格子的最长轮廓线 贪心 将格子分为4类 1.隔一个选一个,互不相邻的格子 2.4个角上的格 ...
- 非法字符:"\ufeff"
Eclipse项目导入IDEA可能遇到这样的问题 ,原因就是: 带BOM的UTF-8」和「无BOM的UTF-8」 方法一.用Notepad++把文件转成无BOM的UTF-8 另存为,替换原来的文件 方 ...