Click here to Skip to main content
Click here to Skip to main content

Undocumented Fusion

, 3 Jul 2003
Rate this:
Please Sign up or sign in to vote.
A simple tool written in C# is presented that uses undocumented Fusion classes to view the contents of the Fusion assembly caches (Gac, Zap & Download) and the Fusion application history.
<!-- Add the rest of your HTML here -->

Introduction

In this article, we'll take a look at the undocumented and internal (but useful) class called Microsoft.CLRAdmin.Fusion that resides in the mscorcfg assembly (a core component in the .NET framework). Using this class, we'll be able to examine the contents of the three Fusion assembly caches as well as the managed application history. Sure - gacutil already provides some of this capability, but we'll access the functionality programmatically and (hopefully) learn some .NET spelunking techniques along the way. 

Disclaimers: The information presented here is intended for educational purposes only - using any of the internal Framework classes in production code is just asking for trouble, especially considering the relatively low version number (and implied maturity level) of the current framework. In addition, the code presented with the article has only been tested with version 1.1 of the .NET Framework.

Fusion

First off, what exactly is Fusion? Fusion is the internal name of the CLR assembly loader/binder subsystem. Loading an assembly in a managed app is somewhat more complicated than loading a DLL from a non-managed app. When you load a DLL using LoadLibrary/Ex, you either specify a path that indicates where the DLL lives or you rely on the LoadLibrary search path logic. This simple binding mechanism, of course, assists in creating the 'DLL Hell' that the .NET designers wanted to avoid. The assembly binding process, on the other hand, involves a number of configurable inputs. It is Fusion that is using those inputs to make decisions about where the bits for a requested assembly actually come from. In order to optimize some of that decision making process, Fusion maintains several assembly caches, of which at least one should be familiar to you (if you're reading this article):

  • the GAC, or Global Assembly Cache - all shared assemblies live here
  • the Download Cache - when you execute an assembly from a URL, this is where the downloaded assemblies end up
  • the 'ZAP' Cache - this cache seems to serve as the home for pre-compiled native assembly images that are produced by NGEN

Speaking of the GAC, some readers may not be aware that the Explorer shell extension that presents a custom view of the GAC & Zap cache can be disabled by adding a binary value named 'DisableCacheViewer' to the registry key HKLM\Software\Microsoft\Fusion and setting it to a non-zero value. This comes in handy if you want to see the physical directory structure in Explorer.

Spelunking the Framework

Now that we have an overly simplified view of Fusion and it's caches, let's turn our attention to the code - what it does and how it works.

Readers who have used the .NET configuration and wizard MMC snap-ins may have wondered how some of that functionality was implemented. The first time I saw the 'Fix an Application' applet, for example, I was immediately curious about the source of the application list since it was presented so quickly (that is, after I finished chuckling about the name of that applet). Using Process Explorer, I quickly discovered that this particular applet is housed in a managed executable called ConfigWizards.exe. Then using a hand-rolled dependency viewer for .NET (which may be the topic of a future article, see Fig 1 for a teaser), I stumbled upon the mscorcfg assembly. Most of the publicly exposed classes in mscorcfg are related to UI logic used by the control panel applet and, as such, are not generally useful outside of that context. However, taking a closer look at mscorcfg with the absolutely indispensable Reflector tool reveals a few gems. One such gem is the Microsoft.CLRAdmin.Fusion class which puts a pretty (and convenient) face over the lower-level and undocumented (and much less convenient) fusion.dll native exports CreateAssemblyEnum & CreateHistoryReader.

At this point, some readers may have popped open Reflector, looked at the CLRAdmin namespace for themselves and didn't see the Fusion class. After going to Reflector's option settings and checking all the currently unchecked visibility settings (hint, hint), they now may be wondering: "What good is an internal or private class? You can't use it outside that assembly or class, right?" Well, not exactly. You see, there's this marvelous facility in the framework called, you guessed it, Reflection. Among other things, reflection allows a caller to create instances and invoke members on types that would otherwise be inaccessible. Recall (or learn if you're not aware of it) that access modifiers like private and internal are only enforced at the language level, not at the IL level. The EE (execution environment) is only concerned with whether the caller has the appropriate ReflectionPermission grant (see the MemberAccess & TypeInformation ReflectionPermissionFlags for more information). As long as the caller has the appropriate permissions, the EE could care less if the member being accessed is marked as superdupertopsecret (ok, I made that up) in the source language, because the caller is trusted.

