我所做的(部分是为了好玩和学习,部分是希望有一天作为一项严肃的虚拟化工作)是通过ILMerge将我的VM与目标程序集合并。
之后,我用dnlib修改新创建的文件,用调用VM函数替换所选方法的方法体。我通过base64编码的二进制字符串传递方法本身中现在缺少的所需元数据,显然参数和旧的方法主体也是如此(将来,我希望实现自己的字节码指令集,但到目前为止,它只是原始代码base64 64编码的代码)。
由于.initlocals是根据我的经验在.NET方法中始终设置的,所以我想要做的是将每个本地的类型保存为数据,这样我就可以在虚拟器运行时使用它初始化本地数组。
我现在的方法就是保存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等)使用ArgumentOutOfRangeException (令牌未找到)失败。所有来自模块外部的值类型(比如System.Drawing.Point)也来自于我可以看到的所有数组类型,而不管底层类型是在哪里定义或引用的。
为什么会这样呢?如果规格在I.9.2.1
然而,
元数据令牌不是持久标识符。相反,它的作用域是特定的元数据二进制。
如果要解释元数据标记在二进制文件被修改时变得无效,为什么它对某些类型非常一致?而且dnlib不应该用PreserveAll标志来修复这个问题吗?为什么这个问题在方法体指令中根本没有发生呢?许多编码InlineType和Module.ResolveType的指令在那里从未失败过。
更重要的是,如何修复?如何为方法的局部变量以二进制形式保存可靠的类型标识符?
发布于 2021-05-16 10:18:44
然而,
元数据令牌不是持久标识符。相反,它的作用域是特定的元数据二进制。
它的意思是,元数据令牌仅在模块的范围内才有意义,您不能从一个模块中获取元数据令牌并在另一个模块中使用它,甚至不能在同一个模块的修改版本中使用它(或者至少不能可靠地)。
当您考虑元数据标记是什么时,这样做的原因就更有意义了。元数据令牌是对模块内元数据表中记录的引用,该记录包含更多详细信息;元数据令牌的高阶字节指示令牌的类型(因此表示记录的表),而其余的3个字节表示行号。
如果您从一个模块获取元数据令牌,并尝试在另一个模块中使用它,则假设每个模块中的相同记录表示相同的内容。如果您用相同的编译器编译相同的代码,那么这个假设可能成立;但是如果您更改源代码或使用不同的编译器(或同一编译器的不同版本),那么行号可能会因任何数量的原因而改变。
以及为什么这个问题在方法体指令中根本没有发生?许多编码InlineType和Module.ResolveType的指令在那里从未失败过。
因为使用元数据令牌发出IL的编译器也会将表发送到同一个文件中。编译器能够使这些事情保持同步。
更重要的是,如何修复
?如何为方法的局部变量以二进制形式保存可靠的类型标识符?
在模块之间引用类型的唯一可靠方法是使用完整的类型名称和作用域(嵌套类型的情况下包含程序集、模块或类型)。
https://stackoverflow.com/questions/67456382
复制相似问题