Ruby调试器一览

百里承业
2023-12-01

有一个关于Ruby的误解在Ruby社区内外广泛流传,即:Ruby没有调试器。有些人说这是Ruby的一个问题。其他人则试图将所谓的缺少调试工具解释为智慧之举和良好风格。这些观点都是误解。Ruby明明是有调试工具的——实际上有很多。让我们来看一看这些现有的工具,包括调试GUI、调试器实现和各种Ruby实现中的调试支持。

\

什么是调试器?

\首先,让我们搞清楚“调试器”实际上涉及了哪些东西?

调试的GUI和接口

\

当然了,交互式调试器最重要的部分——至少对于用户来说——是用户接口。用户可以使用Ruby调试器的命令行接口,例如和Ruby标准库一起提供的Rubinius调试器。它显然可以用来调试代码,只不过设置断点或查看运行状态会比较麻烦。

\

IDE虽然有时在Ruby世界中不太受推崇,但它无疑令调试变得更简单了——毕竟,IDE就是集成开发环境。集成对于调试来说很重要,而IDE正是把代码编辑和调试工具整合在一起了。你可以在源代码编辑器中直接管理断点——而不用记下代码的行号,进入命令行调试器中,然后手工设置断点。在IDE中,诸如基于行的单步调试之类的功能也更加实用,可以正确的找到所打开的文件的栈结构和所在行。

\

带有嵌入式脚本支持的IDE还允许对脚本进行调试。例如 ,Eclipse的EclipseMonkey扩展支持用JRuby写成的脚本。由于这些脚本和Eclipse IDE都运行在同一个JVM上,由此调试器实例便可以被访问和控制了。

\

调试器协议还是连接到后端

\

把像IDE这样的调试器用户接口和调试器后端连接起来的一个简单方法是:使用命令行接口,并通过标准的stdin/stdout/stderr流来进行控制。这样,编辑器或者IDE的调试器支持就可以控制调试器,同时也让用户管理断点变得更加方便了。

\

另外一个方法是采用线路(wire)协议,它允许通过某种模式的进程通讯(IPC),现在一般是通过TCP/IP来连接到调试器。基于网络的协议还允许GUI和调试器分布在不同的机器上,也就是说可以使用本地的用户接口来对远程机器进行调试。

\

基于文本的或者至少基于文档的简单调试协议也允许使用任何语言来编写调试进程脚本。实际上,连接到Ruby调试器和打开telnet一样简单。debug-commonsDBGp命令的协议就是由单行字符串和XML应答构成的。

\

VM支持还是调试后端

\

为了支持断点等功能,语言运行时至少得提供监视和控制执行的支持。可以简单地像Ruby的跟踪(tracing)功能一样:在一行Ruby代码执行之前,Ruby会调用一个叫做set_trace_func的回调函数。传过去的参数包括即将执行的那行代码的环境信息,比如行号,所属文件的名字和所属的类等等。这些信息就足以实现断点功能了:在一个断点注册表里面检查文件名和行号,看看是否被注册了。

\

当遇到一个断点时,执行就被挂起,只要不从回调中返回即可——Ruby运行时只能在回调返回后才能继续运行。基于这些,就可以实现单步调试等功能了。

\

虽然使用跟踪功能可以实现一个调试器,但是在执行每一行之前都要先执行跟踪回调,显然太慢了。理想地解决方案是仅在执行有断点的行时才引发断点处理。运行时可以通过修改已加载的代码来实现此功能——不论是AST还是操作码(opcodes)——在有断点的行上。有些语言的运行时提供了内建的调试支持,与执行机制整合在一起。Java和.NET的二进制代码都提供调试信息(即从文件和行到字节代码位置一个映射),让内建的调试支持能使用这些信息来进行调试。在Java世界中,例如,JVM配合JVM工具接口(JVM TI)一起实现了这个功能以及用来连接到JVM的Java调试线路协议(JDWP)。

\

还有一个方法是Rubinius调试器所使用的,它使用可访问和可修改的Ruby代码中的操作码(Rubinius把Ruby源代码先编译成操作码然后再执行)。

\

通过把一个一般操作码替换成一个特殊操作码来设置一个断点,而这个特殊操作码则用来挂起当前进程并通知调试堆栈中的高层。 通过设置大量的基础体系和管理数据结构以供语言来访问,语言本身就可以用来建立调试机制。

\

各种Ruby实现的调试器和IDE支持

\

有了以上基础,再让我们来看一看现有的调试器。从用得最广、支持得也最多的Matz的Ruby实现(MRI)开始。之后让我们再看一看JRuby、Rubinius以及IronRuby的现状——看看这些Ruby实现的工具支持,还有它们与MRI以及其工具支持和性能的区别。

\

Ruby/MRI

\

调试后端

\

Ruby 1.8.x,也就是MRI,是官方的Ruby解释器,是用C语言实现的。我们最常见的调试器就是针对它的。这个跟踪调试器是配合它的Ruby版本以及标准库一起使用的。另外还有更快的实现。比如ruby-debug,它是使用本地扩展来实现的。

\

还有一个选择是随SapphireSteel的Ruby in Steel IDE提供的:Cylon debugger。它也是通过本地代码来实现功能,使用Ruby钩子来获得诸如方法调用等事件通知而完成的。 SapphireSteel的标准测试表明,Cylon调试器比用Ruby写的调试器快得多,也比ruby-debug要快。

\

GUI

\

