解密 deepin-IDE:如何实现简单灵活的调试技术?
前不久,deepin社区发布了自己的 IDE:deepin-IDE,得到了全网用户尤其是开源社区用户的广泛关注,目前在 GitHub 仓库的 star 数量已经达到 600 多个,说明大家的热情还是很高涨的。
https://github.com/linuxdeepin/deepin-unioncode
为了从技术层面给大家的热情做一个反馈,本文将分享 deepin-IDE 内部的一些实现方法,希望能够解答deepiner的疑惑。
本篇选择了大家最关心的“调试”部分进行分享。需要说明的是,deepin-IDE 的调试功能是选用 DAP(Debug Adapter Protocol )调试适配协议实现的,所以整体架构是围绕该协议搭建的,至于 DAP 具体是什么,让我们带着问号往下看。
什么是 DAP 协议
DAP 即调试适配协议( Debug Adapter Protocol ),顾名思义,是用来对多种调试器进行抽象统一的适配层,将原有 IDE 和调试工具直接交互的模式更改为和 DAP 进行交互。该模式可以让 IDE 集成多种调试器变得更简单,且灵活性更好。
在 IDE 中的调试功能由许多小功能组成,包括单步执行、断点、查看变量值等,常规的实现方式是在每个 IDE 中去实现这些逻辑,且因为调试工具的接口不同,还需要为每个调试工具做一些适配工作,这将产生大量且重复的工作,如下图所示:
DAP 如何工作
以下部分解释了开发工具(例如 IDE 或编辑器)和调试适配器之间的交互,包括具体的协议格式说明、交互流程等。
【单会话模式】
在这种模式下,开发工具启动一个调试适配器作为单独的进程并且通过标准的 std 接口进行通信。在调试会话结束时,调试适配器就终止,对于当前的调试会话,开发工具往往需要实现多个调试适配。
【多会话模式】
在这种模式下,开发工具不会启动调试适配器,而是假定它已经在运行并且会在特定端口上侦听连接尝试,对于每个调试会话,开发工具在特定端口上启动一个新的通信会话并在会话结束时断开连接。
在与调试适配器建立连接后,开发工具和调试适配器之间通过基础协议进行通信。
基础协议
基础协议由两部分组成,包括头和内容(类似于 HTTP),头部分和内容部分通过“\r\n”进行分割:
头字段名 | 值类型 | 描述 |
Content-Length | 数字 | 这个字段是必须的,用来记录内容字段的长度,单位是字节。 |
协议头部分使用的是“ASCII”编码。
【内容部分】
内容部分包含了实际要传输的数据,这些数据用 JSON 格式来描述请求、响应和事件。内容部分用的是 utf-8 编码。
为了有具体的认识,这里举个简单的例子。在调试过程中,开发人员经常会使用到“下一步”操作,在 DAP 中其协议为:
Content-Length: 119\r\n
{
"seq": 153,
"type": "request",
"command": "next",
"arguments": {
"threadId": 3
}
}
这个协议看着挺简单,接下来就讲讲如何使用它。
使用方法
详细的使用方法这里就不涉及了,因为用一个时序图就可以说明:
可以看到,初始化、请求、响应等必要的步骤都在图中。其中调试适配器可以理解为调试器的抽象,调试功能的最终执行者是由对应语言的调试工具实现的。
在deepin-IDE中的实现
通信功能,包括服务端的 TCP 监听,客户端的 TCP 连接等; DAP 协议的封装,并实现协议的串行化和解串行化; 提供注册回调功能,从而可以在回调内处理各种事件、请求等。
左边是客户端,右边是服务端,内部实现如下:
客户端实现
客户端包含了两个主要功能,一个是和 DAP 服务端进行交互,发送调试命令或处理返回的数据;另一个是将 DAP 数据转换后显示到用户界面,并响应界面发送的事件。概括起来共包含业务模块、事件模块、DAP 模块和界面4个部分。
【业务模块】
业务模块包含了插件类、调试参数、调试管理类等,其中插件类负责插件加载、初始化、获取上下文等,调试管理类用来组合事件、DAP 、界面几个模块。
【事件模块】
事件模块包含两个子模块,分别是事件发送和事件接收,比如页面跳转事件、添加\移除断点事件等。
【DAP 模块】
DAP 模块基于 cppdap 开发,采用层级结构,底层是原始 DAP 协议封装,中间层是针对业务做的进一步封装,简化了向外提供的接口,最上层是对整个调试功能的整合,包括数据缓存、界面元素、命令收发。
【界面部分】
界面模块包含堆栈界面、变量界面、断点列表、异步对话框等,用于 DAP 的数据展示。
如上图所示,灰色部分为 DAP 客户端的界面呈现。
服务端实现
服务端的功能分为两个部分,一个是基于 cppdap 实现命令的收发,另一个是与 gdb 交互,实现调试程序的启动、暂停、退出等一系列动作。
【DAP】
和客户端一样,服务端也是基于 cppdap 实现的通信和协议封装和解析。
【调试工具】
和调试工具的交互是通过进程调用的方式实现,接收进程输出得到返回信息。如果调试工具本身支持 DAP 协议,则可以直接交互。
本次的分享就到这里,不知道你对 deepin-IDE 中的调试功能有所了解了吗?
温馨提示:deepin-IDE 还包含很多有意思的功能,如果大家感兴趣可以积极反馈,后续有机会再进行分享。
参考文档
debug-adapter-protocol
https://microsoft.github.io/debug-adapter-protocol/overview
deepin-IDE 使用手册
https://wiki.deepin.org/zh/05_HOW-TO/02_%E5%BC%80%E5%8F%91%E7%9B%B8%E5%85%B3/deepin-unioncode
内容来源:deepin社区
内容作者:deepin-mozart、toberyan