一个原生 x64 和 ARM64 WinDbg 扩展,带有持久的 SWI-Prolog 工作进程。它从用户模式转储或已停止的原生 x64 或 ARM64 进程中公开线程、堆栈帧、符号、已加载模块和内存。加载你自己的 Prolog 文件,并从调试器命令窗口运行查询。
版本: 0.2.0(实验性;原生 ARM64 支持)。
使用为你的调试器架构构建的 DLL。调试器、DLL 和原生目标必须匹配:x64/AMD64 一起,或 ARM64 一起。跨架构作用域上下文、WOW64、ARM32 和 ARM64EC 目标不在本版本的范围内。原生 x64 和 ARM64 构建通过了全部 99 项可移植性检查以及 CDB 转储/实时冒烟测试。有关运行、日志和剩余的手动检查,请参阅 docs/VALIDATION.md。
源代码仓库不包含编译后的二进制文件。使用下面的 Windows 说明进行构建,以填充 dist/x64/ 或 dist/arm64/。发布归档文件命名为 PrologWinDbg-v0.2.0-win-x64.zip 和 PrologWinDbg-v0.2.0-win-arm64.zip;每个归档将其选定的二进制文件放入 dist/。下面的示例使用该发布归档布局。对于源代码构建,请在路径中替换为 dist/arm64 或 dist/x64。
安装 SWI-Prolog 的 Windows 控制台版本。 原生 ARM64 安装程序可从开发下载获取。 CI 矩阵在两种架构上均使用原生 SWI-Prolog 10.1.18-1。SWI 作为单独的进程运行;它不会被链接到 DLL 中,也不会与 DLL 捆绑在一起。
将 dist/prolog.dll 与 dist/prolog 和 dist/examples 目录放在一起。
在匹配的 x64 或 ARM64 WinDbg 中打开原生用户模式转储,或附加到该架构的原生进程并中断进入。要获得可复现的转储,请在 PowerShell 中运行以下命令:
.\dist\prologdbg_target.exe --dump C:\Temp\prologdbg-sample.dmp
在 WinDbg 中,将这些路径替换为你实际的项目目录。在带引号的路径中使用正斜杠:调试器会将反斜杠视为转义字符。
.load C:/PrologWinDbg/dist/prolog.dll
!prolog.runtime "C:/Program Files/swipl/bin/swipl.exe"
!prolog.info
!prolog.consult "C:/PrologWinDbg/dist/examples/patterns.pl"
!prolog.query wd:current_snapshot(S), wd:architecture(S,Architecture).
!prolog.query wd:current_snapshot(S), wd:thread_id(S,T,WindowsTid).
!prolog.query wd:current_snapshot(S), wd:stack_frame(S,T,Depth,IP).
!prolog.query wd:current_snapshot(S), patterns:stack_contains(S,T,Module,Function).
该扩展使用显式运行时路径、PROLOGDBG_SWIPL 环境变量,或标准的 Program Files\swipl\bin\swipl.exe 安装路径。它在首次需要时启动工作进程。所有通信均使用本地管道。
对于所提供的目标,检查已知的八字节标记:
!prolog.query wd:current_snapshot(S), wd:lookup(S,'prologdbg_target!g_magic',ok(A)), patterns:inspect_magic(S,A,Verdict).
预期判定结果:matched(prologdb, Address)。标记字节拼出
PROLOGDB。目标程序导出了这些名称,因此此示例不需要
PDB。私有 PDB 对其他函数和应用程序很有用。
在 WinDbg 下启动 prologdbg_target.exe --live。继续越过初始的
加载器断点。随后目标程序在两个工作线程启动后于 CapturePoint 处中断。
使用与转储相同的查询。
或者,运行 prologdbg_target.exe --wait,附加到其打印的 PID,
然后中断进入。此模式若未提前停止,将在十分钟后退出。
使用常规的 WinDbg bp、g、p 和 t 命令控制执行。每次
!prolog.query 都会收集一份新的快照。已注册的 Prolog 断点
动作和自动继续功能计划在后续版本中提供。
| 命令 | 用途 |
|---|---|
!prolog.help | 显示命令帮助 |
!prolog.runtime "swipl.exe 的绝对路径" | 选择运行时并停止任何现有的工作线程 |
!prolog.info | 启动工作线程并报告其版本 |
!prolog.consult "rules.pl 的绝对路径" | 加载或重新加载 Prolog 源文件 |
!prolog.query Goal. | 收集证据并枚举解 |
!prolog.queryfile "goal.txt 的绝对路径" | 从文件读取单个 UTF-8 目标,然后查询 |
!prolog.eval Goal. | 在没有调试器证据的情况下求值 Prolog |
!prolog.limits | 显示当前限制 |
!prolog.limits 5000 100 128 10000000 | 设置毫秒数、答案数量、每线程帧数和推理限制 |
!prolog.reset | 停止工作线程;已加载的规则和自定义运行时状态将丢失 |
简单目标直接跟在命令之后。标准 Prolog 单引号用于界定原子。
可以省略末尾的句点。每个命令使用一个目标,用逗号连接子目标。
对于 Prolog 析取(;),将整个目标用双引号括起来,以保护其免受 WinDbg 命令分隔符的影响:
!prolog.eval "(X=a;X=b), writeln(X)."
对于复杂的引用、Unicode 文本或多行目标,请将目标放入 UTF-8 文本文件,并使用 !prolog.queryfile "C:/rules/goal.txt"。其内容会直接传给 Prolog,绕过 WinDbg 的命令文本解析。文件大小限制为 1 MiB。对于可复用的逻辑,请在已 consult 的 .pl 文件中定义谓词。
无目标的示例:
!prolog.eval member(X,[1,2,3]), Y is X*X.
答案以 bindings([Name=Value, ...], ResidualGoals) 的形式打印。第二个列表保留残余约束。最终状态区分搜索耗尽、失败、答案数量限制、推理限制、超时和异常。达到答案数量限制并不表示还存在更多解。
查询文件会执行正常的 Prolog 加载和初始化。加载错误会被报告,但 Prolog 加载不是事务性的:在发生错误之前,某些子句可能已经加载完成。请修正并重新加载该文件,或重置 worker。
:- module(my_rules, [in_function/4]).
:- use_module('C:/PrologWinDbg/dist/prolog/wd.pl').
in_function(S, T, Module, Function) :-
wd:stack_frame(S, T, _, IP),
wd:symbol(S, IP, Module, Function, _).
然后:
!prolog.consult "C:/rules/my_rules.pl"
!prolog.query wd:current_snapshot(S), my_rules:in_function(S,T,myapp,'ProcessRequest').
关于谓词模式和证据语义,请参阅 docs/API.md。初始规则提供栈匹配、跨线程共享函数、显式栈证据、不可读地址,以及带有不确定结果的内存签名诊断。
默认查询预算为 5 秒、100 个答案、每线程 128 个帧,以及 1000 万次推理。快照收集拥有同样大小的独立时间预算。初始符号加载可能较慢:请预热符号缓存,或使用 !prolog.limits 提高第一个值。
内存请求每次最多 64 KiB,每次查询总计最多 16 MiB。快照捕获上限为 4,096 个线程、4,096 个已加载模块,以及 8 MiB 的事实文本。工作进程使用 512 MiB 的 Prolog 栈限制和 768 MiB 的 Windows 进程内存限制。原生桥接还限制输出、RPC 调用次数和 IPC 等待时间。
使用 WinDbg 的 Break 操作 / Ctrl+Break 来中断命令。在等待工作进程或遍历快照项时,扩展会检查 GetInterrupt。被中断的 IPC 会重置工作进程;之后请再次查阅你的规则。正常的 Prolog 时间/推理限制会保留工作进程。!prolog.reset 命令可在当前命令返回后使用;它不是并发取消命令。
单个 DbgEng 调用,尤其是符号加载和远程读取,是同步的。扩展在调用之间检查已用时间和中断;当 DbgEng 本身被阻塞时,它无法承诺硬性截止时间。
手动中断检查:
!prolog.limits 600000 100 128 1000000000
!prolog.eval sleep(300).
按 Ctrl+Break。确认提示符返回后,运行 !prolog.info 和
!prolog.eval X is 2+2.。之后恢复正常限制。
安装 Visual Studio 2022(使用 C++ 的桌面开发)、CMake,以及带有 Debugging Tools for Windows 的
Windows SDK。对于 ARM64,请安装 Visual
Studio C++ ARM64 生成工具。运行所有可移植测试需要 SWI-Prolog 和 Python 3。
将 swipl.exe 和 python.exe 添加到 PATH。
在 Visual Studio Developer PowerShell 中:
.\scripts\build-windows.ps1 -Architecture ARM64
.\scripts\smoke-windows.ps1 -Architecture ARM64
使用 -Architecture x64 指定 x64。省略该参数会选择原生 Windows 主机架构,包括从模拟的 PowerShell 进程中选择。构建目录和包是分开的(build-windows-arm64、dist/arm64 以及对应的 x64 版本)。x64 主机可以交叉编译 ARM64,但会跳过原生 C++ 测试;请在 ARM64 Windows 上运行冒烟测试和 CTest。
如果调试器 SDK 位于其他位置,请传入 -DebuggingTools。它应包含 sdk\inc\dbgeng.h(或 inc\dbgeng.h);编译器也可以使用其 Windows SDK 包含路径。如果 CDB 或 SWI-Prolog 位于其他位置,请传入 -Cdb 和 -Swipl。冒烟脚本会选择 Debuggers/arm64/cdb.exe 或 Debuggers/x64/cdb.exe,并在启动前验证 CDB、DLL 和测试目标的 PE 架构。传入 -Dist .\dist 可测试解压后的发布归档。MSVC 构建会静态链接 C++ 运行时,并包含测试目标的 PDB。
冒烟脚本会在新的临时目录中创建自己的目标进程、完整转储、调试器命令文件和日志。它会测试转储会话和实时会话、线程/作用域恢复、内存证据、超时恢复、工作进程重置以及 DLL 卸载/重新加载。它会保留日志和转储并打印其目录。它不会附加到现有的无关进程。
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
可选的 MinGW 交叉构建使用 cmake/mingw-x64.cmake,以及通过 DEBUGGING_TOOLS_ROOT 单独提供的当前 Microsoft DbgEng 头文件。MinGW 自带的头文件对于此处使用的宽字符接口来说过于陈旧。小型的 dbgeng_compat.hpp 包装器可在不修改 Microsoft 头文件或接口布局的情况下处理缺失的 SAL 注解。
cmake -S . -B build-cross -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/mingw-x64.cmake \
-DDEBUGGING_TOOLS_ROOT=/path/to/debugger-sdk
cmake --build build-cross
cmake --install build-cross --prefix dist
构建依赖和运行时声明见 THIRD_PARTY_NOTICES.txt。
版权所有 © 2026 Dmitry Vostokov。原始 PrologWinDbg 项目代码和 文档依据 PolyForm Strict License 1.0.0 许可, 与 VDL Studio 使用的许可相同。 该许可同样适用于发布版 v0.1.0 中的原始项目代码。
你可以下载并将其用于个人及其他非商业目的;
再分发、修改、衍生作品和商业使用需要
作者的单独许可。完整条款见 LICENSE.md。
第三方组件保留其自身许可,记录于
THIRD_PARTY_NOTICES.txt 和 licenses/。