Update: Greg Fee, who is a primary developer on the CLR CAS team, started blogging recently and this blog entry has more information on the importance of reflection permissions.

Armed with Reflector (did I mention that this is an exceedingly useful tool?) and the knowledge that reflection lets us use any type in any assembly, let's take a closer look at Microsoft.CLRAdmin.Fusion and see what potential goodies are in there:

 

The Fusion class has a number of methods and properties, but we're going to zoom in on only two of those methods. Specifically: GetKnownFusionApps and ReadCache. GetKnownFusionApps provides an extremely convenient means of accessing the information exposed by the lower-level CreateHistoryReader export. It takes no parameters and returns a StringCollection, each element of which is a fully qualified file name of a managed application. It is this mechanism that provides the list of applications that can be 'fixed' (chuckle). ReadCache is also a higher-level wrapper, this time for the lower-level CreateAssemblyEnum export. ReadCache takes two parameters: a destination ArrayList and an unsigned int that specifies flags indicating which cache the caller is interested in. (There are two other methods called ReadFusionCache and ReadFusionCacheJustGAC which wrap ReadCache and return the contents of the GAC/ZAP cache or just GAC, respectively.) A quick search through the SSCLI (another very useful learning tool - I highly recommend both it and the excellent book that covers it, Shared Source CLI Essentials) reveals three bit flags that can be used here, one for each of the cache types: ASM_CACHE_ZAP (1), ASM_CACHE_GAC (2) and ASM_CACHE_DOWNLOAD (4). After ReadCache successfully completes, the ArrayList that was passed in will contain a collection of AssemInfo data structures, each of which represents an assembly in the requested cache. AssemInfo is also an internal type (oh bother) and contains information about an assembly such as display Name, Version, Locale, PublicKeyToken, etc.

The sample code

So now that we know how to access the managed apps list (aka Fusion Application History) and the contents of the Fusion caches, all that remains is to collect that information and throw it into a simple (read: very quick, very dirty, only-intended-for-demonstration-purposes) GUI:

 

The GUI consists of the standard tree-splitter-info pane layout. The tree is organized into the four assembly collections, which are further divided into product name groups (where possible). The product name information is obtained by passing a calculated file name of each assembly (where possible) to the framework System.Diagnostics.FileVersionInfo class (which, by the way, makes it trivial to access/interpret the version resource of an image). Implementing additional functionality, such as the ability to add/remove assemblies from the Gac for example, is left as an exercise to the reader (hint: this can be done without resorting to reflection).

The sample code is mostly trivial but the wrapper classes Reflected, AssemInfo & Fusion may warrant additional (if not brief) explanation. Reflected is a general-purpose utility class that simply makes it more convenient to access the fields and properties of a reflected type. AssemInfo and Fusion are simple reflection wrappers around the real AssemInfo and Fusion types (specifically: wrappers around the members that we are interested in). Since we can't access the real types directly, these wrappers make it easy to interact with the real types without having to sprinkle reflection calls all over the code base.

Let's take a quick look at each of these classes. Here is the utility class Reflected

public class Reflected
{
  static object Get(object src, string strName, BindingFlags type)
  {
    bool bStatic = (src is Type);
    Type t = bStatic ? (Type)src : src.GetType();
    BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public |
      BindingFlags.DeclaredOnly |
      (bStatic ? BindingFlags.Static : BindingFlags.Instance) |
      type;
      object target = (bStatic ? null : src);

    return (t.InvokeMember(strName, bindingFlags, null, target, null));
  }

