How to Interpret Warnings and Errors in Dependency Walker
Dependency Walker may generate many warnings and errors for an application. Some errors may cause an application to fail, while others are harmless and can be ignored. Most failures fit into one of two categories: load-time failures or run-time failures.
A load-time failure means that an application or module didn't even have a chance to run. In more technical terms, this usually means that the entry-point to a module was never called since the operating system couldn't load all the required modules. This can occur if an implicit or forward dependency could not be found or was missing a needed function (for more information on dependency types, see the Types of Dependencies Handled By Dependency Walker section). You will also encounter a load-time failure if the application attempts to load a corrupt or non-Windows module, a module for a different CPU type then you are using, or a 16-bit module into a 32-bit application. Here are some common load-time error messages:
|
Most load-time problems can be immediately detected by Dependency Walker. When you first open a module in Dependency Walker, it scans that module for all implicit, forward, and delay-load dependencies. Implicit and forward dependencies are required by the operating system in order for the application to run. If any implicit or forward dependencies are missing or have errors, then it is likely that the application will encounter a load-time failure if run. Delay-load dependencies are not required by the operating system at load-time, so errors or warning with delay load dependencies may or may not cause problems.
Run-time dependencies are modules that an application loads after it has initialized and begun to run. This is usually achieved by calling one of the LoadLibrary type functions. Once a module has been loaded, an application can call the GetProcAddress function to locate a specific function in the newly loaded module. Dependency Walker can track all these calls and reports any failures. However, if the application is prepared to handle the failure, then the warning can be ignored.
There are many reasons for using run-time dependencies. First, they can increase load-time performance since an application can delay the loading of certain modules that may not be needed until later. For example, if an application uses a DLL related to printing, that DLL might not get loaded unless you actually print something from the application. Second, they can be used in cases where a module, or a function within a module, may not exist. For example, an application might need to call a Windows NT specific function when running on Windows NT, but the module or function does not exist on Windows 9x. If the application were to implicitly link to the module that the function lives in, then a load-time failure would occur on Windows 9x since the operating system would not be able to locate the function at load-time. By making it a run-time dependency, the application can check to see if the function exists and only call it if it does.
There are two types of run-time dependencies: explicit dependencies (often referred to as dynamic dependencies) and delay-load dependencies. Explicit dependencies can be loaded at anytime during the life of the application with no prior notice. Because of this, the only way to determine what explicit dependencies an application will use is to run the application and watch it to see what it loads (for more information on profiling, see the Using Application Profiling to Detect Dynamic Dependencies section). With explicit dependencies, the application directly calls LoadLibrary and GetProcAddress to do the work.
Delay-load dependencies are actually implemented as explicit dependencies, but a helper library and the linker do most of the work. Most all Windows modules have an "import table" stored in them. This table is built by the linker and used by the operating system to determine the implicit and forward dependencies of a given module. Any module or function in this list that cannot be found will cause the module to fail. If you tell the linker to make a module a delay-load dependency, then instead of storing that module's information in the main import table, it stores it in a separate delay-load import table. At run-time, if a module calls into a delay-load dependency module, the call is trapped by the helper library. This library then uses LoadLibrary to load the module and GetProcAddress to query all the functions referenced in the module. Once this is complete, the call is passed along to the real function and execution resumes without the module that made the call even knowing what just happen. All future calls from that specific module to the delay-loaded module will be made directly into the already loaded module instead of being trapped by the helper library.
The delay-load helper library has a mechanism for notifying the caller if there is a failure. Like failures with explicit dependencies, if the application is prepared for the failure, then this should not be a problem.
To summarize, implicit and forward dependencies are required dependencies that need to exist and have no errors or warnings. Explicit and delay-load dependencies may not need to exist and may not need to export all the functions that the parent module wishes to import from them. However, if an application is not prepared to handle a missing explicit or delay-load module, or a missing function within an explicit or delay-load module, then this can result in a run-time failure of the application. Dependency Walker cannot predict if an application plans to handle failures, so it just warns you of all potential problems. If you find an application runs smoothly, then you can probably ignore most all warnings. However, if your application were to fail, then the warnings may provide some insight as to what caused the failure.
There is one other type of warning generated by Dependency Walker while profiling that is worth mentioning. This is related to first and second exceptions. When an exception (like an access violation) occurs in an application, the application is given a chance to handle the exception. These are known as first chance exceptions. If the application handles the exception, then there should be no problem and the exception can probably be ignored. If the application does not handle the first chance exception, then it turns into a second chance exception, which are usually fatal to the application. When a second chance exception occurs, the operating system usually puts up a dialog telling you that the application has crashed and needs to exit.
Dependency Walker always logs second chance exceptions and can optionally log first chance exceptions. Many applications routinely generate first chance exceptions and handle them. This is not a sign of a bad application since there are many legitimate reasons to generate first chance exceptions and handle them.