- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
(首先,这是一篇很长的文章,但别担心:我已经实现了所有这些,我只是在询问您的意见或可能的替代方案。)
我在执行以下操作时遇到问题;我很感激一些帮助:
Type
作为参数。 public class OriginalClass {
private int x;
}
public class Subclass : OriginalClass {
private int x;
public int X {
get { return x; }
set { x = value; }
}
}
ldfld x
与 callvirt this.get_X
,也就是说,我不是直接从字段中读取,而是调用 get 访问器。 Instructions
,并轻松替换说明。但是,原始类型不在 .dll 文件中,因此我找不到使用 Mono.Cecil 加载它的方法。将类型写入 .dll,然后加载它,然后修改它并将新类型写入磁盘(我认为这是您使用 Mono.Cecil 创建类型的方式),然后加载它似乎是一个巨大的开销。
Instructions
,但是我不支持替换说明。我已经使用 Mono.Reflection 实现了一个非常丑陋且效率低下的解决方案,但它尚不支持包含 try-catch 语句的方法(尽管我想我可以实现这一点)并且我担心可能还有其他场景这是行不通的,因为我使用的是
ILGenerator
以一种有点不寻常的方式。此外,它非常丑陋;)。这是我所做的:
private void TransformMethod(MethodInfo methodInfo) {
// Create a method with the same signature.
ParameterInfo[] paramList = methodInfo.GetParameters();
Type[] args = new Type[paramList.Length];
for (int i = 0; i < args.Length; i++) {
args[i] = paramList[i].ParameterType;
}
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
methodInfo.Name, methodInfo.Attributes, methodInfo.ReturnType, args);
ILGenerator ilGen = methodBuilder.GetILGenerator();
// Declare the same local variables as in the original method.
IList<LocalVariableInfo> locals = methodInfo.GetMethodBody().LocalVariables;
foreach (LocalVariableInfo local in locals) {
ilGen.DeclareLocal(local.LocalType);
}
// Get readable instructions.
IList<Instruction> instructions = methodInfo.GetInstructions();
// I first need to define labels for every instruction in case I
// later find a jump to that instruction. Once the instruction has
// been emitted I cannot label it, so I'll need to do it in advance.
// Since I'm doing a first pass on the method's body anyway, I could
// instead just create labels where they are truly needed, but for
// now I'm using this quick fix.
Dictionary<int, Label> labels = new Dictionary<int, Label>();
foreach (Instruction instr in instructions) {
labels[instr.Offset] = ilGen.DefineLabel();
}
foreach (Instruction instr in instructions) {
// Mark this instruction with a label, in case there's a branch
// instruction that jumps here.
ilGen.MarkLabel(labels[instr.Offset]);
// If this is the instruction that I want to replace (ldfld x)...
if (instr.OpCode == OpCodes.Ldfld) {
// ...get the get accessor for the accessed field (get_X())
// (I have the accessors in a dictionary; this isn't relevant),
MethodInfo safeReadAccessor = dataMembersSafeAccessors[((FieldInfo) instr.Operand).Name][0];
// ...instead of emitting the original instruction (ldfld x),
// emit a call to the get accessor,
ilGen.Emit(OpCodes.Callvirt, safeReadAccessor);
// Else (it's any other instruction), reemit the instruction, unaltered.
} else {
Reemit(instr, ilGen, labels);
}
}
}
Reemit
方法:
private void Reemit(Instruction instr, ILGenerator ilGen, Dictionary<int, Label> labels) {
// If the instruction doesn't have an operand, emit the opcode and return.
if (instr.Operand == null) {
ilGen.Emit(instr.OpCode);
return;
}
// Else (it has an operand)...
// If it's a branch instruction, retrieve the corresponding label (to
// which we want to jump), emit the instruction and return.
if (instr.OpCode.FlowControl == FlowControl.Branch) {
ilGen.Emit(instr.OpCode, labels[Int32.Parse(instr.Operand.ToString())]);
return;
}
// Otherwise, simply emit the instruction. I need to use the right
// Emit call, so I need to cast the operand to its type.
Type operandType = instr.Operand.GetType();
if (typeof(byte).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (byte) instr.Operand);
else if (typeof(double).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (double) instr.Operand);
else if (typeof(float).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (float) instr.Operand);
else if (typeof(int).IsAssignableFrom(operandType))
ilGen.Emit(instr.OpCode, (int) instr.Operand);
... // you get the idea. This is a pretty long method, all like this.
}
instr.Operand
是
SByte
,但是
Emit
期望类型为
Label
的操作数.因此需要
Dictionary labels
.
BeginExceptionBlock
发出它们。 ,
BeginCatchBlock
等
ILGenerator
.这变得越来越复杂。我想我可以做到:
MethodBody
有一个列表
ExceptionHandlingClause
其中应包含执行此操作所需的信息。但无论如何我都不喜欢这个解决方案,所以我将它保存为最后的解决方案。
MethodBody.GetILAsByteArray()
返回的字节数组,因为我只想将一条指令替换为另一条产生完全相同结果的相同大小的单条指令:它在堆栈上加载相同类型的对象,等等。所以不会有任何标签移位,一切都应该工作完全一样。我已经这样做了,替换了数组的特定字节,然后调用
MethodBuilder.CreateMethodBody(byte[], int)
,但我仍然遇到相同的异常错误,我仍然需要声明局部变量,否则我会得到一个错误......即使我只是简单地复制方法的主体并且不更改任何内容。
private void TransformMethod(MethodInfo methodInfo, Dictionary<string, MethodInfo[]> dataMembersSafeAccessors, ModuleBuilder moduleBuilder) {
ParameterInfo[] paramList = methodInfo.GetParameters();
Type[] args = new Type[paramList.Length];
for (int i = 0; i < args.Length; i++) {
args[i] = paramList[i].ParameterType;
}
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
methodInfo.Name, methodInfo.Attributes, methodInfo.ReturnType, args);
ILGenerator ilGen = methodBuilder.GetILGenerator();
IList<LocalVariableInfo> locals = methodInfo.GetMethodBody().LocalVariables;
foreach (LocalVariableInfo local in locals) {
ilGen.DeclareLocal(local.LocalType);
}
byte[] rawInstructions = methodInfo.GetMethodBody().GetILAsByteArray();
IList<Instruction> instructions = methodInfo.GetInstructions();
int k = 0;
foreach (Instruction instr in instructions) {
if (instr.OpCode == OpCodes.Ldfld) {
MethodInfo safeReadAccessor = dataMembersSafeAccessors[((FieldInfo) instr.Operand).Name][0];
// Copy the opcode: Callvirt.
byte[] bytes = toByteArray(OpCodes.Callvirt.Value);
for (int m = 0; m < OpCodes.Callvirt.Size; m++) {
rawInstructions[k++] = bytes[put.Length - 1 - m];
}
// Copy the operand: the accessor's metadata token.
bytes = toByteArray(moduleBuilder.GetMethodToken(safeReadAccessor).Token);
for (int m = instr.Size - OpCodes.Ldfld.Size - 1; m >= 0; m--) {
rawInstructions[k++] = bytes[m];
}
// Skip this instruction (do not replace it).
} else {
k += instr.Size;
}
}
methodBuilder.CreateMethodBody(rawInstructions, rawInstructions.Length);
}
private static byte[] toByteArray(int intValue) {
byte[] intBytes = BitConverter.GetBytes(intValue);
if (BitConverter.IsLittleEndian)
Array.Reverse(intBytes);
return intBytes;
}
private static byte[] toByteArray(short shortValue) {
byte[] intBytes = BitConverter.GetBytes(shortValue);
if (BitConverter.IsLittleEndian)
Array.Reverse(intBytes);
return intBytes;
}
[The CreateMethodBody method] is currently not fully supported. The user cannot supply the location of token fix ups and exception handlers.
最佳答案
我正在尝试做一件非常相似的事情。我已经尝试过你的 #1 方法,我同意,这会产生巨大的开销(虽然我没有准确地测量它)。
有一个DynamicMethod根据 MSDN,该类是“定义并表示可以编译、执行和丢弃的动态方法。丢弃的方法可用于垃圾收集。”
性能方面听起来不错。
与 ILReader库我可以转换正常 MethodInfo至 DynamicMethod .当您查看 ILReader 的 DyanmicMethodHelper 类的 ConvertFrom 方法时库,您可以找到我们需要的代码:
byte[] code = body.GetILAsByteArray();
ILReader reader = new ILReader(method);
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code);
reader.Accept(visitor);
ilInfo.SetCode(code, body.MaxStackSize);
关于c# - 替换方法的 MethodBody 中的指令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2803583/
对于像下面这样没有局部变量的简单方法 public static int Test1(short i, long j) { j = i + j; switch (j) {
(首先,这是一篇很长的文章,但别担心:我已经实现了所有这些,我只是在询问您的意见或可能的替代方案。) 我在执行以下操作时遇到问题;我很感激一些帮助: 我得到一个 Type作为参数。 我使用反射定义了一
我正在考虑实例方法 Object.Equals(Object)。使用反射,可以将此方法的 IL 作为字节数组获取,如下所示: var mi = typeof(object).GetMethod("Eq
我正在为一个简单的 .Net 打包程序开发一个解包程序。 我想使用 dnlib 从加载的程序集中执行一个方法。 我不能使用 System.Reflection,因为加壳器会打包原始可执行文件,然后它会
作为 Android 游戏开发者,我使用 MethodBody.GetILAsByteArray 方法获取 IL 代码,以检测给定方法是否已被用户使用其他应用程序(例如 Cheat Engine )修
我想知道是否有人可以向我解释一下。出于某种原因,lambda 的 MethodBody 似乎在调用后发生了变化。 以下代码片段使用一个外部变量创建一个简单的 lambda 并检查方法主体。 i
我是一名优秀的程序员,十分优秀!