  public static object GetProperty(object src, string strName)
  {
    return (Get(src, strName, BindingFlags.GetProperty));
  }

  public static object GetField(object src, string strName)
  {
    return (Get(src, strName, BindingFlags.GetField));
  }
}            

The real work is done in the private Get method which takes a src object to invoke on, a member name to invoke and the type of invocation.  Type.InvokeMember allows a caller to invoke any member on a type through reflection, including the ability to get and set fields. The bindingFlags argument tells InvokeMember how to interpret the name of the member as well as the target member's visibility.  

Here is the AssemInfo wrapper: 

public struct AssemInfo
{
  public readonly string Name;
  public readonly string Locale;
  public readonly string Codebase;
  public readonly string Modified;
  public readonly string OSType;
  public readonly string OSVersion;
  public readonly string ProcType;
  public readonly string PublicKey;
  public readonly string PublicKeyToken;
  public readonly string Version;
  public readonly Fusion.CacheType CacheType;
  public readonly string sCustom;
  public readonly string sFusionName;

  public AssemInfo(object assemInfo)
  {
    Name = (string)Reflected.GetField(assemInfo, "Name");
    Locale = (string)Reflected.GetField(assemInfo, "Locale");
    Codebase = (string)Reflected.GetField(assemInfo, "Codebase");
    Modified = (string)Reflected.GetField(assemInfo, "Modified");
    OSType = (string)Reflected.GetField(assemInfo, "OSType");
    OSVersion = (string)Reflected.GetField(assemInfo, "OSVersion");
    ProcType = (string)Reflected.GetField(assemInfo, "ProcType");
    PublicKey = (string)Reflected.GetField(assemInfo, "PublicKey");
    PublicKeyToken = (string)Reflected.GetField(assemInfo, "PublicKeyToken");
    Version = (string)Reflected.GetField(assemInfo, "Version");
    uint nCacheType = (uint)Reflected.GetField(assemInfo, "nCacheType");
    CacheType = (Fusion.CacheType)nCacheType;
    sCustom = (string)Reflected.GetField(assemInfo, "sCustom");
    sFusionName = (string)Reflected.GetField(assemInfo, "sFusionName");
  }
}

Nothing terribly heart pounding here - AssemInfo is a simple wrapper around the real AssemInfo type that we can't access directly. We simply cache the contents of the real instance and then interact with the wrapper.

And finally, the Fusion wrapper: 

public class Fusion
{
  public enum CacheType 
  {
    Zap = 0x1,
    GAC = 0x2,
    Download = 0x4
  }
     
  static Type FusionType;
      
  static Fusion()
  {
    Assembly a = Assembly.Load("mscorcfg, "
      + "Version=1.0.5000.0, "
      + "Culture=neutral, "
      + "PublicKeyToken=b03f5f7f11d50a3a");
    FusionType = a.GetType("Microsoft.CLRAdmin.Fusion");
  }
      
  public static String GetCacheTypeString(UInt32 nFlag)
  {
    object[] args = new object[]{ nFlag };
    BindingFlags bindingFlags = (BindingFlags)314;
    return ((String)
      (FusionType.InvokeMember("GetCacheTypeString",
        bindingFlags, 
        null, 
        null, 
        args)));
  }
      
  public static void ReadCache(ArrayList alAssems, UInt32 nFlag)
  {
    object[] args = new object[]{ alAssems, nFlag };
    BindingFlags bindingFlags = (BindingFlags)314;
    FusionType.InvokeMember("ReadCache", 
      bindingFlags, 
      null, 
      null, 
      args);
  }
      
  public static StringCollection GetKnownFusionApps()
  {
    object[] args = new object[0];
    BindingFlags bindingFlags = (BindingFlags)314;
    return ((StringCollection)
      (FusionType.InvokeMember("GetKnownFusionApps",
        bindingFlags, 
        null, 
        null, 
        args)));
  }
}

