Visualizing Dll Dependencies for native and managed components

Introduction:

Software today is growing quickly and as it is growing, more and more .dlls are being added to our product’s /bin directories. Since each of these .dll files has many relationships to other .dlls, this creates an immensely complex web of dependencies which makes the program structure that much harder to understand.

To battle this phenomenon, while trying to understand a 15 year old program still under active development, I needed a tool which:

  • Maps the .dll dependencies between all the .dlls under my /bin directory
  • Understands ALL types of windows binaries:  Handle Native .dlls as well as .net assemblies
  • Locates external (referenced) dllswithin the different search paths:
    • GAC 2.0/4.0
    • Local bin
    • SxS
    • System search path variable
  • Aggregates file and assembly information: Retrieves .dll versions and file info
  • Creates a visual map which can be filtered and browsed
  • Exports to csv: Saves information needed for further investigation
  • Warns about missing .dlls
  • Finds duplications: Lists .dlls that have multiple possible sources
  • Has a command line interface so this can be used automatically within a build system

As a result, the .Dll Dependency Visualizer was born.

 

 

The image below shows the dependency visualizer tool in action, for the item under the mouse cursor blue arrows show outgoing links, and red arrows show incoming links.

Visualizer.png

 

 

Extracting information from the files:


To get the information I needed from the native .dll files, I had to read the PE format. After exploring the available APIs for extracting references from native dlls, I decided that the best course of action was to use the win32 functions to do it. I did this since it is much easier to use than reading the PE format manually.

Getting the information from .Net assemblies without loading them as modules was made possible by using mono.cecil (which really saves the day each time I encounter this issue), since using the ReflectionOnlyLoad alternative Is very messy.

Discerning whether a file is an assembly or a native binary wasn’t as simple as it might sound and to complicate matters even more, I later discovered that a file can also be both, in which case it should be probed for both native dependencies and .Net ones.

Checking for a native binary is done by reading the WinNT header of the file, as described by Microsoft in their MSDN article: How to determine if an Image is Native or CLR.

If we are reasonably sure that the file is an assembly, in order to verify if the file is a mixed assembly (with native parts), another check was needed. Therefore, I read the assembly definition and checked for the ILOnly flag, which let me know if I was looking at a pure assembly as well as re-verifying that this was actually an assembly file.

Search scope & finding external dependencies:

In order to define the information gathered, I used the following assumptions and limitations:

  • Files found during recursive search under the given root path(s) are first class citizens– which means I will track down all their dependencies (including going to system, path, SxS and GAC)
    • Files which are found as dependencies (not under the root directory) will not be probed for dependencies. This prevents the program from drilling into system file / Runtime engine references.
  • All referenced files should be present on the computer If the files are not present, they are colored Red)
  • Dynamic referenced assemblies and CoCreated COM components which are not referenced in the .dll header are not tracked. A special parser can be added to provide the information if these dependencies are persisted in an IoC-Container Xml or in some Addin file.

Graphics and visualization:

I used the Graphsharp component to visualize my .dlls, since it is built using MVVM architecture, the graphics part (view) does all the visualization work in terms of model binding, and visualization. As result, all I needed to worry about was gathering the actual information about the .dlls, creating the map/tree I wanted to show and then constructing ViewModel objects accordingly.

Constructing the dependency model is done each time the data is filtered, and can be done in several ways:

  • When constructing a general diagram, a reference map will be built, where each .dll has one instance on the graph, and is linked to other .dlls by directed edges (arrows).
  • When getting a dependency tree for a .dll, (by right clicking and selectingdependency tree), referenced .dlls can be drawn once for each referencing item, in order to keep the tree structure. The origin item will be colored blue, but will not always be at the very center due to the nature of Graph Sharp’s layout algorithms.
  • When double clicking a single item – only its immediate references will be shown (both in and outgoing)

Version information is collected both for the .dll files, and for a reference, whenever a version is defined in the metadata, but this data is not currently shown in the graph.

 

 

Basic Usage How to:

Starting up:

  1. A root directory should be defined.
  2. The “Analyze” button should be clicked.
  3. Here you may choose to set a filter (or everything will be displayed at once)
  4. 4.      “Redraw” must be clicked in order to display the map (this should be done after every additional filter change as well).

Defining search root paths:

Defining search directories can be done in two ways:

  1. Inserting a root directory to search under in the top part of the main window.
  2. Adding search directories and ignore directories in the DllDependencyGraphSharp.exe.config file like so:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ScanRootPath1" value="c:\dir1\"/>   
    <add key="ScanRootPath2" value="c:\dir1\dir2\"/>
    <add key="ScanExcludePath1" value="c:\dir1\Tests"/>
    <add key="ScanExcludePath2" value="c:\dir1\dir2\UnitTests"/>
  </appSettings>
</configuration>

  

Browsing:

  • To zoom in and out you can use ctrl + mouse scroll or the zoom control on the upper left corner
  • To view a .dll’s first order relations, double click the .dll file.
  • To get a full tree of a .dll’s dependencies or dependencies(.dlls who depend on it) right- click the .dll file and choose from the menu
  • Properties for the .dll, including versions and paths to the .dll on the current machine can be found by: right clicking the .dll file and selecting properties.
  • Spatial arrangement can be controlled by changing the layout algorithm. Items can also be dragged around by the mouse.
  • Hovering over a node will show its incoming, “depending” relations (red) and outgoing, “depends on” relations (blue).
  • Filtering is done through the filter boxes on the top, after changing the filter press the “Redraw” button. There are several types of filter:
    • Text filter – keeps only files whose name contains the string (case insensitive)
    • Special filter combobox – lets you choose:
      • All – default, see everything.
      • Duplicates - .dlls that appear more than once under the search folder(s)
      • No Dependencies - binaries that are not referenced (this can be due to dynamic usage, or by being top level, but might also be an indicator for dead code)
      • Not found – binaries that are referenced but were not found on the machine.

This image shows an example of displaying a single dll’s references (double click on a dll):

SingleDllRefs.png

 

Command line interface

The command line interface can do almost everything that the GUI can do, with the exception of creating dependency trees for a single binary.

The output is a csv file which contains the binaries which passed the filter, and shows their relations to other dlls, including all version data and path information.

DepGraphCmd.exe /i: <includedPath> /e: <excludedPath> /f: <filter>
/i: <includePath> - includes a path in search (may be used multiple times)
/e: <excludedPath> - excludes a path from the search (may be used multiple times)
/f: <filter> - can be: [duplicates | notFound | noDeps | all] (default is all)
/tf: <textfilter> - a string with wildcards to filter dllnames by (case insensative)
/o: <outfile> - a path for the output csv file

 

Summary:

This tool has helped me a lot during the migration of our program to VC++11 and .net 4.5 when I wanted to know for sure if I succeeded in eliminating a dependency on an old BCL version, or some obsolete 3rd party .dll. This tool can also help a lot when approaching code you know little about, and in reverse engineering tasks.

I very much enjoyed working with GraphSharp, getting fluid movements and zooming without any additional code. I think this makes the UI very intuitive and easy to work with.

I would be happy to receive any feedback, I know much there can be added to this project, including searching in the graph, visualization of version requirements etc., but I think that I already have the core functionality here and there is never an end to adding features. So go and test it out and tell me what you think!

 

download binaries

download sources

 

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
Showing results for 
Search instead for 
Do you mean 
About the Author
I've been all over the coding world since earning my degrees have worked several years in c++ and then several in java, finally setteling i...


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation