Mono.Cecil: Insert a log statement in method's beginning - c#

I am using Mono.Cecil to edit my target method's IL code so that I can log that method's entry point, without editing the actual code.
I am able to insert a call instruction to a method which can perform logging operation.
But I don't know how to log my target method's input parameters.
In short i want to insert an instruction in the target method by changing it's IL code to do a log or say print operation to log the input parameter values passed to that method.
I tried a basic program as sample.
public class Target
{
// My target method.
public void Run(int arg0, string arg1)
{
Console.WriteLine("Run method body");
}
}
public static class Trace{
// This is my log method, which i want to call in begining of Run() method.
public void LogEntry(string methodName, object[] params)
{
System.Console.WriteLine("******Entered in "+ methodName+" method.***********")
// With params :......
//
}
}
Source program.
public class Sample
{
private readonly string _targetFileName;
private readonly ModuleDefinition _module;
public ModuleDefinition TargetModule { get { return _module; } }
public Sample(string targetFileName)
{
_targetFileName = targetFileName;
// Read the module with default parameters
_module = ModuleDefinition.ReadModule(_targetFileName);
}
public void Run(string type, string method)
{
// Retrive the target class.
var targetType = _module.Types.Single(t => t.Name == type);
// Retrieve the target method.
var runMethod = targetType.Methods.Single(m => m.Name == method);
// Get a ILProcessor for the Run method
var processor = runMethod.Body.GetILProcessor();
// get log entry method ref to create instruction
var logEntryMethodReference = targetType.Methods.Single(m => m.Name == "LogEntry");
// Import ..
//
var newInstruction = processor.Create(OpCodes.Call, logEntryMethodReference);
var firstInstruction = runMethod.Body.Instructions[0];
processor.InsertBefore(firstInstruction, newInstruction);
// Write the module with default parameters
_module.Write(_targetFileName);
}
}

Well, this was interesting :)
Here's my working sample (comments in the code, feel free to ask anything, if not clear):
Modified sample (to actually write out the parameters):
public class Target
{
// My target method.
public void Run(int arg0, string arg1)
{
Console.WriteLine("Run method body");
}
}
public static class Trace
{
// This is my log method, which i want to call in begining of Run() method.
public static void LogEntry(string methodName, object[] parameters)
{
Console.WriteLine("******Entered in " + methodName + " method.***********");
Console.WriteLine(parameters[0]);
Console.WriteLine(parameters[1]);
}
}
Source program to handle IL injection:
public class Sample
{
private readonly string _targetFileName;
private readonly ModuleDefinition _module;
public ModuleDefinition TargetModule { get { return _module; } }
public Sample(string targetFileName)
{
_targetFileName = targetFileName;
// Read the module with default parameters
_module = ModuleDefinition.ReadModule(_targetFileName);
}
public void Run(string type, string method)
{
// Retrive the target class.
var targetType = _module.Types.Single(t => t.Name == type);
// Retrieve the target method.
var runMethod = targetType.Methods.Single(m => m.Name == method);
// Get a ILProcessor for the Run method
// get log entry method ref to create instruction
var logEntryMethodReference = _module.Types.Single(t => t.Name == "Trace").Methods.Single(m => m.Name == "LogEntry");
List<Instruction> newInstructions = new List<Instruction>();
var arrayDef = new VariableDefinition(new ArrayType(_module.TypeSystem.Object)); // create variable to hold the array to be passed to the LogEntry() method
runMethod.Body.Variables.Add(arrayDef); // add variable to the method
var processor = runMethod.Body.GetILProcessor();
newInstructions.Add(processor.Create(OpCodes.Ldc_I4, runMethod.Parameters.Count)); // load to the stack the number of parameters
newInstructions.Add(processor.Create(OpCodes.Newarr, _module.TypeSystem.Object)); // create a new object[] with the number loaded to the stack
newInstructions.Add(processor.Create(OpCodes.Stloc, arrayDef)); // store the array in the local variable
// loop through the parameters of the method to run
for (int i = 0; i < runMethod.Parameters.Count; i++)
{
newInstructions.Add(processor.Create(OpCodes.Ldloc, arrayDef)); // load the array from the local variable
newInstructions.Add(processor.Create(OpCodes.Ldc_I4, i)); // load the index
newInstructions.Add(processor.Create(OpCodes.Ldarg, i+1)); // load the argument of the original method (note that parameter 0 is 'this', that's omitted)
if (runMethod.Parameters[i].ParameterType.IsValueType)
{
newInstructions.Add(processor.Create(OpCodes.Box, runMethod.Parameters[i].ParameterType)); // boxing is needed for value types
}
else
{
newInstructions.Add(processor.Create(OpCodes.Castclass, _module.TypeSystem.Object)); // casting for reference types
}
newInstructions.Add(processor.Create(OpCodes.Stelem_Ref)); // store in the array
}
newInstructions.Add(processor.Create(OpCodes.Ldstr, method)); // load the method name to the stack
newInstructions.Add(processor.Create(OpCodes.Ldloc, arrayDef)); // load the array to the stack
newInstructions.Add(processor.Create(OpCodes.Call, logEntryMethodReference)); // call the LogEntry() method
foreach (var newInstruction in newInstructions.Reverse<Instruction>()) // add the new instructions in referse order
{
var firstInstruction = runMethod.Body.Instructions[0];
processor.InsertBefore(firstInstruction, newInstruction);
}
// Write the module with default parameters
_module.Write(_targetFileName);
}
}

