微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 dnlib

如何解决使用 dnlib

我正在做的事情(部分是为了好玩和学习,部分是希望有一天作为认真的虚拟化工作)是通过 ILMerge 将我的 VM dll 与目标程序集合并。

之后我才使用 dnlib 修改新创建的文件,以调用我的 VM 函数来替换所选方法方法体。我通过 base64 编码的二进制字符串传递方法本身中现在不存在的必需元数据,显然参数和旧方法体也是如此(将来我想为此实现我自己的字节码指令集,但到目前为止它只是原始代码 base64 编码)。

根据我的经验,.initlocals 总是设置在 .NET 方法中,所以我想要做的是将每个本地的类型保存为数据,这样我就可以初始化我的本地数组在 Virtualizer 运行时使用它。

我目前的做法只是保存 MDToken writer.Write(local.Type.ToTypeDefOrRef().GetNonnestedTypeRefScope().MDToken.ToInt32());

我使用 PreserveAll 标志 opts.MetadataOptions.Flags = dnlib.DotNet.Writer.MetadataFlags.PreserveAll;

写入我对程序集的更改

并在运行时通过

解析MDToken
    for (int i = 0; i < numLocals; i++)
    {
        int token = ReadInt32(info,ref pos);
        Type t = Module.ResolveType(token);
    }

现在,这仅适用修改后的模块本身中定义的类型,包括值(struct s {...})和引用类型(例如 Form1)以及其他定义的引用类型模块(如 System.Windows.Forms.Form)

对于所有核心 CLR 类型(对象、int32、uint64 等)和来自模块外部的所有值类型(如 System.Drawing.Point),

ResolveType 失败 并带有 ArgumentOutOfRangeException(未找到令牌)从我可以看到的所有数组类型来看,无论底层类型是在哪里定义或引用的。


现在,为什么会这样?如果规范在 I.9.2.1

但是,元数据令牌不是持久标识符。相反,它仅限于特定的元数据二进制文件

被解释为当二进制文件修改时元数据令牌变得无效,为什么它对某些类型的工作非常一致? dnlib 不应该用 PreserveAll 标志来解决这个问题吗?而为什么这个问题在方法体指令中完全没有出现呢?许多指令编码 InlineType 和 Module.ResolveType 从未失败过。

而且,更重要的是,如何修复?如何以二进制形式为方法的局部变量保存可靠的类型标识符?

解决方法

但是,元数据令牌不是持久标识符。相反,它仅限于特定的元数据二进制文件。

它的意思是元数据令牌仅在模块范围内有意义,您不能从一个模块中获取元数据令牌并在另一个模块中甚至在同一模块的修改版本中使用它(或在至少不可靠)。

当您考虑元数据令牌的真正含义时,这样做的原因更有意义。元数据令牌是对模块内元数据表中记录的引用,该记录包含更多详细信息;元数据令牌的高位字节表示令牌的类型(以及包含记录的表),而其余 3 个字节表示行号。

如果您从一个模块中获取元数据令牌并尝试在另一个模块中使用它,则您假设每个模块中的相同记录代表相同的事物。如果你用相同的编译器编译相同的代码,那么这个假设可能成立;但如果您更改源代码或使用不同的编译器(或同一编译器的不同版本),则行号可能会因多种原因而更改。

而为什么这个问题在方法体指令中完全没有出现呢?许多指令编码 InlineType 和 Module.ResolveType 从未失败过。

因为编译器发出带有元数据标记的 IL 也会将表发送到同一个文件中。编译器能够使这些事情保持同步。

而且,更重要的是,如何修复?如何以二进制形式为方法的局部变量保存可靠的类型标识符?

在模块之间引用类型的唯一可靠方法是使用完整的类型名称和范围(在嵌套类型的情况下包含程序集、模块或类型)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。