Sample app to print loaded modules.

Here’s a simple C# sample tool that runs an app and prints the modules loaded.

It’s effectively a highly simplified debugger and uses the ICorDebug debugging APIs as exposed by MDbg.


Here’s the code. [update: 1/26/05: updated code for final 2005 release of MDbg] You can create a a new console app, paste it in, and compile it against the MDbg object model (ref to Mdbgeng.dll, corapi.dll, corapi2.dll from the MDbg sample.):

 // Simple harness to dump Module load events.
using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Samples.Debugging.MdbgEngine;

namespace Stepper
    class Program
        [MTAThread] // MDbg is MTA threaded
        static void Main(string[] args)
            if (args == null || args.Length != 1)
                Console.WriteLine("Usage: PrintMods <filename>");
                Console.WriteLine("   Will run <filename> and print all modules loaded.");

            string nameApplication = args[0];
            Console.WriteLine("Run '{0}' and print loaded modules", nameApplication);

            MDbgEngine debugger = new MDbgEngine();
            debugger.Options.CreateProcessWithNewConsole = true;

            // Specify which debug events we want to receive. 
            // The underlying ICorDebug API will stop on all debug events.
            // The MDbgProcess object implements all of these callbacks, but only stops on a set of them 
            // based off the Options settings.
            // See CorProcess.DispatchEvent and MDbgProcess.InitDebuggerCallbacks for more details.
            debugger.Options.StopOnModuleLoad = true;
            // Launch the debuggee.            
            MDbgProcess proc = debugger.CreateProcess(nameApplication, "", DebugModeFlag.Debug, null);

            while (proc.IsAlive)
                // Let the debuggee run and wait until it hits a debug event.
                object o = proc.StopReason;

                // Process is now stopped. proc.StopReason tells us why we stopped.
                // The process is also safe for inspection.
                ModuleLoadedStopReason mr = o as ModuleLoadedStopReason;
                if (mr != null)
                    Console.WriteLine("Module loaded:" + mr.Module.CorModule.Name);                    

        } // end main


Sample output:

Run 'c:\dev\misc\hello\hello.exe' and print all loaded modules.

Module loaded:C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll

Module loaded:c:\dev\misc\hello\hello.exe

Module loaded:c:\dev\misc\hello\b.dll



MDbg background:

A managed debugger gets a stream of debug events from the managed debuggee. Debug events include notifications such as Thread created / exited, module load / unload / process exit / breakpoint hit, etc.  The debuggee is stopped after each debug event until the debugger continues it. The debugger can inspect it (run callstacks, view variables, etc) during this window.


Here’s the stack:

  1. ICorDebug: (lowest level) unmanaged com classic debugging API
  2. CorApi layer: complete managed wrapper around ICorDebug.
  3. MDbg object model layer: additional logic and synchronization on top of CorApi layer. This includes MdbgEngine and MdbgProcess objects.
  4. Application: (highest level) consumes MDbg layer to do intelligent things.


At the ICorDebug level, all debug events are dispatched via the ICorDebugManagedCallback interface from another thread. MDbg implements that interface in managed code, and then has glue code to convert that into managed events (see Debugger.cs).


The MDbg layer may hide some debug events and just uses them for internal processing and not propagate them up to the application. The application can set various knobs and register for various hooks to control the event processing. (We don’t have a good uniform general purpose way to do this in the current MDbg sample, although this is improved for our beta 2 drop).


How does the code work?

This tool debugs an app, but just sniffs for the module load debug events. You could think of it as a very simplified or highly specialized debugger.


The application calls debugger.Options.StopOnModuleLoad to tell the MDbg layer to not hide module load events.


The proc.Go().WaitOne() runs the debuggee until it hits a debug event. the proc.Go()actually resumes the debuggee and returns a waithandle that gets signaled once a debug event is hit. This synchronization is all handled in the MDbg layer.


The MDbg layer stores the debug event in the proc.StopReason property. If it’s a module load reason, then we print it out. We could also do further inspection, such as printing the callstack.


We do all this in a loop while the process is still alive. We check the IsAlive property, but there’s also an exit process debug event we could have sniffed for.


Other possibilities:

There are other ways to print the loaded modules.

1) The profiling APIs overlap the debugging APIs here and offer module inspection notifications.


2) Or one could try using the native debugging APIs, since many managed modules are also native modules. This is dangerous because it builds on the faulty assumption that managed modules are always built on top of native modules. This breaks down in cases (eg, in-memory modules).


3) This sample consumes the MDbg object model. A harness could also be written against the other layers, such as the CorApi layer or the ICorDebug COM-classic interfaces directly. These solutions would be related to this solution.


4) Instead of writing a dedicated harness, use a debugger script to MDbg.exe. This solution is potentially much more scalable because it can leverage MDbg functionality for future features. For example, with this harness, you can’t stop the harness at a particular module load and do intensive process inspection.