Related

Is it possible to infer this generic type, for type-safe callback?

Is there a way to get rid of the CS0411 error below, and not have to explicitly state the type?
Also do not want to have to use reflection.
var router = new ExampleRouter();
var controller = new ExampleWebController();
// compiles, but not elegant
router.MapPost<string>("/api/bar", controller.ProcessString);
// error CS0411: can't infer type
router.MapPost("/api/foo", controller.ProcessString);
class ExampleWebController {
public ExampleWebController() { }
public bool ProcessNumber(int v) { return true; }
public bool ProcessString(string v) { return true; }
}
class ExampleRouter {
public ExampleRouter() { }
public void MapPost<TBody>(string path, Func<TBody, bool> handler) {
// Save typeof(TBody), since TBody will actually be a class type we
// will construct for each callback
var body_type = typeof(TBody);
}
}
Yep, as someone's mentioned in comments one solution is to pass in the data as a parameter:
public void MapPost<TBody>(string path, Func<TBody, bool> handler, Tbody data) {
object dataType = data.GetType();
}
The reason your code is "inelegant" as you've said, is because the order of your generic arguments specifies an input type (TBody) and an output type (bool). However, in your calls to MapBody, you are only providing methods that return boolean results, so that the compiler doesn't know what to use for the value of TBody.
This is the origin of the CS0411 error you are receiving. The only way around it is to provide a generic type argument at the point of call.
This is why this code works, and should be what you use going forward:
var router = new ExampleRouter();
var controller = new ExampleWebController();
// compiles, but not elegant
router.MapPost<string>("/api/bar", controller.ProcessString);
A bit of a self answer here. If I change it to this, the MapPost() code looks elegant, which was my goal. HOWEVER, I have lost some compile time checking -- for example anything can be passed in as a "handler". I will post a new question on how I refine this.
var router = new ExampleRouter();
var controller = new ExampleWebController();
// We will have to do runtime validation that controller.ProcessString is a
// legal callback (which isn't ideal, but still fine).
// An improvement would be to add some kind of generic constraints?
router.MapPost("/api/foo", controller.ProcessString);
class ExampleWebController {
public ExampleWebController() { }
public bool ProcessNumber(int v) { return true; }
public bool ProcessString(string v) { return true; }
}
class ExampleRouter {
public ExampleRouter() { }
public void MapPost<TFunc>(string path, TFunc handler) {
var func_type = typeof(TFunc);
Console.WriteLine(func_type); // Prints "System.Func"
var args = func_type.GetGenericArguments();
foreach (var arg in args) {
// Prints "System.String", "System.Boolean"...awesome
Console.WriteLine(arg);
}
}
}

