- 开发工具
- 桌面应用
- 逆向工程
【免费下载链接】Dependencies
A rewrite of the old legacy software "depends.exe" in C# for Windows devs to troubleshoot dll load dependencies issues.
demumble是一个同时支持 POSIX(Itanium ABI,GCC/Clang 风格)与 Visual Studio(MSVC 风格)符号反修饰(demangle)的 C++ 命令行工具,原生运行于 POSIX 与 Windows 双平台。在本仓库中,它作为Dependencies(depends.exe 的 C# 重写版)的第三方符号解码依赖被引入,为 PE 导入/导出符号表提供底层还原能力。读完本文你将掌握 demumble 的核心设计动机、完整命令用法、跨平台构建方式,以及它在 Dependencies 符号解析管线中的实际调用位置与回退机制。
一、demumble 是什么:一次反修饰领域的"两条路线都要覆盖"
C/C++ 编译器在生成目标文件时,会通过 name mangling(符号修饰)把函数名、命名空间、参数类型等编码进符号名,以保证链接期重载与命名空间隔离。这类被"修饰"的名字对人类极不友好,例如:
- GCC/Clang(Itanium ABI)把
func(char*, int)编码为_Z4funcPci; - MSVC 把
int __cdecl Fx_i(int (__cdecl*)(int))编码为?Fx_i@@YAHP6AHH@Z@Z。
反修饰(demangling)即把上述编码还原为可读的函数原型。传统工具各有所长但互不兼容:Linux 的c++filt只认 Itanium 符号,Windows 的undname只认 MSVC 符号。demumble的定位非常直接——两种符号都能解,并且在两个平台上都能跑:
$ ./demumble _Z4funcPci func(char*, int) $ ./demumble "?Fx_i@@YAHP6AHH@Z@Z" int __cdecl Fx_i(int (__cdecl*)(int))从仓库目录结构看,demumble 正是通过组合两个第三方解码核心实现"双格式"能力的(见 third_party/demumble/):
- third_party/libcxxabi/cxa_demangle.cpp:来自 libc++abi 的
__cxa_demangle,负责 Itanium/GCC 风格符号,文件末尾的extern "C" __cxa_demangle(...)入口(约 cxa_demangle.cpp 第 4926 行)内部使用 arena 分配器与demangle()递归解析; - third_party/wine/undname.c:来自 Wine 项目、由 Jon Griffiths / Eric Pouech 维护的 MSVC 符号解码器(
__unDName/__unDNameEx的实现,文件中定义了完整的UNDNAME_*标志位,如UNDNAME_NAME_ONLY、UNDNAME_NO_ACCESS_SPECIFIERS等)。
在 Dependencies 仓库中,demumble 不是以独立可执行文件引入的,而是被编译为静态库供 ClrPhlib 链接:见 demumble.vcxproj(ConfigurationType为StaticLibrary,仅编译third_party\libcxxabi\cxa_demangle.cpp与third_party\wine\undname.c两个源文件,不含demumble.cc主程序)。
二、"为什么要用它":与 c++filt 相比的三个关键设计
README 以But why一节集中阐述了 demumble 相比c++filt的几个实用特性,这些特性正是它被选入 Dependencies 的原因。
1. 对下划线的智能处理
Itanium ABI 符号在不同平台上前缀规则不同:Linux 上operator new修饰为_Znw,而 macOS 上会额外多一个前导下划线,写成__Znw。c++filt在不同平台上需要手动传-_/-n来切换剥离行为,非常容易踩坑:
$ c++filt _Znw # Linux 上解 macOS 符号:失败 _Znw $ c++filt __Znw operator new而 demumble 会自动识别并剥离多余的前导下划线,两种写法都能直接得到正确结果:
$ demumble _Znw operator new $ demumble __Znw operator new2. 对 stdin 过滤的智能处理
c++filt和demumble都可以作为 stdin 管道过滤器使用,但策略不同:demumble 默认只解函数符号(形态上不会与普通单词混淆的符号),而c++filt默认连类型名也解——类型名(如Pi→int*)极易与普通英文单词混淆,导致对文本流的误伤:
$ echo 'I like Pi and _Znw' | c++filt I like int* and _Znw $ echo 'I like Pi and _Znw' | demumble I like Pi and operator new但如果把类型名作为命令行参数直接传给 demumble,它同样可以正确解析:
$ c++filt Pi int* $ demumble Pi int*这种"管道过滤保守、参数解析放开"的策略,让 demumble 特别适合对日志、位码(bitcode)等批量文本做安全的后处理。README 给出的示例是从 LLVM bitcode 文件中提取函数定义并批量反修饰:
$ grep '^define' bitcode-win.ll | demumble -m | head -1 unsigned int __cdecl v8::RoundUpToPowerOfTwo32(unsigned int)其中-m(match)标志让 demumble只输出看起来像符号的内容,从而干净地过滤掉非符号行。
3. 真正的跨平台:Windows 符号在 Linux 上也能解
c++filt无法处理 MSVC 风格符号,而 demumble 借助 Wine 的undname.c解码器,在非 Windows 平台上也能解析 Windows 风格符号:
$ demumble '??2@YAPEAX_K@Z' void * __ptr64 __cdecl operator new(unsigned __int64) $ c++filt '??2@YAPEAX_K@Z' ??2@YAPEAX_K@Z这一能力对 Dependencies 这样的 PE 分析工具尤为重要:从 PE 导入表/导出表读到的修饰名,无论来自 MSVC 编译的 DLL 还是 MinGW/Clang 交叉编译的 DLL,都能得到一致的可读原型。
三、获取与构建:CMake 一行命令出可执行文件
直接使用预编译二进制
README 说明项目在 releases 页面提供 Linux、macOS(10.9+)与 Windows 的x64 预编译二进制,适合只想快速使用的场景。
从源码构建(POSIX / Windows 通用)
使用 CMake 构建,Ninja 生成器为推荐方式:
cmake -G Ninja && ninja构建后运行自带测试:
python demumble_test.py从 CMakeLists.txt 可以看到构建配置的若干细节:
project(demumble C CXX),同时编译 C 与 C++ 源文件;add_definitions(-D_LIBCXXABI_FUNC_VIS=):让 libc++abi 的符号可见性宏在非 libc++abi 环境下退化为空定义,这是cxa_demangle.cpp能独立编译的关键;- 目标
demumble由三个源文件组成:主程序demumble.cc、third_party/wine/undname.c、third_party/libcxxabi/cxa_demangle.cpp; CXX_STANDARD 11(CXX_STANDARD_REQUIRED ON):C++11 是硬性要求;- Apple 平台追加
-mmacosx-version-min=10.9(10.9 是 clang 默认切到 libc++ 且 ld64 默认剥离__TEXT,__eh_frame的起始版本,README 与构建脚本注释均提及); - Windows 上通过
string(REGEX REPLACE "/MD" "/MT" ...)将 CRT 从动态链接改为静态链接,保证单文件分发的可移植性。
Windows 上的编译器选择:VS2015+ 或 clang-cl
cxa_demangle.cpp用到的 C++11 特性超出了 Visual Studio 2013 的支持范围,因此 README 明确要求在 Windows 上使用Visual Studio 2015 或更新版本,或改用 clang-cl 作为 C++ 编译器:
cmake -G Ninja -DCMAKE_CXX_COMPILER=path/to/llvm-build/bin/clang-cl.exe注意:本仓库内的 demumble.vcxproj 使用v142(VS2019)工具集并配置为静态库,与 README 的"VS2015+"要求一致,且定义了_LIBCXXABI_FUNC_VIS=、_CRT_SECURE_NO_WARNINGS、_MBCS等预处理宏以保证两个第三方源文件顺利编译。
四、在 Dependencies 中的真实用法:符号解码回退链与测试验证
demumble 在本仓库中并不以命令行工具形式出现,而是作为ClrPhlib 的底层解码器之一被深度集成。理解这条调用链,有助于你在分析 PE 导入/导出符号时判断结果的来源。
1. 解码器枚举与选择
ClrPhlib/include/ClrPhlib.h 定义了CLRPH_DEMANGLER枚举,罗列了全部可用解码器:
public enum class CLRPH_DEMANGLER { None, Demumble, LLVMItanium, LLVMMicrosoft, Microsoft, Default // Synthetic demangler using all the previous ones };其中Default是"综合解码器",按固定顺序依次尝试其余四个。每个具体解码器都有对应的 C# 可调用包装方法,见 ClrPhlib/src/managed/PhSymbolProvider.cpp:
UndecorateNameDemumble→DemumbleDemangleNameUndecorateNameLLVMItanium→LLVMItaniumDemangleNameUndecorateNameLLVMMicrosoft→LLVMMicrosoftDemangleNameUndecorateNamePh→UndecorateSymbolDemangleName
2. 解码回退链的实现细节
核心逻辑位于 ClrPhlib/src/unmanaged/demangle.cpp 的UnmanagedSymPrv::DemangleName,其回退顺序为:
- 先试 demumble:
DemumbleDemangleName把宽字符修饰名转 ASCII 后调用 libc++abi 的__cxa_demangle,成功后标记CLRPH_DEMANGLER::Demumble; - 再试 LLVM Itanium:仅当名字以
_Z开头(!_wcsnicmp(DecoratedName, L"_Z", 2))时调用llvm::itaniumDemangle——注释说明该启发式规则取自 LLVM 7.0.0 的LLVMSymbolizer::DemangleName; - 然后用 phlib 的 UndecorateName:
UndecorateSymbolDemangleName调用 Process Hacker 库的PhUndecorateNameW(底层走 dbghelp 的UnDecorateSymbolName),专门处理 MSVC 符号; - 最后用 LLVM Microsoft 解码器兜底:
llvm::microsoftDemangle作为 last chance。
全部失败时返回CLRPH_DEMANGLER::None与空串。每一层成功或失败都会走DEMANGLER_DEBUGLOG_*宏输出调试日志(默认DEMANGLER_DEBUGLOG_ON为false关闭)。
这里可以清晰看到 demumble(Itanium 解码)与 phlib/LLVM(MSVC 解码)在 Dependencies 中的分工:demumble 是 GCC/Clang 符号的第一选择,MSVC 符号则走 phlib 与 LLVM 两条路。
3. 仓库自带的测试程序
仓库提供了独立的 test/demangler-test/Program.cs 测试工程,可直接验证各解码器行为:
Usage: demangler [options] FILE_OR_SYMBOL Options: -v, --verbose redirect debug traces to console -h, --help show this message and exit -d=, --demangler=NAME Choose demangler name-d可选值:Default、Demumble、LLVMItanium、LLVMMicrosoft、Microsoft,与CLRPH_DEMANGLER枚举一一对应(ParseDemanglerName中会校验非法值);- 无参数运行时,使用
Microsoft解码器对一组已知 MSVC 修饰名做断言自检(TestKnownInputs),例如??1type_info@@UEAA@XZ→public: virtual __cdecl type_info::~type_info(void) __ptr64,全部通过则输出demangler-test : all known inputs OK; - 传入参数时:若参数是磁盘上存在的文件,则按 PE 解析其导出表与导入表(跳过按序号导入的项),逐条反修饰并打印(
TestFilepath);否则把参数当作单个符号直接反修饰。
这组断言既验证了 MSVC 路径的正确性,也为我们排查"某个 DLL 导出名为何显示为空/原样"提供了现成的复现手段。
五、实战小结:何时该用哪个解码器
结合 README 特性和本仓库的集成方式,可以总结出如下选择建议:
| 场景 | 推荐解码器 | 依据 |
|---|---|---|
| 命令行快速解码单个 GCC/Clang 符号 | demumble(或Default) | Itanium 解码首选,智能处理前导下划线 |
| 批量过滤文本/bitcode 中的函数符号 | demumble 加-m标志 | 只输出形似符号的内容,避免类型名误伤 |
| 解码 MSVC 修饰的导出/导入名 | Microsoft(phlib→dbghelp) | UndecorateSymbolDemangleName走PhUndecorateNameW |
| 不确定符号来源,想要最高成功率 | Default | 依次尝试 demumble → LLVM Itanium → phlib → LLVM Microsoft |
| 需要可移植的 Windows 静态库集成 | 直接链接本仓库的 demumble.vcxproj 静态库 | 静态链接 CRT(/MT),v142 工具集 |
需要留意的是:README 中的-m、-_、-n等参数属于 demumble独立可执行文件(demumble.cc主程序)的命令行接口;本仓库的集成方式是静态库形式,暴露给 C# 层的只是CLRPH_DEMANGLER枚举与UndecorateName*系列方法。若要在项目内做符号解码,推荐直接使用 ClrPhlib/src/managed/PhSymbolProvider.cpp 提供的UndecorateName系列 API;若只是想快速验证某个符号,则用demangler-test工具(-d=指定解码器)即可。
相关可继续深入阅读的文件:
- third_party/demumble/README.md:demumble 官方说明(本文所依据的主体文档);
- third_party/demumble/CMakeLists.txt:跨平台构建配置;
- third_party/demumble/demumble.vcxproj:本仓库使用的静态库工程;
- ClrPhlib/src/unmanaged/demangle.cpp:四个解码器的 C++ 实现与回退链;
- ClrPhlib/include/ClrPhlib.h:
CLRPH_DEMANGLER枚举定义; - test/demangler-test/Program.cs:各解码器的可执行测试入口。
- 开发工具
- 桌面应用
- 逆向工程
【免费下载链接】Dependencies
A rewrite of the old legacy software "depends.exe" in C# for Windows devs to troubleshoot dll load dependencies issues.
相关推荐
NativeWind状态修饰符终极指南:掌握hover、focus、active的跨平台实现
NativeWind状态修饰符终极指南:掌握hover、focus、active的跨平台实现 NativeWind作为React Native的实用优先设计系统
移动开发跨平台前端深入解析 Go 符号反修饰库 demangle:C++ 与 Rust 符号名称还原的完整实战指南
深入解析 Go 符号反修饰库 demangle:C++ 与 Rust 符号名称还原的完整实战指南 导读 本文以 Agones 仓库中 vendored 的 de
游戏开发云原生mold 中的 Rust 符号反修饰:rust-demangle.c 单文件 C99 移植版解析
mold 中的 Rust 符号反修饰:rust demangle.c 单文件 C99 移植版解析 本篇技术指南围绕 mold 仓库内嵌的 third party
开发工具构建工具系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考