Search This Blog

Thursday 2 March 2017

What prevents my native images to get loaded?


While investigating a dependency resolution error the other day I came across an interesting situation. Fuslogvw logs indicated that even though the proper native image was present for a managed assembly, it wouldn't get loaded due to the actual load context being a so called LoadFrom context. This was not the main issue I started to investigate, but after figuring that out I decided to conduct some research on load contexts.

After that I felt ready to reproduce the issue with a small console application and a class library it tries to load. I find it very useful to abstract away from the production environment and reproduce issues in a very simple, controlled one, so that you can focus only on the problem being investigated.

ConsoleApplication1.exe =>
static void Main(string[] args)
{
    var asm = Assembly.LoadFrom(@"c:\temp\dllfolder\ClassLibrary1.dll");
}

ClassLibrary1.dll => Just an empty dll


After building the binaries for - let's say- Release x64, deploy them to separate folders and run the appropriate ngen.exe to generate the native image for the dll, i.e.

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe install c:\temp\dllfolder\ClassLibrary1.dll

After all these steps, the following relevant files / folders could be observed :
  • c:\temp\exefolder\ConsoleApplication1.exe
  • c:\temp\dllfolder\ClassLibrary1.dll 
  • c:\Windows\assembly\NativeImages_v4.0.30319_64\ClassLibrary1\b1086c3d9614cbfd539aa5cfb53ae3c9\ClassLibrary1.ni.dll
Note: ClassLibrary1.dll was not present in the managed GAC

At this point I enabled fuslogvw logging then started ConsoleApplication1.exe. Understanding the logs takes some time, so don't be frustrated if you don't put all the pieces together within a few seconds. I highlighted the most important log entries.

LOG: This bind starts in LoadFrom load context. 
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load(). 
LOG: Using application configuration file: c:\temp\exefolder\ConsoleApplication1.exe.Config 
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config. 
LOG: Attempting download of new URL file:///c:/temp/dllfolder/ClassLibrary1.dll. 
LOG: Assembly download was successful. Attempting setup of file: c:\temp\dllfolder\ClassLibrary1.dll 
LOG: Entering run-from-source setup phase. 
LOG: Assembly Name is: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e LOG: Re-apply policy for where-ref bind. 
LOG: Post-policy reference: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e 
LOG: GAC Lookup was unsuccessful. 
LOG: Where-ref bind Codebase does not match what is found in default context. Keep the result in LoadFrom context. 
LOG: Binding succeeds. Returns assembly from file:///c:/temp/dllfolder/ClassLibrary1.dll. 
LOG: Assembly is loaded in LoadFrom load context.
...
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: IL assembly loaded from c:\temp\dllfolder\ClassLibrary1.dll.

See the warning about not probing the native image in case of a LoadFrom context? This can be a bit misleading, but first things first. As GAC lookup fails, the binder leaves the context as is and binds to the managed dll. However, things get changed as soon as you GAC your dll.

gacutil /i c:\temp\dllfolder\ClassLibrary1.dll

That deploys the dll to the following folder:
c:\Windows\Microsoft.NET\assembly\GAC_64\ClassLibrary1\v4.0_1.0.0.0__b91bc0d0c196a74e\ClassLibrary1.dll

LOG: This bind starts in LoadFrom load context. 
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load(). 
LOG: Using application configuration file: c:\temp\exefolder\ConsoleApplication1.exe.Config 
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config. 
LOG: Attempting download of new URL file:///c:/temp/dllfolder/ClassLibrary1.dll. 
LOG: Assembly download was successful. Attempting setup of file: c:\temp\dllfolder\ClassLibrary1.dll 
LOG: Entering run-from-source setup phase. 
LOG: Assembly Name is: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e 
LOG: Re-apply policy for where-ref bind. LOG: Post-policy reference: ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b91bc0d0c196a74e 
LOG: Found assembly by looking in the GAC. 
LOG: Switch from LoadFrom context to default context. 
LOG: Binding succeeds. Returns assembly from C:\windows\Microsoft.Net\assembly\GAC_64\ClassLibrary1\v4.0_1.0.0.0__b91bc0d0c196a74e\ClassLibrary1.dll. 
LOG: Assembly is loaded in default load context.
...
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load(). 
LOG: Start validating all the dependencies. 
LOG: [Level 1]Start validating native image dependency mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089. Native image has correct version information. 
LOG: Validation of dependencies succeeded. 
LOG: Bind to native image succeeded. Attempting to use native image C:\windows\assembly\NativeImages_v4.0.30319_64\ClassLibrary1\b1086c3d9614cbfd539aa5cfb53ae3c9\ClassLibrary1.ni.dll. Native image successfully used.

As you can see the load context was "promoted" to default due to the assembly also residing in the GAC. This causes the binder to look for the native image counterpart of the dll even though several clear logs indicated otherwise. As a result, the native image gets loaded into the address space of the process.

Uninstalling the native image by

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe uninstall ClassLibrary1

of course once again results in only the managed dll being loaded.

...
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load(). 
WRN: No matching native image found. 
LOG: IL assembly loaded from C:\windows\Microsoft.Net\assembly\GAC_64\ClassLibrary1\v4.0_1.0.0.0__b91bc0d0c196a74e\ClassLibrary1.dll.

That speaks for itself.

In some cases however, the situation is not so obvious, i.e. some component may hide the details of assembly loading. A good example is MEF, as composing your CompositionContainer might lead to either the default or a LoadFrom load context. Typically, if the dll you're trying to load is not installed to GAC, then you'll have a LoadFrom load context resulting in the native images not getting loaded.

A nice tool to verify that your native images get loaded properly is VMMap from Sysinternals, but there are tons of others that can do the same.
Figure 1. VMMap showing the loaded images
You might want to look for such issues before your release.
References

No comments:

Post a Comment