65.9K
CodeProject 正在变化。 阅读更多。
Home

利用 LINQ to XML 查询混淆图

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2009年10月5日

CPOL

5分钟阅读

viewsIcon

24405

downloadIcon

297

LINQ to XML 技术实践应用。

引言

在我过去的工作经历中,我曾处理过一些关于我们公司某个产品的用户错误报告。这些报告包含了调用堆栈信息,旨在帮助我们检测错误的根源。

我们在生产代码上使用了一个混淆工具,因此错误报告提供的调用堆栈信息需要结合混淆映射进行一些“查找”,以及手动文本搜索。这种“查找”并不总是容易的事情——混淆映射是一个巨大的 XML 文件,大小超过 25 MB,而大多数文本编辑器都无法很好地处理如此大的信息量。这些编辑器之所以有这样的偏好是合理的,因为一个普通的人类创建的文件很少会超过 1 MB 的边界。

当您需要语法高亮,甚至更进一步——XML 树解析/导航时,情况会变得更糟。另一个大问题是存在大量的 XML 元素具有相同的混淆名称,要识别它们的类型,您需要手动分析父 XML 元素。

任务定义

面对这些问题,我决定通过自动化名称解析过程来帮助我们的支持团队。自动化任务的要求如下:

  • 应该有一个工具,允许根据混淆名称查找原始的类或类成员名称。
  • 用户界面应尽可能简单——我认为简单的任务不应该需要复杂的用户操作。
  • 使用 LINQ to XML——这是一种方便易用的处理 XML 数据的方式。但也许最主要的因素是,我终于有机会在实践中使用这项技术了。

让我们进入具体的步骤。首先,我们定义输入数据。

这是调用堆栈内容示例

Type: System.ArgumentException
Stack:
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary.Insert(TKey key, TValue value, Boolean add)
   at ne.c(IError A_0)
   at ne.c(ErrorListEventArgs A_0)
   at ne.c.a()
   at ne.c(Object A_0, EventArgs A_1)
   at System.Windows.Forms.Timer.OnTick(EventArgs e)
   at System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, 
                                                 IntPtr wparam, IntPtr lparam)

我们公司使用 Dotfuscator 工具,其社区版随 Visual Studio 一起提供。所有混淆器的混淆映射格式都相同,因此任何人都可以使用自己的代码测试此名称解析工具。混淆映射是一个 XML 文件,其结构如下

<dotfuscatorMap version="1.1">
  <header />    <!--Provides timestamp and version information.-->
  <mapping>    <!--Mapping information.-->
    <module>    <!--Module mapping include module’s types.-->
      <name>ModuleName.dll</name>    <!--Original module name.-->
      <type />    <!--Type name and members mapping.-->
      ...
      <type/>    <!--Other type name and members mapping.-->
    </module>
  </mapping>
  <statistics />    <!--Some obfuscation statistics.-->
</dotfuscatorMap>

<type> 元素的结构如下

<type>
  <name>type_name</name>            <!--Original type name.-->
  <newname>obfuscated_name</newname>        <!--Obfusctaed type name (Optional).-->
  <methodlist>                    <!--Type’s method list.-->
    <method>
      <signature>void(object, System.EventArgs)</signature>    <!--Method signature.-->
      <name>method_name</name>         <!--Original method name.-->
      <newname>obfuscated_name</newname>    <!--Obfusctaed method name (Optional).-->
    </method>
    ...
  </methodlist>
  <fieldlist>                     <!--Types filed list.-->
    <field>
      <signature>System.Windows.Forms.Button</signature>    <!--Field signature.-->
      <name>field_name</name>            <!--Original field name.-->
      <newname>obfuscated_name</newname>    <!--Obfuscated field name (Optional).-->
    </field>
    ...
  </fieldlist>
</type>

您会注意到,混淆名称始终放在可选的 <newname> 元素中。如果省略此元素,则对象使用其原始名称。

接下来,我们应该定义用户输入。例如,我们需要查找一个混淆名称为“a”的类型。通常,我们搜索“<newname>a</newname>”字符串——这将找到所有混淆名称为“a”的类型、方法和字段。在复杂的项目中,大约有数千个结果。为了实现我们的搜索目标,我们需要分析父元素并检测它是否是 <type> 元素。

因此,用户通常使用两个参数:第一个参数是混淆映射文件的路径,第二个参数是混淆名称。还有一个(隐式)参数——搜索结果类型(类型/方法/字段),但我们将尝试从第二个参数推断此参数。根据 UI 简洁性的要求,我认为这足够了。

类型名称搜索

