新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论.NET,C#,ASP,VB技术
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 Dot NET,C#,ASP,VB 』 → MS.Net CLR 扩展PE结构分析(3) 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 3735 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: MS.Net CLR 扩展PE结构分析(3) 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     admin 帅哥哟,离线,有人找我吗?
      
      
      
      威望:9
      头衔:W3China站长
      等级:计算机硕士学位(管理员)
      文章:5255
      积分:18406
      门派:W3CHINA.ORG
      注册:2003/10/5

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给admin发送一个短消息 把admin加入好友 查看admin的个人资料 搜索admin在『 Dot NET,C#,ASP,VB 』的所有贴子 点击这里发送电邮给admin  访问admin的主页 引用回复这个贴子 回复这个贴子 查看admin的博客楼主
    发贴心情 MS.Net CLR 扩展PE结构分析(3)


    发信人: flier (小海->找啊找工作 :)), 信区: DotNET        
    标  题: MS.Net CLR 扩展PE结构分析(3)
    发信站: BBS 水木清华站 (Mon Mar 18 23:03:30 2002)

    MS.Net CLR 扩展PE结构分析

    Flier Lu <flier_">lu@sina.com.cn>

    注意:本系列文章在水木清华BBS(smth.org)之.Net版首发,
         转载请保留以上信息,发表请与作者联系

    Metadata 篇

    第二章 Metadata在PE中的结构分析

      上一章大概介绍了Metadata在CLR架构中的作用,以及其在PE
    文件中的组织结构。本章将从实际的二进制流的角度来分析Metadata
    流在PE中的结构。

    2.1 Metadata Header

      前言中我们分析了CLR Header的结构,里面有一个Metadata字段
      IMAGE_DATA_DIRECTORY    MetaData;
      字段指向一个数据块,里面包含着Metadata Header,是关于
    Metadata信息所在,结构定义伪代码如下

      TClrMetadataHeader = packed record
        Signature: DWORD;   // Magic signature for physical metadata : $424A5342.
        MajorVersion,       // Major version, 1
        MinorVersion: Word; // Minor version, 0
        Reserved,           // Reserved, always 0
        Length: DWORD;      // Length of version string in bytes, say m.
        Version: array[0..0] of Char;
        // UTF8-encoded version string of length m
        // Padding to next 4 byte boundary, say x.
        Version: array[0..((m+3) and (not $3))-1] of Char;
        Flags,              // Reserved, always 0
        Streams: Word;      // Number of streams, say n.
        // Array of n StreamHdr structures.
        StreamHeaders: array[0..n-1] of TClrStreamHeader;
      end;

      首先是Signature,是一个DWORD或4字符长的标记,$424A5342或'BSJB'
    可以通过此标记判断Metadata是否存在,如

    function TJclPeCLRHeader.GetHasMetadata: Boolean;
    const
      METADATA_SIGNATURE = $424A5342;
    begin
      with Header.MetaData do
        Result := (VirtualAddress <> 0) and
          (PDWORD(FImage.RvaToVa(VirtualAddress))^ = METADATA_SIGNATURE);
    end;

    通过检测Metadata节是否存在,以及头一个DWORD是否为Metadata标记来判断
    Metadata是否存在。
      接着的MajorVersion和MinorVersion保存Metadata格式的版本号,
    一般设置为1.1。(文档中说明是1.0,实际为1.1)
      跟着的Length+Version指定了UTF8格式的编译环境版本号,这个和你的CLR的编译
    版本相同,如.Net Framework工具的编译环境版本号为v1.x86ret,而你用它编译代码
    生成的版本号则是诸如v1.0.3705类型。注意这里Version字符串是按四字节对齐的,
    在分析二进制流时应按(m+3) and (not $3)来计算实际的长度。
      Streams和StreamHeaders则是Metadata流的信息数组,保存有Metadata不同类型
    流的Offset, Size, Name等信息,没记录结构如下

      TClrStreamHeader = packed record
        Offset,      // Memory offset to start of this stream from start of the me
    tadata root
        Size: DWORD; // Size of this stream in bytes, shall be a multiple of 4.
        // Name of the stream as null terminated variable length
        // array of ASCII characters, padded with \0 characters
        Name: array[0..MaxWord] of Char;
      end;

      根据这些信息,我们可以从PE映像中读取相应流进行分析。

    2.2 Metadata Stream

      上一章我们曾经提过,Metadata Stream有五种常见类型,#String, #Blob,
    #Guid, #US(User String)和#~流,每种类型流最多只能出现一次。

    2.2.1 #String 流

      #String流是一个字符串堆,程序用到的字符串,如类名、函数名、参数名等字符串
    保存在其中。字符串以UTF8编码保存,以#0字符分隔。流首总有一个#0字符代表一个
    空字符串,分析示例代码如下。

    constructor TJclClrStringsStream.Create(
      const AMetadata: TJclPeMetadata; const AHeader: PClrStreamHeader);
    var
      pch: PChar;
      off: DWORD;
    begin
      inherited;
      FStrings := TStringList.Create;
      pch      := Data;
      off      := 0;
      while off < Size do
      begin
        if pch^ <> #0 then
          FStrings.AddObject(pch, TObject(off));
        pch := pch + StrLen(pch) + 1;
        off := DWORD(pch - Data);
      end;
    end;

      如碰到#0字符,直接作为空字符串跳过之,否则,以StrLen取得字符串长度,
    并将字符串保存到一个字符串列表中,使用时需要动态将UTF8解码为Unicode。

    function TJclClrStringsStream.GetString(const Idx: Integer): WideString;
    begin
      Result := UTF8ToWideString(FStrings.Strings[Idx]);
    end;

      因为流中数据是以堆形式存放,指向#String流的索引是字符串偏移值。

    2.2.2 #Guid 流

      #Guid流格式很简单,就是一个GUID结构的数组,以流长度除以SizeOf(TGuid)就是
    数组元素个数。因为流中数据是以数组形式存放,指向#Guid流的索引是以1开始的索引值

    如索引为0则表示此项索引不存在。(注意,CLR中所有索引都是以1开始的,0表示索引值

    不存在)
      示例代码如下:

    constructor TJclClrGuidStream.Create(
      const AMetadata: TJclPeMetadata; const AHeader: PClrStreamHeader);
    var
      I: Integer;
      pg: PGUID;
    begin
      inherited;
      SetLength(FGuids, Size div SizeOf(TGuid));
      pg := Data;
      for I:=0 to GetGuidCount-1 do
      begin
        FGuids[I] := pg^;
        Inc(pg);
      end;
    end;

    2.2.3 #Blob 和 #US 流

      #Blob流是一个二进制数据堆,程序中的所有非字符串形式数据都堆放在这个流里面,
    如常数的值,Public Key的值,方法的Signature等等。
      在每个二进制数据块头,都有一个块长度数据,但为了节约存储空间,CLR使用了比较
    麻烦的编码方法。
      如果开始一个字节最高位为0,则此数据块长度为一个字节;
      如果开始一个字节最高位为10,则此数据块长度为两个字节;
      如果开始一个字节最高位为110,则此数据块长度为四个字节;
      在屏蔽标志位后,通过移位运算即可计算出数据块的实际长度值,并依据此获得数据。

    示例代码如下。
    constructor TJclClrBlobRecord.Create(const AStream: TJclClrStream; const APtr:
    PByteArray);
    var
      b: Byte;
      AData: Pointer;
      ASize: DWORD;
    begin
      FPtr    := APtr;
      FOffset := DWORD(FPtr) - DWORD(AStream.Data);
      b := FPtr[0];
      if b = 0 then
      begin
        AData := @FPtr[1];
        ASize := 0;
      end
      else if ((b and $C0) = $C0) and ((b and $20) = 0) then    // 110bs
      begin
        AData := @FPtr[4];
        ASize := ((b and $1F) shl 24) + (FPtr[1] shl 16) + (FPtr[2] shl 8) + FPtr[
    3];
      end
      else if ((b and $80) = $80) and ((b and $40) = 0) then    // 10bs
      begin
        AData := @FPtr[2];
        ASize := ((b and $3F) shl 8) + FPtr[1];
      end
      else
      begin
        AData := @FPtr[1];
        ASize := b and $7F;
      end;
      Assert(not IsBadReadPtr(AData, ASize));
      inherited Create(AData, ASize);
    end;
    constructor TJclClrBlobStream.Create(
      const AMetadata: TJclPeMetadata; const AHeader: PClrStreamHeader);
    var
      ABlob: TJclClrBlobRecord;
    begin
      inherited;
      FBlobs := TObjectList.Create;
      ABlob := TJclClrBlobRecord.Create(Self, Data);
      while Assigned(ABlob) do
      begin
        if ABlob.Size > 0 then
          FBlobs.Add(ABlob);
        if (Integer(ABlob.Memory) + ABlob.Size) < (Integer(Self.Data) + Integer(Se
    lf.Size)) then
          ABlob := TJclClrBlobRecord.Create(Self, Pointer(Integer(ABlob.Memory) +  
    ABlob.Size))
        else
          ABlob := nil;
      end;
    end;

      #US流是User String的存储空间,结构上和#Blob流相同,只是所有数据都是
    Unicode格式的字符串。
      #Blob和#US流的索引都是数据块偏移。

    2.2.4 #~ 流

      #~流是Metadata中最复杂也是最重要的信息所在,几乎所有Metadata信息
    都以表的形式组织存放在#~流中。流起始处有一个#~ Stream Header如下
      TClrTableStreamHeader = packed record
        Reserved: DWORD; // Reserved, always 0
        MajorVersion,    // Major version of table schemata, always 1
        MinorVersion,    // Minor version of table schemata, always 0
        HeapSizes,       // Bit vector for heap sizes.
        Reserved2: Byte; // Reserved, always 1
        Valid,           // Bit vector of present tables, let n be the number of b
    its that are 1.
        Sorted: Int64;   // Bit vector of sorted tables.
        // Array of n four byte unsigned integers indicating the number of rows
        // for each present table.
        Rows: array[0..n-1] of DWORD;
        Tables: array[0..n-1] of ???
      end;
      字段MajorVersion.MinorVersion是表格式的版本号,一般设置为 1.0;
      在介绍其他字段意义前,我们要先了解Metadata中表的表示方法。
      在Metadata中,表最多有64种,每种表有一个唯一的确定的编号,如Assembly表的
    编号为$20、Module表的编号为$00等等。目前的CLR只使用到了最多$2B(43)种表,
    其间还有一些表编号是被保留或非公开的。对应表编号如下

      TJclClrTableKind = (
        ttModule,               //  $00
        ttTypeRef,              //  $01
        ttTypeDef,              //  $02
        ttUnknown03,            //  $03
        ttFieldDef,             //  $04
        ttUnknown05,            //  $05
        ttMethodDef,            //  $06
        ttUnknown07,            //  $07
        ttParamDef,             //  $08
        ttInterfaceImpl,        //  $09
        ttMemberRef,            //  $0a
        ttConstant,             //  $0b
        ttCustomAttribute,      //  $0c
        ttFieldMarshal,         //  $0d
        ttDeclSecurity,         //  $0e
        ttClassLayout,          //  $0f
        ttFieldLayout,          //  $10
        ttSignature,            //  $11
        ttEventMap,             //  $12
        ttUnknown13,            //  $13
        ttEvent,                //  $14
        ttPropertyMap,          //  $15
        ttUnknown16,            //  $16
        ttProperty,             //  $17
        ttMethodSemantics,      //  $18
        ttMethodImpl,           //  $19
        ttModuleRef,            //  $1a
        ttTypeSpec,             //  $1b
        ttImplMap,              //  $1c
        ttFieldRVA,             //  $1d
        ttUnknown1e,            //  $1e
        ttUnknown1f,            //  $1f
        ttAssembly,             //  $20
        ttAssemblyProcessor,    //  $21
        ttAssemblyOS,           //  $22
        ttAssemblyRef,          //  $23
        ttAssemblyRefProcessor, //  $24
        ttAssemblyRefOS,        //  $25
        ttFile,                 //  $26
        ttExportedType,         //  $27
        ttManifestResource,     //  $28
        ttNestedClass,          //  $29
        ttUnknown2a,            //  $2a
        ttUnknown2b);           //  $2b

      在#~流头中,使用了两个64位位图表示这些表的状态,如Valid位图中第$20位为1
    则Assembly表在当前Metadata中存在,Sorted位图中第$00位为1则Module表已排序等等。

      这里就涉及到在Metadata中非常重要的一个概念Token。一个Token是在一个Metadata
    中可以唯一性确定一个记录的标识符,他是一个32位无符号整型数,高8位表示表的编号
    低24位表示记录在表中的索引。因此一个Token为$20000001实际上表示编号为$20的
    Assembly表中第1项记录。Metadata Unmanaged API就是使用Token来表示每个记录。
      对Valid位图进行从低到高位扫描,发现一个表存在,则可到Rows中获取此表中记录
    的行数,Rows数组大小为Valid位图中设置的位的数量,也就是Metadata中存在的表
    的数量。而Tables则是实际数据的开始。
      这里还有一个HeapSizes,表示其他几个流中索引的大小,位$01表示#String流,
    $02表示#Guid流,#04表示Blob流。如相应位设置为1,则表示对应流大于MaxWord
    也就是大于2^16,因此需要以四字节来做索引,否则缺省用两字节做索引。
      我们可以看到,为了节省存储空间,Metadata中使用了相对多的编码方式来合并字段
    缩减尺寸,如后面将提到的Coded Index亦是如此。

    2.3 相关信息

      关于Metadata结构的详细定义可以在.Net FrameworkSDK的Tool Developers Guide
    目录下的Partition II Metadata.doc文档中找到。
      示例代码可以参考mono工程对metadata的分析,http://go-mono.com/
      或者下载JCL(Delphi Jedi Code Library)项目最新代码,
    http://groups.yahoo.com/group/jedi-jcl,或到www.sourceforge.org
    上查找JCL项目,里面有我的较新版本的CLR分析代码JclCLR和相关使用示例ClrDemo
    或者使用FreeVCS工具到demos.href.com:2106以账号jcluser1:jcluser1下载
    最新版本的JCL代码。

    2.4 小结

      这一章我们大概了解了Metadata在PE中实际的物理结构和分析方法,在对整体结构
    有所了解,对一般概念如Token等有所了解后,我们将在下一章开始对实际的Metadata
    表进行分析,看看在Reflection或者Metadata Unmanaged API后面到底隐藏了些什么。
    --
    .  生命的意义在于   /\   ____\ /\_ \   /\_\                             .  
    .      希望         \ \  \___/_\/\ \   \/_/__     __    _ _★           .  
    .      工作          \ \   ____\\ \ \    /\  \  /'__`\ /\`'_\           .  
    .    爱你的人         \ \  \___/ \ \ \___\ \  \/\  __//\ \ \/           .  
    .   和你爱的人         \ \___\    \ \_____\ \__\ \____\ \ \_\           .  
    .      ……             \/___/     \/_____/\/__/\/____/  \/_/ @126.com.  


    ※ 修改:·flier 於 Mar 18 23:04:54 修改本文·[FROM:  202.114.32.216]
    ※ 来源:·BBS 水木清华站 smth.org·[FROM: 202.114.32.216]
    上一篇
    返回上一页
    回到目录
    回到页首
    下一篇


       收藏   分享  
    顶(0)
      




    ----------------------------------------------

    -----------------------------------------------

    第十二章第一节《用ROR创建面向资源的服务》
    第十二章第二节《用Restlet创建面向资源的服务》
    第三章《REST式服务有什么不同》
    InfoQ SOA首席编辑胡键评《RESTful Web Services中文版》
    [InfoQ文章]解答有关REST的十点疑惑

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2004/11/9 2:25:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 Dot NET,C#,ASP,VB 』的所有贴子 点击这里发送电邮给Google AdSense  访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/12/27 18:29:08

    本主题贴数1,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    93.750ms