使用 c# 将秒表注入所有使用 Mono.Cecil 的 dll 方法,包括具有多个 Return 语句的方法
Inject stopwatch using c# into all dll methods using Mono.Cecil, including methods with multiple Return statements
Case 1 Case 2 Case 3 Case 4
Objective:
使用注入器代码,我试图在目标 dll 的所需代码位置注入秒表方法(在秒表 dll 中),以便计算目标 dll 中每个方法所花费的时间,这可能是也可能不是无效的方法,它可能有多个 return 语句。
目标 dll
public class targetDll
{
void func1(){
//Inject Stopwatch_start(); method here
int a = 3;
int b = 4;
int temp;
temp = a;
a = b;
b =temp;
if (a + b > 2)
{
Console.WriteLine("function____1");
}
#Stopwatch_stop() //Inject stop time here
}
String func2(){
//Inject Stopwatch_start(); method here
int a = 3;
int b = 4;
int c = 5;
int temp;
temp = a;
a = b;
b = c;
c = temp;
if (a + b > 5)
{
Console.WriteLine("function____2");
//inject Stopwatch_stop() method here
return ;
}
a = temp;
//inject Stopwatch_stop(); method here
return;
}
}
源dll(秒表dll)
public static class stopwatch_class
{
static System.Diagnostics.Stopwatch stopwatch_obj = new System.Diagnostics.Stopwatch();
public static void stopwatch_start()
{
stopwatch_obj.Start();
}
public static void stopwatch_stop()
{
stopwatch_obj.Stop();
Console.WriteLine(stopwatch_obj.ElapsedMilliseconds);
}
}
}
喷油器代码
class Trial_injector
{
static void Main(string[] args)
{
var start_method = (dynamic)null;
var stop_method = (dynamic)null;
AssemblyDefinition target_assembly = AssemblyDefinition.ReadAssembly("targetDll.dll",
new ReaderParameters { ReadWrite = true });
var target_modules = target_assembly.MainModule;
TypeDefinition[] target_module = target_modules.Types.ToArray();
AssemblyDefinition source_assembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", new
ReaderParameters { ReadWrite = true });
var source_modules = source_assembly.MainModule;
TypeDefinition[] source_module = source_modules.Types.ToArray();
foreach (var type in source_module)
{
foreach (var method in type.Methods)
{
if (method.Name == "stopwatch_start")
{
start_method = method;
}
if (method.Name == "stopwatch_stop")
{
stop_method = method;
}
}
}
foreach(var module_ in target_module)
{
foreach(var method_ in module_.Methods)
{
String stg="hello_world";
var processor2 = method_.Body.GetILProcessor();
var first_instruction = method_.Body.Instructions.First();
var last_instruction = method_.Body.Instructions.Last();
var ldstr = processor2.Create(OpCodes.Ldstr, stg);
var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
processor2.InsertBefore(first_instruction, ldstr);
processor2.InsertAfter(first_instruction, call);
processor2.InsertBefore(last_instruction, ldstr);
processor2.InsertBefore(last_instruction, call2);
}
}
target_assembly.Write();
}
您的代码几乎是正确的。需要做的修改很少。
不确定为什么需要 ldstr
操作码,因为它在任何地方都不需要。对于您想要插入 before 的调用,而不是在第一个操作码之后。至于最后一条指令,您可以使用 InsertBefore
。所以最终的代码可能是这样的:
foreach (var module_ in target_module)
{
foreach (var method_ in module_.Methods)
{
var processor2 = method_.Body.GetILProcessor();
var first_instruction = method_.Body.Instructions.First();
var last_instruction = method_.Body.Instructions.Last();
var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
processor2.InsertBefore(first_instruction, call);
processor2.InsertBefore(last_instruction, call2);
}
}
但这不适用于某些早期的 return。为什么?早期的 return 被编码为 br
或 br_s
操作码到 ret
在过程结束时,如果我们在 ret 之前注入我们的 call
returns 将跳过它。在您的示例中,不需要它,因为此代码已转换为 if-else
并且我们在两种情况下都正确匹配了分支。但是图像我们有这样的代码:
int a = 3;
if (a == 3)
{
return; // very early return here
}
// the rest as in original one
我们不会看到为此方法打印的经过时间,因为 return 将在我们注入的调用之后指导执行。我们在这里需要做的是更新所有负责早期 returns 的分支指令(因此它们跳转到 ret
操作码)并将它们指向我们的调用。我们可以通过以下方式做到这一点:
foreach (var bodyInstruction in method_.Body.Instructions)
{
if (bodyInstruction.OpCode != OpCodes.Br && bodyInstruction.OpCode != OpCodes.Br_S) continue;
if (((Instruction)bodyInstruction.Operand).OpCode != OpCodes.Ret) continue;
bodyInstruction.Operand = call2;
}
所以我们在这里做的是扫描所有操作码,看看是否有 br
或 br_s
跳转到 return 我们更新它以跳转到我们的调用反而。中提琴
注意:使用 Elapsed
而不是 ElapsedMilliseconds
,因为前者给出全零。
完整代码:
var start_method = (dynamic) null;
var stop_method = (dynamic) null;
AssemblyDefinition target_assembly = AssemblyDefinition.ReadAssembly("target.exe", new ReaderParameters {ReadWrite = true});
var target_modules = target_assembly.MainModule;
TypeDefinition[] target_module = target_modules.Types.ToArray();
AssemblyDefinition source_assembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", new ReaderParameters {ReadWrite = true});
var source_modules = source_assembly.MainModule;
TypeDefinition[] source_module = source_modules.Types.ToArray();
foreach (var type in source_module)
{
foreach (var method in type.Methods)
{
if (method.Name == "stopwatch_start")
{
start_method = method;
}
if (method.Name == "stopwatch_stop")
{
stop_method = method;
}
}
}
foreach (var module_ in target_module)
{
foreach (var method_ in module_.Methods)
{
var processor2 = method_.Body.GetILProcessor();
var first_instruction = method_.Body.Instructions.First();
var last_instruction = method_.Body.Instructions.Last();
var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
processor2.InsertBefore(first_instruction, call);
processor2.InsertBefore(last_instruction, call2);
foreach (var bodyInstruction in method_.Body.Instructions)
{
if (bodyInstruction.OpCode != OpCodes.Br && bodyInstruction.OpCode != OpCodes.Br_S) continue;
if (((Instruction)bodyInstruction.Operand).OpCode != OpCodes.Ret) continue;
bodyInstruction.Operand = call2;
}
}
}
target_assembly.Write();
上的自我推销
我碰巧用 Mono.Cecil 录制了两个关于这样做的视频(以有点不同的方式)。你可以找到它 Writing simple .NET execution tracer with Mono.Cecil and Instrumenting .NET assemblies to measure method's execution time in with Mono.Cecil。
关闭自我推销
Case 1 Case 2 Case 3 Case 4
Objective: 使用注入器代码,我试图在目标 dll 的所需代码位置注入秒表方法(在秒表 dll 中),以便计算目标 dll 中每个方法所花费的时间,这可能是也可能不是无效的方法,它可能有多个 return 语句。
目标 dll
public class targetDll
{
void func1(){
//Inject Stopwatch_start(); method here
int a = 3;
int b = 4;
int temp;
temp = a;
a = b;
b =temp;
if (a + b > 2)
{
Console.WriteLine("function____1");
}
#Stopwatch_stop() //Inject stop time here
}
String func2(){
//Inject Stopwatch_start(); method here
int a = 3;
int b = 4;
int c = 5;
int temp;
temp = a;
a = b;
b = c;
c = temp;
if (a + b > 5)
{
Console.WriteLine("function____2");
//inject Stopwatch_stop() method here
return ;
}
a = temp;
//inject Stopwatch_stop(); method here
return;
}
}
源dll(秒表dll)
public static class stopwatch_class
{
static System.Diagnostics.Stopwatch stopwatch_obj = new System.Diagnostics.Stopwatch();
public static void stopwatch_start()
{
stopwatch_obj.Start();
}
public static void stopwatch_stop()
{
stopwatch_obj.Stop();
Console.WriteLine(stopwatch_obj.ElapsedMilliseconds);
}
}
}
喷油器代码
class Trial_injector
{
static void Main(string[] args)
{
var start_method = (dynamic)null;
var stop_method = (dynamic)null;
AssemblyDefinition target_assembly = AssemblyDefinition.ReadAssembly("targetDll.dll",
new ReaderParameters { ReadWrite = true });
var target_modules = target_assembly.MainModule;
TypeDefinition[] target_module = target_modules.Types.ToArray();
AssemblyDefinition source_assembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", new
ReaderParameters { ReadWrite = true });
var source_modules = source_assembly.MainModule;
TypeDefinition[] source_module = source_modules.Types.ToArray();
foreach (var type in source_module)
{
foreach (var method in type.Methods)
{
if (method.Name == "stopwatch_start")
{
start_method = method;
}
if (method.Name == "stopwatch_stop")
{
stop_method = method;
}
}
}
foreach(var module_ in target_module)
{
foreach(var method_ in module_.Methods)
{
String stg="hello_world";
var processor2 = method_.Body.GetILProcessor();
var first_instruction = method_.Body.Instructions.First();
var last_instruction = method_.Body.Instructions.Last();
var ldstr = processor2.Create(OpCodes.Ldstr, stg);
var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
processor2.InsertBefore(first_instruction, ldstr);
processor2.InsertAfter(first_instruction, call);
processor2.InsertBefore(last_instruction, ldstr);
processor2.InsertBefore(last_instruction, call2);
}
}
target_assembly.Write();
}
您的代码几乎是正确的。需要做的修改很少。
不确定为什么需要 ldstr
操作码,因为它在任何地方都不需要。对于您想要插入 before 的调用,而不是在第一个操作码之后。至于最后一条指令,您可以使用 InsertBefore
。所以最终的代码可能是这样的:
foreach (var module_ in target_module)
{
foreach (var method_ in module_.Methods)
{
var processor2 = method_.Body.GetILProcessor();
var first_instruction = method_.Body.Instructions.First();
var last_instruction = method_.Body.Instructions.Last();
var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
processor2.InsertBefore(first_instruction, call);
processor2.InsertBefore(last_instruction, call2);
}
}
但这不适用于某些早期的 return。为什么?早期的 return 被编码为 br
或 br_s
操作码到 ret
在过程结束时,如果我们在 ret 之前注入我们的 call
returns 将跳过它。在您的示例中,不需要它,因为此代码已转换为 if-else
并且我们在两种情况下都正确匹配了分支。但是图像我们有这样的代码:
int a = 3;
if (a == 3)
{
return; // very early return here
}
// the rest as in original one
我们不会看到为此方法打印的经过时间,因为 return 将在我们注入的调用之后指导执行。我们在这里需要做的是更新所有负责早期 returns 的分支指令(因此它们跳转到 ret
操作码)并将它们指向我们的调用。我们可以通过以下方式做到这一点:
foreach (var bodyInstruction in method_.Body.Instructions)
{
if (bodyInstruction.OpCode != OpCodes.Br && bodyInstruction.OpCode != OpCodes.Br_S) continue;
if (((Instruction)bodyInstruction.Operand).OpCode != OpCodes.Ret) continue;
bodyInstruction.Operand = call2;
}
所以我们在这里做的是扫描所有操作码,看看是否有 br
或 br_s
跳转到 return 我们更新它以跳转到我们的调用反而。中提琴
注意:使用 Elapsed
而不是 ElapsedMilliseconds
,因为前者给出全零。
完整代码:
var start_method = (dynamic) null;
var stop_method = (dynamic) null;
AssemblyDefinition target_assembly = AssemblyDefinition.ReadAssembly("target.exe", new ReaderParameters {ReadWrite = true});
var target_modules = target_assembly.MainModule;
TypeDefinition[] target_module = target_modules.Types.ToArray();
AssemblyDefinition source_assembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", new ReaderParameters {ReadWrite = true});
var source_modules = source_assembly.MainModule;
TypeDefinition[] source_module = source_modules.Types.ToArray();
foreach (var type in source_module)
{
foreach (var method in type.Methods)
{
if (method.Name == "stopwatch_start")
{
start_method = method;
}
if (method.Name == "stopwatch_stop")
{
stop_method = method;
}
}
}
foreach (var module_ in target_module)
{
foreach (var method_ in module_.Methods)
{
var processor2 = method_.Body.GetILProcessor();
var first_instruction = method_.Body.Instructions.First();
var last_instruction = method_.Body.Instructions.Last();
var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
processor2.InsertBefore(first_instruction, call);
processor2.InsertBefore(last_instruction, call2);
foreach (var bodyInstruction in method_.Body.Instructions)
{
if (bodyInstruction.OpCode != OpCodes.Br && bodyInstruction.OpCode != OpCodes.Br_S) continue;
if (((Instruction)bodyInstruction.Operand).OpCode != OpCodes.Ret) continue;
bodyInstruction.Operand = call2;
}
}
}
target_assembly.Write();
上的自我推销
我碰巧用 Mono.Cecil 录制了两个关于这样做的视频(以有点不同的方式)。你可以找到它 Writing simple .NET execution tracer with Mono.Cecil and Instrumenting .NET assemblies to measure method's execution time in with Mono.Cecil。
关闭自我推销