第一个任务是使用混淆名称搜索原始类型名称。让我们来做。首先,我们需要列出映射文件中的所有类型。这很简单

// Construct the XElement to access map file.
XElement map = XElement.Load("sample.xml");
var types = map.Descendants("type");

这里的主要操作是 map.Descendants("type") 调用,它会返回 XElement 内容中的所有 <type> 元素。

Descendants() 方法返回一个普通的后代 XML 元素集合。这个集合包括子元素、孙子元素等。因此,如果我们写 map.Descendants(),我们将获得映射文档中所有 XML 元素的枚举。这个方法有一个重载,允许通过指定匹配元素名称过滤器来过滤输出集合。我使用了这个重载来过滤掉除 <type> 之外的所有元素。

注意:过滤器名称应该是完全限定名称;这意味着,如果被过滤的元素有命名空间,过滤器名称也必须带有命名空间。

注意:请记住,Descendants 使用延迟执行,这意味着对底层 XML 的实际访问将在您首次访问 Descendants 结果时执行,而不是在调用此函数时执行。

map.Descendants("type") 将扫描整个 XML 树来查找指定的元素类型;这不是最有效的解决方案,但它是最简单的。使用直接元素导航可以减少整个 XML 扫描,从而更有效率。例如,我们可以使用这样的表达式

var types = map.Elements("mapping").Elements("module").Elements("type");

根据 XML 内容,此表达式可以提供比 Descendants 调用快十倍的性能。但对于这个应用程序,我更喜欢 Descendants 函数的简洁性。

现在我们有了所有的 <type> 元素,需要找到与混淆名称匹配的项。我使用 LINQ 查询来实现这一点:

string obfuscatedName = "a"; // Define the search criteria.
var found = from type in types
          // Filter type elements by obfuscatedName matching.
          where type.Element("newname").Value == obfuscatedName
          select type;

types 集合通过匹配类型的子元素 <newname> 的内容与传入的混淆名称来过滤。这也可以使用带有 lambda 表达式的 Where 扩展方法来完成:

var found = types.Where(t => t.Element("newname").Value == obfuscatedName);

如前所述,<newname> 元素是可选的,因此当类型未混淆时,Element("newname") 返回 null。为了避免可能的 NRE,我将 LINQ 查询修改为以下内容:

var found = from type in map.Descendants("type")
// Declare name element variable.
let name = type.Element("newname") ?? type.Element("name")
// Filter type elements by obfuscatedName matching.
where name.Value == obfuscatedName
select type;

此代码将搜索混淆名称或原始名称与 obfuscatedName 匹配的类型。

let 关键字引入了一个新变量 name,它保存 <newname> 元素或在没有 <newname> 元素时保存 <name> 元素。这个新变量是一个匿名类型,它由当前的 <type> 元素和一个 <name>/<newname> 元素组成。类似于这样:

new { Type = type, Name = type.Element("newname") ?? type.Element("name") };

整个查询可以用 C# 表示为:

IEnumerable<xelement> found = map.Descendants("type").
      Select(type => new { Type = type, Name = type.Element("newname") }).
      Where(tn => tn.Name.Value == obfuscatedName).
      Select(tn => tn.Type);

正如我们所见, there is the second Select function call, which (in conjunction with the anonymous type projection) will give us some performance penalty, so I rewrite the query to the following

var found = from type in map.Descendants("type")
// Filter type elements by obfuscatedName matching.
where (type.Element("newname") ?? type.Element("name")).Value == 
    obfuscatedName
select type;

接下来要做的是处理复杂的类型名称。在 XML 中,这些名称用“/”分隔而不是“.”;例如,“MyClass.MyInternalClass” 名称由 “MyClass/MyInternalClass” 字符串值表示。我们只需要在 obfuscatedName 变量中将“.”替换为“/”即可匹配:

obfuscatedName = obfuscatedName.Replace('.', '/');

最后,我们提供匿名类型投影,这将帮助我们在 C# 中处理搜索结果:

var types = from type in found
          select new {
            ModuleName = type.Parent.Element("name").Value,
            TypeName = type.Element("name").Value
          };

之后,您可以按需处理搜索结果,例如将其输出到控制台:

foreach (var type in types) {
    Console.WriteLine("Module:      {0}", type.ModuleName);
    Console.WriteLine("Type:        {0}", type.TypeName);
    Console.WriteLine();
}

摘要

就是这样。我们已经找到了提供混淆名称的类型。在下一部分中,我将深入探讨 LINQ 查询,提供字段和方法名称解析解决方案。

感谢您的时间,欢迎提出任何问题或建议。

© . All rights reserved.