使用 dnlib 重写后,局部变量类型的某些元数据标记无效
Some metadata tokens for local variable types invalid after rewrite with 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)
ResolveType 失败 所有核心 CLR 类型(对象、int32、uint64 等)和模块外部的所有值类型(如 System.Drawing.Point) 也从我所看到的所有数组类型来看,无论基础类型在哪里定义或引用。
现在,为什么会这样?如果 I.9.2.1
的规范
However, a metadata token is not a persistent identifier. Rather it is scoped to a specific metadata binary.
应该解释为当二进制文件被修改时元数据令牌变得无效,为什么它对某些类型非常一致?难道 dnlib 不应该用 PreserveAll 标志来解决这个问题吗?而为什么在方法体指令中完全没有出现这个问题呢?许多指令编码一个 InlineType 并且 Module.ResolveType 在那里从未失败过。
而且,更重要的是,如何修复?如何为方法的局部变量以二进制形式保存可靠的类型标识符?
However, a metadata token is not a persistent identifier. Rather it is scoped to a specific metadata binary.
意思是元数据令牌仅在模块范围内有意义,您不能从一个模块中获取元数据令牌并在另一个模块中使用它,甚至不能在同一模块的修改版本中使用它(或者至少不可靠)。
当您考虑元数据令牌的真正含义时,这样做的原因就更有意义了。元数据令牌是对模块内元数据 table 中记录的引用,该记录包含更多详细信息;元数据令牌的高位字节指示令牌的类型(因此 table 包含记录),而其余 3 个字节指示行号。
如果您从一个模块中获取元数据标记并尝试在另一个模块中使用它,则您假设每个模块中的相同记录代表相同的事物。如果用同一个编译器编译同一个代码,那么这个假设可能成立;但如果您更改源代码或使用不同的编译器(或同一编译器的不同版本),则行号可能会因多种原因而更改。
And why does this problem not occur at all in the method body instructions? Many instructions encode an InlineType and Module.ResolveType has never failed there.
因为编译器发出带有元数据标记的 IL 也会将 tables 发出到同一个文件中。编译器能够使这些东西保持同步。
And, more importantly, how to fix? How do I save a reliable type identifier in a binary form for the locals of a method?
在模块之间引用类型的唯一可靠方法是使用完整的类型名称和范围(在嵌套类型的情况下包含程序集、模块或类型)。
我正在做的事情(部分是为了娱乐和学习,部分是希望有朝一日作为一项严肃的虚拟化工作)是通过 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)
ResolveType 失败 所有核心 CLR 类型(对象、int32、uint64 等)和模块外部的所有值类型(如 System.Drawing.Point) 也从我所看到的所有数组类型来看,无论基础类型在哪里定义或引用。
现在,为什么会这样?如果 I.9.2.1
的规范However, a metadata token is not a persistent identifier. Rather it is scoped to a specific metadata binary.
应该解释为当二进制文件被修改时元数据令牌变得无效,为什么它对某些类型非常一致?难道 dnlib 不应该用 PreserveAll 标志来解决这个问题吗?而为什么在方法体指令中完全没有出现这个问题呢?许多指令编码一个 InlineType 并且 Module.ResolveType 在那里从未失败过。
而且,更重要的是,如何修复?如何为方法的局部变量以二进制形式保存可靠的类型标识符?
However, a metadata token is not a persistent identifier. Rather it is scoped to a specific metadata binary.
意思是元数据令牌仅在模块范围内有意义,您不能从一个模块中获取元数据令牌并在另一个模块中使用它,甚至不能在同一模块的修改版本中使用它(或者至少不可靠)。
当您考虑元数据令牌的真正含义时,这样做的原因就更有意义了。元数据令牌是对模块内元数据 table 中记录的引用,该记录包含更多详细信息;元数据令牌的高位字节指示令牌的类型(因此 table 包含记录),而其余 3 个字节指示行号。
如果您从一个模块中获取元数据标记并尝试在另一个模块中使用它,则您假设每个模块中的相同记录代表相同的事物。如果用同一个编译器编译同一个代码,那么这个假设可能成立;但如果您更改源代码或使用不同的编译器(或同一编译器的不同版本),则行号可能会因多种原因而更改。
And why does this problem not occur at all in the method body instructions? Many instructions encode an InlineType and Module.ResolveType has never failed there.
因为编译器发出带有元数据标记的 IL 也会将 tables 发出到同一个文件中。编译器能够使这些东西保持同步。
And, more importantly, how to fix? How do I save a reliable type identifier in a binary form for the locals of a method?
在模块之间引用类型的唯一可靠方法是使用完整的类型名称和范围(在嵌套类型的情况下包含程序集、模块或类型)。