元数据与IL简介

 

文/玄魂

1.3.2       元数据

元数据是描述数据的数据。在CLR的上下文中,元数据表示由描述符组成的一套体系,这些操作符包括了在一个模块中被声明或引用的所有项。由于CLR模型是面向对象的,因此在元数据中描述的项是类和它们的成员,以及它们伴随着的特性、属性和关联。本节简单地介绍元数据,与原数据安全相关的内容会在后续章节中继续讲解,元数据的详细内容不在本书的论述范围之内。

元数据实际上是一块二进制数据,包含了三种表:定义表、引用表和清单表。

元数据定义表主要是模块定义、类型定义、方法定义、字段定义、事件定义、参数定义、属性定义等一系列定义表的集合。当编译器编译代码时,所有定义的内容都会生成对应的定义表。

元数据引用表用于记录编译器中源代码引用的类型、方法、字段、事件。常用的引用表如:AssemblyRef(程序集引用表)、ModuleRef(模块引用表)、TypeRef(类型引用表)等。

元数据清单表包含了组成程序集所需要的所有信息,同时包含了对其他程序集的引用信息。它明确地指出了哪些条目可以对外开放,哪些条目只可以在程序集内部进行访问。

下面通过经典的HelloWorld程序简要分析其中的元数据信息,如代码清单1-7所示。

代码清单1-7 HelloWorld程序代码

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace HelloWorld

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("Hello World!");

            Console.Read();

 

        }

    }

}

 

下面使用反汇编工具ILDasm打开HelloWorld.exe,双击MANIFEST,图1-7为查看清单信息的截图。ILDasm的使用方法和参数说明请读者参考MSDN文档。

 

图1-7  查看程序清单信息

详细的清单信息如代码清单1-8所示。

代码清单1-8 HelloWorld.EXE的清单信息

// Metadata version: v4.0.21006

.assembly extern mscorlib

{

  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..

  .ver 4:0:0:0

}

.assembly HelloWorld

{

......

}

  .hash algorithm 0x00008004

  .ver 1:0:0:0

.module HelloWorld.exe

// MVID: {B8EB35DD-5AD2-402C-B422-AA63B0AACCFA}

.imagebase 0x00400000

.file alignment 0x00000200


.stackreserve 0x00100000

.subsystem 0x0003       // WINDOWS_CUI

.corflags 0x00000003    //  ILONLY 32BITREQUIRED

// Image base: 0x05C40000

 

程序比较简单,代码中包含了版本、外部引用等简单的信息。表1-2描述了示例中所使用的 HelloWord.exe 程序集的程序集清单中的各项指令。

表1-2 HelloWorld.exe指令说明

指令

说明

.assembly extern < assembly name >

指定包含当前模块所引用项目的另一程序集(在此示例中为 mscorlib)

.publickeytoken < token >

指定所引用程序集的实际密钥的标记

.ver < version number >

指定引用程序集的版本号

.assembly < assembly name >

指定程序集名称

.hash algorithm < int32 value >

指定使用的哈希算法

.module < file name >

指定组成程序集的模块名称,在此示例中,程序集只包含一个文件

.subsystem < value >

指定程序要求的应用程序环境。在此示例中,值 3 表示该可执行文件从控制台运行

.corflags

当前是元数据中的一个保留字段

根据程序集的内容,程序集清单可包含许多不同的指令。有关程序集清单中指令的完整列表请读者参考相关文档,本例旨在抛砖引玉。若要查看完整的元数据信息,可以使用快捷键“Ctlr+M”,如图1-8所示。

 

图1-8 查看元数据信息

1.3.3       IL常用指令

为方便起见,还是以HelloWorld.exe为例讲解IL的相关内容。由于篇幅所限,关于IL的详细内容还请各位读者参考相关资料。图1-9为Main方法的IL代码。

 

图1-9 HelloWorld.exe Main方法的IL代码

在一个中间语言程序中,如果某一行以“.”开始,代表这是一个传输给汇编工具的指令;而没有以“.”开始的行是中间语言的代码。上图中.method是方法定义指令,定义了Main方法,参数在“()”中,IL代码在“{}”中。.entrypoint是入口指令,表明该方法是入口方法。.maxstack指定了最大栈的深度为8。下面的IL_n是代码标签,后面是IL代码。nop是空指令;ldstr指令向栈中压入字符串“Hello World!”;call指令调用静态方法Console.WriteLine(string)和Console.read();pop弹出栈顶的值;ret指令表示方法体的结束。IL支持“//”和“/* */”的注释方法。

