Dependencies 源码解析①:PE 文件内存映射全景图——Process Hacker phlib 到底做了什么
2026/9/19 1:57:48 网站建设 项目流程

Dependencies 源码解析①:PE 文件内存映射全景图——Process Hacker phlib 到底做了什么

【免费下载链接】DependenciesA rewrite of the old legacy software "depends.exe" in C# for Windows devs to troubleshoot dll load dependencies issues.项目地址: https://gitcode.com/gh_mirrors/de/Dependencies

Dependencies是一个用 C# 重写的经典 Windows 开发工具(旧版 depends.exe),帮助开发者快速排查PE 文件(.exe / .dll)的DLL 依赖加载问题——比如"缺少某个 DLL"、"位数不匹配"这类让新手抓狂的报错。它的解析引擎并非从零手搓,而是深度集成了 Process Hacker 项目中的phlib内存映射库。本文带你走一遍 PE 文件从磁盘到内存的完整链路,看懂 phlib 到底做了什么。

一、先建立全景图:一次 PE 解析的调用链

当你把一个 exe 拖进 Dependencies 时,数据是这样流动的:

C# 业务层 (DependenciesLib/FindPeModule.cs) │ 调用 PE 类的 Load() ▼ C++/CLI 桥接层 (ClrPhlib/src/managed/PE.cpp) │ 把托管字符串转成原生宽字符串 ▼ UnmanagedPE 封装 (ClrPhlib/src/unmanaged/UnmanagedPE.cpp) │ 持有 PH_MAPPED_IMAGE 结构体 ▼ Process Hacker phlib (third_party/phlib/mapimg.c) │ NtCreateSection + NtMapViewOfSection ▼ Windows 内存映射视图 —— PE 内容以"指针偏移"方式被读取

理解这条链的关键是:phlib 把整个 PE 文件一次性映射进进程地址空间,之后所有解析都只是在内存视图里"按偏移取数据",不再碰磁盘。这就是它又快又稳的根本原因。

二、PE 文件如何进入内存:内存映射技术

1. 打开文件并创建内核 Section 对象

入口函数在 mapimg.c 的PhLoadMappedImage,它先调用PhMapViewOfEntireFile完成三件事:

  1. 以只读方式打开目标文件(允许其他进程同时读取);
  2. 调用NtCreateSection基于文件创建 Section 对象;
  3. 调用NtMapViewOfSection把整个文件映射到当前进程地址空间,得到一个viewBase指针。

相关实现可精读 mapimg.c。这里有两个值得注意的细节:

  • 权限按需申请:只读解析只申请FILE_READ_ATTRIBUTES | FILE_READ_DATA,不会锁死文件;
  • 卸载同样直接PhUnloadMappedImage只是一句NtUnmapViewOfSection,立即释放内存并解除对文件的占用(见 mapimg.c)。

2. PhInitializeMappedImage:逐层"体检" PE 结构

拿到内存视图后,PhInitializeMappedImage 开始校验文件是否真的是一个合法 PE:

校验步骤做什么失败返回值
DOS 头魔数检查文件头是否为MZSTATUS_INVALID_IMAGE_NOT_MZ
e_lfanew 偏移必须非 0 且小于文件大小STATUS_INVALID_IMAGE_FORMAT
PE 签名检查PE\0\0签名STATUS_INVALID_IMAGE_FORMAT
Optional Header 魔数必须是 32 位或 64 位魔数STATUS_INVALID_IMAGE_FORMAT
内存探测用 SEH 异常探测指针访问越界返回异常码

最后它把NtHeaders指针、节区表、可选头信息全部填进PH_MAPPED_IMAGE结构体——这个结构体就是后续所有解析的"锚点"。

💡为什么用 SEH 探测而不是先判断长度?因为 PE 文件可能被恶意构造或截断,光看声明的长度不可信,真正访问一下内存才能确认数据安全。

三、映射之后:phlib 提供的三大解析能力

