前往Shuct.Net首页

Shudepb PB反编译专家长时间以来,为业内同类软件事实上的唯一选择.细节,彰显专业.态度,决定品质.

关于反编译的搜索

人肉反编译使用yield关键字的方法 - 百科教程网_经验分享平台[上学吧经验教程频道] 首页 我的上学吧 网站导航 2014年考试日历 帮助 充值 分类导航 首页 经验教程大全 专题 赚钱攻略 移动客户端 我的分享 分享赚钱 经验教程 > 分类导航 > 计算机/互联网 > 程序设计&开发 > Asp/Asp.net > 人肉反编译使用yield关键字的方法 人肉反编译使用yield关键字的方法 作者:hi_720 累计赚钱 6.069 元 我也要“分享赚钱” 2013-2-19 关注(204) 评论(0) 声明:此内容仅代表网友个人经验或观点,不代表本网站立场和观点。 我认为这是一个真命题:“没有用.NET Reflector反编译并阅读过代码的程序员不是专业的.NET程序 员”。.NET Reflector强大的地方就在于可以把IL代码反编译成可读性颇高的高级语言代码,并且能够支 持相当多的“模式”,根据这些模式它可以在一定程度上把某些语法糖给还原,甚至可以支持简单的 Lambda表达式和LINQ。只可惜,.NET Reflector还是无法做到极致,某些情况下生成的代码还是无法还原 到易于理解——yield关键字便是这样一个典型的情况。不过还行,对于不复杂的逻辑,我们可以通过人 肉来“整理”个大概。 简单yield方法编译结果分析 yeild的作用是简化枚举器,也就是IEnumerator或IEnumerable的实现。“人肉” 反编译的关键在于发现编译器的规律,因此我们先来观察编译器的处理结果。值得注意的是,我们这里所 谈的“分析”,都采用的是微软目前的C# 3.0编译器。从理论上来说,这些结果或是规律,都有可能无法 运用在Mono和微软之前或今后的C#编译器上。首先我们准备一段使用yield的代码: static IEnumerator GetSimpleEnumerator() {    Console.WriteLine("Creating Enumerator");    yield return 0;    yield return 1;    yield return 2;    Console.WriteLine("Enumerator Created"); } 为了简化问题,我们在这里采用IEnumerator。自动生成的IEnumerable和 IEnumerator区别不大,您可以自己观察一下,有机会我会单独讨论和分析其中的区别。经过编 译之后再使用.NET Reflector进行反编译,得到的结果是: private static IEnumerator GetSimpleEnumerator() {    return new d__0(0); } [CompilerGenerated] private sealed class d__0 : IEnumerator,  ... {    // Fields    private int 1__state;    private int 2__current;    // Methods    [DebuggerHidden]    public d__0(int 1__state)    {      this.1__state = 1__state;    }    private bool MoveNext()    {      switch (this.1__state)      {        case 0:          this.1__state = -1;          Console.WriteLine("Creating Enumerator");          this.2__current = 0;          this.1__state = 1;          return true;        case 1:          this.1__state = -1;          this.2__current = 1;          this.1__state = 2;          return true;        case 2:          this.1__state = -1;          this.2__current = 2;          this.1__state = 3;          return true;        case 3:          this.1__state = -1;          Console.WriteLine("Enumerator Created");          break;      }      return false;    }    ... } 以上便是编译器生成的逻辑,它将yield关键字这个语法糖转化为普通的.NET结构(再次强调,这只是 微软目前的C# 3.0编译器所产生的结果)。从中我们可以得出一些结论: 原本GetSimpleEnumerator方法中包含yield的逻辑不复存在,取而代之的是一个由编译器自动生成的 IEnumerator类的实例。 原本GetSimpleEnumerator方法中包含yield的逻辑,被编译器自动转化为对应IEnumerator类中的 MoveNext方法的逻辑。 编译器将包含yield逻辑转化为一个状态机,并使用自动生成的state字段保存当前状态。 每次调用MoveNext方法时,都通过switch语句判断state的值,直接进入特定的逻辑片断,并指定下一 个状态。 因为从yield关键字的作用便是“中断”一个方法的逻辑,使它在下次执行MoveNext方法的时候继续执 行。这就意味着自动生成的 MoveNext代码必须通过某一个手段来保留上次调用结束之后的“状态”,并 根据这个状态决定下次调用的“入口”——这是个典型的状态机的“思路”。由此看来,编译器如此实现 ,其“设计”意图也是比较直观的,相信您理解起来也不会有太大问题。 较为复杂的yield方法 上一个例子非常简单,因为GetSimpleEnumerator的逻辑非常简单(只有“顺序”,而没有“循环”和 “选择”)。此外,这个方法也没有使用局部变量及参数,于是我们这里不妨再准备一个相对复杂的方法 : private static IEnumerator GetComplexEnumerator(int[] array) {    d__2 d__ = new d__2(0);    d__.array = array;    return d__; } [CompilerGenerated] private sealed class d__2 : IEnumerator,  ... {    // Fields    private int 1__state;    private int 2__current;    public int 5__4;    public int 5__6;    public int 5__3;    public int 5__5;    public int[] array;    // Methods    [DebuggerHidden]    public d__2(int 1__state)    {      this.1__state = 1__state;    }    private bool MoveNext()    {      // 第一部分      switch (this.1__state)      {        case 0:          this.1__state = -1;          Console.WriteLine("Creating Enumerator");          this.5__3 = 0;          this.5__4 = 0;          goto Label_0094 ;        case 1:          this.1__state = -1;          goto Label_0086 ;        case 2:          goto Label_00F4 ;        default:          goto Label_0123 ;      }      // 第二部分 Label_0086:      this.5__4++; Label_0094:      if (this.5__4       {        if ((this.array[this.5__4] % 2) == 0)        {          this.5__3 += this.array[this.5__4];          this.2__current = this.5__3;          this.1__state = 1;          return true;        }        goto Label_0086 ;      }      this.5__5 = 0;      this.5__6 = 0;      while (this.5__6       {        if ((this.array[this.5__6] % 2) == 0)        {          goto Label_00FB ;        }        this.5__5 += this.array[this.5__6];        this.2__current = this.5__5;        this.1__state = 2;        return true; Label_00F4:        this.1__state = -1; Label_00FB:        this.5__6++;      }      Console.WriteLine("Enumerator Created."); Label_0123:      return false;    }    ... } 这下MoveNext的逻辑便一下子复杂了很多。我认为,这是由于编译器期望生成体积小的代码,于是它 使用了goto来进行自由的跳转。其实从理论上说,把这个方法分为N个阶段之后,便可以让它们完全独立 地分开,只不过此时各状态间便会出现许多重复的逻辑。不过,这段代码看似复杂,其实您仔细分析便会 发现,它其实也只是将代码拆成了上下两部分(如代码注释所示): 第一部分:状态机的控制逻辑,即根据当前状态进行跳转。 第二部分:主体逻辑,只不过使用goto代替了普通语句中由for/if组成的逻辑,这么做的目的是为了 插入Label,可以让第一部分的代码直接跳转到合适的地方——换句话说,由第一部分跳转到的Label便是 yield return出现的地方。 从上面的代码中我们还可以看出方法的“参数”及“局部变量”的转化规则: 参数被转化为IEnumerator类的公开字段,命名方式不变,原本的array参数直接变成array字段。 局部变量被转化为IEnumerator类的公开字段,并运用一定的命名规则改名(主要是为了避免和自动生 成的current及state字段产生冲突)。对于局部变量localVar,将被转化为X__Y的形式 。 其他需要自动生成的字段为1__state及2__current,它们只是进行辅助逻辑,不再 赘述。 至此,我们已经掌握了编译器基本的转化规律,可以将其运用到“人肉反编译”的过程中去。 试验:人肉反编译OrderedEnumerable 事实上,.NET框架中的System.Linq.OrderedEnumerable类便是一个包含yield方法的逻辑,使用.NET Reflector得到的相关代码如下: internal abstract class OrderedEnumerable :  IOrderedEnumerable, ... {    internal IEnumerable source;    internal abstract EnumerableSorter GetEnumerableSorter (EnumerableSorter next);    public IEnumerator GetEnumerator()    {      d__0 d__ = new  d__0(0);      d__.4__this = (OrderedEnumerable) this;      return d__;    }    [CompilerGenerated]    private sealed class d__0 : IEnumerator,  ...    {      // Fields      private int 1__state;      private TElement 2__current;      public OrderedEnumerable 4__this;      public Buffer 5__1;      public int 5__4;      public int[] 5__3;      public EnumerableSorter 5__2;      [DebuggerHidden]      public d__0(int 1__state)      {        this.1__state = 1__state;      }      private bool MoveNext()      {        switch (this.1__state)        {          case 0:            this.1__state = -1;            this.5__1 = new Buffer (this.4__this.source);            if (this.5__1.count             {              goto Label_00EA ;            }            this.5__2 = this.4__this.GetEnumerableSorter (null);            this.5__3 = this.5__2.Sort (this.5__1.items, this.5__1.count);            this.5__2 = null;            this.5__4 = 0;            break;          case 1:            this.1__state = -1;            this.5__4++;            break;          default:            goto Label_00EA ;        }        if (this.5__4 5__1.count)        {          this.2__current = this.5__1.items [this.5__3[this.5__4]];          this.1__state = 1;          return true;        } Label_00EA:        return false;      }      ...    } } 很自然,我们需要“人肉反编译”的便是OrderedEnumerable类的GetEnumerator方法。首先,为了便 于理解代码,我们首先还原各名称。既然我们已经知道了局部变量及current/state的命名规则,因此这 个工作其实并不困难: private bool MoveNext() {    switch (__state)    {      case 0:        __state = -1;        var buffer = new Buffer(this.source);        if (buffer.count         {          goto Label_00EA;        }        var sorter = this.GetEnumerableSorter(null);        var map = sorter.Sort(buffer.items, buffer.count);        sorter = null;        var i = 0;        break;      case 1:        __state = -1;        i++;        break;      default:        goto Label_00EA;    }    if (i     {      __current = buffer.items[map[i]];      __state = 1;      return true;    } Label_00EA:    return false; } 值得注意的是,在上面的方法中,this是由原来的4__this字段还原而来,它表示的是 OrderedEnumerable类型(而不是自动生成的IEnumerator类)的实例。此外,其中的局部变量您需要将其 理解为“自动在多次MoveNext调用中保持状态的变量”—— 这和C语言中的静态局部变量有些接近。自然 ,__state和__current变量都是自动生成用于保存状态的变量,我们姑且保留它们。 接下来,我们将要还原state等于0时的逻辑。因为我们知道,它其实是yield方法中“第一个yield return”之前的逻辑: private IEnumerator GetEnumerator() {    var buffer = new Buffer(this.source);    if (buffer.count     var sorter = this.GetEnumerableSorter(null);    var map = sorter.Sort(buffer.items, buffer.count);    // 省略sorter = null(为什么?:P)     var i = 0;    if (i     {      yield return buffer.items[map[i]];    }    ... } 我们发现,在buffer.count小于等于0的时候MoveNext直接返回false了,于是在GetEnumerator方法中 我们便使用 yield break直接退出。在上面的代码中我们已经还原至第一个yield return,那么当调用下 一个MoveNext时(即state为1)逻辑又该如何进行呢?我们再“机械”地还原一下: private IEnumerator GetEnumerator() {    ...    i++;    if (i     {      yield return buffer.items[map[i]];    }    else     {      yield break;    }    ... } 接着,我们会发现代码会不断重复上面这段逻辑,因此我们可以使用一个“死循环”将其包装起来。 至此,GetEnumerator便还原成功了: private IEnumerator GetEnumerator() {    var buffer = new Buffer(this.source);    if (buffer.count     var sorter = this.GetEnumerableSorter(null);    var map = sorter.Sort(buffer.items, buffer.count);    var i = 0;    if (i     {      yield return buffer.items[map[i]];    }    while (true)    {      i++;      if (i       {        yield return buffer.items[map[i]];      }      else       {        yield break;      }    } } 不过,又有多少人会写这样的代码呢?的确,这段代码是我们“机械翻译”的结果。不过经过观察, 事实上这段代码可以被修改成如下写法: private IEnumerator GetEnumerator() {    var buffer = new Buffer(this.source);    if (buffer.count     var sorter = this.GetEnumerableSorter(null);    var map = sorter.Sort(buffer.items, buffer.count);    for (var i = 0; i     {      yield return buffer.items[map[i]];    } } 至此就完美了。最后这步转换我们利用了人脑的优越性,这样“看出”一种优雅的模式也并非难事— —不过这也并非只能靠“感觉”,因为我在上面谈到,编译器会尽可能生成紧凑的代码,这意味着它和“ 源代码”相比不会有太多的重复。但经由我们“机械还原”之后,会发现这样一段代码其实是重复出现的 : if (i  {    yield return buffer.items[map[i]]; } 于是我们便可以朝着“合并代码片断”的方向去思考,得到最终的结果还是有规律可循的。 总结 如果您关注我最近的文章,并且在看到OrderedEnumerable这个类型之后应该会有所察觉:这篇文章只 是我在“分析Array和LINQ排序实现” 过程中的一个插曲。没错,这是LINQ排序实现的一小部分。 OrderedEnumerable利用了yield关键字,这样我们使用.NET反编译之后代码的可读性很差。为此,我便特 地研究了一下对yield进行“人肉反编译”的做法。不过在一开始,我原本其实是想仔细分析一下yield相 关的“编译规律”,但是我发现在《C# in Depth》一书中已经对这个话题有了非常详尽的描述,只得作 罢。 事实上,自从ASP.NET 2.0开始,我似乎就没有看过任何一本ASP.NET 2.0/3.0或是C# 2.0/3.0/4.0的 书了,因为我认为这些书中的所有内容都可以从MSDN文档,互联网(如博客)以及自己使用、分析的过程 中了解到。不过现在,《C# in Depth》似乎让我对此类技术图书的“偏见”有所动摇了——但只此一本 而已,估计我还是不会去买这样的书。 我认为这是一个真命题:“没有用.NET Reflector反编译并阅读过代码的程序员不是专业的.NET程序 员”。.NET Reflector强大的地方就在于可以把IL代码反编译成可读性颇高的高级语言代码,并且能够支 持相当多的“模式”,根据这些模式它可以在一定程度上把某些语法糖给还原,甚至可以支持简单的 Lambda表达式和.. 此经验为精品经验,需要进行验证才能阅读: 换一换 此经验为付费阅读方式,您需要支付: 0 更多 挺好(0) 一般(0) 相关经验教程 人肉反编译使用yield关键字的方法专家支招如何反“人肉搜索”Java多线程初学者指南(9):为什么要进行数据同步编写多线程Java应用程序常见问题Effective C#原则49:为C#2.0做好准备 上传我的经验教程 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 soarsoft 的原创经验被浏览,获得 ¥0.005 收益 staven 的原创经验被浏览,获得 ¥0.001 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 beijihu008 的原创经验被浏览,获得 ¥0.005 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 sr0529 的原创经验被浏览,获得 ¥0.005 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 yanwuliu1234 的原创经验被浏览,获得 ¥0.005 收益 wzheng4451 的原创经验被浏览,获得 ¥0.001 收益 yanwuliu1234 的原创经验被浏览,获得 ¥0.005 收益 q1447475712 的原创经验被浏览,获得 ¥0.005 收益 yixiuyuanshizixiu 的原创经验被浏览,获得 ¥0.005 收益 anbgsck 的原创经验被浏览,获得 ¥0.005 收益 jy8640166 的原创经验被浏览,获得 ¥0.005 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 hua616228 的原创经验被浏览,获得 ¥0.005 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 jy8640166 的原创经验被浏览,获得 ¥0.005 收益 xuechengqi 的原创经验被浏览,获得 ¥0.003 收益 luosha2012 的原创经验被浏览,获得 ¥0.005 收益 猜你喜欢 [资料] 图文并茂:反编译E书软件使用教程[资料] 基于编译优化和反汇编的程序相似性检测方法.pdf[资料] abel的反编译操作方法.doc[资料] 使用加密来保护你的Flash文件被反编译[图书] 哈佛告诉你:小学教育是掌握学习方法的关键[图书] 人肉搜索(一部关于“人肉搜索”的百科全书式小说)[图书] 反就业歧视的策略与方法[图书] 孤注一掷:现代体育的反兴奋剂斗争-体育运动中的使用兴奋剂现象及反兴奋剂政策的制定[视频] 国际篮球教学-运球视频教程[视频] 淘宝网彩蛋与swf反编译[视频] 编译原理全集视频教程[视频] 编译原理与实践视频教程 应考经验 学历类 职业资格类 财务金融类 建筑工程类 外语类 医卫类 公务员类 外贸类 计算机类 其它 计算机/互联网 办公软件 常用软件 程序设计&开发 存储 多媒体 服务器 互联网 计算机硬件 软件工程 数据库 网络与通信 信息安全 PC操作系统 UI设计 网页特效 平面设计 其它 管理/营销/职场 资料范文 项目管理 企业管理 人力资源 销售营销 市场推广 职场/励志 面试指南 简历攻略 职业规划 其他 金融/投资 经济学基础 货币银行学基础 创业 理财知识 经济贸易 债券 股票 基金 外汇 保险 期货 黄金 信托 房地产 收藏鉴宝 行业投资 其他 工程/机械 机械工程设计 城市规划 工程设备 安全工程 公路桥梁 其它 生活百科 养生保健 美容时尚 美食烹饪 购房置业 家居装修 家电维修 汽车保养 育儿母婴 礼节礼仪 文体艺术 旅游户外 占卜风水 宠物饲养 家庭关系 交友技巧 心理咨询 哲理故事 情感&心情 兴趣爱好 家庭教育 摄影摄像 其它 外语/方言 韩语 德语 粤语 闽南语 英语 法语 日语 其它 数码/游戏/手机 Andriod手机 iOS手机&笔记本 Windows Phone 数码相机 数码摄像机 游戏机 网络游戏 网页游戏 单机游戏 小游戏 PS系列 桌面游戏 其它 法律法规 法律法规 其它 其他 关于我们 | 常见问题&帮助 | 免责声明 | 投诉&建议 | 联系我们 CopyRight 2009-2014 上学吧 | 湘ICP备10203241号