提示  在中间语言中,如果需要调用一个方法,需要指定方法的全名,包括它的名称域(namespace)、类名、返回值类型和参数的数据类型。

表1-3列举了IL的其他一些常用指令,更多的指令可以查看IL指令表。

表1-3 IL常用指令

指令

描述

.assembly <程序集名称> {}

设置程序集

ldc.i4.n

把一个 32位的常量(n从0到8)装入堆栈

stloc.n

把一个从堆栈中返回的值存入第n(n取0~8)个局部变量

add

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

sub

2个值相减

mul

2个值相乘

newarr type

生成一个元素类型为type 的数组。数组的大小必须在调用该命令前装入堆栈。该命令会把一个数组的引用装入堆栈

stelem.i4

给一个数组成员赋值。数组的引用、下标和值必须在调用该命令前装入堆栈

ldelema type

把数组元素的地址装入堆栈。数组的引用和下标必须在调用该命令前装入堆栈。地址用来调用非静态函数

ldlen

把数组的长度装入堆栈。数组的引用必须在调用该命令前装入堆栈

ldloca.s variable

把变量的地址装入堆栈

ldc.i4.s value

把一个Int32的常量装入堆栈(用于大于8位的数)

conv.i4

把堆栈中值转换成Int32类型

call instance function(arguments)

调用类的非静态函数

bge.s label

跳转至label 如果value1≥value 2. Values 1和 2 必须在调用本命令前装入堆栈

br.s label

跳转至label

box value type

把一个值类型转成一个Object,并把该Object的引用装入堆栈

blt.s label

跳转至label 。如果value 1小于 value 2. Values 1 和 2 必须在调用本命令之前装入堆栈

ldelem.i4

把一个数组元素装入堆栈。数组引用和下标必须在调用本命令之前装入堆栈

ldarga.s argument

把函数参数的地址装入堆栈

dup

在堆栈上复制一个值

stind.i4

存储值的地址。地址和值必须在调用本命令之前装入堆栈

.field

定义类成员。和关键字public、private、static等一起使用

stsfld static field

用堆栈中的值替换静态字段的值

ldfld field

把一个非静态字段装入堆栈。类实例的地址必须在调用本命令之前装入堆栈

ldarg.n

把第n个参数装入堆栈。在非静态函数中,第0个参数是一个隐含的参数,代表this

newobj constructor

用构造函数constructor生成一个类的实例。构造函数的参数必须在调用本函数之前先装入堆栈。一个类的实例会被生成并装入堆栈

callvirt instance function

调用一个对象的后期绑定方法

 

1.3.4       IL与代码验证

在将MSIL编译为本机代码的过程中,MSIL代码必须通过验证过程,除非管理员已经建立了允许代码跳过验证的安全策略。验证过程检查MSIL和元数据以确定代码是否是类型安全的,这意味着它仅访问已被授权访问的内存位置。类型安全帮助将对象彼此隔离,因而可以保护它们免遭无意或恶意的破坏。它还提供了对代码可以可靠地强制安全限制的保证。

运行库使用下列条件来验证代码是否为类型安全:

q  对类型的引用与被引用的类型严格兼容。

q  在对象上只调用正确定义的操作。

q  标识与声称的要求一致。

验证过程中检查 MSIL 代码,尝试确认该代码只能通过正确定义的类型访问内存位置和调用方法。例如,代码不允许以超出内存范围的方式来访问对象。另外,验证过程检查代码以确定 MSIL 是否已正确生成,这是因为不正确的 MSIL 会导致违反类型安全规则。验证过程通过正确定义的类型安全代码集,并且它只通过类型安全的代码。然而,由于验证过程存在一些限制,某些类型安全代码可能无法通过验证,而某些语言在设计上并不产生可验证的类型安全代码。如果安全策略要求提供类型安全代码,而该代码不能通过验证,则在运行该代码时将引发异常。

-------------------------注:本文摘抄自《.NET 安全揭秘》1.3节

posted @ 2012-05-24 12:33  玄魂  阅读(4585)  评论(0编辑  收藏  举报