内容
# Delphi Win64 调试器
调试 Delphi Win64 应用程序,不需要在 Embarcadero IDE 中 — 一个真正的调试器,基于 Windows 调试 API,使用 Delphi 编写。
该仓库包含 **三个程序**,它们共享一个调试引擎:
| | 它是什么 | 它位于哪里 |
|---|---|---|
| **调试适配器** | 一个 **调试适配器协议(DAP)** 服务器。这是调试器本身:断点,步进,调用堆栈,变量,表达式求值。任何 DAP 客户端都可以驱动它。 | `VisualStudioCodeDelphiDebugger\` |
| **VS Code 扩展** | 使其在编辑器中可用的客户端:`delphi-win64` 调试类型,附加进程选择器,状态栏进度和异常规则编辑器。 | `install\local.delphi-win64-debug\` |
| **MCP 服务器** | 通过模型上下文协议将同一个引擎暴露给 **AI 代理** — 33 个工具(`set_breakpoint`,`step_into`,`get_locals`,`evaluate_expression`,`get_call_stack`,`read_memory`,...)。它允许代理运行程序,停止它并读取其实际状态,而不是从源代码中猜测。 | `MCPDebugger\` |
引擎是共享的:`DebuggerCore\` 包含 Windows 调试 API 循环,符号读取器(`.rsm`,TD32,`.map`,`.dcp`,JCL)和表达式求值器。三个前端都很薄。
> **您可能还需要 [Delphi IDE 插件](https://github.com/csm101/EditInVsCodeDelphiPlugin)**。
> 它在 Delphi IDE 中添加了一个 *打开 VS Code* 命令,该命令为当前项目生成工作区和启动配置:输出路径,源根目录,单元搜索路径和 BPL 项目的包列表。一个真正的 Delphi 项目带有数百个搜索路径,没有人愿意手动编写这些路径。如果您自己编写 `launch.json`,则调试器无需此插件即可工作,但该插件使调试更加实用 — 请先安装它。
> ⚠️ **编译要调试的程序时请包含完整的调试信息,否则大多数功能将无法正常工作。** 调试器只能显示编译器生成的内容。在 Delphi 项目选项中,对于 **Win64 调试** 配置:
>
> - *编译* → **优化关闭**,**调试信息** 打开,**本地符号** 打开;
> - *链接* → **调试信息** 打开,**包含远程调试符号** 打开(这是 `.rsm`),以及 **映射文件:详细**。
>
> 在命令行上,这是 `-$O- -V -VN -VR`,这是此仓库中的 `Debugme.cfg` 所使用的配置。测量该项目:仅这四个标志就已经生成了带有 TD32 部分的 `.exe`,`.map` 和 `.rsm` — `-GD`(映射文件:详细)结果证明对于生成 `.map` 并不必要,尽管它是 IDE 设置的内容,不会造成伤害。请将 `.map` 和 `.rsm` 放在 `.exe` 旁边。
>
> 要 **步入 RTL 和 VCL** 而不是跳过它们,还请启用 *编译* → **使用调试 .dcus**。
>
> 每个伪影的作用:
>
> | 伪影 | 没有它 |
> |---|---|
> | TD32 部分,`.map` 或 JCL 数据 — **任意一个** | 没有源代码行:没有断点,没有步进 |
> | **`.rsm`** | 断点和步进仍然有效,但本地变量,类型和表达式求值受到严重限制 |
> | 优化 **关闭** | 断点落在错误的 行上,本地变量读取为垃圾,因为代码不再与源代码匹配 |
>
> 这也适用于您要步入的每个 **运行时包**:编译时没有调试信息的 BPL 仍然是一个黑盒,即使主机具有完整的符号。
> **状态**:工作且功能丰富。启动和附加,断点(行 / 条件 / 命中断点 / 日志点),步进(过 / 进入 / 出)以及设置下一语句,调用堆栈带有函数名称,变量检查(本地,寄存器,全局,嵌套对象 / 记录 / 动态数组扩展)带有类型感知格式化,一个完整的 Pascal 表达式求值器用于监视 / 悬停 / REPL,`setVariable`(基元,字符串,枚举,集合,`var` 参数),以及一个可配置的异常引擎(过滤器 + 每异常规则)都已功能正常。参见 [功能列表](#功能列表) 以获取完整列表。
---
## 先决条件
| 工具 | 用途 |
|---|---|
| **Delphi 10.3 Rio 或更高版本** (`dcc64` 在 `rsvars.bat` 后在 PATH 中) | 编译适配器和调试目标 |
| **VS Code** | 编辑器扩展插件进入 |
| **[Delphi IDE 插件](https://github.com/csm101/EditInVsCodeDelphiPlugin)** | 从 Delphi 项目生成工作区和启动配置。不是严格必需的,但请参见上面的注释 |
| **DelphiLSP 扩展** (`embarcaderotechnologies.delphilsp`) | VS Code 中的 Delphi 语言支持(语法,自动完成)。Embarcadero 的单独扩展;此扩展仅用于调试 |
| **JCL 源代码** (可选) | 仅用于默认的 JCL 调试信息支持;使用 `JCL_DEBUG_OFF` 构建以省略它 — 参见 [可选:JCL 调试信息支持](#可选:JCL-调试信息支持) |
---
## 快速入门
> **您只想使用它?** 不需要构建任何内容。从
> [最新版本](https://github.com/csm101/delphi-visual-studio-code-debugger/releases)
> 中获取 `delphi-win64-debugger-setup-*.zip`,解压到任意位置,然后运行 `Setup.exe`。调试适配器,MCP 服务器和 VS Code 扩展已经编译在内,因此该机器上不需要 Delphi 工具链和构建步骤。Windows 会警告可执行文件是未签名的 — 参见发布说明。
>
> 本节的其余部分适用于从源代码 **开发** 调试器。
假设已安装 Delphi(Athens 或更高版本)并且 `dcc64` 在 `rsvars.bat` 后在 PATH 中,并且已安装 VS Code。
这是从源代码 **开发** 调试器的工作路径 — 它在开发模式下安装扩展,VS Code 直接从编译器输出启动适配器,因此重建足够更新(无需复制步骤)。要仅安装调试器或将其部署到没有源代码的机器上,请参见后面的替代方法。
```bat
:: 1. 获取仓库
git clone <仓库 URL> Win64Debugger
cd Win64Debugger
:: 2. 构建适配器并在 **开发模式** 下安装扩展。它写入一个清单,其“程序”直接指向构建输出的可执行文件,因此重建后会立即反映在下一个调试会话中,无需复制。运行一次。
call install-dev.bat
:: 3. 构建捆绑的示例,以便有东西可以调试。新的克隆没有 `.exe/.map/.rsm`(它们被 gitignore),因此构建它们一次。
call rsvars.bat
dcc64 Debugme.dpr
```
现在打开项目并启动会话:
1. 如果 VS Code 提示安装推荐的 **DelphiLSP** 扩展(`embarcaderotechnologies.delphilsp`,来自 `.vscode/extensions.json`),请接受。
2. 打开 **`Win64DebuggerProj.code-workspace`** — 打开 *工作区文件*,而不是文件夹;它连接了 DelphiLSP 和编译器设置。
3. 重载窗口(`Ctrl+Shift+P` → *开发人员:重载窗口*)。
4. 在 **运行和调试** (`Ctrl+Shift+D`)中,选择 **“调试 Debugme(Delphi DAP)”** 并按 `F5`。您应该到达入口点并能够步进,设置断点和检查变量。
更改适配器源代码后,编辑/测试循环仅为:
1. 停止正在运行的调试会话(`Shift+F5`)— 可执行文件在运行时被锁定。
2. `call build_dap.bat`
3. `F5` — VS Code 重新启动新构建的适配器。无需重新安装,无需复制。
要调试您自己的 **应用程序**,请用调试信息编译它(`-V -VN -VR`,优化关闭),以便在 `.exe` 旁边生成 `.map` 和 `.rsm`,然后添加启动配置 — 参见
[启动配置](#启动配置)。
> **安装以使用,而不是开发** — 要将适配器复制到扩展文件夹(因此它不再依赖于代码仓库),请运行 `build_installer.bat`,然后运行 `install\Install.exe`。它是交互式的:需要时构建适配器,检测 VS Code / VS Code Insiders,确认后更新现有安装。参见 [VS Code 扩展设置](#vs-code-扩展设置)。
> **部署到另一台机器** — 要在没有仓库或 Delphi 工具链的机器上安装,构建一个自包含的 zip 文件,运行 `build_setup_zip.bat` 并部署 — 参见
> [可部署的 zip 文件](#可部署的-zip-文件-无-repo-无-delphi-在目标上)。
---
## 构建
### 一次性:构建所有内容
```bat
call build_debug.bat
```
这会初始化 Delphi 编译器环境(`rsvars.bat`),编译 `Debugme.exe`(生成其 `.map` 和 `.rsm`),并编译 `VisualStudioCodeDelphiDebugger.exe`。
### 仅适配器(更快迭代)
```bat
call build_dap.bat
```
### 调试目标的编译器标志
编译要调试的程序时必须激活的关键标志:
| 标志 | 用途 |
|---|---|
| `-$O-` | 禁用优化 |
| `-V -VN -VR` | 生成调试信息和 `.map` 文件 |
| `-DDEBUG` | 可选的条件定义 |
| `-E.\Win64\Debug` | 输出目录 |
对于 `Debugme`,这些设置在 `Debugme.cfg` 中(每行一个,没有前导 `-`)。
### 可选:JCL 调试信息支持
适配器还可以读取 **JCL** (JEDI Code Library)调试数据 — JCL 以链接的 `JCLDEBUG` PE 部分或边车 `.jdbg` 文件存储的 MAP 派生的符号表 — 作为地址→位置和过程名称的备用程序,用于没有嵌入式 TD32 / 没有 `.map` 的模块。它在 `DebuggerCore\JclDebugReader.pas` 中实现,并由条件 `JCL_DEBUG` 控制。
`JCL_DEBUG` **默认开启**,这需要 JCL 源代码位于 `C:\Athens\jcl\jcl\source` (子目录 `windows`,`common`,`include`);调整这些路径如果您的 JCL 位于其他位置。受影响的配置是适配器 `VisualStudioCodeDelphiDebugger\VisualStudioCodeDelphiDebugger.cfg`,测试运行器 `DebuggerTests\RunTests.cfg` 和 `build_mcp.bat`。
**要禁用 JCL** (例如 JCL 未安装):用 `JCL_DEBUG_OFF` 条件定义编译。 这完全删除了 `uses JclDebug`,因此不需要安装 JCL,JCL 搜索路径被忽略(它们是无害的,当定义关闭时)。将其添加到相关 `.cfg` 文件中,例如:
```text
-DJCL_DEBUG_OFF
```
进入 `VisualStudioCodeDelphiDebugger.cfg`,`DebuggerTests\RunTests.cfg`,并在 `build_mcp.bat` 中传递 `-DJCL_DEBUG_OFF` 到 `dcc64` 命令行。关闭定义后,`JclDebugReader` 编译为 no-op 工厂(它从不注册提供程序);所有其他功能 - TD32,`.map`,`.rsm`/`.dcp` 符号解析 - 不受影响。
> 如果您从 **IDE** (通过 `.dproj` 的 msbuild)而不是这些命令行脚本构建适配器,MCP 服务器或测试运行器,请在项目选项中应用相同的选择:添加 JCL 搜索路径(以保持开启)或定义 `JCL_DEBUG_OFF`(以关闭),因为 IDE 构建会从 `.dproj` 重新生成 `.cfg`。
### 可选:预构建符号索引侧车
调试器第一次读取模块的 `.rsm` 或 `.dcp` 时,它会扫描整个容器以构建查找索引,然后将该索引缓存在 `<file>.idx` 侧车中。
捕获的是 **何时** 发生扫描:延迟,在停止时,在调试器回答请求的线程上。 因此,在第一次触及多个运行时包时停止会暂停会话,而这些包正在被索引。
RTL 和第三方包在您的构建之间永远不会更改,因此可以离线构建它们的侧车:
```bat
cmd /c "C:\Athens\GitHub\Win64Debugger\DevTools\build_all.bat"
rem Delphi DCP Corpus - 几百个文件,一次通过,几分钟
DevTools\Win64\Debug\PrebuildIdx.exe "C:\Users\Public\Documents\Embarcadero\Studio\23.0\Dcp\Win64" -j 2
```
添加 `-force` 当侧车已经存在但由该项目的旧版本写入时:格式或解析器的更改使其过时,过时的侧车被忽略,因此在每次会话中都会支付冷扫描费用,直到它们被重建。
这 **不** 有助于您自己的包 — 您不断重新编译这些包,这会因设计而使其侧车失效。如果值得自动化,请在构建后将工具运行到您的输出目录作为后期构建步骤。
源代码位于 `DevTools\PrebuildIdx.dpr`;`DevTools\README.md` 提供了完整的标志列表,包括 `-verify`,它重建每个侧车并通过 SHA-256 与磁盘上已有的文件进行比较(在触摸解析器后很有用)。
---
## 测试
自动集成套件位于 `DebuggerTests\`。它构建一个专用测试目标,对其启动真正的 DAP 适配器,并断言断点,步进,本地变量和表达式求值器的正确性。 在适配器或 RSM/TD32 解析器发生任何更改后运行完整的套件。
```bat
:: 构建目标 + 运行器,然后运行所有内容
call DebuggerTests\build_and_run.bat
```
脚本内部使用 `cd /d %~dp0`,因此它们可以从任意工作目录调用。更细粒度的入口点:
| 脚本 | 用途 |
|---|---|
| `DebuggerTests\build_and_run.bat` | 构建调试目标和运行器,然后运行套件 |
| `DebuggerTests\build_target.bat` | 仅构建调试目标 (`TestTarget.exe`) |
| `DebuggerTests\build_runner.bat` | 仅构建测试运行器 (`RunTests.exe`) |
| `DebuggerTests\run_tests.bat` | 运行已构建的测试 |
单独的诊断工具(RSM/TD32 检查器,PE 转储程序)位于 `DevTools\`;参见 `DevTools\README.md`。
---
## VS Code 扩展设置
扩展程序位于 VS Code 扩展文件夹下的本地目录中。 **不** 发布到市场。
### 推荐:安装程序
构建适配器(`build_dap.bat`)后,运行:
```bat
call build_installer.bat
install\Install.exe
```
`Install.exe` 将新构建的适配器放到扩展清单旁边,检测您的 VS Code 安装(s),并将扩展复制到 `%USERPROFILE%\.vscode\extensions\local.delphi-win64-debug\`。如果已经安装了以前的版本,它会确认后在原地更新。之后重新加载 VS Code。
### 脚本化替代方案
```bat
:: 构建适配器并将其分段到 install\local.delphi-win64-debug\
call update-install.bat
:: 将分段的文件夹复制到 VS Code 扩展目录
call install\install.bat
```
### 手动
1. 构建适配器(`build_dap.bat`)。
2. 创建文件夹 `%USERPROFILE%\.vscode\extensions\local.delphi-win64-debug\`。
3. 复制 `install\local.delphi-win64-debug\package.json` 和构建的 `VisualStudioCodeDelphiDebugger.exe`(`VisualStudioCodeDelphiDebugger\Win64\Debug\`) 到其中。
4. 重新加载 VS Code(`Ctrl+Shift+P` → *开发人员:重新加载窗口*)。
捆绑的 `package.json` 注册 `delphi-win64` 调试类型,并通过相对路径 `./VisualStudioCodeDelphiDebugger.exe` 指向适配器,因此不需要编辑路径,只要可执行文件与其位于同一位置即可。
### 适配器可执行文件的位置
构建、分段和安装都保留一份副本:
| 阶段 | 位置 |
|---|---|
| 构建输出 | `VisualStudioCodeDelphiDebugger\Win64\Debug\VisualStudioCodeDelphiDebugger.exe` |
| 分段(gitignored,仓库内) | `install\local.delphi-win64-debug\VisualStudioCodeDelphiDebugger.exe` |
| 已安装(VS Code 启动的是什么) | `%USERPROFILE%\.vscode\extensions\local.delphi-win64-debug\VisualStudioCodeDelphiDebugger.exe` |
### 可部署的 zip 文件(无 repo,无 Delphi 目标)
要在没有仓库或 Delphi 工具链的机器上安装调试器,请构建一个自包含的 zip 文件:
```bat
call build_setup_zip.bat
```
它构建适配器,构建可移植安装程序,并将所有内容捆绑到 `dist\delphi-win64-debugger-setup-v<version>.zip`:
```text
Setup.exe ← 可移植安装程序 / 更新程序
local.delphi-win64-debug\ ← 扩展(清单 + 预构建的适配器 exe)
INSTALL_INSTRUCTIONS.md
```
在目标机器上:提取 zip 文件并运行 `|install\Install.exe`。它会检测本地 VS Code 安装(s),并将扩展复制到 `%USERPROFILE%\.vscode\extensions\local.delphi-win64-debug\`。如果已经安装了以前的版本,它会确认后在原地更新。无需构建步骤 — 适配器已经在 zip 文件中编译完成。
`Setup.exe` 与 `install\Install.exe` 是同一个程序。它会自我检测其上下文:当适配器旁边有 zip 文件布局(zip 布局)时,它会以 **可移植** 模式运行,并直接安装捆绑的文件;在仓库内运行时,它会以 **仓库** 模式运行,并首先从构建输出中构建/分段。
### 开发安装(快速迭代)
上述安装方式会复制适配器,这适合于发布,但不适合于适配器的开发和调试。在开发适配器时,每次修改都需要重新编译和复制适配器。为此,可以使用 `install-dev.bat` 脚本进行开发安装。
`install-dev.bat` 脚本会编译适配器,然后在扩展文件夹中生成一个 `package.json` 文件,该文件的 `program` 字段直接指向编译输出的可执行文件。这样,在开发过程中,无需重新安装适配器,只需重新编译即可。
以下是开发安装的步骤:
1. 运行 `call install-dev.bat` 脚本进行编译和安装。
2. 在 VS Code 中停止正在运行的调试会话(`Shift+F5`)。
3. 运行 `call build_dap.bat` 脚本进行编译。
4. 按 `F5` 键启动调试。
对于扩展的开发,可以使用 `install-dev.bat` 脚本将扩展文件镜像到安装文件夹中。这样,在修改扩展文件后,无需重新安装扩展,只需重新运行 `install-dev.bat` 脚本即可。
---
## 启动配置
`.vscode/launch.json` 文件用于配置调试会话。
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "调试 Delphi Win64",
"type": "delphi-win64",
"request": "launch",
"program": "${workspaceFolder}/Win64/Debug/MyApp.exe",
"mapFile": "${workspaceFolder}/Win64/Debug/MyApp.map",
"sourceRoot": "${workspaceFolder}",
"stopAtEntry": true
}
]
}
```
| 属性 | 默认值 | 描述 |
|---|---|---|
| `program` | *必填* | 被调试的可执行文件路径 |
| `mapFile` | 与 `program` 相同路径,但扩展名为 `.map` | Delphi MAP 文件 |
| `sourceRoot` | *空* | 源文件查找的根目录 |
| `stopAtEntry` | `false` | 是否在进程入口点暂停 |
| `exceptionRules` | *空* | 异常规则表 - 参见 [异常处理](#异常处理) |
| `useGlobalExceptionRules` | `true` | 是否加载共享的全局异常规则文件 - 参见 [共享规则](#共享规则) |
| `globalExceptionRulesPath` | `%USERPROFILE%\.DelphiWinDebugger\exceptionRules.json` | 自定义共享规则文件位置 |
源文件通过搜索 `sourceRoot` 和其子目录来解析。如果一个源文件在多个位置出现,则优先使用第一个匹配的结果。
---
## 异常处理
### 过滤器和显示内容
**断点** 视图提供了四个异常过滤器:
| 过滤器 | 含义 |
|---|---|
| `delphi` | 第一机会 Delphi 异常(`$0EEDFADE`)。可选项:一个以逗号分隔的类名列表。 |
| `av` | 第一机会访问冲突 |
| `all` | 其他所有第一机会异常(可能会产生较多噪音) |
| `unhandled` | 未处理的 / 第二机会异常(始终开启) |
当调试器停止在异常上时,它会报告**类名**和**消息**(从 `Exception.FMessage` 读取),例如 `EOracleError: ORA-00942: 表或视图不存在`。VS Code 在异常小部件中显示此信息,并通过 `exceptionInfo` 请求在详细信息面板中显示。
### 检查当前异常(`$exception`)
在停止于 Delphi 异常时,可以通过合成的 **`$exception`** 伪变量访问实时异常对象:
- **变量面板** - 它出现在 **Locals** 作用域的顶部。其值为 `Class: Message`;单击箭头展开它并检查类、消息和任何字段/属性,就像普通对象一样。
- **监视** - 添加一个条目 `$exception`(将 Locals 行拖到监视中,或键入它),然后展开它。对于特定成员,展开对象 - 例如打开 `$exception` 并读取其 `Message` 子项。
- **悬停** - 在编辑器中悬停 `$exception` 显示相同的值。
它仅在异常停止时存在(在下一个断点/步骤停止时消失),并反映 Delphi 异常对象;访问冲突没有 Delphi 对象,因此不显示 `$exception`(但仍显示类/消息摘要)。
### 按异常规则(`exceptionRules`)
四个过滤器是粗粒度的。要实现细粒度控制,请在启动配置中添加一个 `exceptionRules` 数组。每个规则根据任意组合的条件匹配异常并选择一个**操作**。规则从上到下评估,**第一个匹配的规则生效**;如果没有规则匹配,则上述过滤器选择决定(因此现有的设置仍然有效)。
**匹配条件**(所有指定的条件都必须满足;省略 = 通配符):
| 字段 | 匹配 |
|---|---|
| `class` | 运行时类名的精确匹配,或名称数组(任意),不区分大小写。只有叶节点 - 不匹配后代 |
| `classIs` | 匹配运行时类 **或任何祖先**(Delphi `is` 语义),单个名称或数组,不区分大小写 - 例如 `"classIs": "Exception"` 捕获所有 `Exception` 后代 |
| `code` | Win32 异常代码,或代码数组(任意)。十六进制(`"0xC0000005"`,`"$C0000005"`)或十进制(`3221225477`,或带符号的 ` -1073741819`),作为 JSON 字符串或数字 |
| `message` | 异常消息的不区分大小写的子字符串 |
| `messageRegex` | 消息的正则表达式(忽略大小写) |
| `unit` | 引发异常的源单元(`OracleData` 或 `OracleData.pas`)。特殊值 `"*unknown*"` 匹配没有可解析源的引发 |
| `line` | 引发的精确源行 |
| `lineFrom` / `lineTo` | 包含源行范围 |
> 单元/行指向 **引发站点** - 具有已知源的第一个堆栈帧。对于 Delphi `raise`,OS 异常地址在 RTL 中,因此调试器会步进到第一个用户帧;对于访问冲突,它是故障行。
> **`code` 是唯一可以匹配本机 Windows 异常的标准。**
> 本机异常 - 通过 `RaiseException` 引发,而不是通过 Delphi `raise` - 不携带异常对象,因此没有类和消息:Delphi 标准无法匹配它,而没有 `code` 唯一的方法是关闭整个 `all` 过滤器。典型的案例是 `0xE06D7363`(在 C++ DLL 中引发的 C++ 异常),`0xC0000094`(整数除以零)和任何第三方库定义的客户代码。Delphi 自己的引发是 `0x0EEDFADE`,而访问冲突是 `0xC0000005`,因此 `code` 也适用于这些。
**操作:**
| 操作 | 效果 |
|---|---|
| `ignore` | 立即恢复 - 没有停止,没有日志 |
| `log` | 将 `class: message` 写入调试控制台,然后恢复 |
| `logStack` | 像 `log` 一样,加上格式化调用堆栈 |
| `break` | 在调试器中暂停调试程序(默认行为) |
**示例:**
```jsonc
{
"type": "delphi-win64",
"request": "launch",
"program": "${workspaceFolder}/Win64/Debug/MyApp.exe",
"exceptionRules": [
// EAbort 是控制流噪声:永远不要在此处断开。
{ "class": "EAbort", "action": "ignore" },
// 记录每个 Oracle 错误(带有堆栈)但继续运行。
{ "messageRegex": "ORA-\\d+", "action": "logStack" },
// 仅在热方法行范围内引发 AV 时断开。
{ "class": "EAccessViolation", "unit": "OracleData",
"lineFrom": 2700, "lineTo": 2800, "action": "break" },
// 静默记录来自没有源映射的代码引发的任何内容。
{ "unit": "*unknown*", "action": "log" },
// 捕获所有:仅在其他所有情况下断开。
{ "action": "break" }
]
}
```
```jsonc
// "允许列表" 样式:忽略所有内容,仅在您列出的内容上断开。
"exceptionRules": [
{ "class": ["EMyDomainError", "EValidationError"], "action": "break" },
{ "action": "ignore" }
]
```
```jsonc
// classIs(继承):忽略 EDatabaseError 和所有后代。
"exceptionRules": [
{ "classIs": "EDatabaseError", "action": "ignore" }
]
```
```jsonc
// code:保持“所有第一机会”过滤器开启,同时屏蔽第三方库的本机异常。
// 它们没有类和消息,因此只有 `code` 才能表达这一点。
"exceptionRules": [
{ "code": "0xE06D7363", "action": "ignore" }, // C++ DLL 中的 C++ 异常
{ "code": ["0xC0000094", "0xC0000095"], "action": "logStack" } // int divide by zero / overflow
]
```
### 跨项目共享规则
计算机范围的规则文件允许您定义一个基线并使其适用于所有项目,而无需每个项目都进行配置。默认情况下,适配器读取:
```text
%USERPROFILE%\.DelphiWinDebugger\exceptionRules.json
```
它是一个 JSON 对象,包含一个 `exceptionRules` 数组(也接受一个纯数组),使用与上述相同的规则模式:
```jsonc
// %USERPROFILE%\.DelphiWinDebugger\exceptionRules.json
{
"exceptionRules": [
{ "class": "EAbort", "action": "ignore" },
{ "messageRegex": "ORA-\\d+", "action": "logStack" }
]
}
```
优先级:项目的 `exceptionRules`(在 `launch.json` 中)**首先**被评估,然后是共享文件中的规则 - 因此项目可以覆盖共享基线,而任何未解决的问题都将回落到共享规则(最后是异常过滤器)。在 `launch.json` 中控制它:
| 属性 | 默认值 | 描述 |
|---|---|---|
| `useGlobalExceptionRules` | `true` | 加载共享规则文件 |
| `globalExceptionRulesPath` | *上述路径* | 使用自定义共享规则文件位置 |
缺少或格式错误的共享文件将被忽略(它永远不会中断调试)。
共享文件是 **热重载的**:在断点或异常处编辑它,然后恢复(继续/步骤),新规则将应用于后续异常 - 无需重启会话。适配器仅在恢复时重新读取文件,并且当其时间戳更改时记录一行到调试控制台(项目 `exceptionRules` 来自 `launch.json` 并且不热重载)。
### 从 UI 编辑规则
VS Code 的本机异常 UI 仅提供四个筛选器的开启/关闭复选框,因此扩展程序提供了一个用于规则表的专用编辑器:
**命令调色板(`Ctrl+Shift+P`)→ `Delphi Win64:编辑异常规则...`**
这是始终有效的方法:它无条件地贡献,因此它在没有调试会话、没有打开的 Delphi 文件和没有启动配置的情况下都存在。
相同的命令也作为一个编号列表图标出现在 **断点** 部分的标题栏中 - 部分标题本身,当悬停时会出现图标,而不是上面的面板标题 - 以及 **调用堆栈** 部分标题栏中在会话期间。断点是主要主页:VS Code 在没有会话运行和没有选择启动配置时删除调用堆栈部分,而断点部分只要存在一个断点或工作区被调试一次就会保留。
当停止异常时,`Delphi Win64:为此异常创建规则...` 也会作为浮动调试工具栏中的一个按钮出现,预填充您面前的异常。
选择 `delphi-win64` 启动配置,编辑器打开,每个规则都有一个卡片,按评估顺序编号。每个卡片将 **匹配条件** 与 **操作** 分开,并且上下按钮重新排序规则 - 顺序是语义的,因为第一个匹配项获胜。规则在您键入时进行验证(未知字段,无效操作,不编译的正则表达式,倒置的行范围);保存被阻止,直到表格有效。
**保存到 launch.json** 仅替换所选配置的 `exceptionRules` 值;文件的其他字节 - 包括注释 - 保持不变。文件保持打开且未保存,以便进行更改审查或撤销,然后再进行磁盘操作。如果工作区不受信任,则编辑器为只读的,并提供 **复制 JSON** 替代。
---
## MCP 服务器:从 AI 代理进行调试
相同的引擎通过 **模型上下文协议** 公开,因此代理可以像开发人员一样调试 Delphi 程序 - 运行它,停止它并读取实际状态 - 而不是从源推断行为。
```powershell
cmd /c build_mcp.bat
powershell -ExecutionPolicy Bypass -File register-mcp.ps1 MCPDebugger\Win64\Debug\DelphiDebuggerMcp.exe
```
脚本将服务器注册为 `delphi-win64-debugger`,并使用 `claude` CLI(用户范围)注册到 Claude Code,并通过合并用户 `mcp.json` 注册到 VS Code。它是幂等的,而 `-Unregister` 删除它。`install\Install.exe` 在安装结束时提供相同的注册。
33 个工具涵盖了调试周期:
| 组 | 工具 |
|---|---|
| 会话 | `launch_debuggee`, `launch_from_config`, `attach_to_process`, `attach_from_config`, `list_debuggable_processes`, `detach_debugger`, `terminate_debuggee`, `stop_debugging`, `get_debug_session_status` |
| 断点 | `set_breakpoint`, `set_breakpoints`, `list_breakpoints`, `remove_all_breakpoints`, `set_exception_filters` |
| 执行 | `continue_and_wait`, `step_over`, `step_into`, `step_out`, `pause_execution`, `wait_until_stopped` |
| 状态 | `get_call_stack`, `get_threads`, `get_locals`, `get_variable`, `expand_variable`, `evaluate_expression`, `get_current_source_location`, `get_exception_details`, `get_compact_debug_snapshot` |
| 内存 | `read_memory`, `write_memory` |
| 输出 | `get_debuggee_output`, `get_debugger_output` |
`launch_from_config` 和 `attach_from_config` 读取 IDE 插件生成的 `launch.json`,因此代理程序使用项目的实际符号和源路径启动会话,而不是手动构建的近似值。
`get_compact_debug_snapshot` 返回位置、堆栈和局部变量在一个调用中,这是代理程序通常在停止后想要的,并且比三个往返少得多。
---
## 架构
```
VS Code AI 代理(Claude Code,...)
│ DAP(JSON via stdin/stdout) │ MCP(JSON-RPC via stdin/stdout)
▼ ▼
VisualStudioCodeDelphiDebugger.exe DelphiDebuggerMcp.exe
│ │
└────────────────┬───────────────────────┘
▼
DebuggerCore\ ← 引擎:调试循环、符号读取器(`.rsm`, TD32,`.map`, `.dcp`, JCL)、表达式求值器 │ Windows Debug API(CreateProcess, WaitForDebugEvent, INT3)
▼
被调试的进程
```
两个前端,一个引擎。任一都不拥有自己的调试逻辑:它们
将协议转换为对同一个核心的调用,因此对 stepping 或符号解析的修复适用于两者。
| 组件 | 描述 |
|---|---|
| `DebuggerCore\` | 调试器:Windows 调试循环,符号读取器(`.rsm`, TD32,`.map`, `.dcp`, JCL)、表达式求值器 |
| `VisualStudioCodeDelphiDebugger\` | DAP 服务器:从 stdin 读取 DAP,驱动引擎 |
| `MCPDebugger\` | MCP 服务器:引擎的 33 个工具,代理程序可以调用 |
| `install\local.delphi-win64-debug\` | VS Code 扩展程序:注册 `delphi-win64` 调试类型、附加选择器和异常规则编辑器,并打包适配器 |
| `Debugme.dpr` | 一个小程序用作调试目标开发调试器 |
### 关键源文件
| 文件 | 责任 |
|---|---|
| `DebuggerCore\DapProtocol.pas` | DAP 帧:`Content-Length` 头,JSON 发送/接收,日志 |
| `VisualStudioCodeDelphiDebugger\DapServer.pas` | DAP 请求调度器开发时的调试目标 |
### 技术说明
### DAP 协议
通过 stdin/stdout 传递 JSON 消息,使用 HTTP 风格的 `Content-Length` 帧。VS Code 发送请求;适配器发送响应和事件。完整的规范请参见 <https://microsoft.github.io/debug-adapter-protocol/>。
### Windows 调试 API
使用 `CreateProcess` 和 `DEBUG_ONLY_THIS_PROCESS`。适配器循环调用 `WaitForDebugEvent` 和 `ContinueDebugEvent`。断点设置为 INT3 字节;原始字节在第一次命中时恢复,并在单步执行后重新设置。
### Delphi MAP 文件
Delphi 编译器会生成一个带有段基地址和行号表的 `.map` 文件。MAP 格式使用段相对偏移量(不是 RVAs from ImageBase),因此适配器会解析 `Start` 部分以获取每个段的基 RVA,然后添加每行偏移量以获取正确的 RVA。
运行时,适配器会将调试程序的实际 `ImageBase`(来自 `CREATE_PROCESS_DEBUG_INFO`)与 PE 首选基地址进行比较,以计算每个符号的正确虚拟地址。
---
## 已实现功能
- [x] 启动(Windows 调试 API,`DEBUG_ONLY_THIS_PROCESS`)
- [x] 附加到正在运行的进程(`request: attach`,需要 SeDebugPrivilege;可能时自动提升)
- [x] 在入口点停止(`stopAtEntry: true`)
- [x] 源文件中的断点(从 VS Code gutter 设置)
- [x] 条件断点、命中次数断点和日志点
- [x] 单步跳过(`F10`)、单步进入(`F11`)、单步跳出(`Shift+F11`)
- [x] 继续(`F5`)、暂停(注入 `DebugBreakProcess`)
- [x] 设置下一语句(DAP `gotoTargets` / `goto`)
- [x] 编辑器中突出显示当前源代码行
- [x] 多模块调试:DLL 和运行时加载的 BPL(惰性每模块符号加载)
- [x] 所有线程枚举(带有名称);整个进程一起停止(`allThreadsStopped`)
- [x] 线程特定检查:选择任何线程都会显示该线程自己的调用堆栈,其帧的局部变量/监视是可检查的(只读)
- [x] 来自 Delphi `.map` 文件的 RVA/地址解析(正确处理段相对偏移量)
- [x] ASLR:运行时处理实际 ImageBase 与首选基地址
- [x] 带有函数名称的调用堆栈(帧展开超过当前帧)
- [x] 从反向工程的 Delphi `.rsm` 文件进行局部变量检查
- [x] 从嵌套过程内部可见的父过程局部变量
- [x] 可检查的匿名方法参数和闭包捕获的变量(捕获的 `$ActRec` 字段像正常对象一样展开)
- [x] 在程序的主要 `begin..end` 块中声明的局部变量
- [x] 全局变量检查(带有解码的 `typeId`)
- [x] 变量面板中的寄存器范围
- [x] 类型感知值格式化(整数、浮点数、`TDateTime`、字符、字符串、变体、C 字符串指针、`[...]` 表示法中的动态数组)
- [x] 变量树中的对象/记录/动态数组展开(所有字段、所有祖先类、嵌套)
- [x] 泛型集合检查:`TList<T>` 和 `TDictionary<...>` 展开并枚举其元素
- [x] 编辑器中的悬停数据提示(`evaluateForHovers`)
- [x] 完整的 Pascal 表达式求值器:限定标识符、算术/比较/布尔/集合操作、字符串连接、索引、类型转换、`is` / `as`、`Length` / `SizeOf` / `Ord` / `Low` / `High`、枚举/集合文字、方法和属性调用(在调试程序中执行,干净地中止)
- [x] `setVariable` 用于原始类型、浮点数、日期、字符、大小整数写入
- [x] `setVariable` 用于枚举(按名称/序数)和集合(按位掩码),存储宽度正确
- [x] 通过进程内 `@UStrAsg` / `@LStrAsg` 进行字符串 `setVariable`(无引用计数泄漏)
- [x] `setVariable` 通过指针写入 `var` 参数
- [x] 读取和写入原始调试程序进程内存(MCP `read_memory` / `write_memory`)
- [x] 首机会话异常断点(继续过去已处理的异常)
- [x] 停止时显示异常类名称和消息(`exceptionInfo` 详细信息面板)
- [x] `$exception` 伪变量:Locals 和 Watch 中可检查的实时异常对象
- [x] 异常筛选器 UI(`delphi` / `av` / `all` / `unhandled`,带有每类条件)
- [x] 每异常规则引擎(`exceptionRules`):根据类/消息/正则表达式/单元/行进行匹配,并执行忽略/日志/日志堆栈/断点操作
- [x] 共享机器范围的异常规则(`%USERPROFILE%\.DelphiWinDebugger\exceptionRules.json`)跨项目应用
- [x] 将准确的断点验证状态报告回 VS Code
- [x] 通过 `sourceSearchPaths` 和 `BDS` 环境变量进行 Delphi RTL/VCL 源代码解析
## 路线图 / 待实现
调试器功能:
- [ ] 子进程跟踪(目前仅 `DEBUG_ONLY_THIS_PROCESS`)
- [ ] 反汇编视图(DAP `disassemble` 请求)
- [ ] 32 位(Win32)目标
变量/类型系统:
- [ ] 超出常见集合类之外的更广泛的泛型 *类型* 覆盖
- [ ] 超出已处理的 RTL 类型的更漂亮的打印
项目/打包:
- [ ] 使无 `.map` 构建成为文档化的默认设置(`.map` 在加载时已经是可选的,并且存在 PE 导入表读取器;这是关于消除端到端的依赖)
- [ ] 将 VS Code 扩展发布到市场,而不是本地安装
---
## 已知限制
- **类型信息来自 `.rsm` + TD32,而非 `.map`**:Delphi `.map` 文件仅携带行号和符号地址。完整的变量/类型检查由反向工程的 `.rsm` 文件和 TD32 `.debug` PE 部分驱动,因此目标必须使用 `-V -VN -VR` 编译以生成它们。没有 `.rsm`,断点和单步执行仍然有效,但变量检查受到限制。
- **单进程**:仅使用 `DEBUG_ONLY_THIS_PROCESS`。调试程序启动的子进程不被跟踪。
- **整个进程停止**:进程作为一个单元停止 — 没有“冻结一个线程并让其他线程运行”。单步执行确实针对您选择的线程(其他线程在此期间被暂停),并且可以检查任何线程的调用堆栈和变量。
- **仅 x64**:在整个过程中假设 Win64 调用约定和堆栈布局。
---
## 诊断日志
适配器可以将带时间戳的日志写入 `%TEMP%\dap_adapter.log`。它**默认关闭**;在启动配置中通过 `"diagnosticLog": true` (或 `DAP_LOG=1` 环境变量)启用它。日志非常详细 — 每个 DAP 请求/响应和调试事件 — 因此仅在诊断适配器行为时使用。
---
## 许可证
MIT — 请参见 [LICENSE](LICENSE)。
调试器依赖于第三方组件,但不重新分发:
- **JEDI 代码库(JCL)** — 用于读取 `JCLDEBUG` / `.jdbg` 行信息。Mozilla 公共许可证 1.1 / GPL。将 `JCL_ROOT` (请参见 `setpaths.bat`)设置为指向您自己的检出。
- **DUnitX** — 仅集成测试套件。Apache 许可证 2.0。将 `DUNITX_ROOT` 设置。
- **Embarcadero Delphi RTL/VCL** — 构建所需;由您自己的 Delphi 许可证覆盖。
存储库中的 `.rsm` 和 TD32 格式说明是作者在其自己的机器上生成的 Delphi 编译器文件的独立分析结果。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器