PH_MAPPED_IMAGE就绪后,mapimg.h 中声明的一系列 API 就能"无磁盘 I/O"地读取 PE 的各个目录:

  • PhGetMappedImageImports/PhGetMappedImageDelayImports:解析导入表(含延迟加载导入),返回按 DLL 分组的PH_MAPPED_IMAGE_IMPORTS结构——这正是 Dependencies 界面里"导入的 DLL 列表"的数据来源;
  • PhGetMappedImageExports:解析导出表,得到PH_MAPPED_IMAGE_EXPORTS,支持按名称或序号查函数地址;
  • PhGetMappedImageResources:枚举资源段,用于提取嵌入的清单(manifest);
  • PhCheckSumMappedImage:计算 PE 校验和,与文件头声明值比对,判断二进制是否被修改过。

四、ClrPhlib 桥接层:C# 如何拥抱 phlib

phlib 是纯 C 代码,而 Dependencies 上层是 C#。两者之间的翻译官是ClrPhlib项目(C++/CLI),核心结构分两层:

1. UnmanagedPE —— 原生封装层

UnmanagedPh.h 中定义的UnmanagedPE类直接持有PH_MAPPED_IMAGE、导入/延迟导入/导出等原生结构体。它的 LoadPE 方法 只做了两件事:确保旧映像已卸载,然后调用PhLoadMappedImage加载新文件。析构时自动卸载,杜绝内存泄漏。

2. PE 类 —— C# 可见的托管门面

PE.cpp 把上述原生能力翻译成 C# 友好的接口,公开类型在 ClrPhlib.h 中声明:

  • Load()/Unload():映射与卸载,并填充PeProperties(机器类型、映像基址、入口点、子系统、校验和等,见 InitProperties);
  • GetImports()/GetExports()惰性解析 + 本地缓存,首次调用才解析,之后直接返回缓存列表;
  • GetManifest():从资源段提取 UTF-8 嵌入清单,供 SxS(侧载)分析使用;
  • IsWow64Dll()/GetProcessor():判断 32 位/64 位、x86/arm/arm64 架构——这正是"32 位进程装 64 位 DLL 失败"这类问题的判断依据。

一个容易被忽视的细节:NativeFile

NativeFile.h 是System.IO.File的部分重写,专门绕开 64 位系统中的WoW64 文件系统重定向(32 位程序访问 System32 会被悄悄转到 SysWOW64)。分析 DLL 依赖时这一点致命——它保证了 32 位 Dependencies 也能如实看到 64 位目录下的文件,还顺带实现了基于 bcrypt 的 SHA256 文件哈希。

五、源码导读:建议按这个顺序精读

顺序文件看点
1third_party/phlib/mapimg.c内存映射与 PE 校验的完整实现(约 1800 行)
2third_party/phlib/include/mapimg.hPH_MAPPED_IMAGE系列结构与全部 API 声明
3ClrPhlib/src/unmanaged/UnmanagedPE.cpp最薄的原生封装,理解桥接层的最佳起点
4ClrPhlib/src/managed/PE.cpp托管门面:缓存策略、清单提取、架构判断
5DependenciesLib/FindPeModule.csC# 业务层如何组合使用 PE 类做 DLL 搜索
6test/manifest-regress/清单回归测试样例,含.dll.manifest实例

📌 后续文章会基于这条链路继续深入:ApiSet Schema 解析(Windows 8 之后 DLL 名称如何被虚拟化)与DLL 搜索顺序模拟(FindPeModule 是如何复刻加载器行为的)。

六、小结

用三句话记住本文:

  1. phlib 的核心思想是"先映射、后解析"——一次NtCreateSection + NtMapViewOfSection把 PE 文件搬进内存视图,之后全是指针运算;
  2. 健壮性靠 SEH 探测——不信任文件自报的头部长度,访问即校验,畸形 PE 不会让工具崩溃;
  3. 分层清晰——C 层(phlib)做重活,C++/CLI 层(ClrPhlib)做翻译,C# 层(DependenciesLib)做体验,各层通过PH_MAPPED_IMAGE这一个结构体解耦。

掌握这条 PE 内存映射全景链路,你就拿到了读懂 Dependencies 整个解析引擎的地图。🗺️

【免费下载链接】DependenciesA rewrite of the old legacy software "depends.exe" in C# for Windows devs to troubleshoot dll load dependencies issues.项目地址: https://gitcode.com/gh_mirrors/de/Dependencies

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询