使用 Process Monitor 解决依赖项解析问题






4.93/5 (22投票s)
使用 Process Monitor 解决依赖解析问题。
未处理的异常: System.IO.FileNotFoundException: 无法加载文件或程序集“NativeLibWrapper.dll”或其某一个依赖项。指定的模块未能找到。 在 MainApp.Program.Main(String[] args)
上面的错误可能是 Google 上搜索次数最多的 .Net 异常之一。无法加载文件或程序集异常发生在 Windows 无法加载调用代码所需的 dll 时。加载程序可能无法加载所需 DLL 的原因有很多,例如缺少依赖项、调用代码没有权限读取所需的 DLL 等。
Dependency Walker 和 Fusion Log Viewer 是用于解决丢失依赖项问题的常用工具。Dependency Walker 静态解析原生 PE 文件所需的所有 DLL 并标记丢失的依赖项,而 Fusion Log Viewer 在运行时捕获托管代码中的程序集绑定问题。两者都是很棒的工具,并且能很好地完成各自的工作,但对于动态加载原生 DLL 或包含原生和托管程序集的应用程序,它们就显得不足了。在这篇博文中,我将讨论如何使用 Process Monitor 对“无法加载文件或程序集”问题进行探索性调试,以及如何自动化此过程。
问题
Process Monitor 是一个易于使用的 Windows 实时事件监控工具,可向您显示文件系统、注册表、网络、进程和线程活动日志。为了演示如何使用 Process Monitor 进行故障排除,我创建了一个名为MainApp.exe的托管应用程序。MainApp 引用了一个名为NativeWrapper.dll的托管包装库,该库又引用了一个名为NativeLib.dll的原生 C++ dll。(您可以在 github 此处下载完整的解决方案)。为了模拟缺少依赖项,让我们删除NativeLib.dll。现在执行MainApp.exe将导致以下错误
>MainApp.exe
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'NativeLibWrapper.dll' or one of its dependencies.
The specified module could not be found. at MainApp.Program.Main(String[] args)
请注意,异常没有提及实际丢失的依赖项,它只指出NativeLibWrapper.dll的某个依赖项丢失了,但具体是哪个呢?在这个玩具应用程序中,由于其范围小,很容易找出问题。然而,在真实应用程序中,每个程序集都可能在系统中散布着数十个依赖项,这可能会成为一场噩梦。如上所述,Dependency Walker 不适用于托管程序集,因此无济于事。*NativeLib.dll*是一个非托管 DLL,因此 Fusion Log Viewer 无法检测到它。让我们看看如何使用 Process Monitor 解决这个问题。
运行 Process Monitor 并监控 MainApp.exe
- 运行 Process Monitor 并开始捕获事件。
- 为MainApp.exe添加一个进程名过滤器,将显示事件限制在仅与 MainApp 相关的事件(这可以防止日志变得过大)。
- 捕获开始后运行MainApp.exe
- 一旦出现
FileNotFoundException
,切换到 Process Monitor 并停止捕获事件(文件 > 捕获事件)。
使用 Process Monitor 手动分析日志
现在我们可以分析日志了。我们正在查看的异常是文件未找到异常,这几乎肯定与文件系统事件有关。因此,我们可以过滤掉除文件系统事件之外的所有内容。您可以通过在工具栏中关闭进程、注册表和网络按钮来做到这一点。Process Monitor 窗口现在应该看起来像这样:
修剪不相关的事件
- 我们试图查找与 DLL 相关的问题,因此排除所有与 DLL 文件无关的事件。
- 由于我们有兴趣找出文件是否已成功加载,让我们排除结果不是以下之一的事件:找不到名称、找不到路径、拒绝访问、文件被锁定(仅读)和成功。Process Monitor 不允许您添加 OR 过滤器,因此您必须为不在上述列表中的所有结果添加排除过滤器。
- 当 Windows 尝试加载 DLL 时,会触发 **CreateFile** 和 **CreateFileMapping** 操作,因此排除所有其他操作。我不得不排除操作为 **缓冲区溢出**、**文件被锁定(仅读)**和 **Query*** 的事件。根据您的进程,您可能需要排除一些其他操作。
以下快照显示了我为上述操作生效所应用的所有过滤器。
现在是所有努力获得回报的时候了。滚动到日志底部,尝试找到加载程序未能找到的文件(即,没有一个事件的结果为成功)。这应该很容易发现,因为大多数时候它将是带有大量连续找不到名称结果的条目。对于我的演示应用程序,目视检查清楚地表明加载程序未能找到NativeLib.dll。
使用 Dependz 自动分析日志
现在,这个探索过程在前几次很有趣,但很快就会失去魅力。为了减少繁琐,我自动化了过程的分析部分。而不是手动过滤和检查日志,我编写了一个名为 Dependz 的小型工具来分析日志文件并列出所有未解析的依赖项。您可以在此处下载 Dependz 的源代码,在此处下载二进制文件。
Dependz 需要一个 Process Monitor 日志文件(xml 格式)以及要分析的应用程序名称作为输入,如下所示:
这次让我们用 Dependz 来分析NativeLib.dll问题。转到 Process Monitor 并重置我们刚刚应用的所有过滤器(Ctrl + L > 重置),然后以 xml 格式保存日志。
使用所需的输入运行Dependz.exe,如下所示:
太棒了!Dependz发现NativeLib.dll丢失了,并且还列出了加载程序在尝试查找它时探测的所有路径。除了NativeLib.dll之外,还有一些其他 DLL,如rpcss.dll和mscorrc.dll未能找到,但我认为这些库是可选的,并没有引起这个问题。我通常从日志的底部向上工作。我发现日志中最后记录的 DLL 几乎总是问题的根源。
权限相关问题
Dependz 还可以帮助您找出由于权限不足而导致的依赖项错误。假设一个用户没有读取NativeLib.dll的权限,然后运行MainApp.exe,而MainApp.exe崩溃并出现以下异常
>MainApp.exe
Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly
at MainApp.Program.Main(String[] args)
正如预期的那样,这个异常没有提供太多信息。启动 Process Monitor,捕获日志并使用 Dependz 运行。
奏效了!Dependz 发现MainApp.exe因访问被拒绝而无法加载NativeLib.dll,并显示了MainApp.exe尝试从何处加载NativeLib.dll的实际路径。
Dependz 仍在开发中。随着我遇到更多可以自动化 Process Monitor 日志分析的场景,我将添加更多功能。如果您有任何经常使用 Process Monitor 重复的特定故障排除场景,请留下评论,我将尽力提供帮助。