1

我正在做的(部分是为了娱乐和学习,部分希望有一天能作为一项严肃的虚拟化工作)是通过 ILMerge 将我的 VM dll 与目标程序集合并。

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

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

我目前的方法只是保存 MDTokenwriter.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 从未失败过。

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

4

1 回答 1

0

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

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

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

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

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

因为使用元数据令牌发出 IL 的编译器也将表发送到同一个文件中。编译器能够使这些东西保持同步。

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

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

于 2021-05-16T10:18:44.057 回答