许多Ruby IDE提供都调试功能。基于Eclipse的RDT (现在是Aptana和RadRails的一部分)在很久以前就开始提供调试支持了,一开始是连接到基于Ruby的跟踪调试器上,后来转而支持ruby-debug。RDT的调试协议被分解到了debug-commons项目中,该项目用于Netbeans Ruby,以提供调试功能。在Ruby IDE世界中还有一个古老的ActiveState's Komodo,它是基于DBGp协议的。另外一个能与Eclipse的调试器GUI抗衡的IDE是Eclipse DLTK Ruby,它也是CodeGear 3dRail的基础。DLTK也使用DBGp来连接到后端。SapphireSteel的Ruby in Steel包含了一个调试器GUI,它允许使用Cylon调试器来进行快速调试。

\

这些IDE的功能虽不尽相同,但至少都提供了断点、单步调试和变量查看功能。 注意:尽管IntelliJ在它们的IDE中提供了编辑Ruby功能,但在IntelliJ Ruby的蓝图中调试支持是作为一个未来项目的

\

JRuby

\

调试后端

\

基于跟踪的常规Ruby调试器也能用于JRuby。除此之外,更快的版本是jruby-debug(也属于debug-commons项目),它是用Java而不是Ruby语言来实现的,从而减少了每行的执行开销。

\

还有一个新的来自SapphireSteel的JRuby调试后端。刚才提到了这个公司,他们还做了MRI的快速Cylon调试器。和jruby-debug不同,SapphireSteel的解决方案同时使用Java和本地代码(通过JNI)实现了调试器后端。

\

GUI

\

支持set_trace_func调试的Ruby IDE也能用于JRuby。另外Netbeans和Apatana也提供了jruby-debug支持。对于那些不止把JRuby当作普通Ruby运行时、还要用Ruby调用的Java类的人来说,显然很需要支持跨语言的调试。当Ruby核调用Java核时,最好同时显示Ruby和Java的堆栈和变量。

\

SapphireSteel IDE使用他们自己实现的后端和通讯协议,而不是基于ruby-debug或者jruby-debug,这意味着它是被绑定在Ruby in Steel IDE中的。

\

Rubinius

\

调试后端

\

毫无疑问,Rubinius取得了长足的进步——特别是在过去的几个月中,它的调试支持从没有一跃成为Ruby界中的佼佼者(根据调试性能表现)。全速Ruby调试器允许伴随调试运行一个Ruby程序,而没有其他方案中的那种性能消耗,正如前面解释的或者链接新闻中所述的一般。

\

Rubinius的设计决定了其调试功能的强大,使得在运行时常规的Ruby核可以使用大量的VM基础结构和原数据。操作码和已加载Ruby核的解析树(ParseTree),以及堆栈踪迹(stacktrace)都是可访问的。内部追查的能力更强了,例如使用SendSites。 SendSites指出了消息传递到哪(“方法调用”),它还能链接到方法上。这样就可以获得在运行时中已加载代码的配置,但也起到了代码分析和覆盖工具的作用。每发一条信息,Sendsite的计数器就会增加;由于这个信息也能用于Ruby代码,所以写一个简单的代码分析工具或者至少是代码覆盖工具就只是几行代码的事。

\

GUI

\

现在Rubinius调试器的用户接口还是命令行界面,它可以管理断点、单步调试,也能查看正在运行的Ruby核的操作码或者它们的源文件。 sexp [method]是一个实用的命令,它返回[method]的AST的ParseTree符号表达式(s-expr,忽略参数的情况下把当前方法的AST表示为ParseTree符号表达式)。这是十分有用的信息,特别是对于那些使用元编程(metaprogramming)的代码——运行时所生成的代码显然不含源代码。能够看到那些生成的被加载的代码显然对于调试那些元编程的代码有帮助。另外,能够看到符号表达式也比试图去猜测生成的代码是干什么的更进了一步,也更加方便了——通过采用基于ParseTree的工具,比如Ruby2Ruby,它是一个接收符号表达式并格式化后返回给Ruby源代码的工具。

\

直到本文发布之日为止,Rubinius和调试器GUI的连接还不没有出现。不过,由于现在调试协议实现已经可以工作了,这个状况即将发生改变。从实现调试支持的速度来判断,对调试器GUI的支持也不远了(调试协议的实现是调试器实现中的一个简单部分)。一旦它支持了debug-commons或者DBGp 协议,采用这些协议的IDE就能够用于Rubinius了。

\

IronRuby

\

IronRuby生成的是MS IL代码,它目标是.NET平台。它使用DLR,这个系统收集各种语言的公共功能,比如表达式树等产生的MS IL。

\

调试后端

\

DLR生成 .NET MS IL,也生成MS IL调试信息。这意味着IronRuby既可以使用.NET调试工具,也可以使用Visual Studio调试器的GUI。

\

GUI

\

你可以使用Visual Studio,而Ruby的SapphireSteel Ruby in Steel IDE——也是基于Visual Studio的IDE——也支持IronRuby开发。在以后的版本中肯定会增加调试功能。

\

其他问题

\

这篇文章介绍了一部分现有的Ruby调试工具,而不是全部。还有别的IDE的GUI和后端,比如ActiveState的Komodo,还有一些不同程度地支持Ruby实现或调试功能的后端。这里没有提到XRuby这个Ruby实现,它也支持调试。同样也没有提到Ruby 1.9,尽管已经官方发行了,但它还在紧锣密鼓的开发中。由于Ruby 1.9的VM也使用字节码解释器,那么很可能会采用类似Rubinius的方案。

\

最后是免责声明:现在,其他Ruby实现和调试支持正在飞快地开发着。所以,请把此文看作是对Ruby调试支持的概述——实际上,在你阅读时,各种Ruby实现的调试支持和工具很可能已经有了变化和改进。

\

查看原文:A Look at Ruby Debuggers

 类似资料: