折腾
这篇文章主要介绍一下DAP的框架,以及debugpy的简单实现。
Debug Adapter Protocal
DAP和LSP是一样的,本质上就是用JSON格式定义的对象消息。DAP用来链接某种语言的调试器和IDE或者编辑器端的一个协议。IDE只需要对着这个协议编写代码,就可以利用已有的DAP来快速实现调试功能。或者对着这个协议编写代码,就可以把调试器的快速集成到已经采用这个DAP的IDE里。理想情况下,只需要对语言的调试功能实现一次,就可以用在不同的开发工具中。在此之前,各大IDE的调试功能都是特化实现的,当然也包括对语言的智能支持(这部分对应LSP)。比如VS,Jetbrain这些重量级的IDE,对某种语言开发的智能功能基本上是内置支持,或者也可以用非常强大的插件(Visual Assist,Resharper)来增强,优点是高效,健壮,功能强大。缺点是不能用在其他轻量级编辑器中。随着现在各种轻量级的编辑器越来越多,开发者都喜欢用自己顺手的编辑器,对语言的智能支持是编辑器的最核心的功能。没有DAP或者LSP之前,也是各自实现一套,几乎没有迁移能力。VSCode算是第一次明确LSP和DAP这些协议。让各种编辑器都可以低成本的享受到支持不同语言的功能。
一般认为,DAP应该能满足让代码编辑器实现以下功能:
- 源码、函数、条件断点等。
- 调试时变量值显示
- 多线程和多进程调试支持
- 查看复杂的数据结构
- 监视表达式
- 自动补全,交互式求值
- 输出日志
协议基本的通信方式
协议分成Request
,Response
, Event
三种消息类型。因为语言调试的复杂性,所以具体的消息实现种类非常多。不在这里做详细介绍。
用几个简单的场景说明一下大概的流程:
初始化
首先编辑器发送Initialize
类型的请求,查询DAP是否有相应的功能
interface InitializeRequest extends Request {
command: 'initialize';
arguments: InitializeRequestArguments;
}
interface InitializeRequestArguments {
clientID?: string;
clientName?: string;
adapterID: string;
locale?: string;
linesStartAt1?: boolean;
columnsStartAt1?: boolean;
pathFormat?: 'path' | 'uri' | string;
supportsVariableType?: boolean;
supportsVariablePaging?: boolean;
supportsRunInTerminalRequest?: boolean;
supportsMemoryReferences?: boolean;
supportsProgressReporting?: boolean;
supportsInvalidatedEvent?: boolean;
supportsMemoryEvent?: boolean;
}
DAP做响应, 列出支持的功能,完成初始化流程。
interface InitializeResponse extends Response {
body?: Capabilities;
}
大概就是这种通信的流程。
打断点
断点调试是调试功能的核心,这里用一张图展示整个流程。 这个是一个编辑器断点调试时通过DAP和GDB通信的流程。
详细的消息格式在上面给出的协议规范里。
当调试器触发一个断点时,会发出stop消息,此时DAP把stop消息转发一下,编辑器端收到stop后可以请求栈的状态,然后DAP从调试器当中获取数据再发给编辑器,编辑器做展示。
详细地来说,触发stop之后(里面有线程编号),编辑器根据需求请个线程的栈信息,得到响应,再根据需要请求栈下的Scope,得到响应,根据需要请求Scope下的变量, 得到响应。再根据需要(比如要展开一个复杂的数据结构)请求这个数据结构下的变量。 这样层层递进。
Threads
StackTrace
Scopes
Variables
...
Variables
Debugpy,一个Python的DAP实现
Debugpy是针对python实现的DAP,是 vscode python插件使用的DAP。这里简单的介绍一下实现细节