Runtime method modification

I have the following code, using Mono.Cecil:
{
ModuleDefinition module = ModuleDefinition.ReadModule("library.dll");
TypeDefinition type1 = module.Types.Where(t => "Namespace.Destination".Equals(t.FullName)).Single();
TypeDefinition type2 = module.Types.Where(t => "Namespace.Target".Equals(t.FullName)).Single();
MethodDefinition method1 = type1.Methods.Where(m => "Test".Equals(m.Name)).Single();
MethodDefinition method2 = type2.Methods.Where(m => "Test".Equals(m.Name)).Single();
var processor = methodTesta1.Body.GetILProcessor();
var newInstruction = processor.Create(OpCodes.Call, methodTesta2);
var firstInstruction = methodTesta1.Body.Instructions[0];
processor.Replace(firstInstruction, newInstruction);
}
namespace Namespace
{
public class Destination
{
public String Test()
{
Console.Write("Destination method");
}
}
public class Target
{
public String Test()
{
Console.Write("Target Method");
}
}
}
I'd not like to create a new "dll" file or overwrite the current, I want to modify class only at runtime.
How can I "persist" the modification and create a new instance of Destination class with modified method?
Is there a way to do it?
EDIT: The objective is execute a different method body, when certain method is called, wich return a certain type.

How to get real value from the ParameterInfo[] in Reflection

Actually I have few question to be easier to understand what I'm asking about I have to show my code first.
public static void Main(string[] args)
{
CustomClass customClass = new CustomClass();
customClass.SomeMethod("asdas", true, 30.5);
}
public class CustomClass
{
[MyAttribute]
public Boolean SomeMethod(String a, Boolean b, Double c)
{
return true;
}
}
public class MyAttribute : Attribute
{
public MyAttribute()
{
SomeIntercepter.InterceptEverything();
}
public void DoSomethingBeforeMethodexecutes()
{
....
}
public void DoSomethingAfterMethodExecutes()
{
....
}
}
public class SomeIntercepter
{
public static void InterceptEverything()
{
StackTrace stackTrace = new StackTrace();
var method = stackTrace.GetFrame(2).GetMethod();
var parameters = method.GetParameters();
if (parameters.Length > 3)
return;
String cacheKey = method.Name;
for (int i = 0; i < parameters.Length; i++)
{
//HOW TO GET THE PARAMETER DATA ASSIGNED
cacheKey += "_" + parameters[i];
}
}
}
So what I try to do. I try to intercept method on each it call and do something based on the incomming data of the method which is marked with [MyAttribute]. So I access the method through the StackTrace and try to get all incomming data with GetParameters.
And here is my questions:
1) How to object[] of all incomming data of the SomeMethod in the InterceptEverything()
2) How to say to the MyAttribute to run before marked method with MyAttribute to run method DoSomethingBeforeMethodexecutes()
3) How to say to the MyAttribute to run after marked method with MyAttribute to run method DoSomethingAfterMethodexecutes()
Thanks for any advice.
You cannot get the values from any particular frame via ParameterInfo. This facility does not exist.
Also: attributes do not execute. Unless you are using something like postsharp, they are just information. To get them to execute you must explicitly write reflection code to do that.

Pass delegate together with parameter to a function

I want enqueue a list of tasks and then perform on certain event. Code:
internal class MyClass
{
private Queue<Task> m_taskQueue;
protected MyClass()
{
m_taskQueue = new Queue<Task>();
}
public delegate bool Task(object[] args);
public void EnqueueTask(Task task)
{
m_taskQueue.Enqueue(task);
}
public virtual bool Save()
{
// save by processing work queue
while (m_taskQueue.Count > 0)
{
var task = m_taskQueue.Dequeue();
var workItemResult = task.Invoke();
if (!workItemResult)
{
// give up on a failure
m_taskQueue.Clear();
return false;
}
}
return true;
}
}
Each delegate task may have their own list of parameters: Task(object[] args). My question is how to pass the parameter to each task for the task queue?
Okay, now we have a bit more information, it sounds like your EnqueueTask method should actually look like this:
public void EnqueueTask(Task task, object[] values)
Right?
For starters I would avoid using the name Task, which is already part of the core of .NET 4 and will become very prominent in .NET 5. As Joshua said, you've basically got a Func<object[], bool>.
Next, you could keep two lists - one for the delegates and one for the values, but it's easier just to keep a Queue<Func<bool>> like this:
private readonly Queue<Func<bool>> taskQueue = new Queue<Func<bool>>();
public void EnqueueTask(Task task, object[] values)
{
taskQueue.Enqueue(() => task(values));
}
Then the rest of your code will actually work "as is". The lambda expression there will capture values and task, so when you invoke the Func<bool>, it will supply those values to the original delegate.
Provided understanding your question correctly you just pass the information like a normal call. Have you considered using Func? You can just pass arguments to the Task.Invoke i.e. Task.Invoke([arguments here as a *single* object array]).
object[] arguments = null; // assign arguments to something
var workItemResult = task.Invoke(arguments);
Below is an example with the Func type.
internal class MyClass
{
private Queue<Func<object[], bool>> m_taskQueue;
protected MyClass()
{
m_taskQueue = new Queue<Func<object[], bool>>();
}
public void EnqueueTask(Func<object[], bool> task)
{
m_taskQueue.Enqueue(task);
}
public virtual bool Save()
{
object[] arguments = null; // assign arguments to something
// save by processing work queue
while (m_taskQueue.Count > 0)
{
var task = m_taskQueue.Dequeue();
var workItemResult = task(arguments);
if (!workItemResult)
{
// give up on a failure
m_taskQueue.Clear();
return false;
}
}
return true;
}
}

Function as an argument to a method

You can do anonymous functions in C# like you can in JavaScript:
JavaScript:
var s = (function ()
{
return "Hello World!";
}());
C#:
var s = new Func<String>(() =>
{
return "Hello World!";
})();
... In JavaScript you can pass functions to be executed by other functions. On top of that; you can pass parameters to the function which gets executed:
var f = function (message) // function to be executed
{
alert(message);
};
function execute(f) // function executing another function
{
f("Hello World!"); // executing f; passing parameter ("message")
}
Is the above example possible in C#?
Update
Use-case: I am iterating over a bunch of database, logging specific entities. Instead of calling my second function (F()) inside Log() of Logger, I'd like to call F() outside the class.
... Something along the lines of:
public void F(String databaseName)
{
}
public class Logger
{
public void Log(Function f)
{
var databaseName = "";
f(databaseName);
}
}
Absolutely - you just need to give the method an appropriate signature:
public void Execute(Action<string> action)
{
action("Hello world");
}
...
Execute(x => Console.WriteLine(x));
Note that you do have to specify the particular delegate type in the parameter - you can't just declare it as Delegate for example.
EDIT: Your database example is exactly the same as this - you want to pass in a string and not get any output, which is exactly what Action<string> does. Except if you're trying to call an existing method (F() in your code) you don't even need a lambda expression - you can use method group conversions instead:
public void F(String databaseName)
{
}
public class Logger
{
public void Log(Action<string> f)
{
var databaseName = "";
f(databaseName);
}
}
// Call it like this:
Logger logger = new Logger(...);
logger.Log(F);
You can pass delegate:
var f = (Action<string>)
(x =>
{
Console.WriteLine(x);
}
);
var execute = (Action<Action<string>>)
(cmd =>
{
cmd("Hello");
}
);
execute(f);
according your Update part:
you need a container to keep your functions
IList<Action<string>> actionList = new List<Action<Sstring>>();
in your Log() function you can add your F() to the container:
actionList.Add(F);
then invoke the function(s) somewhere outside:
foreach (Action<string> func in actionList)
{
func("databasename");
}
Like:
var s = new Func<String, string>((string name) =>
{
return string.Format("Hello {0}!", name);
});
?

Categories

Resources