在 VSCode 中实现 Jupyter Debug Adapter

说起 VSCode 中广受好评的功能,必须算上其优秀的调试(debug)功能,它拥有丰富的功能和直观的用户界面。

更为难得的是,VSCode 为这套调试架构实现的插件化机制,使得我们可以很方便地为不同的自定义语言和框架实现调试功能,并具有统一且通用的用户界面。

[图片上传失败...(image-bc2d36-1719312259569)]](https://zakum-1252497671.cos.ap-guangzhou.myqcloud.com/20240625155834.png)

本文将以 Jupyter 接入 VSCode 调试的功能为例,介绍如何在 VSCode 中实现 Jupyter Debug Adapter。

在 VSCode 中注册 Debug Adapter Protocol

[图片上传失败...(image-69a2da-1719312259569)]

从 VSCode 的相关介绍中我们可以看到,VSCode 的 Debug Adapter 是一个独立的进程,它负责处理 IDE 和调试器之间的通信。而调试功能正是通过 Debug Adapter Protocol(DAP)来实现的,它是一个标准的调试协议,用于在 IDE 和调试器之间进行通信。

而为了给 VSCode 挂载自定义的调试器,我们可以通过 registerDebugAdapterDescriptorFactory 这个 API 来注册我们的 Debug Adapter。由于 Jupyter 是基于 Python 语言的,因此这里的 debugType 选择 python。

之后我们就启动 debugpy 后在 vscode 中设置好 launch.json 入口,调试普通 python 代码了。

如果你想了解更多关于 Debug Adapter 的内容,可以参考 VSCode 官方文档

在 DAP 中,要实现一套完成的 debugger 流程,要求我们需要实现一些基本的功能,如:

  1. initialize:初始化调试器。
  2. setBreakpoints:设置断点。
  3. variables/stackTrace/threads:获取相关变量、调用栈等信息。
  4. stepInto/stepOut/stepOver:单步调试。
  5. break statement:中断调试。

接下来我们就以 Jupyter 为例,看看如果要实现其他语言的 debug 接入,应该做哪些工作。

实现 Jupyter Debug Protocol

通过翻阅 Jupyter 文档,我们可以知道,若要实现调试功能,其连接的 kernel 里必须要支持 Jupyter Debug Protocol,这也是我们主要需要实现的地方。

我们可以通过以下的流程图来了解 debug 从发起到结束的全过程:

[图片上传失败...(image-472c23-1719312259569)]

  1. Initialize 阶段。IDE 会向 kernel 发送 initialize_request,这个 request 将会帮我们创建一个新的 channel 用来交换调试信息。

  2. Attach 阶段。该请求将会帮我们将建立好的 debug adaptor channel 与当前的 IDE 进行绑定,将在这里负责 debug 交互的全过程。

  3. Configuring breakpoints and exception behavior 阶段。这一部分是将 IDE 中设置的断点等相关信息传递给 kernel,以便 kernel 能够在适当的时候中断。

    其中,setBreakpoints 等将会传递设置断点的行/函数/异常处理等信息,dumpCell 是将单元格内的内容或状态信息传递给 kernel。

    值得注意的是,这里所有的请求 msg_type 将以 debug_event 或者 status 发出而不再是 request

    随着 configureationDone 的出现,标志着客户端配置过程的结束。

  4. Execute request 阶段。这一步与正常的代码执行完全一致,发出代码执行请求,最终收获到执行结果。

    但此时,如果我们设置了 breakpoint,该过程将在执行到断点处时被一个内容为 stoppeddebug_event 给暂时 block。

  5. Pausing and extract context 阶段。

    当 kernel 收到 stoppeddebug_event 后,将会暂停当前的执行,此时会发送一系列channel typecontrolcommand,将当前的各种相关 context 传递给 IDE。

    其中 variables 是获取当前 code 中的变量信息,包括其名称类型等。stackTrace 帮助 IDE 获取当前的调用栈信息。scopes 则是获取函数或变量的作用域信息。threads 则是获取当前处理 debug 功能所处的线程信息。

  6. Dispose 阶段。当所有断点都被跳过后,之前的 execute_request 将被执行完成返回 execute_reply。至此 debug 流程结束。此时我们需要让 IDE 发送 disconnect request 来关闭当前的 debug adaptor channel。如果我们想正确执行完整的 debug 生命周期。无论是否异常结束,都需要执行 disconnect request

了解了 Jupyter Debug Protocol 的全流程后,我们就可以开始着手为 VSCode 的交互实现做准备了。

利用 Jupyterlab 库为沟通 kernel 提供 API 支持

上面我们简要介绍了一下 Jupyter 调试过程的原理,而事实上我们并不需要完全从零开始实现 Jupyter Debug Protocol,因为开源社区里已经有了很多现成的库可以帮助我们实现这一功能。

Jupyterlab 是一个为 Jupyter 打造的第一方开发环境工具库,它提供了丰富的 API 支持,可以帮助我们更方便地与 kernel 进行交互。

在 Jupyterlab 中,我们可以通过 jupyterlab/debugger 这个插件来实现对 Jupyter Debug Protocol 的支持。它提供了一套完整的调试功能,包括设置断点、单步调试、查看变量等。

因此,我们真正需要实现的触发事件与交互逻辑也就变得更为清晰了。只需要处理好 debugging 的这几个实现即可:

  1. debugging 的开始与终止事件
  2. 断点的设置与清除
  3. 代码执行
  4. 变量、调用栈等的查看
  5. 单步调试(包括stepInto/stepOut/stepOver

因此,我们可以得出基于 jupyterlab 的调试器实现的基本流程:

[图片上传失败...(image-3cc815-1719312259569)]

实现 Debugging Manager 完成对调试器的管理

到了最终代码实现的阶段了。我们需要实现一个 Debugging Manager,用于通过registerDebugAdapterDescriptorFactory 注册给 VSCode,管理调试器的启动、停止、断点设置等操作。

具体的工程实现方案可以有很多,具体就不展开了,这里只 po 一下 vscode-jupyter 的实现方案:

[图片上传失败...(image-37a6ba-1719312259569)]

vscode-jupyter 通过额外增加了 KernelDebugAdapter 类实现了 debug_event 消息的收发,通过 DebugCellController 类来管理单元格的 debug 执行信息,实现了不同消息走不同 Controller 的分离。

总结

通过本文的介绍,我们可以了解 VSCode 的 Debug Adapter 的实现原理,并以 Jupyter 为例,成功在 VSCode 中实现 Jupyter Debug Adapter,并实现完全的调试能力。

得益于 VSCode 灵活的调试注入能力,我们可以方便地为更多的语言,甚至是一些自定义框架实现调试能力,这可能可以为更多的小众语言或框架的开发者带来工作效率上的帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容