缘起
最近,经常需要调查 dll 加载失败的问题。前一阵子刚分享了一篇,感兴趣的小伙伴而可以点击 这里。相信,有 windows 开发经验的小伙伴儿一定听过 Dependency Walker 这款工具,它可是查看模块依赖关系的神兵利器。但是在我的机器上,Dependency Walker 运行的特别慢,经常无响应!慢了这么久,是时候提提速了。
重现问题
很容易就可以重现问题,随便拖动一个 dll 文件(或者 exe 文件)到 Dependency Walker 中,等大概 5 秒钟,就会出现大名鼎鼎的无响应界面,需要等待很久才能等到分析结果。
探查敌情
在大动干戈之前,先用 process explorer 粗略查看 Dependency walker 中各个线程的运行情况。

看来,只有一个线程在工作(其它两个线程是系统工作线程),也是界面线程。如果界面线程都用来处理其它工作,长时间没有处理界面消息的话,界面就会无响应。
从调用栈可知,Dependency Walker 在运行过程中会不断的调用 SearchPath 和 FindFirstFile。从这两个函数的名字可以猜测是在搜索文件。
如果有超多文件需要搜素,那么整个过程慢是合理的。只从 process explorer 中并不能知道 Dependency Waler 在搜索哪些文件,在哪些地方搜索。
小贴士:使用
process explorer的线程功能,可以很方便的快速定位一些问题,如有必要,再上调试器进行分析。
process monitor
观察进程文件操作记录对于 process monitor 来说简直是小菜一碟。因为预计时间会比较长,而且只需要关心 Dependency Walker 相关的事件,所以在开始前,先做好过滤。
拖动 process monitor 上的靶子图标到 Dependency Walker 上,表示只关心当前这个 Dependency Walker 实例相关的事件。勾选 Drop filtered Event 表示丢弃过滤的事件,因为会过滤掉大部分无关的事件,生成的记录文件会比较小,可以记录更多有用的事件。

等 Dependency Walker 完成分析后,停止记录,查看文件相关事件。 dll 相关的事件一共有 247688 条。
好奇我是怎么知道的?请看下图:

观察一个比较典型的文件( api-ms-win-core-rtlsupport-l1-1-0.dll)的相关记录。该文件相关的事件数量是 908 条。

该文件相关的部分事件截图如下:

在上图中,可以很明显的看到 Result 列大部分结果不是 Success。这个在意料之中,因为正常情况下这个 dll 应该只在特定路径下才有。从 Path 列中的记录可知,搜索路径有很多个。
深入观察
如果仔细观察,可以发现搜索路径非常像典型的 Dll 搜索路径。Dll 搜索路径的官方参考资料链接为 https://docs.microsoft.com/zh-cn/windows/win32/dlls/dynamic-link-library-search-order?redirectedfrom=MSDN。
为了方便大家查看,我截取了相关部分,如下图:

我机器上的 PATH 环境变量如下图:

如此看来,Dependency Walker 正是按照典型的 Dll 搜索路径在查找啊(默认情况,可以修改)!
通过上面的简单分析,可以初步判定:除了把应该写在工作线程的代码写在 UI 线程有点问题,其它地方没有明显问题,确实有很多工作需要处理。
只能从减少搜索路径的方向着手了。如果可以少搜索一些地方,那么工作量会显著下降,速度相应的会提高上来了。我第一个想到的办法就是改变 PATH 环境变量的值。
手动设置 PATH
最简单的办法是,设置 PATH 环境变量的值为空,这样就可以避免搜索 PATH 环境变量指定的相关路径了。如果调整全局的 PATH 环境变量会影响所有程序,不可取。但是可以通过脚本启动 Dependency Walker ,在启动 Dependency Walker 之前,设置 PATH 为空,这样只会影响 Dependency Walker。保存下面的批处理脚本为 start_depends.bat,双击运行即可。
1 | set PATH="" |
通过以上脚本启动的 Dependency Walker 眼中的 PATH 是空。
如果再次拖动相同的文件到 Dependency Walker 中,基本上是秒出结果。如下图:

在修改 PATH 环境变量之前,大概需要 2 分钟左右才能看到结果,而现在只需要用 1 秒左右,是不是不止快了 100 倍呢?对于一些依赖更加复杂的程序,效率提升的会更加明显!
小贴士:只有确定不会从
PATH中加载对应的DLL,才可以设置PATH为空。既然可以设置PATH为空,就可以设置PATH为任意值。
但是,这样的操作终究有些不自然。难道,Dependency Walker 本身就不支持设置搜索路径吗?
Dependency Walker 设置
简单的浏览了一遍 Dependency Walker 的菜单,发现可以通过 Options -> Configure Module Search Order... 来设置模块搜索顺序及搜索路径。

设置界面如下图:

可以根据自己的需要进行设置。
小贴士:
The system's ’PATH‘ environment variable directories指的是系统PATH环境变量对应的路径,排除它,效果与把PATH设置成空一样。
总结
Dependency Walker可以自定义搜索目录,合理设置搜索目录,会大大提高搜索速度!默认情况下,
Dependency Walker会搜索PATH指定的路径,所以也可以通过修改PATH环境变量的值达到相同的效果。如果长时间(大概
5秒钟)没有处理界面消息的话,界面就会无响应。