Essential tools for debugging and troubleshooting : Dependency Walker aka Depends

Introduction

This is the first article of the “Essential tools for debugging and troubleshooting” series.
Visit the series main page for more information and to discover other tools.

Dependency Walker

Dependency walker (a.k.a Depends) is a simple yet powerful tool that you’ll find invaluable if you have to track the native dependencies of your components (EXE or DLL).
Dependency Walker can be used for static (without having to run any code) or dynamic analysis (with dependencies tracked while running your code).

I’ll illustrate its usefulness with a from scratch sample.

Say you are in the very common situation where you have a native dependency which itself has dependencies.
Whatever the technology used you have this situation :

 ________       _________       _________
| CALLER | --> | NATIVE1 | --> | NATIVE2 |
 --------       ---------       ---------

(By the way isn’t it a stunning demonstration of my awesome ASCII art skills?)

When the “native2” dependency is missing, often the root cause remains hidden and you get a generic error message like “Unable to load DLL ‘native1.dll’ : The specified module could not be found” without mentioning “native2”.

So you first naively double-check that your “native1.dll” dependency is here and accessible but once you’re convinced “native1.dll” is not the root-cause you realize you definitely need more information to troubleshoot the issue.

Let’s reproduce this situation with a very basic sample and troubleshoot it with Dependency Walker (source code is packaged here : Depends sample source code).

Here is the native2 C source-code:

__declspec(dllexport) void g()
{
}

and the native1 C code that uses native2 function g:

extern void g();

__declspec(dllexport) void f()
{
    g();
}

Now is the consumer of native1, a C# application, Test:

using System.Runtime.InteropServices;

class Test
{
    [DllImport("native1.dll")]
    extern static void f();

    static void Main()
    {
        f();
    }
}

So the Test application calls the f function of native1 and f calls itself the g function of native2.
As you’ve guessed all this stuff does…nothing 🙂

So let’s build our amazing application that does nothing!

First open a Visual Studio Command Prompt and navigate to the folder containing your source-code files and then follow the guide:

  • we first compile native2:
    >cl /LD native2.c
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    native2.c
    Microsoft (R) Incremental Linker Version 10.00.40219.01
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:native2.dll
    /dll
    /implib:native2.lib
    native2.obj
       Creating library native2.lib and object native2.exp

    We now have a “native2.dll” DLL and a “native2.lib” exports file.

  • then we compile native1:
    >cl /LD native1.c native2.lib
    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    native1.c
    Microsoft (R) Incremental Linker Version 10.00.40219.01
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    /out:native1.dll
    /dll
    /implib:native1.lib
    native1.obj
    native2.lib
       Creating library native1.lib and object native1.exp

    This generates “native1.dll”.

  • and finally we compile Test:
    >csc /platform:x86 Test.cs
    Microsoft (R) Visual C# Compiler version 4.0.30319.17929
    for Microsoft (R) .NET Framework 4.5
    Copyright (C) Microsoft Corporation. All rights reserved.

We now have a useless application, Test.exe, that does what it is intended for and we can prove it by running it:

>Test.exe

>

Nothing, no errors, the boss will be happy.

So far so good…

Now let’s play and imagine that the application has been deployed on another environment where native2.dll is missing or is not where it is expected.
To simulate this situation delete or rename the native2.dll file an rerun Test.exe :

>Test.exe

Unhandled Exception: System.DllNotFoundException: Unable to load DLL 'native1.dl
l': The specified module could not be found. (Exception from HRESULT: 0x8007007E
)
   at Test.f()
   at Test.Main()

Oops! we’ve broken something, let’s see what Depends have to say about it.

Run the “depends.exe” application you’ve downloaded from the Dependency Walker website.
From here open the native1.dll file (you can simply drag-and-drop it from your Windows explorer). You should first see an error popup saying that something is wrong with your DLL :

Depends errors popup

Depends errors popup

So at this point Depends is only telling you something you already knew.

So click OK to be presented with the main interface where you have more information :

Depends UI

Depends UI

Interesting : we now see the root cause : the “native2.dll” file is missing.

From here you can check if the “native2.dll” is on the system; if no install it, if yes check that its folder is correctly referenced in the PATH environment variable.

Conclusion

I hope this quick introduction and demo of Depends have been helpful to you and that you will leverage Depends in your future troubleshooting sessions.

Actually Depends can do more than dependency tracking: for instance in the screenshot you may have noticed that Depends also displays the list of all the exported functions of the DLL (here only “f“) which can at times be a very useful information when you don’t understand why you are not able to access (e.g. via DllImport) a function that should be available.

If you have any remarks, questions, suggestions about this article or want to share your experience using Depends please let a comment.

3 thoughts on “Essential tools for debugging and troubleshooting : Dependency Walker aka Depends

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove me you\'re human :) *