EpicGames/raddebugger
GitHub: EpicGames/raddebugger
Epic Games 开源的原生多进程图形化调试器及配套高性能链接器,专为超大 C/C++ 项目的编译-调试循环优化。
Stars: 7272 | Forks: 332
# RAD Debugger 项目
_**注意:** 本 README 不包含调试器本身的使用说明和提示,旨在作为该项目的技术概述。调试器的 README 包含使用说明和提示,它可以在调试器发布版本中找到,或者在本地构建副本后的 `build` 文件夹中找到。您可以在此处找到预构建的发布二进制文件 [这里](https://github.com/EpicGamesExt/raddebugger/releases)。_
RAD Debugger 是一个原生的、用户态的、多进程的图形化调试器。它目前仅支持具有 PDB 的本地 Windows x64 调试,并计划在未来进行扩展和移植。未来我们将扩展以支持原生 Linux 调试和 DWARF 调试信息。
该调试器目前处于 *ALPHA* 阶段。为了使调试器坚不可摧,如果您能将发现的问题提交到 [这里](https://github.com/EpicGamesExt/raddebugger/issues),并附上您能收集到的任何信息,例如转储文件(连同您所使用的构建版本)、重现步骤、测试可执行文件等,那将对我们有极大的帮助。
除了调试器,我们还旨在通过另外两项相关技术进一步改进工具链:**(1)** RAD Debug Info (RDI) 格式,以及 **(2)** RAD Linker。
## RAD Debug Info (RDI) 格式
RAD Debug Info (RDI) 格式是我们自定义的调试信息格式,调试器解析并使用它,而不是原生由工具链(如 PDB 或 DWARF)产生的调试信息。为了配合这些现有的工具链工作,我们会按需将 PDB(以及最终带有内嵌 DWARF 的 PE/ELF 文件)转换为 RDI 格式。
RDI 格式目前在代码中定义,位于 `src/lib_rdi` 文件夹内的文件中。在 [`rdi.h`](src/lib_rdi/rdi.h) 和 [`rdi.c`](src/lib_rdi/rdi.c) 中,指定了定义该格式本身的类型和函数。在 [`rdi_parse.h`](src/lib_rdi/rdi_parse.h) 和 [`rdi_parse.c`](src/lib_rdi/rdi_parse.c) 中,包含了解析该格式的辅助工具。
我们还有一个正在进行中的用于构建和序列化 RDI 数据的库,位于 `src/lib_rdi_make` 文件夹中。
我们的 `radbin` 实用程序(也可通过 `--bin` 命令行参数在调试器中访问)能够将原生的调试信息格式转换为 RDI,并能生成 RDI 文件中存储内容的文本转储。
## RAD Linker
RAD Linker 是一个用于生成 x64 PE/COFF 二进制文件的全新高性能链接器。它旨在创建超大型可执行文件时具备极快的速度。它生成标准的 PDB 文件用于调试,但它也可以(可选地)原生创建 RAD Debug Info,这不仅有助于消除调试时的按需转换时间,而且对于超大型可执行文件很有用,否则这些文件可能会生成导致内部 32 位表溢出的损坏的 PDB。
RAD Linker 主要针对处理超大型链接项目进行了优化。在我们的测试用例(调试信息多达数 GB)中,我们看到链接时间缩短了 50%。
命令行语法与 MSVC 完全兼容;您可以通过 `/help` 获取已实现开关的完整列表。
我们目前为该链接器设计的使用场景是协助超大型项目的编译-调试循环。我们尚未支持链接时优化(link-time-optimizations),但该功能已在路线图上。
默认情况下,该链接器生成的线程数与核心数相同,因此如果您计划并行运行多个链接器,可以通过 `/rad_workers` 限制线程工作线程的数量。
我们还支持大内存页(large memory pages),启用后,可将链接时间再缩短 25%。要使用大内存页进行链接,您需要通过 `/rad_large_pages` 显式请求。大内存页默认是关闭的,因为 Windows 对大内存页的支持存在一些缺陷;我们建议仅在每次链接后环境会重置的 Docker 或 VM 映像中使用它们。在标准的 Windows 环境中,否则使用大内存页会迅速导致内存碎片化,迫使系统重启。我们正在开发该链接器的 Linux 移植版,该版本将能够稳健地使用大内存页进行构建。
链接器性能的基准测试如下:

# 项目开发环境设置说明
**注意:目前该项目仅支持 x64 Windows 开发。**
## 1. 安装必需的工具(MSVC 和 Windows SDK)
为了配合代码库工作,您需要 [Microsoft C/C++ Build Tools v15 (2017) 或更高版本](https://aka.ms/vs/17/release/vs_BuildTools.exe),以获取 Windows SDK 以及 MSVC 编译器和链接器。
如果已安装 Windows SDK(例如通过安装 Microsoft C/C++ Build Tools),您也可以使用 [Clang](https://releases.llvm.org/) 进行构建。
## 2. 构建环境设置
构建代码库可以在配备了从命令行调用 MSVC 或 Clang 能力的终端中完成。
这通常通过调用 `vcvarsall.bat x64` 来完成,该脚本包含在 Microsoft C/C++ Build Tools 中。此脚本会由原生的 `cmd.exe` 变体 `x64 Native Tools Command Prompt for VS ` 自动调用。如果您已经安装了构建工具,可以通过在 Windows 开始菜单搜索中搜索 `Native` 轻松找到此命令提示符。
您可以通过运行以下命令来确保 MSVC 编译器可以从命令行访问:
```
cl
```
如果一切设置正确,您应该会看到与以下非常相似的输出:
```
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30151 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
usage: cl [ option... ] filename... [ /link linkoption... ]
```
### 3. 构建
在此终端中,`cd` 到代码库的根目录,然后运行 `build.bat` 脚本:
```
build
```
您应该会看到以下输出:
```
[debug mode]
[msvc compile]
[default mode, assuming `raddbg` build]
metagen_main.c
searching C:\devel\raddebugger/src... 458 files found
parsing metadesk... 16 metadesk files parsed
gathering tables... 97 tables found
generating layer code...
raddbg_main.c
```
如果一切正常,在代码库的根目录下将生成一个 `build` 文件夹,其中包含一个全新构建的 `raddbg.exe`。
这个 `raddbg.exe` 将以 **debug 模式** 构建,该模式没有进行优化,性能可能会差一些。要生成一个 **release 模式的可执行文件**,请使用 `release` 参数运行 `build.bat`:
```
build release
```
此构建将花费更长的时间。
默认情况下,如果不传递参数(或仅传递 `release`),`build.bat` 只构建调试器,但可以传递额外的参数来构建 RAD Linker 或 `radbin` CLI 二进制文件实用程序:
```
build radlink release
build radbin release
```
# 项目路线图
### 初期 Alpha 实战测试阶段
该项目的首要任务是确保最关键的组件对于本地、x64、Windows 开发能够极其可靠地运行。
对于调试器来说,这包括诸如调试信息转换、调试信息加载、进程控制、单步调试、表达式求值(正确使用位置信息和类型信息),以及确保底层部分可用的健壮前端等部分。对于链接器来说,这关乎于可靠性与现有链接器行为的趋同。
我们觉得在所有这些方面我们已经取得了长足的进步,但考虑到语言、构建设置、工具链、使用的语言特性以及生成的代码模式的组合极其庞杂,我们仍然预计会出现一些问题,并优先解决这些问题。
我们也希望在这一阶段继续提升性能。对于调试器,这主要包括前端性能,在具有经济效益时引入缓存,以及收紧现有系统。对于链接器,目前它主要针对超大型项目进行了调优,因此我们也希望改善中小型项目的链接速度。
对于链接器,还有许多功能即将推出,例如死代码消除 (`/opt:ref`),以及在 `clang` 的帮助下进行链接时优化(我们不会支持 MSVC 的 LTCG,因为它是未公开的)。
### 本地 x64 Linux 调试阶段
该项目的下一个优先级是采用坚如磐石的 x64 Windows 调试体验,并将所有相关部分移植以支持本地 x64 Linux 调试。
调试器在编写时已经对需要在 Linux 或 Windows 上区分的部分进行了抽象,这主要将是一项为这些抽象层构建不同后端的任务。
该阶段的主要部分包括:
- 移植 `src/demon` 层以实现 Demon 本地进程控制抽象 API。
- 在 `src/ctrl` 层实现 x64 ELF Linux 堆栈展开器。
- 创建一个 DWARF 到 RDI 的转换器(就像我们构建了 PDB 到 RDI 的转换器一样)。这部分实现位于 `src/rdi_from_dwarf`。
- 移植 `src/render` 层,以基于 Linux 兼容的 API 实现前端所需的所有渲染功能(Windows 上使用的后端是 D3D11)。
- 将 `src/font_provider` 层移植到与 Linux 兼容的字体光栅化后端,例如 FreeType(Windows 上使用的后端是 DirectWrite)。
- 将 `src/os` 层移植到 Linux。这包括核心操作系统抽象(虚拟内存分配、线程和同步原语等)以及图形操作系统抽象(窗口、输入事件等)。
一旦上述列表完成,并且每个部分都坚如磐石,我们努力打造的 Windows 调试体验也将原生地在 Linux 机器上提供。
### 展望未来!
在这两个主要阶段之后,我们可能会采取几个方向,例如远程调试、移植到不同的架构、进一步改进调试器的功能(例如改进可视化引擎)等等。但目前,我们主要集中在前两个阶段。
# 代码库简介
## 顶层目录说明
- `data`:构建时使用的小型二进制文件,要么嵌入到构建产物中,要么与它们打包在一起。
- `src`:所有源代码。
在设置好代码库并构建后,还将存在以下目录:
- `build`:所有构建产物。不提交到版本控制中。
- `local`:本地文件,用于本地构建配置输入文件。不提交到版本控制中。
## 层说明
代码库被组织为多个*层*。将层进行分离要么是为了隔离特定的问题,要么是为了允许将其包含到各种构建中,而无需将代码库中的所有内容拉入到构建中。层对应于 `src` 目录内的文件夹。有时,`src` 目录内的一个文件夹会包含多个子层,但其结构被设计得相当扁平。
层与*命名空间(namespaces)*大致呈 1 对 1 对应关系。在这种语境下,“命名空间”一词并不指代特定的命名空间语言特性,而是指一种 C 风格命名空间的命名约定,在代码库中写为一个短前缀(通常为 1 到 3 个字符)后跟一个下划线。使用这些命名空间是为了使得只需瞥一眼代码就能快速理解某些代码属于哪一层。这些命名空间通常非常短,以确保它们写起来不会太麻烦。有时,多个子层会共享一个命名空间。少数层没有命名空间,但大多数都有。根据使用上下文的不同,命名空间要么全大写,要么全小写。对于类型、枚举值和某些宏,它们是大写的。对于函数和全局变量,它们是小写的。
各层依赖于其他层,但循环依赖会破坏层的可分离性和隔离性(实际上会形成一个大层),换言之,各层被排列成一个有向无环图。
少数层被构建为完全独立于代码库的其余部分使用,作为其他代码库和项目中的库。因此,这些层不依赖于代码库中的任何其他层。包含这些层的文件夹以 `lib_` 为前缀,例如 `lib_rdi`。
代码库中的各层及其关联的命名空间列表如下:
- `artifact_cache` (`AC_`):实现了一个异步填充的计算产物缓存,当未被访问时会自动逐出。用于异步流式传输和缓存进程内存及文件系统内容,以及异步准备可视化工具数据。
- `base`(无命名空间):通用的、全代码库的构造。字符串、数学、内存分配器、辅助宏、命令行解析等。不需要其他代码库层。
- `codeview` (`CV_`):用于解析和写入 CodeView 格式的代码。
- `coff` (`COFF_`):用于解析和写入 COFF(通用对象文件格式)文件格式的代码。
- `content` (`C_`):实现了一个通用数据 blob 的缓存,以数据的 128 位哈希值为键。还在其上实现了一个键系统,其中键指的是对应于 128 位哈希历史的唯一身份。被其他层用作通用数据存储。
- `ctrl` (`CTRL_`):调试器的“控制系统”层。实现所有附加进程的异步进程控制、单步执行和断点。与附加进程同步运行。当它运行时,附加的进程会被暂停。当附加的进程运行时,它会被暂停。由另一个线程上的调试器前端驱动。
- `dbg_engine` (`D_`):实现核心调试器系统,不包含任何图形组件。这包含了顶层逻辑,例如单步执行、启动、冻结线程、运行期间添加断点、某些缓存等。
- `dbg_info` (`DI_`):实现异步调试信息转换和加载。维护一个用于已加载调试信息的缓存。加载 RAD Debug Info (RDI) 文件。如果需要,启动单独的进程进行到 RDI 格式的按需转换。还提供各种使用调试信息的异步操作,例如在已加载调试中的所有记录进行模糊搜索。
- `demon` (`DMN_`):用于本地机器、底层进程控制的抽象层。该抽象用于在目标平台上为进程控制提供公共接口。用于实现 `ctrl` 的一部分。
- `disasm` (`DASM_`):实现反汇编生成,包括暴露异步计算和缓存反汇编的能力。
- `draw` (`DR_`):使用底层的 `render` 抽象层,为调试器的目的实现了一个高级图形绘制 API。为各种绘图命令提供高级 API,同时负责处理批处理等。
- `dwarf` (`DW_`):用于解析 DWARF 格式的代码。
- `eh` (`EH_`):用于解析 EH 帧格式的代码。
- `elf` (`ELF_`):用于解析 ELF 格式的代码。
- `eval` (`E_`):一个表达式语言编译器,旨在从调试器附加的进程、调试信息、调试器状态和文件中评估变量、寄存器、类型等。分为几个阶段,主要对应于传统的编译器阶段:词法分析器、语法分析器、类型检查器、IR 生成和 IR 评估。
- `eval_visualization` (`EV_`):实现核心的非图形化评估可视化引擎,可用于以多种方式可视化(由 `eval` 层提供的)评估结果。实现监视表的核心数据结构和转换。
- `file_stream` (`FS_`):实现异步文件流,将产物存储在由 `content` 和 `artifact_cache` 层实现的缓存中,并在文件更改时热重载文件内容。允许调用者将文件路径映射到数据哈希,随后可用于获取文件的数据。
- `font_cache` (`FNT_`):实现光栅化字体数据的缓存,既包含用于文本整形的 CPU 侧数据,也包含用于光栅化字形的 GPU 纹理图集。所有缓存信息均源自 `font_provider` 抽象层。
- `font_provider` (`FP_`):用于各种字体文件解码和字体光栅化后端的抽象层。
- `lib_raddbg_markup` (`RADDBG_`):用于标记用户程序以与调试器中各种功能配合使用的独立库。不依赖于 `base`,可独立迁移到其他代码库。
- `lib_rdi` (`RDI_`):定义核心 RDI 类型以及用于读写 RDI 调试信息文件格式的辅助函数的独立库。不依赖于 `base`,可独立迁移到其他代码库。
- `lib_rdi_make` (`RDIM_`):用于构建 RDI 调试信息数据的独立库。不依赖于 `base`,可独立迁移到其他代码库。
- `linker` (`LNK_`):实现 RAD Linker 可执行文件本身的层。
- `mdesk` (`MD_`):用于解析 Metadesk 文件(存储为 `.mdesk`)的代码,Metadesk 是类似 JSON(严格来说是 JSON 的超集)的文本格式,用于调试器的用户和项目配置文件及元代码,这些文件经过解析后会使用 `metagen` 层生成代码。
- `metagen` (`MG_`):一个元程序,用于主要生成代码和数据表。使用扩展名为 `.mdesk` 的 Metadesk 文件,并生成随后被手写 C 代码包含的 C 代码。目前,它不分析代码库的手写 C 代码,但原则上这是可能的。这使得大型数据表的管理更容易且不易出错,这些表随后用于生成例如 C `enum` 和大量相关的数据表。还有许多其他的生成功能,比如将二进制文件或复杂的多行字符串嵌入到源代码中。
- `msf` (`MSF_`):用于解析和写入 MSF 文件格式的代码。
- `msvc_crt` (`MSCRT_`):专门用于解析 MSVC CRT 的代码。
- `mule`(无命名空间):用于实战测试调试器功能的测试可执行文件。
- `mutable_text` (`MTX_`):为随时间变异的文本缓冲区实现了一个异步填充和变异的缓存。在调试器中,这被用于实现 `Output` 日志。
- `natvis`(无命名空间):用于在其他调试器中对代码库类型进行类型可视化的 NatVis 文件。
- `os/core` (`OS_`):提供了一个抽象层,在抽象 API 下从操作系统提供核心的非图形化功能,该 API 是针对每个目标操作系统实现的。
- `os/gfx` (`OS_`):建立在 `os/core` 之上的抽象层,在抽象 API 下提供图形化操作系统功能,该 API 是针对每个目标操作系统实现的。
- `pdb` (`PDB_`):用于解析和写入 PDB 文件格式的代码。
- `pe` (`PE_`):用于解析和写入 PE(可移植可执行文件)文件格式的代码。
- `radbin` (`RB_`):实现 `radbin` 二进制实用程序可执行文件的层。
- `raddbg` (`RD_`):将所有内容整合在一起以形成主图形化调试器可执行文件的层。实现调试器的图形前端、所有调试器特定的 UI、调试器可执行文件的命令行界面以及所有内置的可视化工具。
- `rdi` (`RDI_`):包含 `lib_rdi` 层并将其与代码库特定的辅助工具捆绑在一起的层,以便轻松地将该库包含在代码库程序中,并使其与代码库构造相集成。
- `rdi_from_coff` (`C2R_`):用于将 COFF 文件中的信息转换为等效 RDI 数据的代码。
- `rdi_from_dwarf` (`D2R_`):用于将 DWARF 转换为等效 RDI 数据的进行中代码。
- `rdi_from_elf` (`E2R_`):用于将 ELF 数据转换为等效 RDI 数据的代码。
- `rdi_from_pdb` (`P2R_`):用于将 PDB 数据转换为等效 RDI 数据的代码。
- `rdi_make` (`RDIM_`):包含 `lib_rdi_make` 层并将其与代码库特定的辅助程序捆绑在一起的层,以便轻松地将该库包含在代码库程序中,并使其与代码库构造相集成。
- `regs` (`REGS_`):受支持架构上的寄存器类型、辅助函数和元数据。用于在 `demon` 中读写寄存器,或用于查找寄存器元数据。
- `render` (`R_`):提供了一个抽象层,在公共接口下使用各种 GPU API 提供渲染的抽象 API。不实现高级绘制 API——该层严格用于在按需的基础上进行最低限度的抽象。更高级别的绘图功能在 `draw` 层中实现。
- `scratch`(无命名空间):用于小型和临时测试程序的临时工作区。
- `tester`(无命名空间):用于自动化测试的程序。
- `text` (`TXT_`):实现文本处理功能,例如解析换行符,以及词法分析和解析源代码。还提供了一个异步执行此操作的 API。
- `third_party`(无命名空间):来自其他项目的外部代码,代码库中的某些层依赖于它们。所有外部代码都直接包含并在代码库中构建。
- `ui` (`UI_`):构建图形用户界面的机制。提供了一个核心的即时模式分层用户界面数据结构构建 API,并具有用于构建一些高级组件的辅助层。
标签:PDB, SOC Prime, UML, Windows, 图形化界面, 安全意识培训, 客户端加密, 底层开发, 开发工具