There are couple of small items to note about this code. First, notice that there is a hard-coded version reference in the type constructor. This practice should obviously be avoided in production code (duh). Second, note how we use a uniform 'cookie-cutter' approach in each of the member implementations. It's as though each of the method bodies started life as a copy-paste snippet - only the names, arg lists and return value casts  change. (In actuality, the method bodies didn't start life as snippets; they were generated by a hand-rolled reflection wrapper generator that uses CodeDOM - yet another possible topic for a future article).

Points of Interest

Hopefully you've taken away the following points from this article: (and if not, I blame Jack Mott)

  • Fusion information is easier to extract than you may have previously assumed
  • Because of the CLI's rich type descriptions in the form of metadata, spelunking has never been easier - go explore!
  • Reflector is The Tool for browsing types and method decompilation (although Anakrino gets an honorable mention)
  • Reflection is very useful for accessing and invoking types that would otherwise be inaccessible in your language of choice (unless that happens to be IL)
  • The SSCLI source distribution can be quite helpful in filling in the 'missing pieces' and just generally learning how things work in the framework (be aware, however, that the SSCLI GC and JITter source are pretty different than that of the shipping .NET product)

History

  1. 6/12/2003 - Initial article
  2. 6/20/2003 - Resized screenshot and did some reformatting in order to prevent horizontal scrolling
  3. 7/2/2003 - Linked Gree Fee's "Security Ramifications of Reflection" blog entry

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

John Renaud
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
QuestionException on illegal entries? Pinmemberfred1232127-Jan-06 4:26 
GeneralDoesn't work with Whidbey/Beta 2 PinmemberDerek Greer23-Sep-05 3:35 
Generaldelete from the download cache Pinmemberdavidbeseke5-Jan-05 11:27 
I tried to modify the routine for deleting a member from the GAC by changing the Fusion.ReadCache call within the uninstall wrapper to read entries from the download cache instead of the GAC. I passed it the name of the assembly I wanted to delete, which it found, but the call to the remove function returns false. I assumed that meant this particular procedure did not work for deleting items from the download cache. I looked for another function to call but did not see anything listed that looked like it would work. Is there a way to delete individual members from the download cache in a program?
GeneralClear donwload cache PinmemberPaolo Pagano23-Apr-04 2:03 
GeneralRe: Clear donwload cache PinsussRajesh Cr5-May-04 1:55 
GeneralRe: Clear donwload cache PinmemberDaniel Vasquez Lopez9-May-04 10:28 
GeneralRe: Clear donwload cache Pinmemberbesobeso3-Dec-08 9:10 
Generalvery helpful post Pinmemberhuangwj5-Mar-04 5:06 
Generallocalization a small change Pinmemberalbanp28-Dec-03 23:20 
GeneralRemoveAssemblyFromGac Pinmemberrmorgan25-Aug-03 8:51 
GeneralRe: RemoveAssemblyFromGac Pinmembersentha18-Jan-04 17:05 
GeneralDependency walker Pinmembert9mike12-Jul-03 9:46 
GeneralRe: Dependency walker PinmemberJohn Renaud12-Jul-03 12:06 
GeneralRe: Dependency walker PineditorHeath Stewart29-Jul-03 12:26 
GeneralPlease consider... PinmemberShog919-Jun-03 18:47 
GeneralRe: Please consider... PinmemberJohn Renaud20-Jun-03 3:55 
GeneralRe: Please consider... PineditorHeath Stewart2-Jul-03 2:51 
GeneralRe: Please consider... PinmemberJohn Renaud2-Jul-03 3:18 
GeneralRe: Please consider... PinmemberThomas Freudenberg2-Jul-03 3:27 
GeneralRe: Please consider... PineditorHeath Stewart2-Jul-03 3:38 
GeneralRe: Please consider... PinmemberJohn Renaud2-Jul-03 3:58 
Generalsweet Pinmemberchadb12-Jun-03 7:37 
GeneralRe: sweet PinsussAnonymous19-May-04 11:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 4 Jul 2003
Article Copyright 2003 by John Renaud
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid