Click here to Skip to main content
15,887,175 members
Articles / General Programming / Debugging

Injecting Code into .NET Applications

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
29 Dec 2013CPOL3 min read 9.4K   7  
In this post, I will present you my MDbg plugin (includes a command: inject) that adopts the funceval API and an example diagnostics case in which I used it.

Recently, I have been playing with Function Evaluation available in .NET debugging API. This functionality allows a managed debugger to inject some arbitrary code while the debuggee is stopped. The injected code might be simply a call to the object’s ToString method or to a property getter. As a developer, you profit from this service when you use Watch windows or when you run some code in the Immediate window in Visual Studio. In this post, I will present you my MDbg plug in (includes a command: inject) that adopts the funceval API and an example diagnostics case in which I used it.

Introducing Inject plug in

A detailed description on how to use plugins in MDbg can be found in my previous post. In order to use inject, you must attach to a process whose code you would like to modify. Then you need to check if you are at the point where you can use Function Evaluation: you can simply evaluate an expression such as System.Console.WriteLine. Function Evaluation might be disabled at a given location - the thread is not at a GC-safe point or the thread is in a sleep (you may read about other problems in a great Mark Stall's post devoted to this topic). If you encounter an error, you will need to find a better place to break into a process - setting a breakpoint might be a good choice here.

I tried to automate this process, but found too many different situations in which I would need to find the valid break location. For instance, for console applications, I was traversing stacks of all the running threads and placing breakpoints on managed stack frames. With this approach, I was able to break just after the current function return. Unfortunately, it did not work with web application where worker threads often do not have any managed frames and the best place to stop is at the beginning of a request (for instance ASP.global_asax.Application_BeginRequest). It would be again different with WCF applications. Finally, I decided I'll place the responsibility for a safe break location upon the user.

After the breakpoint is hit, we can perform code injection. You can call inject command passing as parameters a name of the assembly containing the code to be executed. Example call could be inject c:\temp\testasm.exe. Additionally, we may add an appdomain name in which we would like to run the code.

Under the hood, my plug in creates an evaluator object on the current active thread. Then, it evaluates the current appdomain (System.AppDomain.CurrentDomain) and calls ExecuteAssembly on it:

C#
CorEval eval = thread.CreateEval();
// if we do not create a local copy we will get write memory problem
String asm = String.Copy(assembly);
eval.NewString(asm);
debugger.Processes.Active.Go().WaitOne();
if (!(debugger.Processes.Active.StopReason is EvalCompleteStopReason))
{
    throw new InjectionException(
      "ERROR: injection was unsuccessful: assembly name evaluation failed.");
}
var assemblyName = (debugger.Processes.Active.StopReason 
  as EvalCompleteStopReason).Eval.Result.CastToReferenceValue();

MDbgFunction func = debugger.Processes.Active.ResolveFunctionNameFromScope(
            "System.AppDomain.get_CurrentDomain", appdomain);
eval.CallFunction(func.CorFunction, null);
debugger.Processes.Active.Go().WaitOne();
if (!(debugger.Processes.Active.StopReason is EvalCompleteStopReason))
{
    throw new InjectionException("ERROR: injection was unsuccessful: get_CurrentDomain failed");
}
var currentDomain = (debugger.Processes.Active.StopReason 
  as EvalCompleteStopReason).Eval.Result.CastToReferenceValue();

// call execute assembly
func = debugger.Processes.Active.ResolveFunctionNameFromScope(
  "System.AppDomain.ExecuteAssembly", appdomain);
eval.CallFunction(func.CorFunction, new[] { currentDomain, assemblyName });
debugger.Processes.Active.Go().WaitOne();

// now display result of the funceval
if (!(debugger.Processes.Active.StopReason is EvalCompleteStopReason))
{
    throw new InjectionException
    ("ERROR: injection was unsuccessful: ExecuteAssembly failed");
}

If we were successful, the code in the Main method of our assembly should have been executed in the target application appdomain. We can then detach from the target application and our changes will remain till it is restarted.

Code Injection in Application Diagnostics

There are many different situations in which you can use inject plug in. I will show you one usage example in which it proved useful. We have a Windows service running in production which is responsible for processing emails to our clients. One day, we observed that some of the emails are not going out. No error was logged, service CPU and memory usage was also normal. I prepared a simple code to inject into the app that will enable verbose tracing:

C#
using System;
using System.Diagnostics;
using System.Reflection;

public static class Program
{
    public static void Main() {
        var listener = new TextWriterTraceListener(@"C:\logs\email.log");
        Trace.AutoFlush = true;
        Trace.Listeners.Add(listener);

        // we need to fool a bit framework
        var asm = typeof(Trace).Assembly;
        var logtype = asm.GetType("System.Net.Logging");
        if (!(bool)logtype.GetProperty("On", 
                  BindingFlags.NonPublic | BindingFlags.Static).GetValue(null, null))
        {
            logtype.GetField("s_LoggingEnabled", 
              BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, true);
        }

        var traceSourceProps = 
          new[] { "Web", "Http", "Sockets", "WebSockets" };
        var sw = new SourceSwitch("sw", "Verbose");
        foreach (var tsp in traceSourceProps)
        {
            var source = (TraceSource)logtype.GetProperty(tsp, 
              BindingFlags.NonPublic | BindingFlags.Static).GetValue(null, null);
            source.Switch = sw;
            source.Listeners.Add(listener);
        }

        AppDomain.CurrentDomain.FirstChanceException += (o, e) => {
            Trace.TraceWarning("First chance exception occured: {0}", e.Exception);
        };
        Trace.TraceInformation("injected");
    }
}

After examining the log file, I was able to identify an error in one of the email templates. The main service was silently swallowing the exception and thus making it invisible to us.

Imagine other scenarios in which injecting might also work, such as dynamically enabling specific traces, registering proxy classes in the containers, adding global filters in ASP.NET MVC applications etc. The good thing about this way of diagnostics is that it stops your application only for a moment in order to inject the code. Keep in mind though that if MDbg terminates without detaching it will also terminate the target application. The source code of the inject plug in and the binaries are available for download from my .NET Diagnostics Toolkit site.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Poland Poland
Interested in tracing, debugging and performance tuning of the .NET applications.

My twitter: @lowleveldesign
My website: http://www.lowleveldesign.org

Comments and Discussions

 
-- There are no messages in this forum --