查看源代码 调试器
入门
要使用调试器,基本步骤如下:
步骤 1. 通过调用 debugger:start()
启动调试器。
监视窗口将显示有关所有被调试进程、解释模块和所选选项的信息。通常,最初没有被调试的进程。首先,必须指定要调试(也称为解释)的模块。按如下步骤操作:
步骤 2. 在监视窗口中选择模块 > 解释...。
将显示解释模块窗口。
步骤 3. 从“解释对话框”窗口中选择适当的模块。
注意
只有使用选项
debug_info
设置编译的模块才能被解释。不可解释的模块在“解释模块”窗口中显示在括号内。
步骤 4. 在监视窗口中,选择模块 > 要解释的模块 > 查看。
源代码文件的内容将显示在查看模块窗口中。
步骤 5. 设置断点(如果有)。
步骤 6. 启动要调试的程序。这通常从 Erlang shell 中完成。
所有在解释模块中执行代码的进程都会显示在监视窗口中。
步骤 7. 要附加到这些进程之一,请双击它,或选择该进程,然后选择进程 > 附加。附加到进程会为此进程打开一个附加进程窗口。
步骤 8. 从“附加进程”窗口中,您可以控制进程执行、检查变量值、设置断点等等。
断点和断点对话框窗口
一旦解释了适当的模块,就可以在源代码中的相关位置设置断点。断点是按行指定的。当进程到达断点时,它会停止并等待用户发出的命令(单步执行、跳过、继续 ...)。
注意
当进程到达断点时,只会停止该进程。其他进程不受影响。
断点是使用监视窗口、“查看模块”窗口或“附加进程”窗口的断点菜单创建和删除的。
可执行行
要生效,必须在可执行行上设置断点,该行是包含可执行表达式(例如匹配或函数调用)的代码行。空行或包含注释、函数头或 case
语句或 receive
语句中的模式的行不是可执行的。
在以下示例中,第 2、4、6、8 和 11 行是可执行行
1: is_loaded(Module,Compiled) ->
2: case get_file(Module,Compiled) of
3: {ok,File} ->
4: case code:which(Module) of
5: ?TAG ->
6: {loaded,File};
7: _ ->
8: unloaded
9: end;
10: false ->
11: false
12: end.
状态和触发操作
断点可以是活动的或非活动的。非活动的断点将被忽略。
每个断点都有一个触发操作,用于指定当进程到达它(并停止)时会发生什么
- 启用 - 断点保持活动状态(默认)。
- 禁用 - 断点被设为非活动状态。
- 删除 - 断点被删除。
行断点
行断点是在模块中的特定行创建的。
右键单击模块条目以打开弹出菜单,从中可以选择适当的模块。
当模块在“查看模块”窗口或“附加进程”窗口中显示时,也可以通过双击该行来创建(和删除)行断点。
条件断点
条件断点是在模块中的特定行创建的,但只有当指定的条件为真时,到达该断点的进程才会停止。
条件由用户指定为模块名称 CModule
和函数名称 CFunction
。当进程到达断点时,将评估 CModule:CFunction(Bindings)
。当且仅当此函数调用返回 true
时,进程才会停止。如果函数调用返回 false
,则会静默忽略断点。
Bindings
是一个变量绑定列表。要检索 Variable
(作为原子给出)的值,请使用函数 int:get_binding(Variable, Bindings)
。该函数返回 unbound
或 {value,Value}
。
右键单击模块条目以打开弹出菜单,从中可以选择适当的模块。
示例
在模块 fact
的第 6 行添加一个调用 c_test:c_break/1
的条件断点。每次到达断点时,都会调用该函数。当 N
等于 3 时,该函数返回 true
,进程停止。
从 fact.erl
中提取
5. fac(0) -> 1;
6. fac(N) when N > 0, is_integer(N) -> N * fac(N-1).
c_test:c_break/1
的定义
-module(c_test).
-export([c_break/1]).
c_break(Bindings) ->
case int:get_binding('N', Bindings) of
{value, 3} ->
true;
_ ->
false
end.
函数断点
函数断点是一组行断点,每个行断点位于指定函数中每个子句的第一行。
要打开弹出菜单,从中可以选择适当的模块,请右键单击模块条目。
要在列表框中显示模块的所有函数,请在指定模块名称后单击确定按钮(或按 Return 或 Tab 键)。
堆栈跟踪
Erlang 模拟器会跟踪堆栈跟踪,即有关最近函数调用的信息。如果发生错误,则会使用此信息,例如
1> catch a+1.
{'EXIT',{badarith,[{erlang,'+',[a,1],[]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,573}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,357}]},
{shell,exprs,7,[{file,"shell.erl"},{line,674}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,629}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,614}]}]}}
有关堆栈跟踪的详细信息,请参阅 Erlang 参考手册中的错误和错误处理部分。
调试器通过跟踪最近调用的解释函数来模拟堆栈跟踪。(不能使用真实的堆栈跟踪,因为它显示的是调用了调试器的哪些函数,而不是调用了哪些解释函数。)
可以使用“附加进程”窗口中的向上和向下按钮来遍历函数调用链。
默认情况下,调试器仅保存有关递归函数调用的信息,即尚未返回值的函数调用(选项启用堆栈,无尾部)。
但是,有时保存所有调用(即使是尾递归调用)也很有用。这是通过选项启用堆栈,尾部完成的。请注意,当有许多尾递归调用时,此选项会消耗更多内存并减慢解释函数的执行速度。
要关闭调试器堆栈跟踪功能,请选择选项关闭堆栈。
注意
如果发生错误,在这种情况下,堆栈跟踪将变为空。
有关如何更改堆栈跟踪选项的信息,请参阅监视窗口部分。
监视窗口
监视窗口是调试器的主要窗口,并显示以下内容:
一个列表框,其中包含所有解释模块的名称
双击模块会调出“查看模块”窗口。
选择了哪些选项
有关所有被调试进程的信息,即所有在解释模块中执行过或正在执行代码的进程
自动附加框、堆栈跟踪标签、回溯大小标签和字符串框显示一些设置的选项。有关这些选项的详细信息,请参阅选项菜单部分。
进程网格
Pid - 进程标识符。
初始调用 - 此进程对解释函数的第一次调用。(
Module:Function/Arity
)名称 - 已注册的名称(如果有)。如果未显示注册的名称,则可能是调试器在注册名称之前收到了有关该进程的信息。尝试选择编辑 > 刷新。
状态 - 当前状态,为以下之一:
idle - 解释的函数调用已返回值,并且该进程不再执行解释的代码。
running - 进程正在运行。
waiting - 进程正在
receive
语句中等待。break - 进程在断点处停止。
exit - 进程已终止。
no_conn - 与进程所在的节点没有连接。
信息 - 更多信息(如果有)。如果进程在断点处停止,则该字段包含有关位置
{Module,Line}
的信息。如果进程已终止,则该字段包含退出原因。
文件菜单
加载设置... - 尝试从先前使用保存设置...(见下文)保存的文件中加载并还原调试器设置。任何错误都将被静默忽略。
请注意,Erlang/OTP R16B01 或更高版本保存的设置不能被 Erlang/OTP R16B 或更早版本读取。
保存设置... - 将调试器设置保存到文件。设置包括解释的文件集、断点和所选选项。可以使用加载设置...(见上文)在以后的调试器会话中还原设置。任何错误都将被静默忽略。
退出 - 停止调试器。
编辑菜单
刷新 - 更新有关被调试进程的信息。有关所有已终止进程的信息将从窗口中删除。所有已终止进程的“附加进程”窗口都将关闭。
全部终止 - 使用
exit(Pid, kill)
终止窗口中列出的所有进程。
模块菜单
解释... - 打开解释模块窗口,可以在其中指定要解释的新模块。
全部删除 - 停止解释所有模块。在解释模块中执行的进程将终止。
对于每个解释的模块,都会向模块菜单添加一个相应的条目,其中包含以下子菜单:
删除 - 停止解释所选模块。在此模块中执行的进程将终止。
查看 - 打开一个查看模块窗口,显示所选模块的内容。
进程菜单
以下菜单项适用于当前选定的进程,前提是该进程已在断点处停止(详情请参阅附加进程窗口部分)
单步执行
下一步
继续
完成
以下菜单项适用于当前选定的进程
附加 - 附加到进程并打开附加进程窗口。
终止 - 使用
exit(Pid, kill)
终止进程。
断点菜单
此菜单中的项用于创建和删除断点。详情请参阅断点部分。
行断点... - 设置行断点。
条件断点... - 设置条件断点。
函数断点... - 设置函数断点。
启用所有 - 启用所有断点。
禁用所有 - 禁用所有断点。
删除所有 - 删除所有断点。
对于每个断点,都会在“断点”菜单中添加一个对应的条目,从中可以禁用、启用或删除该断点,并更改其触发操作。
选项菜单
跟踪窗口 - 设置在附加进程窗口中可见的区域。不影响现有的附加进程窗口。
自动附加 - 设置要自动附加调试进程的事件。影响现有的调试进程。
- 首次调用 - 进程首次调用已解释模块中的函数。
- 退出时 - 在进程终止时。
- 在断点处 - 当进程到达断点时。
堆栈跟踪 - 设置堆栈跟踪选项,请参阅堆栈跟踪部分。不影响现有的调试进程。
- 堆栈开启,尾部 - 保存有关所有当前调用的信息。
- 堆栈开启,无尾部 - 保存有关当前调用的信息,在进行尾递归调用时丢弃先前的信息。
- 堆栈关闭 - 不保存有关当前调用的任何信息。
字符串 - 设置要打印为字符串的整数列表。不影响现有的调试进程。
回溯大小... - 设置从附加进程窗口检查调用堆栈时要获取的调用帧数量。不影响现有的附加进程窗口。
窗口菜单
包含每个打开的调试器窗口的菜单项。选择其中一项会提升相应的窗口。
帮助菜单
- 帮助 - 显示调试器文档。此功能需要 Web 浏览器。
解释模块窗口
解释模块窗口用于选择要解释的模块。最初,该窗口显示当前工作目录的模块(erl
文件)和子目录。
可解释的模块是指,与源代码位于同一目录,或在源代码旁边的 ebin
目录中,存在使用 debug_info
选项编译的 .beam
文件的模块。
不满足这些要求的模块不可解释,因此显示在括号内。
debug_info
选项会导致将调试信息或抽象代码添加到 .beam
文件中。这会增加文件大小,并使得重新构造源代码成为可能。因此,建议不要在面向目标系统的代码中包含调试信息。
以下示例说明如何使用 erlc
编译包含调试信息的代码
% erlc +debug_info module.erl
以下示例说明如何从 Erlang shell 编译包含调试信息的代码
4> c(module, debug_info).
要浏览文件层次结构并解释相应的模块,请选择一个模块名称并单击“选择”(或按回车键),或双击模块名称。解释后的模块的类型为 erl src
。
要解释所选目录中显示的所有模块,请单击“全部”。
要关闭窗口,请单击“完成”。
注意
当调试器以全局模式启动时(这是默认设置,请参阅
debugger:start/0
),为解释添加(或删除)的模块将在所有已知的 Erlang 节点上添加(或删除)。
附加进程窗口
从附加进程窗口中,您可以与调试的进程进行交互。为每个已附加的进程打开一个窗口。请注意,附加到进程时,其执行会自动停止。
该窗口分为以下五个部分
代码区域,显示正在执行的代码。代码会缩进,并且每行都以行号为前缀。如果进程执行已停止,则当前行将标记为
-->
。某行上已存在的断点会标记一个停止符号。在图中显示的示例中,执行在第 6 行处停止,在执行fac/1
之前。活动的断点以红色显示,非活动的断点以蓝色显示。
按钮区域,带有快速访问“进程”菜单中常用功能的按钮。
求值器区域,您可以在其中评估调试进程上下文中的函数,如果该进程的执行已停止。
绑定区域,显示所有变量绑定。如果单击变量名称,则该值将显示在求值器区域中。双击变量名称可打开一个窗口,可在其中编辑变量值。但是请注意,除非可以在运行的系统中表示,否则 pid、端口、引用或 fun 值无法编辑。
跟踪区域,显示进程的跟踪输出。
++ (N) <L>
- 函数调用,其中N
是调用级别,L
是行号。-- (N)
- 函数返回值.
==> Pid : Msg
- 消息Msg
被发送到进程Pid
。<== Msg
- 接收到消息Msg
。++ (N) receive
- 等待receive
。++ (N) receive with timeout
- 在receive...after
中等待。
跟踪区域还显示回溯,即堆栈上当前函数调用的摘要。
使用“选项”菜单,您可以设置要显示的区域。默认情况下,将显示除跟踪区域以外的所有区域。
文件菜单
- 关闭 - 关闭此窗口并从进程分离。
编辑菜单
转到行... - 转到指定的行号。
搜索... - 搜索指定的字符串。
进程菜单
单步执行 - 执行当前代码行,步入任何(已解释的)函数调用。
下一步 - 执行当前代码行,并在下一行停止。
继续 - 继续执行。
完成 - 继续执行,直到当前函数返回。
跳过 - 跳过当前代码行,并在下一行停止。如果在函数体中的最后一行使用,则函数返回
skipped
。超时 - 在执行
receive...after
语句时模拟超时。停止 - 停止正在运行的进程的执行,即让进程在断点处停止。该命令将在进程下次收到消息时生效(可见)。
位置 - 验证执行的当前位置在代码区域中可见。
终止 - 使用
exit(Pid, kill)
终止进程。消息 - 检查进程的消息队列。该队列显示在求值器区域中。
回溯 - 在跟踪区域中显示进程的回溯,即堆栈上当前函数调用的摘要。要求跟踪区域可见,且堆栈跟踪选项为“堆栈开启,尾部”或“堆栈开启,无尾部”。
向上 - 检查堆栈上之前的函数调用,显示位置和变量绑定。
向下 - 检查堆栈上的下一个函数调用,显示位置和变量绑定。
选项菜单
跟踪窗口 - 设置要可见的区域。不影响其他附加进程窗口。
堆栈跟踪 - 与监视器窗口中的相同,但仅影响窗口附加的调试进程。
字符串 - 与监视器窗口中的相同,但仅影响窗口附加的调试进程。
回溯大小... - 设置在检查调用堆栈时要获取的调用帧的数量。不影响其他“附加进程”窗口。
中断、窗口和帮助菜单
中断、窗口和帮助菜单与监视窗口中的相同,但中断菜单仅适用于本地断点。
查看模块窗口
“查看模块”窗口显示解释模块的内容,并允许设置断点。
源代码会缩进,并且每行都以其行号为前缀。
单击某行会高亮显示该行,并将其选为中断菜单中可用断点功能的目标。 要在该行上设置行断点,请双击它。 要删除断点,请双击带有现有断点的行。
断点用停止符号标记。
文件和编辑菜单
文件和编辑菜单与附加进程窗口中的相同。
中断、窗口和帮助菜单
中断、窗口和帮助菜单与监视窗口中的相同,但中断菜单仅适用于本地断点。
性能
解释代码的执行速度自然比常规编译模块慢。使用调试器也会增加系统中的进程数量,因为对于每个调试的进程,都会创建一个额外的进程(元进程)。
还值得注意的是,带有计时器的程序在调试时可能会表现不同。当停止进程的执行时(例如,在断点处)尤其如此。超时可能会在其他正常继续执行的进程中发生。
代码加载机制
代码加载的工作方式几乎与往常一样,只是解释的模块也存储在数据库中,并且调试的进程仅使用此存储的代码。重新解释一个解释的模块也会导致新版本被存储,但不会影响执行旧版本代码的现有进程。这意味着 Erlang 的代码替换机制不适用于调试的进程。
调试远程节点
通过使用debugger:start/1
,您可以指定调试器是在本地模式还是全局模式下启动
debugger:start(local | global)
如果调用debugger:start/0
,则调试器将在全局模式下启动。
在本地模式下,代码仅在当前节点上解释。在全局模式下,代码在所有已知节点上解释。其他节点上执行解释代码的进程会自动显示在“监视”窗口中,并且可以像任何其他调试的进程一样附加。
注意
不建议在网络中的多个节点上以全局模式启动调试器,因为节点会相互干扰,导致行为不一致。